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 "common/darktable.h"
30#include "control/control.h"
31
32// bump this when the noiseprofiles are getting a different layout or meaning (raw-raw data, ...)
33#define DT_NOISE_PROFILE_VERSION 0
34
35const dt_noiseprofile_t dt_noiseprofile_generic = {N_("generic poissonian"), "", "", 0, {0.0001f, 0.0001f, 0.0001}, {0.0f, 0.0f, 0.0f}};
37
38static gboolean dt_noiseprofile_verify(JsonParser *parser);
39
40JsonParser *dt_noiseprofile_init(const char *alternative)
41{
42 GError *error = NULL;
43 char filename[PATH_MAX] = { 0 };
44
45 if(IS_NULL_PTR(alternative))
46 {
47 char dir[PATH_MAX] = { 0 };
48
49 dt_loc_get_user_config_dir(dir, sizeof(dir));
50 dt_concat_path_file(filename, dir, "noiseprofiles.json");
51
52 if(!g_file_test(filename, G_FILE_TEST_EXISTS))
53 {
54 dt_loc_get_datadir(dir, sizeof(dir));
55 dt_concat_path_file(filename, dir, "noiseprofiles.json");
56 }
57 }
58 else
59 g_strlcpy(filename, alternative, sizeof(filename));
60
61 dt_print(DT_DEBUG_CONTROL, "[noiseprofile] loading noiseprofiles from `%s'\n", filename);
62 if(!g_file_test(filename, G_FILE_TEST_EXISTS)) return NULL;
63
64 // TODO: shall we cache the content? for now this looks fast enough(TM)
65 JsonParser *parser = json_parser_new();
66 if(!json_parser_load_from_file(parser, filename, &error))
67 {
68 fprintf(stderr, "[noiseprofile] error: parsing json from `%s' failed\n%s\n", filename, error->message);
69 g_error_free(error);
70 g_object_unref(parser);
71 return NULL;
72 }
73
74 // run over the file once to verify that it is sane
75 if(!dt_noiseprofile_verify(parser))
76 {
77 dt_control_log(_("noiseprofile file `%s' is not valid"), filename);
78 fprintf(stderr, "[noiseprofile] error: `%s' is not a valid noiseprofile file. run with -d control for details\n", filename);
79 g_object_unref(parser);
80 return NULL;
81 }
82
83 return parser;
84}
85
86int is_member(gchar** names, char* name)
87{
88 while(*names)
89 {
90 if(!g_strcmp0(*names, name))
91 return 1;
92 names++;
93 }
94 return 0;
95}
96
97static gint _sort_by_iso(gconstpointer a, gconstpointer b)
98{
99 const dt_noiseprofile_t *profile_a = (dt_noiseprofile_t *)a;
100 const dt_noiseprofile_t *profile_b = (dt_noiseprofile_t *)b;
101
102 return profile_a->iso - profile_b->iso;
103}
104
105#define _ERROR(...) {\
106 dt_print(DT_DEBUG_CONTROL, "[noiseprofile] error: " );\
107 dt_print(DT_DEBUG_CONTROL, __VA_ARGS__);\
108 dt_print(DT_DEBUG_CONTROL, "\n");\
109 valid = FALSE;\
110 goto end;\
111 }
112
113static gboolean dt_noiseprofile_verify(JsonParser *parser)
114{
115 JsonReader *reader = NULL;
116 gboolean valid = TRUE;
117
118 dt_print(DT_DEBUG_CONTROL, "[noiseprofile] verifying noiseprofile file\n");
119
120 JsonNode *root = json_parser_get_root(parser);
121 if(IS_NULL_PTR(root)) _ERROR("can't get the root node");
122
123 reader = json_reader_new(root);
124
125 if(!json_reader_read_member(reader, "version")) _ERROR("can't find file version.");
126
127 // check the file version
128 const int version = json_reader_get_int_value(reader);
129 json_reader_end_member(reader);
130
131 if(version != DT_NOISE_PROFILE_VERSION) _ERROR("file version is not what this code understands");
132
133 if(!json_reader_read_member(reader, "noiseprofiles")) _ERROR("can't find `noiseprofiles' entry.");
134
135 if(!json_reader_is_array(reader)) _ERROR("`noiseprofiles' is supposed to be an array");
136
137 // go through all makers
138 const int n_makers = json_reader_count_elements(reader);
139
140 for(int i = 0; i < n_makers; i++)
141 {
142 if(!json_reader_read_element(reader, i)) _ERROR("can't access maker at position %d / %d", i+1, n_makers);
143
144 if(!json_reader_read_member(reader, "maker")) _ERROR("missing `maker`");
145
146 // go through all models and check those
147 json_reader_end_member(reader);
148
149 if(!json_reader_read_member(reader, "models")) _ERROR("missing `models`");
150
151 const int n_models = json_reader_count_elements(reader);
152
153 for(int j = 0; j < n_models; j++)
154 {
155 if(!json_reader_read_element(reader, j)) _ERROR("can't access model at position %d / %d", j+1, n_models);
156
157 if(!json_reader_read_member(reader, "model")) _ERROR("missing `model`");
158
159 json_reader_end_member(reader);
160
161 if(!json_reader_read_member(reader, "profiles")) _ERROR("missing `profiles`");
162
163 const int n_profiles = json_reader_count_elements(reader);
164 for(int k = 0; k < n_profiles; k++)
165 {
166 if(!json_reader_read_element(reader, k)) _ERROR("can't access profile at position %d / %d", k+1, n_profiles);
167
168 gchar** member_names = json_reader_list_members(reader);
169
170 // name
171 if(!is_member(member_names, "name"))
172 {
173 g_strfreev(member_names);
174 _ERROR("missing `name`");
175 }
176
177 // iso
178 if(!is_member(member_names, "iso"))
179 {
180 g_strfreev(member_names);
181 _ERROR("missing `iso`");
182 }
183
184 // a
185 if(!is_member(member_names, "a"))
186 {
187 g_strfreev(member_names);
188 _ERROR("missing `a`");
189 }
190 json_reader_read_member(reader, "a");
191 if(json_reader_count_elements(reader) != 3)
192 {
193 g_strfreev(member_names);
194 _ERROR("`a` with size != 3");
195 }
196 json_reader_end_member(reader);
197
198 // b
199 if(!is_member(member_names, "b"))
200 {
201 g_strfreev(member_names);
202 _ERROR("missing `b`");
203 }
204 json_reader_read_member(reader, "b");
205 if(json_reader_count_elements(reader) != 3)
206 {
207 g_strfreev(member_names);
208 _ERROR("`b` with size != 3");
209 }
210 json_reader_end_member(reader);
211
212 json_reader_end_element(reader);
213
214 g_strfreev(member_names);
215 } // profiles
216
217 json_reader_end_member(reader);
218 json_reader_end_element(reader);
219 } // models
220
221 json_reader_end_member(reader);
222 json_reader_end_element(reader);
223 } // makers
224
225 json_reader_end_member(reader);
226
227end:
228 if(reader) g_object_unref(reader);
229 return valid;
230}
231#undef _ERROR
232
234{
235 JsonParser *parser = darktable.noiseprofile_parser;
236 JsonReader *reader = NULL;
237 GList *result = NULL;
238 gboolean parser_locked = FALSE;
239
240 if(IS_NULL_PTR(cimg)) goto end;
241 if(cimg->camera_maker[0] == '\0' || cimg->camera_model[0] == '\0') goto end;
242 if(IS_NULL_PTR(parser)) goto end;
243
244 // Json-glib parser/tree access is shared process-wide and not re-entrant.
245 // Serialize lookup while creating and walking readers from the global parser.
246 g_mutex_lock(&_noiseprofiles_parser_mutex);
247 parser_locked = TRUE;
248
249 JsonNode *root = json_parser_get_root(parser);
250 if(IS_NULL_PTR(root)) goto end;
251
252 reader = json_reader_new(root);
253 if(IS_NULL_PTR(reader)) goto end;
254
255 if(!json_reader_read_member(reader, "noiseprofiles")) goto end;
256
257 // go through all makers
258 const int n_makers = json_reader_count_elements(reader);
259 for(int i = 0; i < n_makers; i++)
260 {
261 json_reader_read_element(reader, i);
262
263 json_reader_read_member(reader, "maker");
264
265 if(g_strstr_len(cimg->camera_maker, -1, json_reader_get_string_value(reader)))
266 {
267 // go through all models and check those
268 json_reader_end_member(reader);
269
270 json_reader_read_member(reader, "models");
271
272 const int n_models = json_reader_count_elements(reader);
273 for(int j = 0; j < n_models; j++)
274 {
275 json_reader_read_element(reader, j);
276
277 json_reader_read_member(reader, "model");
278
279 if(!g_strcmp0(cimg->camera_model, json_reader_get_string_value(reader)))
280 {
281 // we got a match, return at most bufsize elements
282 json_reader_end_member(reader);
283
284 json_reader_read_member(reader, "profiles");
285
286 const int n_profiles = json_reader_count_elements(reader);
287 for(int k = 0; k < n_profiles; k++)
288 {
289 dt_noiseprofile_t tmp_profile = { 0 };
290
291 json_reader_read_element(reader, k);
292
293 gchar** member_names = json_reader_list_members(reader);
294
295 // do we want to skip this entry?
296 if(is_member(member_names, "skip"))
297 {
298 json_reader_read_member(reader, "skip");
299 gboolean skip = json_reader_get_boolean_value(reader);
300 json_reader_end_member(reader);
301 if(skip)
302 {
303 json_reader_end_element(reader);
304 g_strfreev(member_names);
305 continue;
306 }
307 }
308
309 // maker
310 tmp_profile.maker = g_strdup(cimg->camera_maker);
311
312 // model
313 tmp_profile.model = g_strdup(cimg->camera_model);
314
315 // name
316 json_reader_read_member(reader, "name");
317 tmp_profile.name = g_strdup(json_reader_get_string_value(reader));
318 json_reader_end_member(reader);
319
320 // iso
321 json_reader_read_member(reader, "iso");
322 tmp_profile.iso = json_reader_get_double_value(reader);
323 json_reader_end_member(reader);
324
325 // a
326 json_reader_read_member(reader, "a");
327 for(int a = 0; a < 3; a++)
328 {
329 json_reader_read_element(reader, a);
330 tmp_profile.a[a] = json_reader_get_double_value(reader);
331 json_reader_end_element(reader);
332 }
333 json_reader_end_member(reader);
334
335 // b
336 json_reader_read_member(reader, "b");
337 for(int b = 0; b < 3; b++)
338 {
339 json_reader_read_element(reader, b);
340 tmp_profile.b[b] = json_reader_get_double_value(reader);
341 json_reader_end_element(reader);
342 }
343 json_reader_end_member(reader);
344
345 json_reader_end_element(reader);
346
347 // everything worked out, add tmp_profile to result
348 dt_noiseprofile_t *new_profile = (dt_noiseprofile_t *)malloc(sizeof(dt_noiseprofile_t));
349 *new_profile = tmp_profile;
350 result = g_list_prepend(result, new_profile);
351
352 g_strfreev(member_names);
353 } // profiles
354
355 goto end;
356 }
357
358 json_reader_end_member(reader);
359 json_reader_end_element(reader);
360 } // models
361 }
362
363 json_reader_end_member(reader);
364 json_reader_end_element(reader);
365 } // makers
366
367 json_reader_end_member(reader);
368
369end:
370 if(!IS_NULL_PTR(reader)) g_object_unref(reader);
371 if(parser_locked) g_mutex_unlock(&_noiseprofiles_parser_mutex);
372 if(!IS_NULL_PTR(result)) result = g_list_sort(result, _sort_by_iso);
373 return result;
374}
375
376void dt_noiseprofile_free(gpointer data)
377{
378 dt_noiseprofile_t *profile = (dt_noiseprofile_t *)data;
379 dt_free(profile->name);
380 dt_free(profile->maker);
381 dt_free(profile->model);
382 dt_free(profile);
383}
384
386 const dt_noiseprofile_t *const p1, // the smaller iso
387 const dt_noiseprofile_t *const p2, // the larger iso (can't be == iso1)
388 dt_noiseprofile_t *out) // has iso initialized
389{
390 // stupid linear interpolation.
391 // to be confirmed for gaussian part.
392 const float t = CLAMP(
393 (float)(out->iso - p1->iso) / (float)(p2->iso - p1->iso),
394 0.0f, 1.0f);
395 for(int k=0; k<3; k++)
396 {
397 out->a[k] = (1.0f-t)*p1->a[k] + t*p2->a[k];
398 out->b[k] = (1.0f-t)*p1->b[k] + t*p2->b[k];
399 }
400}
401
402
403// clang-format off
404// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
405// vim: shiftwidth=2 expandtab tabstop=2 cindent
406// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
407// clang-format on
408
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 dt_colormatrix_t dt_aligned_pixel_t out
char * name
void dt_control_log(const char *msg,...)
Definition control.c:761
void dt_concat_path_file(char destination[PATH_MAX], const char path[PATH_MAX], const char *const file)
Definition darktable.c:1889
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
@ DT_DEBUG_CONTROL
Definition darktable.h:716
#define dt_free(ptr)
Definition darktable.h:456
#define PATH_MAX
Definition darktable.h:1062
#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
void dt_loc_get_datadir(char *datadir, size_t bufsize)
void dt_loc_get_user_config_dir(char *configdir, size_t bufsize)
const int t
float *const restrict const size_t k
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
#define _ERROR(...)
int is_member(gchar **names, char *name)
void dt_noiseprofile_free(gpointer data)
JsonParser * dt_noiseprofile_init(const char *alternative)
#define DT_NOISE_PROFILE_VERSION
static gint _sort_by_iso(gconstpointer a, gconstpointer b)
GList * dt_noiseprofile_get_matching(const dt_image_t *cimg)
static gboolean dt_noiseprofile_verify(JsonParser *parser)
JsonParser * noiseprofile_parser
Definition darktable.h:768
char camera_model[64]
Definition image.h:298
char camera_maker[64]
Definition image.h:297
dt_aligned_pixel_t a
dt_aligned_pixel_t b