Skip to content

Commit f937bd7

Browse files
dregsistclaude
andcommitted
demosaic: rewrite ARI to paper-exact, optimize; honest about outcome
The previous ARI implementation had quality issues (color fringing, maze patterns on mire1) because it cut too many corners in the adaptive selection heuristic. This rewrites ARI to match the Monno 2015 paper line-by-line (structurally equivalent to the authors' MATLAB reference, within 0.002 dB CPSNR of a Python port). Quality is indeed better: - Kodak low-ISO CPSNR 39.94 dB, +0.8 dB over amaze - Chroma zipper smallest across all tested methods and ISOs - SIDD real raw iso_clean: +0.38 dB over amaze+dual However, the paper-exact pipeline is slow: - 30-80x slower than existing methods - 53s per 15 MP patch at 16 threads (standalone) - 113s through darktable pipeline on mire1.cr2 - Memory-bandwidth bound; OpenCL is unlikely to reach practical speeds Optimizations applied (all preserve paper-exact output): - Rolling sum box filter replacing double integral image - Row-major vertical pass for cache-line reuse on wide images - Paired GF sum sharing (~33 % box-call reduction) - Separable Gaussian 5x5 for criterion smoothing - Separable bicubic 7x7 for R/B residual upsample - Removed redundant refined-estimate recomputation in RI-H - Criterion-side adjacent-loop fusion Menon (2007) is separately included as a reference implementation. It is fast (~amaze speed), but benchmarking across Kodak-24 + SIDD-medium did not show a clear advantage over amaze - chroma zipper is similar or worse (especially cz_peak), and CPSNR is within 0.1 dB. Both algorithms looked strong on paper; extensive benchmarking showed neither clearly beats the existing methods across the tested conditions. I'll leave the merge decision to maintainers. The benchmark numbers themselves should be useful regardless; full writeup + reproducible scripts + Python reference ported from the original MATLAB are available in the sibling darktable-rawforge/demosaic_test/ directory (BENCHMARK.md). menon.c: keep the unused FIR/k_b locals (they're referenced via OpenMP firstprivate clauses) to fix -Werror=unused-variable with GCC 15. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 83fa112 commit f937bd7

3 files changed

Lines changed: 2087 additions & 616 deletions

File tree

src/iop/demosaic.c

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
#include <string.h>
4848
#include <time.h>
4949

50-
DT_MODULE_INTROSPECTION(7, dt_iop_demosaic_params_t)
50+
DT_MODULE_INTROSPECTION(8, dt_iop_demosaic_params_t)
5151

5252
#define DT_DEMOSAIC_XTRANS 1024 // masks for non-Bayer demosaic ops
5353
#define DT_DEMOSAIC_DUAL 2048 // masks for dual demosaicing methods
@@ -168,6 +168,7 @@ typedef struct dt_iop_demosaic_params_t
168168
float cs_center; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "sharp center"
169169
gboolean cs_enabled; // $DEFAULT: FALSE $DESCRIPTION: "capture sharpen"
170170
dt_iop_demosaic_ari_quality_t ari_quality; // $DEFAULT: DT_ARI_QUALITY_BALANCED $DESCRIPTION: "ARI quality"
171+
gboolean menon_refine; // $DEFAULT: TRUE $DESCRIPTION: "Menon refining step"
171172
} dt_iop_demosaic_params_t;
172173

173174
typedef struct dt_iop_demosaic_gui_data_t
@@ -182,6 +183,7 @@ typedef struct dt_iop_demosaic_gui_data_t
182183
GtkWidget *dual_thrs;
183184
GtkWidget *lmmse_refine;
184185
GtkWidget *ari_quality;
186+
GtkWidget *menon_refine;
185187
GtkWidget *cs_thrs;
186188
GtkWidget *cs_radius;
187189
GtkWidget *cs_boost;
@@ -274,6 +276,7 @@ typedef struct dt_iop_demosaic_data_t
274276
float cs_center;
275277
gboolean cs_enabled;
276278
dt_iop_demosaic_ari_quality_t ari_quality;
279+
gboolean menon_refine;
277280
} dt_iop_demosaic_data_t;
278281

279282
static gboolean _get_thumb_quality(const int width, const int height)
@@ -481,19 +484,49 @@ int legacy_params(dt_iop_module_t *self,
481484
return 0;
482485
}
483486

487+
typedef struct dt_iop_demosaic_params_v7_t
488+
{
489+
dt_iop_demosaic_greeneq_t green_eq;
490+
float median_thrs;
491+
dt_iop_demosaic_smooth_t color_smoothing;
492+
dt_iop_demosaic_method_t demosaicing_method;
493+
dt_iop_demosaic_lmmse_t lmmse_refine;
494+
float dual_thrs;
495+
float cs_radius;
496+
float cs_thrs;
497+
float cs_boost;
498+
int cs_iter;
499+
float cs_center;
500+
gboolean cs_enabled;
501+
dt_iop_demosaic_ari_quality_t ari_quality;
502+
} dt_iop_demosaic_params_v7_t;
503+
484504
if(old_version == 6)
485505
{
486506
const dt_iop_demosaic_params_v6_t *o = (dt_iop_demosaic_params_v6_t *)old_params;
487-
dt_iop_demosaic_params_t *n = malloc(sizeof(dt_iop_demosaic_params_t));
507+
dt_iop_demosaic_params_v7_t *n = malloc(sizeof(dt_iop_demosaic_params_v7_t));
488508
memcpy(n, o, sizeof *o);
489509
n->ari_quality = DT_ARI_QUALITY_BALANCED;
490510

491511
*new_params = n;
492-
*new_params_size = sizeof(dt_iop_demosaic_params_t);
512+
*new_params_size = sizeof(dt_iop_demosaic_params_v7_t);
493513
*new_version = 7;
494514
return 0;
495515
}
496516

517+
if(old_version == 7)
518+
{
519+
const dt_iop_demosaic_params_v7_t *o = (dt_iop_demosaic_params_v7_t *)old_params;
520+
dt_iop_demosaic_params_t *n = malloc(sizeof(dt_iop_demosaic_params_t));
521+
memcpy(n, o, sizeof *o);
522+
n->menon_refine = TRUE; // default: always refine (preserves previous behavior)
523+
524+
*new_params = n;
525+
*new_params_size = sizeof(dt_iop_demosaic_params_t);
526+
*new_version = 8;
527+
return 0;
528+
}
529+
497530
return 1;
498531
}
499532

@@ -906,7 +939,7 @@ void process(dt_iop_module_t *self,
906939
}
907940
}
908941
else if(method == DT_IOP_DEMOSAIC_MENON)
909-
menon_demosaic(t_out, t_in, width, t_rows, filters);
942+
menon_demosaic(t_out, t_in, width, t_rows, filters, d->menon_refine);
910943
else if(method == DT_IOP_DEMOSAIC_ARI)
911944
ari_demosaic(t_out, t_in, width, t_rows, filters, d->ari_quality);
912945
else if(method == DT_IOP_DEMOSAIC_RCD)
@@ -1420,6 +1453,7 @@ void commit_params(dt_iop_module_t *self,
14201453
d->dual_thrs = p->dual_thrs;
14211454
d->lmmse_refine = p->lmmse_refine;
14221455
d->ari_quality = p->ari_quality;
1456+
d->menon_refine = p->menon_refine;
14231457
dt_iop_demosaic_method_t use_method = p->demosaicing_method;
14241458
d->cs_radius = p->cs_radius;
14251459
d->cs_thrs = p->cs_thrs;
@@ -1634,6 +1668,7 @@ void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
16341668
gtk_widget_set_visible(g->dual_thrs, isdual);
16351669
gtk_widget_set_visible(g->lmmse_refine, islmmse);
16361670
gtk_widget_set_visible(g->ari_quality, use_method == DT_IOP_DEMOSAIC_ARI);
1671+
gtk_widget_set_visible(g->menon_refine, use_method == DT_IOP_DEMOSAIC_MENON);
16371672

16381673
const gboolean monomode = use_method == DT_IOP_DEMOSAIC_PASSTHROUGH_MONOCHROME
16391674
|| use_method == DT_IOP_DEMOSAIC_PASSTHR_MONOX;
@@ -1830,6 +1865,11 @@ void gui_init(dt_iop_module_t *self)
18301865
"balanced: MLRI + guided-filter RI adaptive selection\n"
18311866
"quality: full 3-candidate adaptive selection"));
18321867

1868+
g->menon_refine = dt_bauhaus_toggle_from_params(self, "menon_refine");
1869+
gtk_widget_set_tooltip_text(g->menon_refine,
1870+
_("Menon refining step: iterative refinement of G, R, and B channels.\n"
1871+
"Improves quality at the cost of slightly more processing time."));
1872+
18331873
g->color_smoothing = dt_bauhaus_combobox_from_params(self, "color_smoothing");
18341874
gtk_widget_set_tooltip_text(g->color_smoothing, _("how many color smoothing median steps after demosaicing"));
18351875

0 commit comments

Comments
 (0)