Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
thinplate.c
Go to the documentation of this file.
1/*
2 * This file is part of darktable,
3 * Copyright (C) 2016 Erwin Burema.
4 * Copyright (C) 2016 johannes hanika.
5 * Copyright (C) 2016 Roman Lebedev.
6 * Copyright (C) 2016 Tobias Ellinghaus.
7 * Copyright (C) 2019 Jacques Le Clerc.
8 * Copyright (C) 2020 Hubert Kowalski.
9 * Copyright (C) 2020 Pascal Obry.
10 * Copyright (C) 2021 Ralf Brown.
11 * Copyright (C) 2022 Martin Bařinka.
12 * Copyright (C) 2025 Aurélien PIERRE.
13 *
14 * darktable is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 3 of the License, or
17 * (at your option) any later version.
18 *
19 * darktable is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with darktable. If not, see <http://www.gnu.org/licenses/>.
26 */
27
28#ifndef MAX
29#define MAX(a, b) ((a) < (b) ? (b) : (a))
30#endif
31#ifndef MIN
32#define MIN(a, b) ((a) > (b) ? (b) : (a))
33#endif
34
35#include "common/darktable.h"
36#include "chart/thinplate.h"
37#include "chart/deltaE.h"
38#include "common/svd/svd.h"
39
40#include <assert.h>
41#include <float.h>
42#include <stdlib.h>
43#include <string.h>
44
45// #define REPLACEMENT // either broken code or doesn't help at all
46// #define EXACT // use full solve instead of dot in inner loop
47
48// very fast, very approximate
49static inline float __attribute__((__unused__)) fasterlog(float x)
50{
51 union { float f; uint32_t i; } vx = { x };
52 float y = vx.i;
53 y *= 8.2629582881927490e-8f;
54 return y - 87.989971088f;
55}
56
57// thinplate spline kernel \phi(r)
58static inline double thinplate_kernel(const double *x, const double *y)
59{
60 const double r
61 = sqrt((x[0] - y[0]) * (x[0] - y[0]) + (x[1] - y[1]) * (x[1] - y[1]) + (x[2] - y[2]) * (x[2] - y[2]));
62 return r * r * logf(MAX(1e-8f, r));
63 // even when using both here and in the iop the approximate version,
64 // it still doesn't work so well. need to be a bit more precise it seems.
65 // return r * r * fasterlog(MAX(1e-8f, r));
66}
67
68static inline double compute_error(
69 const tonecurve_t *c,
70 const double **target,
71 const double *residual_L,
72 const double *residual_a,
73 const double *residual_b,
74 const int wd,
75 double *maxerr)
76{
77 // compute error:
78 double err = 0.0;
79 double merr = 0.0;
80 for(int i = 0; i < wd; i++)
81 {
82#ifdef EXACT
83 const double Lt = target[0][i];
84 const double L0 = tonecurve_apply(c, Lt);
85 const double L1 = tonecurve_apply(c, Lt + residual_L[i]);
86 dt_aligned_pixel_t Lab0 = { L0, target[1][i], target[2][i] };
87 dt_aligned_pixel_t Lab1 = { L1, target[1][i], target[2][i] };
88 const double localerr = dt_colorspaces_deltaE_2000(Lab0, Lab1);
89 err += localerr;
90#else
91 const double localerr = sqrt(residual_L[i] * residual_L[i] + residual_a[i] * residual_a[i] + residual_b[i] * residual_b[i]);
92 err += localerr/wd;
93#endif
94 merr = MAX(merr, localerr);
95
96#if 0
97#if 0 // max rmse
98 // err = MAX(err, residual_L[i]*residual_L[i] +
99 // residual_a[i]*residual_a[i] + residual_b[i]*residual_b[i]);
100#elif 0 // total rmse
101 err += (residual_L[i]*residual_L[i] +
102 residual_a[i]*residual_a[i] + residual_b[i]*residual_b[i])/wd;
103#elif 1 // delta e 2000
104 const double Lt = target[0][i];
105 const double L0 = tonecurve_apply(c, Lt);
106 const double L1 = tonecurve_apply(c, Lt + residual_L[i]);
107 dt_aligned_pixel_t Lab0 = {L0, target[1][i], target[2][i]};
108 dt_aligned_pixel_t Lab1 = {L1, target[1][i], target[2][i]};
109 err += dt_colorspaces_deltaE_2000(Lab0, Lab1);
110#else
111 const double Lt = target[0][i];
112 const double L = tonecurve_apply(c, Lt + residual_L[i]);
113 const double dL = L - tonecurve_apply(c, Lt);
114 err = MAX(err, dL*dL +
115 residual_a[i]*residual_a[i] + residual_b[i]*residual_b[i]);
116#endif
117#endif
118 }
119 if(maxerr) *maxerr = merr;
120 return err;
121}
122
123static inline int solve(double *As, double *w, double *v, const double *b, double *coeff, int wd, int s, int S)
124{
125 // A'[wd][s+1] = u[wd][s+1] diag(w[s+1]) v[s+1][s+1]^t
126 //
127 // svd to solve for c:
128 // A * c = b
129 // A = u w v^t => A-1 = v 1/w u^t
130 dsvd(As, wd, s + 1, S, w, v); // As is wd x s+1 but row stride S.
131 if(w[s] < 1e-3) // if the smallest singular value becomes too small, we're done
132 return 1;
133
134 double *tmp = malloc(sizeof(double) * S);
135 if(IS_NULL_PTR(tmp)) return 1;
136
137 for(int i = 0; i <= s; i++) // compute tmp = u^t * b
138 {
139 tmp[i] = 0.0;
140 for(int j = 0; j < wd; j++) tmp[i] += As[j * S + i] * b[j];
141 }
142 for(int i = 0; i <= s; i++) // apply singular values:
143 tmp[i] /= w[i];
144 for(int j = 0; j <= s; j++)
145 { // compute first s output coefficients coeff[j]
146 coeff[j] = 0.0;
147 for(int i = 0; i <= s; i++) coeff[j] += v[j * (s + 1) + i] * tmp[i];
148 }
149 dt_free(tmp);
150 return 0;
151}
152
153#pragma GCC diagnostic push
154#pragma GCC diagnostic ignored "-Wvla"
155
156// returns sparsity <= S
157int thinplate_match(const tonecurve_t *curve, // tonecurve to apply after this (needed for error estimation)
158 int dim, // dimensionality of points
159 int N, // number of points
160 const double *point, // dim-strided points
161 const double **target, // target values, one pointer per dimension
162 int S, // desired sparsity level, actual result will be returned
163 int *permutation, // pointing to original order of points, to identify correct output coeff
164 double **coeff, // output coefficient arrays for each dimension, ordered according to
165 // permutation[dim]
166 double *avgerr, // average error
167 double *maxerr) // max error
168{
169 if(avgerr) *avgerr = 0.0;
170 if(maxerr) *maxerr = 0.0;
171
172 const int wd = N + 4;
173 double *A = malloc(sizeof(double) * wd * wd);
174 // construct system matrix A such that:
175 // A c = f
176 //
177 // | R P | |c| = |f|
178 // | P^t 0 | |d| |0|
179 //
180 // to interpolate values f_i with the radial basis function system matrix R and a polynomial term P.
181 // P is a 3D linear polynomial a + b x + c y + d z
182 //
183 // radial basis function part R
184 for(int j = 0; j < N; j++)
185 for(int i = j; i < N; i++) A[j * wd + i] = A[i * wd + j] = thinplate_kernel(point + 3 * i, point + 3 * j);
186
187 // polynomial part P: constant + 3x linear
188 for(int i = 0; i < N; i++) A[i * wd + N + 0] = A[(N + 0) * wd + i] = 1.0f;
189 for(int i = 0; i < N; i++) A[i * wd + N + 1] = A[(N + 1) * wd + i] = point[3 * i + 0];
190 for(int i = 0; i < N; i++) A[i * wd + N + 2] = A[(N + 2) * wd + i] = point[3 * i + 1];
191 for(int i = 0; i < N; i++) A[i * wd + N + 3] = A[(N + 3) * wd + i] = point[3 * i + 2];
192
193 for(int j = N; j < wd; j++)
194 for(int i = N; i < wd; i++) A[j * wd + i] = 0.0f;
195
196 // precompute normalisation factors for columns of A
197 double *norm = malloc(sizeof(double) * wd);
198 for(int i = 0; i < wd; i++)
199 {
200 norm[i] = 0.0;
201 for(int j = 0; j < wd; j++) norm[i] += A[j * wd + i] * A[j * wd + i];
202 norm[i] = 1.0 / sqrt(norm[i]);
203 }
204
205 // XXX do we need these explicitly?
206 // residual = target vector
207 double(*r)[wd] = malloc(sizeof(double) * dim * wd);
208 const double **b = malloc(sizeof(double *) * dim);
209 for(int k = 0; k < dim; k++) b[k] = target[k];
210 for(int k = 0; k < dim; k++) memcpy(r[k], b[k], sizeof(double) * wd);
211
212 double *w = malloc(sizeof(double) * S);
213 double *v = malloc(sizeof(double) * S * S);
214 double *As = calloc((size_t)wd * S, sizeof(double));
215
216 // for rank from 0 to sparsity level
217 int s = 0, patches = 0;
218 double olderr = FLT_MAX;
219 // in case of replacement, iterate all the way to wd
220 for(; s < wd; s++)
221 {
222 const int sparsity = MIN(s, S);
223#ifndef REPLACEMENT
224 if(patches >= S - 4)
225 {
226 dt_free(r);
227 dt_free(b);
228 dt_free(w);
229 dt_free(v);
230 dt_free(As);
231 dt_free(norm);
232 dt_free(A);
233 return sparsity;
234 }
235 assert(sparsity < S + 4);
236#endif
237 // find (sparsity+1)-th column a_m by m = argmax_t{ a_t^t r . norm_t}
238 // by searching over all three residuals
239 double maxdot = 0.0;
240 int maxcol = 0;
241 for(int t = 0; t < wd; t++)
242 {
243 double dot = 0.0;
244 if(norm[t] > 0.0)
245 {
246#ifdef EXACT // use full solve
247 permutation[sparsity] = t;
248 for(int ch = 0; ch < dim; ch++)
249 {
250 // re-init columns in As
251 // svd will destroy its contents:
252 for(int i = 0; i <= sparsity; i++)
253 for(int j = 0; j < wd; j++) As[j * S + i] = A[j * wd + permutation[i]];
254
255 if(solve(As, w, v, b[ch], coeff[ch], wd, sparsity, S))
256 {
257 dt_free(r);
258 dt_free(b);
259 dt_free(w);
260 dt_free(v);
261 dt_free(As);
262 dt_free(norm);
263 dt_free(A);
264 return sparsity;
265 }
266
267 // compute tentative residual:
268 // r = b - As c
269 for(int j = 0; j < wd; j++)
270 {
271 r[ch][j] = b[ch][j];
272 for(int i = 0; i <= sparsity; i++) r[ch][j] -= A[j * wd + permutation[i]] * coeff[ch][i];
273 }
274 }
275
276 // compute error:
277 const double err = compute_error(curve, target, r[0], r[1], r[2], wd, 0);
278 dot = 1. / err; // searching for smallest error or largest dot
279#else // use dot product
280 for(int ch = 0; ch < dim; ch++)
281 {
282 double chdot = 0.0;
283 for(int j = 0; j < wd; j++) chdot += A[j * wd + t] * r[ch][j];
284 dot += fabs(chdot);
285 }
286 dot *= norm[t];
287#endif
288 }
289 // fprintf(stderr, "dot %d = %g\n", i, dot);
290 if(dot > maxdot)
291 {
292 maxcol = t;
293 maxdot = dot;
294 }
295 }
296
297 if(patches < S - 4)
298 {
299 // remember which column that was, we'll need it to evaluate later:
300 permutation[s] = maxcol;
301 if(maxcol < N) patches++;
302 // make sure we won't choose it again:
303 norm[maxcol] = 0.0;
304 }
305 else
306 { // already have chosen S-4 patches as columns, now do the replacement:
307 int mincol = 0;
308 double minerr = FLT_MAX;
309 for(int t = 0; t < sparsity; t++)
310 {
311 // find already chosen column t with min error reduction when replacing
312 // XXX do we set perm[t] above, ever? for t=S-1?
313 int oldperm = permutation[t];
314 permutation[t] = maxcol;
315#ifdef EXACT
316 for(int ch = 0; ch < dim; ch++)
317 {
318 // re-init all columns in As
319 for(int i = 0; i < sparsity; i++)
320 for(int j = 0; j < wd; j++) As[j * S + i] = A[j * wd + permutation[i]];
321
322 if(solve(As, w, v, b[ch], coeff[ch], wd, sparsity-1, S))
323 {
324 dt_free(r);
325 dt_free(b);
326 dt_free(w);
327 dt_free(v);
328 dt_free(As);
329 dt_free(norm);
330 dt_free(A);
331 return s;
332 }
333
334 // compute tentative residual:
335 // r = b - As c
336 for(int j = 0; j < wd; j++)
337 {
338 r[ch][j] = b[ch][j];
339 for(int i = 0; i < sparsity; i++) r[ch][j] -= A[j * wd + permutation[i]] * coeff[ch][i];
340 }
341 }
342
343 // compute error:
344 const double err = compute_error(curve, target, r[0], r[1], r[2], wd, 0);
345#else
346 double dot = 0.0;
347 for(int ch = 0; ch < dim; ch++)
348 {
349 double chdot = 0.0;
350 for(int j = 0; j < wd; j++) chdot += A[j * wd + t] * r[ch][j];
351 dot += fabs(chdot);
352 }
353 double n = 0.0; // recompute column norm
354 for(int j = 0; j < wd; j++) n += A[j * wd + mincol] * A[j * wd + mincol];
355 dot *= n;
356 double err = 1. / dot;
357#endif
358
359 if(err < minerr)
360 {
361 mincol = t;
362 minerr = err;
363 }
364 permutation[t] = oldperm;
365 // fprintf(stderr, "perm %d %d\n", t, oldperm);
366 }
367 // if(minerr >= 1. / maxdot) return sparsity + 1;
368 if(minerr < 1. / maxdot)
369 {
370 fprintf(stderr, "replacing %d <- %d\n", mincol, maxcol);
371 // replace column
372 permutation[mincol] = maxcol;
373 // reset norm[] of discarded column to something > 0
374#ifdef EXACT
375 norm[mincol] = 1.0;
376#else
377 norm[mincol] = 0.0;
378 for(int j = 0; j < wd; j++) norm[mincol] += A[j * wd + mincol] * A[j * wd + mincol];
379 norm[mincol] = 1.0 / sqrt(norm[mincol]);
380#endif
381 norm[maxcol] = 0.0;
382 }
383 }
384
385#ifdef EXACT
386 double err = 1. / maxdot;
387#else
388 const int sp = MIN(sparsity, S-1); // need to fix up for replacement
389 // solve linear least squares for sparse c for every output channel:
390 for(int ch = 0; ch < dim; ch++)
391 {
392 // re-init all of the previous columns in As since
393 // svd will destroy its contents:
394 for(int i = 0; i <= sp; i++)
395 for(int j = 0; j < wd; j++) As[j * S + i] = A[j * wd + permutation[i]];
396
397 // on error, return last valid configuration
398 if(solve(As, w, v, b[ch], coeff[ch], wd, sp, S))
399 {
400 dt_free(r);
401 dt_free(b);
402 dt_free(w);
403 dt_free(v);
404 dt_free(As);
405 dt_free(norm);
406 dt_free(A);
407 return sparsity;
408 }
409
410 // compute new residual:
411 // r = b - As c
412 for(int j = 0; j < wd; j++)
413 {
414 r[ch][j] = b[ch][j];
415 for(int i = 0; i <= sp; i++) r[ch][j] -= A[j * wd + permutation[i]] * coeff[ch][i];
416 }
417 }
418
419 double merr = 0.0;
420 const double err = compute_error(curve, target, r[0], r[1], r[2], wd, &merr);
421#endif
422 // residual is max CIE76 delta E now
423 // everything < 2 is usually considired a very good approximation:
424 if(patches == S-4)
425 {
426 if(avgerr) *avgerr = err;
427 if(maxerr) *maxerr = merr;
428 fprintf(stderr, "rank %d/%d avg DE %g max DE %g\n", sp + 1, patches, err, merr);
429 }
430 if(s >= S && err >= olderr)
431 fprintf(stderr, "error increased!\n");
432 // return sparsity + 1;
433 // if(err < 2.0) return sparsity+1;
434 olderr = err;
435 }
436 dt_free(r);
437 dt_free(b);
438 dt_free(w);
439 dt_free(v);
440 dt_free(As);
441 dt_free(norm);
442 dt_free(A);
443 return -1;
444}
445
446#pragma GCC diagnostic pop
447
448float thinplate_color_pos(float L, float a, float b)
449{
450 const float pi = 3.14153f; // clearly true.
451 const float h = atan2f(b, a) + pi;
452 // const float C = sqrtf(a*a + b*b);
453 const int sector = 4.0f * h / (2.0f * pi);
454 return 256.0 * sector + L; // C;
455}
456
457// clang-format off
458// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
459// vim: shiftwidth=2 expandtab tabstop=2 cindent
460// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
461// clang-format on
double tonecurve_apply(const tonecurve_t *c, const double L)
#define A(y, x)
const dt_aligned_pixel_t f
#define S(V, params)
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Enable aggressive floating-point arithmetic optimizations, in denormals handling. Set through user pr...
Definition darktable.h:524
#define dt_free(ptr)
Definition darktable.h:456
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
float dt_colorspaces_deltaE_2000(dt_aligned_pixel_t Lab0, dt_aligned_pixel_t Lab1)
Definition deltaE.c:48
static int permutation[]
Definition grain.c:160
static const float x
const float *const const float coeff[3]
const int t
const float v
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define N
float dt_aligned_pixel_t[4]
const float r
Singular value decomposition helper shared by chart and color-math code.
static int dsvd(double *a, int m, int n, int str, double *w, double *v)
Compute the singular value decomposition of a dense matrix.
Definition svd.h:80
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MIN(a, b)
Definition thinplate.c:32
float thinplate_color_pos(float L, float a, float b)
Definition thinplate.c:448
static double thinplate_kernel(const double *x, const double *y)
Definition thinplate.c:58
static int solve(double *As, double *w, double *v, const double *b, double *coeff, int wd, int s, int S)
Definition thinplate.c:123
static double compute_error(const tonecurve_t *c, const double **target, const double *residual_L, const double *residual_a, const double *residual_b, const int wd, double *maxerr)
Definition thinplate.c:68
int thinplate_match(const tonecurve_t *curve, int dim, int N, const double *point, const double **target, int S, int *permutation, double **coeff, double *avgerr, double *maxerr)
Definition thinplate.c:157
#define MAX(a, b)
Definition thinplate.c:29