Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/common/ai/restore.c
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ dt_restore_context_t *dt_restore_load_denoise(dt_restore_env_t *env)

dt_restore_sensor_class_t dt_restore_classify_sensor(const dt_image_t *img)
{
if(!img || !(img->flags & DT_IMAGE_RAW))
if(!img || !(img->flags & (DT_IMAGE_RAW | DT_IMAGE_S_RAW)))
return DT_RESTORE_SENSOR_CLASS_UNSUPPORTED;
if(img->flags & (DT_IMAGE_MONOCHROME | DT_IMAGE_MONOCHROME_BAYER))
return DT_RESTORE_SENSOR_CLASS_UNSUPPORTED;
Expand Down
65 changes: 43 additions & 22 deletions src/common/ai/restore_raw_linear.c
Original file line number Diff line number Diff line change
Expand Up @@ -1061,19 +1061,20 @@ int dt_restore_raw_linear_preview_piped(dt_restore_context_t *ctx,
g_free(tile_out);
return 1;
}
const size_t pixel_sz = is_uint16 ? 2 : 4;
// classify (not filters==0) so 4BAYER routes as LINEAR like upstream
const uint32_t mip_channels = img->buf_dsc.channels;
const gboolean is_linear =
(dt_restore_classify_sensor(img) == DT_RESTORE_SENSOR_CLASS_LINEAR);
const size_t pixel_sz = mip_channels * (is_uint16 ? 2 : 4);
const size_t total_bytes = (size_t)raw_w * raw_h * pixel_sz;

// rawprepare's normalisation: pipe will do (value - sub) / div where
// sub is per-CFA-site black level and div is (white - black). to get
// back to raw ADC space we compute per-site (value * range[idx]) + black[idx]
// NOTE: raw_black_level_separate is indexed by CFA position k in 0..3
// (even if X-Trans has 6 colours, darktable's per-sensel black is 4-
// entry; typical cameras use one value for all positions anyway)
// black[]/range[] are CFA-parity indexed; LINEAR uses scalar pair
float black[4], range[4], white;
_compute_cfa_black_range(img, black, range, &white);
const float lin_black = (float)img->raw_black_level;
const float lin_range
= (white - lin_black > 0.0f) ? (white - lin_black) : 1.0f;

// build patched CFA: copy original, overwrite crop with re-mosaiced denoised
void *patched = g_try_malloc(total_bytes);
if(!patched)
{
Expand All @@ -1087,7 +1088,7 @@ int dt_restore_raw_linear_preview_piped(dt_restore_context_t *ctx,
// buffer extent) rather than just the display crop. this gives the
// pipe's geometry chain ~tile-size/2 pixels of slop on each side so
// any residual coordinate drift falls inside denoised data instead of
// showing original CFA at the preview edge
// showing original at the preview edge
const int patch_x0 = (inf_x < 0) ? 0 : inf_x;
const int patch_y0 = (inf_y < 0) ? 0 : inf_y;
const int patch_x1 = (inf_x + T > width) ? width : inf_x + T;
Expand All @@ -1114,20 +1115,40 @@ int dt_restore_raw_linear_preview_piped(dt_restore_context_t *ctx,
if(cam[c] < 0.0f) cam[c] = 0.0f;
if(cam[c] > 1.0f) cam[c] = 1.0f;
}
// re-mosaic: pick the single colour that the X-Trans pattern
// wants at this sensor position, scaled back to raw ADC range.
// FCxtrans uses raw-sensor parity (since xtrans[6][6] is aligned
// with the raw, not the post-crop buffer)
const int ch = FCxtrans(sr_raw, sc_raw, NULL, img->buf_dsc.xtrans);
const int bl_idx = ((sr_raw & 1) << 1) | (sc_raw & 1);
const float adc = cam[ch] * range[bl_idx] + black[bl_idx];
const float clipped
= adc < 0.0f ? 0.0f : (adc > white ? white : adc);
const size_t idx = (size_t)sr_raw * raw_w + sc_raw;
if(is_uint16)
((uint16_t *)patched)[idx] = (uint16_t)(clipped + 0.5f);
const size_t pix = (size_t)sr_raw * raw_w + sc_raw;

if(is_linear)
Comment thread
andriiryzhkov marked this conversation as resolved.
{
// float mipmaps are already in [0, 1]; uint16 needs un-prepare
for(int c = 0; c < 3; c++)
{
const size_t off = pix * mip_channels + c;
if(is_uint16)
{
const float adc = cam[c] * lin_range + lin_black;
const float clipped
= adc < 0.0f ? 0.0f : (adc > white ? white : adc);
((uint16_t *)patched)[off] = (uint16_t)(clipped + 0.5f);
}
else
{
((float *)patched)[off] = cam[c];
}
}
}
else
((float *)patched)[idx] = clipped;
{
// X-Trans: re-mosaic
const int ch = FCxtrans(sr_raw, sc_raw, NULL, img->buf_dsc.xtrans);
const int bl_idx = ((sr_raw & 1) << 1) | (sc_raw & 1);
const float adc = cam[ch] * range[bl_idx] + black[bl_idx];
const float clipped
= adc < 0.0f ? 0.0f : (adc > white ? white : adc);
if(is_uint16)
((uint16_t *)patched)[pix] = (uint16_t)(clipped + 0.5f);
else
((float *)patched)[pix] = clipped;
}
}
}

Expand Down
67 changes: 52 additions & 15 deletions src/libs/neural_restore.c
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,9 @@ typedef struct dt_lib_neural_restore_t
// X-Trans / linear) so re-picking a new crop on the same image skips
// the slow load + demosaic; freed on imgid or sensor-type change.
dt_imgid_t preview_raw_imgid;
// imgid we last showed the "pre-demosaiced" warning for, so the
// toast doesn't fire every patch-click move during preview
dt_imgid_t preview_linear_warned_imgid;
dt_restore_sensor_class_t preview_raw_sensor_class;
float *preview_full_cfa; // Bayer: full sensor (w*h floats)
int preview_full_w;
Expand Down Expand Up @@ -391,6 +394,10 @@ typedef struct dt_neural_job_t
char *icc_filename; // only used when icc_type == DT_COLORSPACE_FILE
// when TRUE, wide-gamut pixels pass through unchanged on denoise
gboolean preserve_wide_gamut;
// raw denoise only: TRUE after we've toasted the "pre-demosaiced"
// caveat for any LINEAR image in this batch; avoids one warning
// per file when the user kicks off a multi-image job
gboolean linear_warned;
} dt_neural_job_t;

typedef struct dt_neural_format_params_t
Expand Down Expand Up @@ -1356,6 +1363,16 @@ static int _process_raw_denoise_one(dt_neural_job_t *j,
return _process_raw_denoise_linear(j, imgid, out_filename,
src_path, &img_meta);
case DT_RESTORE_SENSOR_CLASS_LINEAR:
// most LINEAR-class inputs are computational raws (Apple
// ProRAW, Google HDR+, etc.) that have already been denoised
// on-device; the model will run but adds little; Canon/Nikon
// sRAW is the exception; warn once per job and proceed
if(!j->linear_warned)
{
j->linear_warned = TRUE;
dt_control_log(_("raw denoise: this image is already pre-demosaiced "
"(sRaw / computational raw); results may be limited"));
}
return _process_raw_denoise_linear(j, imgid, out_filename,
src_path, &img_meta);
default:
Expand Down Expand Up @@ -2676,24 +2693,42 @@ static gpointer _preview_thread_raw(gpointer data)

const uint32_t filters = img_meta.buf_dsc.filters;
const dt_restore_sensor_class_t cls = dt_restore_classify_sensor(&img_meta);
const gboolean is_xtrans = (cls == DT_RESTORE_SENSOR_CLASS_XTRANS);
if(cls != DT_RESTORE_SENSOR_CLASS_BAYER
&& cls != DT_RESTORE_SENSOR_CLASS_XTRANS)
// today x-trans piggybacks on the linear pipeline because we don't
// ship a dedicated x-trans denoise model yet. when one lands, drop
// XTRANS from use_linear and add an x-trans branch alongside the
// use_linear and bayer paths (buffer fetch, preview pipe, max_disp)
const gboolean use_linear = (cls == DT_RESTORE_SENSOR_CLASS_XTRANS
|| cls == DT_RESTORE_SENSOR_CLASS_LINEAR);
if(cls != DT_RESTORE_SENSOR_CLASS_BAYER && !use_linear)
{
dt_print(DT_DEBUG_AI,
"[neural_restore] raw preview: imgid %d is not bayer/xtrans "
"[neural_restore] raw preview: imgid %d unsupported "
"(filters=0x%x class=%d)",
pd->imgid, filters, cls);
bail_err = DT_NR_PREVIEW_ERR_UNSUPPORTED;
goto cleanup;
}
const char *cls_name = (cls == DT_RESTORE_SENSOR_CLASS_XTRANS) ? "x-trans"
: use_linear ? "linear"
: "bayer";
dt_print(DT_DEBUG_AI,
"[neural_restore] raw preview: imgid=%d %s patch=(%.3f,%.3f) "
"widget=%dx%d filters=0x%x",
pd->imgid, is_xtrans ? "x-trans" : "bayer",
pd->imgid, cls_name,
pd->patch_center[0], pd->patch_center[1],
pd->preview_w, pd->preview_h, filters);

// same caveat as the batch path: LINEAR is usually a computational
// raw that's already been denoised on-device. warn once per imgid
// so picking a different patch doesn't re-toast
if(cls == DT_RESTORE_SENSOR_CLASS_LINEAR
&& d->preview_linear_warned_imgid != pd->imgid)
{
d->preview_linear_warned_imgid = pd->imgid;
dt_control_log(_("raw denoise: this image is already pre-demosaiced "
"(sRaw / computational raw); results may be limited"));
}

// 2. ensure the right ctx is loaded (matches batch logic in
// _ensure_raw_ctx). reload if cached_task is wrong or if the
// cached sensor class doesn't match this image.
Expand All @@ -2715,6 +2750,9 @@ static gpointer _preview_thread_raw(gpointer data)
case DT_RESTORE_SENSOR_CLASS_XTRANS:
d->cached_ctx = dt_restore_load_rawdenoise_xtrans(pd->env);
break;
case DT_RESTORE_SENSOR_CLASS_LINEAR:
d->cached_ctx = dt_restore_load_rawdenoise_linear(pd->env);
break;
default:
d->cached_ctx = NULL;
break;
Expand Down Expand Up @@ -2753,12 +2791,12 @@ static gpointer _preview_thread_raw(gpointer data)
const gboolean cache_matches
= d->preview_raw_imgid == pd->imgid
&& d->preview_raw_sensor_class == cls
&& ((is_xtrans && d->preview_full_lin)
|| (!is_xtrans && d->preview_full_cfa));
&& ((use_linear && d->preview_full_lin)
|| (!use_linear && d->preview_full_cfa));

if(cache_matches)
{
if(is_xtrans)
if(use_linear)
{
full_lin_use = d->preview_full_lin;
full_w = d->preview_lin_w;
Expand All @@ -2771,7 +2809,7 @@ static gpointer _preview_thread_raw(gpointer data)
full_h = d->preview_full_h;
}
}
else if(is_xtrans)
else if(use_linear)
{
if(dt_restore_raw_linear_prepare(ctx, pd->imgid, &take_full_lin,
&full_w, &full_h) != 0
Expand Down Expand Up @@ -2901,7 +2939,7 @@ static gpointer _preview_thread_raw(gpointer data)
// crop in sensor pixels:
// bayer: 2*T - 4*overlap_packed = 2*T - 128 (for OVERLAP_PACKED=32)
// linear: T - 2*overlap_linear = T - 64 (for OVERLAP_LINEAR=32)
const int max_disp = is_xtrans ? (T - 64) : (2 * T - 128);
const int max_disp = use_linear ? (T - 64) : (2 * T - 128);

// the raw buffer is always landscape (sensor layout), but the preview
// thumbnail the user clicks on is oriented per EXIF. un-rotate the
Expand All @@ -2913,7 +2951,7 @@ static gpointer _preview_thread_raw(gpointer data)
int crop_w = MIN(swap_xy ? pd->preview_h : pd->preview_w, max_disp);
int crop_h = MIN(swap_xy ? pd->preview_w : pd->preview_h, max_disp);
// Bayer: snap to mod 2 (CFA grid)
if(!is_xtrans)
if(!use_linear)
{
crop_w = (crop_w / 2) * 2;
crop_h = (crop_h / 2) * 2;
Expand Down Expand Up @@ -2943,7 +2981,7 @@ static gpointer _preview_thread_raw(gpointer data)
int crop_y = (int)sy - crop_h / 2;
crop_x = CLAMP(crop_x, 0, full_w - crop_w);
crop_y = CLAMP(crop_y, 0, full_h - crop_h);
if(!is_xtrans)
if(!use_linear)
{
crop_x = (crop_x / 2) * 2;
crop_y = (crop_y / 2) * 2;
Expand All @@ -2954,8 +2992,7 @@ static gpointer _preview_thread_raw(gpointer data)
"patch_center=(%.3f,%.3f) -> sensor=(%d,%d %dx%d) %s",
full_w, full_h, (unsigned)ori,
pd->patch_center[0], pd->patch_center[1],
crop_x, crop_y, crop_w, crop_h,
is_xtrans ? "linear" : "bayer");
crop_x, crop_y, crop_w, crop_h, cls_name);

// 5. inference
// Bayer path uses the _piped variant which runs darktable's full
Expand All @@ -2968,7 +3005,7 @@ static gpointer _preview_thread_raw(gpointer data)
float *denoised_rgb = NULL;
int actual_w = 0, actual_h = 0;
int err;
if(is_xtrans)
if(use_linear)
err = dt_restore_raw_linear_preview_piped(ctx, &img_meta, pd->imgid,
full_lin_use,
full_w, full_h,
Expand Down
Loading