Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
hotpixels.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2011 Antony Dovgal.
4 Copyright (C) 2011 Bruce Guenter.
5 Copyright (C) 2011-2012 Henrik Andersson.
6 Copyright (C) 2011-2013, 2016 johannes hanika.
7 Copyright (C) 2011 Olivier Tribout.
8 Copyright (C) 2011 Robert Bieber.
9 Copyright (C) 2011 Rostyslav Pidgornyi.
10 Copyright (C) 2011-2016, 2019 Tobias Ellinghaus.
11 Copyright (C) 2012-2013 Pascal de Bruijn.
12 Copyright (C) 2012 Richard Wonka.
13 Copyright (C) 2012, 2014 Ulrich Pegelow.
14 Copyright (C) 2013, 2020 Aldric Renaudin.
15 Copyright (C) 2014, 2016 Dan Torop.
16 Copyright (C) 2014-2016 Roman Lebedev.
17 Copyright (C) 2015-2016 Pedro Côrte-Real.
18 Copyright (C) 2017 Heiko Bauke.
19 Copyright (C) 2018, 2020, 2023, 2025-2026 Aurélien PIERRE.
20 Copyright (C) 2018 Edgardo Hoszowski.
21 Copyright (C) 2018 Maurizio Paglia.
22 Copyright (C) 2018, 2020, 2022 Pascal Obry.
23 Copyright (C) 2018 rawfiner.
24 Copyright (C) 2018 Stéphane Gourichon.
25 Copyright (C) 2019 Andreas Schneider.
26 Copyright (C) 2019-2020, 2022 Diederik Ter Rahe.
27 Copyright (C) 2019, 2022 Hanno Schwalm.
28 Copyright (C) 2020 Hubert Kowalski.
29 Copyright (C) 2020 Ralf Brown.
30 Copyright (C) 2022 Martin Bařinka.
31 Copyright (C) 2022 Philipp Lutz.
32
33 darktable is free software: you can redistribute it and/or modify
34 it under the terms of the GNU General Public License as published by
35 the Free Software Foundation, either version 3 of the License, or
36 (at your option) any later version.
37
38 darktable is distributed in the hope that it will be useful,
39 but WITHOUT ANY WARRANTY; without even the implied warranty of
40 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
41 GNU General Public License for more details.
42
43 You should have received a copy of the GNU General Public License
44 along with darktable. If not, see <http://www.gnu.org/licenses/>.
45*/
46
47#ifdef HAVE_CONFIG_H
48#include "common/darktable.h"
49#include "config.h"
50#endif
51#include "bauhaus/bauhaus.h"
52#include "common/imagebuf.h"
53#include "control/control.h"
54#include "develop/imageop.h"
56#include "develop/imageop_gui.h"
57#include "develop/develop.h"
58#include "dtgtk/resetlabel.h"
59
60#include "gui/gtk.h"
61#include "iop/iop_api.h"
62
63#ifdef HAVE_OPENCL
64#include "common/opencl.h"
65#endif
66
67#include <gtk/gtk.h>
68#include <stdlib.h>
69
71
73{
74 float strength; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.25
75 float threshold; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.05
76 gboolean markfixed; // $DEFAULT: FALSE $DESCRIPTION: "mark fixed pixels"
77 gboolean permissive; // $DEFAULT: FALSE $DESCRIPTION: "detect by 3 neighbors"
79
85
93
99
100
101const char *name()
102{
103 return _("hot pixels");
104}
105
106const char **description(struct dt_iop_module_t *self)
107{
108 return dt_iop_set_description(self, _("remove abnormally bright pixels by dampening them with neighbours"),
109 _("corrective"),
110 _("linear, raw, scene-referred"),
111 _("reconstruction, raw"),
112 _("linear, raw, scene-referred"));
113}
114
115
117{
118 return IOP_GROUP_REPAIR;
119}
120
125
127{
128 return IOP_CS_RAW;
129}
130
133{
134 default_input_format(self, pipe, piece, dsc);
135 dsc->channels = 1;
137}
138
139static inline void hotpixels_testone(const float *const in, const float mid, const int offset,
140 int *const count, float *const maxin)
141{
142 const float other = in[offset];
143 if(mid > other)
144 {
145 (*count)++;
146 if(other > *maxin) *maxin = other;
147 }
148}
149
150/* Detect hot sensor pixels based on the 4 surrounding sites. Pixels
151 * having 3 or 4 (depending on permissive setting) surrounding pixels that
152 * than value*multiplier are considered "hot", and are replaced by the maximum of
153 * the neighbour pixels. The permissive variant allows for
154 * correcting pairs of hot pixels in adjacent sites. Replacement using
155 * the maximum produces fewer artifacts when inadvertently replacing
156 * non-hot pixels.
157 * This is the Bayer sensor variant. */
160 const void *const ivoid, void *const ovoid,
161 const dt_iop_roi_t *const roi_out)
162{
163 const float threshold = data->threshold;
164 const float multiplier = data->multiplier;
165 const int min_neighbours = data->permissive ? 3 : 4;
166 const int width = roi_out->width;
167 const int widthx2 = width * 2;
169 for(int row = 2; row < roi_out->height - 2; row++)
170 {
171 const float *in = (float *)ivoid + (size_t)width * row + 2;
172 float *out = (float *)ovoid + (size_t)width * row + 2;
173 for(int col = 2; col < width - 2; col++, in++, out++)
174 {
175 float mid = *in * multiplier;
176 if(*in > threshold)
177 {
178 int count = 0;
179 float maxin = 0.0;
180 hotpixels_testone(in, mid, -2, &count, &maxin);
181 hotpixels_testone(in, mid, -widthx2, &count, &maxin);
182 hotpixels_testone(in, mid, +2, &count, &maxin);
183 hotpixels_testone(in, mid, +widthx2, &count, &maxin);
184 if(count >= min_neighbours)
185 {
186 *out = maxin;
187 }
188 }
189 }
190 }
191}
192
193/* X-Trans sensor equivalent of process_bayer(). */
196 const void *const ivoid, void *const ovoid,
197 const dt_iop_roi_t *const roi_out, const uint8_t (*const xtrans)[6])
198{
199 // for each cell of sensor array, pre-calculate, a list of the x/y
200 // offsets of the four radially nearest pixels of the same color
201 int offsets[6][6][4][2];
202 // increasing offsets from pixel to find nearest like-colored pixels
203 const int search[20][2] = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 }, { -1, -1 }, { -1, 1 }, { 1, -1 },
204 { 1, 1 }, { -2, 0 }, { 2, 0 }, { 0, -2 }, { 0, 2 }, { -2, -1 }, { -2, 1 },
205 { 2, -1 }, { 2, 1 }, { -1, -2 }, { 1, -2 }, { -1, 2 }, { 1, 2 } };
206
207 for(int j = 0; j < 6; ++j)
208 {
209 for(int i = 0; i < 6; ++i)
210 {
211 const uint8_t c = FCxtrans(j, i, roi_out, xtrans);
212 for(int s = 0, found = 0; s < 20 && found < 4; ++s)
213 {
214 if(c == FCxtrans(j + search[s][1], i + search[s][0], roi_out, xtrans))
215 {
216 offsets[j][i][found][0] = search[s][0];
217 offsets[j][i][found][1] = search[s][1];
218 ++found;
219 }
220 }
221 }
222 }
223
224 const float threshold = data->threshold;
225 const float multiplier = data->multiplier;
226 const int min_neighbours = data->permissive ? 3 : 4;
227 const int width = roi_out->width;
229 for(int row = 2; row < roi_out->height - 2; row++)
230 {
231 const float *in = (float *)ivoid + (size_t)width * row + 2;
232 float *out = (float *)ovoid + (size_t)width * row + 2;
233 for(int col = 2; col < width - 2; col++, in++, out++)
234 {
235 float mid = *in * multiplier;
236 if(*in > threshold)
237 {
238 int count = 0;
239 float maxin = 0.0;
240 for(int n = 0; n < 4; ++n)
241 {
242 int xx = offsets[row % 6][col % 6][n][0];
243 int yy = offsets[row % 6][col % 6][n][1];
244 float other = *(in + xx + yy * (size_t)width);
245 if(mid > other)
246 {
247 count++;
248 if(other > maxin) maxin = other;
249 }
250 }
251 // NOTE: it seems that detecting by 2 neighbors would help for extreme cases
252 if(count >= min_neighbours)
253 {
254 *out = maxin;
255 }
256 }
257 }
258 }
259}
260
261int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
262 void *const ovoid)
263{
264 const dt_iop_roi_t *const roi_out = &piece->roi_out;
266
267 // The processing loop should output only a few pixels, so just copy everything first
268 dt_iop_image_copy_by_size(ovoid, ivoid, roi_out->width, roi_out->height, 1);
269
270 if(piece->dsc_in.filters == 9u)
271 {
272 process_xtrans(data, ivoid, ovoid, roi_out, (const uint8_t(*const)[6])piece->dsc_in.xtrans);
273 }
274 else
275 {
276 process_bayer(data, ivoid, ovoid, roi_out);
277 }
278
279 return 0;
280}
281
282#ifdef HAVE_OPENCL
283int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
284 const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
285{
286 const dt_iop_roi_t *const roi_out = &piece->roi_out;
287 const dt_iop_hotpixels_data_t *const data = (dt_iop_hotpixels_data_t *)piece->data;
289
290 const int devid = pipe->devid;
291 const int min_neighbours = data->permissive ? 3 : 4;
292 const int width = roi_out->width;
293 const int height = roi_out->height;
294 const uint32_t filters = piece->dsc_in.filters;
295 cl_mem dev_xtrans = NULL;
296 cl_int err = 0;
297 int kernel = filters == 9u ? gd->kernel_hotpixels_xtrans : gd->kernel_hotpixels_bayer;
298
299 if(filters == 9u)
300 {
301 dev_xtrans = dt_opencl_copy_host_to_device_constant(devid, sizeof(piece->dsc_in.xtrans), (void *)piece->dsc_in.xtrans);
302 if(IS_NULL_PTR(dev_xtrans)) goto error;
303 }
304
305 size_t sizes[3] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
306 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_in);
307 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_out);
308 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&width);
309 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), (void *)&height);
310 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(float), (void *)&data->threshold);
311 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float), (void *)&data->multiplier);
312 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), (void *)&min_neighbours);
313
314 if(filters == 9u)
315 {
316 const int rx = roi_out->x;
317 const int ry = roi_out->y;
318 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), (void *)&rx);
319 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(int), (void *)&ry);
320 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(cl_mem), (void *)&dev_xtrans);
321 }
322
323 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
324 if(err != CL_SUCCESS) goto error;
325
327 return TRUE;
328
329error:
331 return FALSE;
332}
333
335{
336 const int program = 2; // basic.cl
338 module->data = gd;
339 gd->kernel_hotpixels_bayer = dt_opencl_create_kernel(program, "hotpixels_bayer");
340 gd->kernel_hotpixels_xtrans = dt_opencl_create_kernel(program, "hotpixels_xtrans");
341}
342
350#endif
351
352/* Single source of truth: hot-pixel detection works on the raw CFA mosaic, so it requires a
353 * mosaiced buffer (needs_demosaic), NOT merely the RAW flag — an already-demosaiced raw
354 * (sRAW / linear DNG) has no CFA to analyse. Shared by reload_defaults() and force_enable(). */
355static gboolean _hotpixels_supported(const dt_image_t *img)
356{
358}
359
361{
362 const dt_image_t *img = &module->dev->image_storage;
363 const gboolean enabled = _hotpixels_supported(img);
364 // can't be switched on for non-mosaiced images:
365 module->hide_enable_button = !enabled;
366 dt_iop_fmt_log(module, "reload_defaults: class=%s needs_demosaic=%d mono=%d -> hide_enable=%d",
369}
370
371gboolean force_enable(struct dt_iop_module_t *self, const gboolean current_state)
372{
373 // History sanitization: a hotpixels entry pasted onto a non-mosaic/monochrome image is forced
374 // off here. The runtime-only "strength == 0" no-op shortcut stays in commit_params().
375 const gboolean active = _hotpixels_supported(&self->dev->image_storage);
376 const gboolean state = current_state && active;
377 dt_iop_fmt_log(self, "force_enable: class=%s supported=%d current=%d -> %d",
379 active, current_state, state);
380 return state;
381}
382
385{
388 d->filters = pipe->dev->image_storage.dsc.filters;
389 d->multiplier = p->strength / 2.0;
390 d->threshold = p->threshold;
391 d->permissive = p->permissive;
392
393 // Image-type gating is handled at history level (force_enable/reload_defaults). Only the
394 // runtime, param-driven no-op shortcut remains here: a zero strength does nothing.
395 if(p->strength == 0.0) piece->enabled = 0;
396 dt_iop_fmt_log(self, "commit: class=%s filters=%u strength=%.3f -> enabled=%d",
398 d->filters, p->strength, piece->enabled);
399}
400
402{
404 piece->data_size = sizeof(dt_iop_hotpixels_data_t);
405}
406
408{
409 dt_free_align(piece->data);
410 piece->data = NULL;
411}
412
413
415{
418 gtk_toggle_button_set_active(g->permissive, p->permissive);
419
420 const dt_image_t *img = &self->dev->image_storage;
421 const gboolean enabled = _hotpixels_supported(img);
422 // can't be switched on for non-mosaiced images:
423 self->hide_enable_button = !enabled;
424
425 gtk_stack_set_visible_child_name(GTK_STACK(self->widget), self->hide_enable_button ? "non_raw" : "raw");
426}
427
429{
431
432 GtkWidget *box_raw = self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
433
434 g->threshold = dt_bauhaus_slider_from_params(self, N_("threshold"));
435 dt_bauhaus_slider_set_digits(g->threshold, 4);
436 gtk_widget_set_tooltip_text(g->threshold, _("lower threshold for hot pixel"));
437
438 g->strength = dt_bauhaus_slider_from_params(self, N_("strength"));
439 dt_bauhaus_slider_set_digits(g->strength, 4);
440 gtk_widget_set_tooltip_text(g->strength, _("strength of hot pixel correction"));
441
442 // 3 neighbours
443 g->permissive = GTK_TOGGLE_BUTTON(dt_bauhaus_toggle_from_params(self, "permissive"));
444
445 // start building top level widget
446 self->widget = gtk_stack_new();
447 gtk_stack_set_homogeneous(GTK_STACK(self->widget), FALSE);
448
449 GtkWidget *label_non_raw = dt_ui_label_new(_("hot pixel correction\nonly works for raw images."));
450
451 gtk_stack_add_named(GTK_STACK(self->widget), label_non_raw, "non_raw");
452 gtk_stack_add_named(GTK_STACK(self->widget), box_raw, "raw");
453}
454
455// clang-format off
456// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
457// vim: shiftwidth=2 expandtab tabstop=2 cindent
458// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
459// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3534
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
@ IOP_CS_RAW
const float threshold
const dt_colormatrix_t dt_aligned_pixel_t out
static const int row
dt_image_pipe_class_t dt_image_pipe_class(const dt_image_t *img)
const char * dt_image_pipe_class_name(const dt_image_pipe_class_t klass)
gboolean dt_image_is_monochrome(const dt_image_t *img)
gboolean dt_image_needs_demosaic(const dt_image_t *img)
#define dt_free_align(ptr)
Definition darktable.h:481
static void * dt_calloc_align(size_t size)
Definition darktable.h:488
#define dt_free(ptr)
Definition darktable.h:456
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define __DT_CLONE_TARGETS__
Definition darktable.h:367
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
#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
static int FCxtrans(const int row, const int col, global const unsigned char(*const xtrans)[6])
void dt_iop_params_t
Definition dev_history.h:41
void default_input_format(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_iop_buffer_dsc_t *dsc)
Definition format.c:57
void dt_iop_buffer_dsc_update_bpp(dt_iop_buffer_dsc_t *dsc)
Definition format.c:28
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
static GtkWidget * dt_ui_label_new(const gchar *str)
Definition gtk.h:461
const char ** description(struct dt_iop_module_t *self)
Definition hotpixels.c:106
int default_group()
Definition hotpixels.c:116
void reload_defaults(dt_iop_module_t *module)
Definition hotpixels.c:360
void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *params, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition hotpixels.c:383
void gui_update(dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
Definition hotpixels.c:414
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition hotpixels.c:401
const char * name()
Definition hotpixels.c:101
static void hotpixels_testone(const float *const in, const float mid, const int offset, int *const count, float *const maxin)
Definition hotpixels.c:139
void gui_init(dt_iop_module_t *self)
Definition hotpixels.c:428
static __DT_CLONE_TARGETS__ void process_bayer(const dt_iop_hotpixels_data_t *data, const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_out)
Definition hotpixels.c:159
void cleanup_global(dt_iop_module_so_t *module)
Definition hotpixels.c:343
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition hotpixels.c:126
void input_format(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_iop_buffer_dsc_t *dsc)
Definition hotpixels.c:131
int flags()
Definition hotpixels.c:121
int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid)
Definition hotpixels.c:261
gboolean force_enable(struct dt_iop_module_t *self, const gboolean current_state)
Definition hotpixels.c:371
static __DT_CLONE_TARGETS__ void process_xtrans(const dt_iop_hotpixels_data_t *data, const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_out, const uint8_t(*const xtrans)[6])
Definition hotpixels.c:195
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition hotpixels.c:407
void init_global(dt_iop_module_so_t *module)
Definition hotpixels.c:334
int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
Definition hotpixels.c:283
static gboolean _hotpixels_supported(const dt_image_t *img)
Definition hotpixels.c:355
static void dt_iop_image_copy_by_size(float *const __restrict__ out, const float *const __restrict__ in, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.h:87
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:3141
#define dt_iop_fmt_log(module, fmt,...)
Debug helper to trace a module's input-format-driven decisions on the -d pipe channel (DT_DEBUG_PIPE)...
Definition imageop.h:453
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:167
@ IOP_FLAGS_ONE_INSTANCE
Definition imageop.h:172
@ IOP_GROUP_REPAIR
Definition imageop.h:140
#define IOP_GUI_ALLOC(module)
Definition imageop.h:599
GtkWidget * dt_bauhaus_toggle_from_params(dt_iop_module_t *self, const char *param)
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
Definition imageop_gui.c:77
void *const ovoid
static float kernel(const float *x, const float *y)
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2136
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2030
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2332
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2073
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2127
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2383
#define ROUNDUPDHT(a, b)
Definition opencl.h:82
#define ROUNDUPDWD(a, b)
Definition opencl.h:81
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
struct dt_develop_t * dev
dt_image_t image_storage
Definition develop.h:259
dt_iop_buffer_dsc_t dsc
Definition image.h:337
uint32_t filters
Definition format.h:60
unsigned int channels
Definition format.h:54
uint8_t xtrans[6][6]
Definition format.h:70
GtkToggleButton * permissive
Definition hotpixels.c:83
dt_iop_global_data_t * data
Definition imageop.h:233
int32_t hide_enable_button
Definition imageop.h:262
GtkWidget * widget
Definition imageop.h:337
struct dt_develop_t * dev
Definition imageop.h:296
dt_iop_gui_data_t * gui_data
Definition imageop.h:311
dt_iop_global_data_t * global_data
Definition imageop.h:314
dt_iop_params_t * params
Definition imageop.h:307
Region of interest passed through the pixelpipe.
Definition imageop.h:72