Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
nn_model_test.c
Go to the documentation of this file.
1/*
2 This file is part of Ansel,
3 Copyright (C) 2026 Aurélien PIERRE.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19/* Golden-fixture parity test for the .anselnn loader + CPU U-Net executor.
20 * The fixture is produced by scripts/make_fixture.py in the ansel-denoise
21 * training repo from the same model file; the C output must match the torch
22 * reference within the stated absolute tolerance.
23 *
24 * Standalone build (no ansel build system needed):
25 * gcc -O2 -fopenmp -Isrc src/common/nn_model.c src/tests/nn_model_test.c \
26 * $(pkg-config --cflags --libs json-glib-1.0) -lm -o nn_model_test
27 * Usage: nn_model_test <model.anselnn> <fixture-dir> [N] (N defaults to 96)
28 */
29
30#include "common/nn_model.h"
31
32#include <math.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <time.h>
37
38static double max_abs_diff(const float *a, const float *b, size_t count)
39{
40 double max_abs = 0.0;
41 for(size_t i = 0; i < count; i++)
42 {
43 const double d = fabs((double)a[i] - b[i]);
44 if(d > max_abs) max_abs = d;
45 }
46 return max_abs;
47}
48
49static float *read_f32(const char *dir, const char *name, size_t count)
50{
51 char path[1024];
52 snprintf(path, sizeof(path), "%s/%s", dir, name);
53 FILE *f = fopen(path, "rb");
54 if(!f)
55 {
56 fprintf(stderr, "cannot open %s\n", path);
57 return NULL;
58 }
59 float *buf = malloc(count * sizeof(float));
60 const size_t got = buf ? fread(buf, sizeof(float), count, f) : 0;
61 fclose(f);
62 if(got != count)
63 {
64 fprintf(stderr, "%s: expected %zu floats, got %zu\n", path, count, got);
65 free(buf);
66 return NULL;
67 }
68 return buf;
69}
70
71int main(int argc, char *argv[])
72{
73 if(argc < 3)
74 {
75 fprintf(stderr, "usage: %s <model.anselnn> <fixture-dir> [N]\n", argv[0]);
76 return 2;
77 }
78 const int n = argc > 3 ? atoi(argv[3]) : 96;
79
80 char err[256] = "";
81 dt_nn_model_t *model = dt_nn_model_load(argv[1], err, sizeof(err));
82 if(!model)
83 {
84 fprintf(stderr, "model load failed: %s\n", err);
85 return 2;
86 }
87 printf("model loaded: in=%d out=%d alignment=%d, scratch for %dx%d: %.1f MB\n", dt_nn_model_in_channels(model),
89 dt_nn_unet_scratch_bytes(model, n, n) / 1048576.0);
90
91 const size_t plane = (size_t)n * n;
92 float *in = read_f32(argv[2], "fixture-input.f32", plane * dt_nn_model_in_channels(model));
93 float *expected = read_f32(argv[2], "fixture-expected.f32", plane);
94 float *out = calloc(plane, sizeof(float));
95 if(!in || !expected || !out) return 2;
96
97 /* multi-scale model: gate the binning contract and the coarse stage before
98 * the fine parity below (which runs on the fixture's torch-built guide). */
99 const int bin = dt_nn_model_bin(model, 0);
100 if(bin > 1)
101 {
102 const int cn = n / bin;
103 const size_t cplane = (size_t)cn * cn;
106 float *base = read_f32(argv[2], "fixture-base-planes.f32", plane * 5);
107 float *c_in_exp = read_f32(argv[2], "fixture-coarse-input.f32", cplane * c_in);
108 float *c_out_exp = read_f32(argv[2], "fixture-coarse-expected.f32", cplane * c_out);
109 float *rgb = malloc(cplane * 3 * sizeof(float));
110 float *cnt = malloc(cplane * 3 * sizeof(float));
111 float *c_out_got = malloc(cplane * c_out * sizeof(float));
112 if(!base || !c_in_exp || !c_out_exp || !rgb || !cnt || !c_out_got) return 2;
113
114 /* 1. binning contract: our RGB means vs torch's binned planes 0..2 */
115 dt_nn_bin_planes(base, n, n, bin, rgb, cnt);
116 const double bin_err = max_abs_diff(rgb, c_in_exp, cplane * 3);
117 printf("binning contract: max abs err %.3g (tolerance 1e-6)\n", bin_err);
118 if(bin_err > 1e-6)
119 {
120 fprintf(stderr, "FAIL: binning contract\n");
121 return 1;
122 }
123
124 /* 2. coarse stage parity on torch's own input */
126 {
127 fprintf(stderr, "coarse stage apply failed\n");
128 return 2;
129 }
130 const double c_err = max_abs_diff(c_out_got, c_out_exp, cplane * c_out);
131 printf("coarse stage parity: max abs err %.3g (tolerance 2e-4)\n", c_err);
132 if(c_err > 2e-4)
133 {
134 fprintf(stderr, "FAIL: coarse stage parity\n");
135 return 1;
136 }
137
138 /* 3. end-to-end: our binning -> our coarse -> our guide injection -> fine,
139 * compared against the torch final output (looser: coarse error propagates) */
141 float *fine_in = malloc(plane * fine_in_ch * sizeof(float));
142 float *e2e_out = malloc(plane * sizeof(float));
143 if(!fine_in || !e2e_out) return 2;
144 memcpy(fine_in, base, plane * 5 * sizeof(float));
145 /* rebuild the coarse input from our own binning + the fixture's sigma
146 * planes (positions 3..5 of the coarse input are the binned sigma, which
147 * needs the profile constants — reuse torch's, the contract test above
148 * already pinned our RGB planes) */
152 {
153 fprintf(stderr, "fine stage apply failed\n");
154 return 2;
155 }
156 const double e2e_err = max_abs_diff(e2e_out, expected, plane);
157 printf("end-to-end parity: max abs err %.3g (tolerance 5e-4)\n", e2e_err);
158 if(e2e_err > 5e-4)
159 {
160 fprintf(stderr, "FAIL: end-to-end parity\n");
161 return 1;
162 }
163 free(base);
164 free(c_in_exp);
165 free(c_out_exp);
166 free(rgb);
167 free(cnt);
168 free(c_out_got);
169 free(fine_in);
170 free(e2e_out);
171 }
172
173 struct timespec t0, t1;
175 const int rc = dt_nn_unet_apply(model, in, out, n, n);
177 if(rc)
178 {
179 fprintf(stderr, "dt_nn_unet_apply failed (%d)\n", rc);
180 return 2;
181 }
182 const double ms = (t1.tv_sec - t0.tv_sec) * 1e3 + (t1.tv_nsec - t0.tv_nsec) / 1e6;
183
184 double max_abs = 0.0, sum_sq = 0.0;
185 size_t worst = 0;
186 for(size_t i = 0; i < plane; i++)
187 {
188 const double d = fabs((double)out[i] - expected[i]);
189 if(d > max_abs)
190 {
191 max_abs = d;
192 worst = i;
193 }
194 sum_sq += d * d;
195 }
196 const double rms = sqrt(sum_sq / plane);
197 const double tolerance = 2e-4;
198 printf("parity vs torch: max abs err %.3g (at %zu: %.6f vs %.6f), rms %.3g | %.1f ms for %dx%d\n", max_abs,
199 worst, out[worst], expected[worst], rms, ms, n, n);
200
202 free(in);
203 free(expected);
204 free(out);
205 if(max_abs > tolerance)
206 {
207 fprintf(stderr, "FAIL: max abs err %.3g > %.3g\n", max_abs, tolerance);
208 return 1;
209 }
210 printf("PASS\n");
211 return 0;
212}
const float f
static dt_aligned_pixel_t rgb
const dt_colormatrix_t dt_aligned_pixel_t out
const char * model
void dt_nn_model_free(dt_nn_model_t *m)
Definition nn_model.c:405
int dt_nn_model_in_channels(const dt_nn_model_t *m)
Definition nn_model.c:416
int dt_nn_model_coarse_in_channels(const dt_nn_model_t *m)
Definition nn_model.c:432
size_t dt_nn_unet_scratch_bytes(const dt_nn_model_t *m, int width, int height)
Definition nn_model.c:736
int dt_nn_unet_apply_stage(const dt_nn_model_t *m, int stage, const float *in, float *out, int width, int height, int apply_residual)
Definition nn_model.c:1009
dt_nn_model_t * dt_nn_model_load(const char *path, char *err, size_t err_len)
Definition nn_model.c:237
__DT_CLONE_TARGETS__ void dt_nn_upsample_nearest(const float *in, int ch, int w, int h, int factor, float *out)
Definition nn_model.c:1058
int dt_nn_model_coarse_out_channels(const dt_nn_model_t *m)
Definition nn_model.c:437
int dt_nn_model_alignment(const dt_nn_model_t *m)
Definition nn_model.c:460
int dt_nn_unet_apply(const dt_nn_model_t *m, const float *in, float *out, int width, int height)
Definition nn_model.c:1004
__DT_CLONE_TARGETS__ void dt_nn_bin_planes(const float *planes, int pw, int ph, int bin, float *out_rgb, float *out_cnt)
Definition nn_model.c:1022
int dt_nn_model_bin(const dt_nn_model_t *m, const int is_xtrans)
Definition nn_model.c:426
int dt_nn_model_out_channels(const dt_nn_model_t *m)
Definition nn_model.c:421
static float * read_f32(const char *dir, const char *name, size_t count)
static double max_abs_diff(const float *a, const float *b, size_t count)
const char * name
Definition pdf.h:90
int main()
Definition prova.c:47