Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
noiseprofiles.c
Go to the documentation of this file.
1/*
2 * This file is part of darktable,
3 * Copyright (C) 2015 Pedro Côrte-Real.
4 * Copyright (C) 2015-2016 Tobias Ellinghaus.
5 * Copyright (C) 2016 Roman Lebedev.
6 * Copyright (C) 2019 luzpaz.
7 * Copyright (C) 2020-2021 Pascal Obry.
8 * Copyright (C) 2021 Ralf Brown.
9 * Copyright (C) 2022 Martin Bařinka.
10 * Copyright (C) 2023 Alynx Zhou.
11 * Copyright (C) 2026 Aurélien PIERRE.
12 *
13 * darktable is free software: you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation, either version 3 of the License, or
16 * (at your option) any later version.
17 *
18 * darktable is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with darktable. If not, see <http://www.gnu.org/licenses/>.
25 */
26
27#include <json-glib/json-glib.h>
28#include "system/macros.h"
29#include "system/mem_alloc.h"
30#include "common/logging.h"
31#include "common/paths.h"
35
36// bump this when the noiseprofiles are getting a different layout or meaning (raw-raw data, ...)
37#define DT_NOISE_PROFILE_VERSION 0
38
39const dt_noiseprofile_t dt_noiseprofile_generic = {N_("generic poissonian"), "", "", 0, {0.0001f, 0.0001f, 0.0001}, {0.0f, 0.0f, 0.0f}};
41
42/* The parsed noiseprofiles.json, module-owned and LAZY.
43 *
44 * Parsing and verifying the JSON costs ~60 ms -- measured as the single largest item of
45 * dt_init(), over half of a warm ansel-cli startup -- and its only consumer is
46 * dt_noiseprofile_get_matching(), which the denoise IOPs call when a module actually needs
47 * a profile for an image. So the file is read on the first such call, under the same mutex
48 * that already serializes every walk of the parser tree, and a process that never opens a
49 * denoise module (every plain CLI export) never reads it at all.
50 *
51 * _parser_tried keeps a missing or invalid file from being re-parsed on every lookup:
52 * one attempt, then the NULL answer is as final as a parsed tree. */
53static JsonParser *_noiseprofiles_parser = NULL;
54static gboolean _noiseprofiles_tried = FALSE;
55static char *_noiseprofiles_alternative = NULL; // --noiseprofiles override, owned here
56
57void dt_noiseprofile_set_path(const char *alternative)
58{
60 _noiseprofiles_alternative = g_strdup(alternative);
61}
62
63static gboolean dt_noiseprofile_verify(JsonParser *parser);
64
65static JsonParser *_noiseprofile_load(const char *alternative)
66{
67 GError *error = NULL;
68 char filename[DT_PATH_MAX] = { 0 };
69
70 if(IS_NULL_PTR(alternative))
71 {
72 char dir[DT_PATH_MAX] = { 0 };
73
74 dt_loc_get_user_config_dir(dir, sizeof(dir));
75 dt_concat_path_file(filename, dir, "noiseprofiles.json");
76
77 if(!g_file_test(filename, G_FILE_TEST_EXISTS))
78 {
79 dt_loc_get_datadir(dir, sizeof(dir));
80 dt_concat_path_file(filename, dir, "noiseprofiles.json");
81 }
82 }
83 else
84 g_strlcpy(filename, alternative, sizeof(filename));
85
86 dt_print(DT_DEBUG_CONTROL, "[noiseprofile] loading noiseprofiles from `%s'\n", filename);
87 if(!g_file_test(filename, G_FILE_TEST_EXISTS)) return NULL;
88
89 // TODO: shall we cache the content? for now this looks fast enough(TM)
90 JsonParser *parser = json_parser_new();
91 if(!json_parser_load_from_file(parser, filename, &error))
92 {
93 fprintf(stderr, "[noiseprofile] error: parsing json from `%s' failed\n%s\n", filename, error->message);
94 g_error_free(error);
95 g_object_unref(parser);
96 return NULL;
97 }
98
99 // run over the file once to verify that it is sane
100 if(!dt_noiseprofile_verify(parser))
101 {
102 dt_control_log(_("noiseprofile file `%s' is not valid"), filename);
103 fprintf(stderr, "[noiseprofile] error: `%s' is not a valid noiseprofile file. run with -d control for details\n", filename);
104 g_object_unref(parser);
105 return NULL;
106 }
107
108 return parser;
109}
110
111int is_member(gchar** names, char* name)
112{
113 while(*names)
114 {
115 if(!g_strcmp0(*names, name))
116 return 1;
117 names++;
118 }
119 return 0;
120}
121
122static gint _sort_by_iso(gconstpointer a, gconstpointer b)
123{
124 const dt_noiseprofile_t *profile_a = (dt_noiseprofile_t *)a;
125 const dt_noiseprofile_t *profile_b = (dt_noiseprofile_t *)b;
126
127 return profile_a->iso - profile_b->iso;
128}
129
130#define _ERROR(...) {\
131 dt_print(DT_DEBUG_CONTROL, "[noiseprofile] error: " );\
132 dt_print(DT_DEBUG_CONTROL, __VA_ARGS__);\
133 dt_print(DT_DEBUG_CONTROL, "\n");\
134 valid = FALSE;\
135 goto end;\
136 }
137
138static gboolean dt_noiseprofile_verify(JsonParser *parser)
139{
140 JsonReader *reader = NULL;
141 gboolean valid = TRUE;
142
143 dt_print(DT_DEBUG_CONTROL, "[noiseprofile] verifying noiseprofile file\n");
144
145 JsonNode *root = json_parser_get_root(parser);
146 if(IS_NULL_PTR(root)) _ERROR("can't get the root node");
147
148 reader = json_reader_new(root);
149
150 if(!json_reader_read_member(reader, "version")) _ERROR("can't find file version.");
151
152 // check the file version
153 const int version = json_reader_get_int_value(reader);
154 json_reader_end_member(reader);
155
156 if(version != DT_NOISE_PROFILE_VERSION) _ERROR("file version is not what this code understands");
157
158 if(!json_reader_read_member(reader, "noiseprofiles")) _ERROR("can't find `noiseprofiles' entry.");
159
160 if(!json_reader_is_array(reader)) _ERROR("`noiseprofiles' is supposed to be an array");
161
162 // go through all makers
163 const int n_makers = json_reader_count_elements(reader);
164
165 for(int i = 0; i < n_makers; i++)
166 {
167 if(!json_reader_read_element(reader, i)) _ERROR("can't access maker at position %d / %d", i+1, n_makers);
168
169 if(!json_reader_read_member(reader, "maker")) _ERROR("missing `maker`");
170
171 // go through all models and check those
172 json_reader_end_member(reader);
173
174 if(!json_reader_read_member(reader, "models")) _ERROR("missing `models`");
175
176 const int n_models = json_reader_count_elements(reader);
177
178 for(int j = 0; j < n_models; j++)
179 {
180 if(!json_reader_read_element(reader, j)) _ERROR("can't access model at position %d / %d", j+1, n_models);
181
182 if(!json_reader_read_member(reader, "model")) _ERROR("missing `model`");
183
184 json_reader_end_member(reader);
185
186 if(!json_reader_read_member(reader, "profiles")) _ERROR("missing `profiles`");
187
188 const int n_profiles = json_reader_count_elements(reader);
189 for(int k = 0; k < n_profiles; k++)
190 {
191 if(!json_reader_read_element(reader, k)) _ERROR("can't access profile at position %d / %d", k+1, n_profiles);
192
193 gchar** member_names = json_reader_list_members(reader);
194
195 // name
196 if(!is_member(member_names, "name"))
197 {
198 g_strfreev(member_names);
199 _ERROR("missing `name`");
200 }
201
202 // iso
203 if(!is_member(member_names, "iso"))
204 {
205 g_strfreev(member_names);
206 _ERROR("missing `iso`");
207 }
208
209 // a
210 if(!is_member(member_names, "a"))
211 {
212 g_strfreev(member_names);
213 _ERROR("missing `a`");
214 }
215 json_reader_read_member(reader, "a");
216 if(json_reader_count_elements(reader) != 3)
217 {
218 g_strfreev(member_names);
219 _ERROR("`a` with size != 3");
220 }
221 json_reader_end_member(reader);
222
223 // b
224 if(!is_member(member_names, "b"))
225 {
226 g_strfreev(member_names);
227 _ERROR("missing `b`");
228 }
229 json_reader_read_member(reader, "b");
230 if(json_reader_count_elements(reader) != 3)
231 {
232 g_strfreev(member_names);
233 _ERROR("`b` with size != 3");
234 }
235 json_reader_end_member(reader);
236
237 json_reader_end_element(reader);
238
239 g_strfreev(member_names);
240 } // profiles
241
242 json_reader_end_member(reader);
243 json_reader_end_element(reader);
244 } // models
245
246 json_reader_end_member(reader);
247 json_reader_end_element(reader);
248 } // makers
249
250 json_reader_end_member(reader);
251
252end:
253 if(reader) g_object_unref(reader);
254 return valid;
255}
256#undef _ERROR
257
258// Callers hold _noiseprofiles_parser_mutex.
268
278
280{
281 JsonReader *reader = NULL;
282 GList *result = NULL;
283 gboolean parser_locked = FALSE;
284
285 if(IS_NULL_PTR(cimg)) goto end;
286 if(cimg->camera_maker[0] == '\0' || cimg->camera_model[0] == '\0') goto end;
287
288 // Json-glib parser/tree access is shared process-wide and not re-entrant.
289 // Serialize lookup while creating and walking readers from the global parser --
290 // including the lazy first parse itself.
291 g_mutex_lock(&_noiseprofiles_parser_mutex);
292 parser_locked = TRUE;
293
294 JsonParser *parser = _get_parser_locked();
295 if(IS_NULL_PTR(parser)) goto end;
296
297 JsonNode *root = json_parser_get_root(parser);
298 if(IS_NULL_PTR(root)) goto end;
299
300 reader = json_reader_new(root);
301 if(IS_NULL_PTR(reader)) goto end;
302
303 if(!json_reader_read_member(reader, "noiseprofiles")) goto end;
304
305 // go through all makers
306 const int n_makers = json_reader_count_elements(reader);
307 for(int i = 0; i < n_makers; i++)
308 {
309 json_reader_read_element(reader, i);
310
311 json_reader_read_member(reader, "maker");
312
313 if(g_strstr_len(cimg->camera_maker, -1, json_reader_get_string_value(reader)))
314 {
315 // go through all models and check those
316 json_reader_end_member(reader);
317
318 json_reader_read_member(reader, "models");
319
320 const int n_models = json_reader_count_elements(reader);
321 for(int j = 0; j < n_models; j++)
322 {
323 json_reader_read_element(reader, j);
324
325 json_reader_read_member(reader, "model");
326
327 if(!g_strcmp0(cimg->camera_model, json_reader_get_string_value(reader)))
328 {
329 // we got a match, return at most bufsize elements
330 json_reader_end_member(reader);
331
332 json_reader_read_member(reader, "profiles");
333
334 const int n_profiles = json_reader_count_elements(reader);
335 for(int k = 0; k < n_profiles; k++)
336 {
337 dt_noiseprofile_t tmp_profile = { 0 };
338
339 json_reader_read_element(reader, k);
340
341 gchar** member_names = json_reader_list_members(reader);
342
343 // do we want to skip this entry?
344 if(is_member(member_names, "skip"))
345 {
346 json_reader_read_member(reader, "skip");
347 gboolean skip = json_reader_get_boolean_value(reader);
348 json_reader_end_member(reader);
349 if(skip)
350 {
351 json_reader_end_element(reader);
352 g_strfreev(member_names);
353 continue;
354 }
355 }
356
357 // maker
358 tmp_profile.maker = g_strdup(cimg->camera_maker);
359
360 // model
361 tmp_profile.model = g_strdup(cimg->camera_model);
362
363 // name
364 json_reader_read_member(reader, "name");
365 tmp_profile.name = g_strdup(json_reader_get_string_value(reader));
366 json_reader_end_member(reader);
367
368 // iso
369 json_reader_read_member(reader, "iso");
370 tmp_profile.iso = json_reader_get_double_value(reader);
371 json_reader_end_member(reader);
372
373 // a
374 json_reader_read_member(reader, "a");
375 for(int a = 0; a < 3; a++)
376 {
377 json_reader_read_element(reader, a);
378 tmp_profile.a[a] = json_reader_get_double_value(reader);
379 json_reader_end_element(reader);
380 }
381 json_reader_end_member(reader);
382
383 // b
384 json_reader_read_member(reader, "b");
385 for(int b = 0; b < 3; b++)
386 {
387 json_reader_read_element(reader, b);
388 tmp_profile.b[b] = json_reader_get_double_value(reader);
389 json_reader_end_element(reader);
390 }
391 json_reader_end_member(reader);
392
393 json_reader_end_element(reader);
394
395 // everything worked out, add tmp_profile to result
396 // dt_alloc_align: dt_noiseprofile_t is 64-aligned; dt_noiseprofile_free() pairs with it.
398 *new_profile = tmp_profile;
399 result = g_list_prepend(result, new_profile);
400
401 g_strfreev(member_names);
402 } // profiles
403
404 goto end;
405 }
406
407 json_reader_end_member(reader);
408 json_reader_end_element(reader);
409 } // models
410 }
411
412 json_reader_end_member(reader);
413 json_reader_end_element(reader);
414 } // makers
415
416 json_reader_end_member(reader);
417
418end:
419 if(!IS_NULL_PTR(reader)) g_object_unref(reader);
420 if(parser_locked) g_mutex_unlock(&_noiseprofiles_parser_mutex);
421 if(!IS_NULL_PTR(result)) result = g_list_sort(result, _sort_by_iso);
422 return result;
423}
424
425void dt_noiseprofile_free(gpointer data)
426{
427 dt_noiseprofile_t *profile = (dt_noiseprofile_t *)data;
428 dt_free(profile->name);
429 dt_free(profile->maker);
430 dt_free(profile->model);
431 dt_free_align(profile);
432}
433
435 const dt_noiseprofile_t *const p1, // the smaller iso
436 const dt_noiseprofile_t *const p2, // the larger iso (can't be == iso1)
437 dt_noiseprofile_t *out) // has iso initialized
438{
439 // stupid linear interpolation.
440 // to be confirmed for gaussian part.
441 const float t = CLAMP(
442 (float)(out->iso - p1->iso) / (float)(p2->iso - p1->iso),
443 0.0f, 1.0f);
444 for(int k=0; k<3; k++)
445 {
446 out->a[k] = (1.0f-t)*p1->a[k] + t*p2->a[k];
447 out->b[k] = (1.0f-t)*p1->b[k] + t*p2->b[k];
448 }
449}
450
451
452// clang-format off
453// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
454// vim: shiftwidth=2 expandtab tabstop=2 cindent
455// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
456// clang-format on
457
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
const int t
const dt_colormatrix_t dt_aligned_pixel_t out
void dt_control_log(const char *msg,...)
Definition control.c:824
void * dt_alloc_align(size_t size)
Allocate cacheline-aligned memory.
Definition darktable.c:508
GHashTable * names
GtkWidget* -> the name the application knows it by.
void dt_loc_get_datadir(char *datadir, size_t bufsize)
void dt_loc_get_user_config_dir(char *configdir, size_t bufsize)
@ DT_DEBUG_CONTROL
Definition logging.h:52
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
#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 macros.h:96
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
const dt_noiseprofile_t dt_noiseprofile_generic
void dt_noiseprofile_interpolate(const dt_noiseprofile_t *const p1, const dt_noiseprofile_t *const p2, dt_noiseprofile_t *out)
static GMutex _noiseprofiles_parser_mutex
void dt_noiseprofile_set_path(const char *alternative)
#define _ERROR(...)
void dt_noiseprofile_cleanup(void)
static gboolean _noiseprofiles_tried
int is_member(gchar **names, char *name)
void dt_noiseprofile_free(gpointer data)
#define DT_NOISE_PROFILE_VERSION
static char * _noiseprofiles_alternative
static gint _sort_by_iso(gconstpointer a, gconstpointer b)
static JsonParser * _noiseprofiles_parser
static JsonParser * _get_parser_locked(void)
static JsonParser * _noiseprofile_load(const char *alternative)
GList * dt_noiseprofile_get_matching(const dt_image_t *cimg)
static gboolean dt_noiseprofile_verify(JsonParser *parser)
#define DT_PATH_MAX
Buffer size for a filesystem path anywhere in Ansel.
Definition paths.h:57
void dt_concat_path_file(char destination[4096], const char path[4096], const char *const file)
Append a constant filename to a variable, stack-based, fixed-sized, directory, and add a / in-between...
Definition darktable.c:2674
const char * name
Definition pdf.h:90
char camera_model[64]
Definition image.h:380
char camera_maker[64]
Definition image.h:379
dt_aligned_pixel_t a
dt_aligned_pixel_t b
Telling the user something happened.