Skip to content

Commit 45ca7c9

Browse files
authored
Merge pull request #20854 from andriiryzhkov/nr_rawdenoise
[AI] Add RAW denoise (RawNIND, Bayer + Linear) to neural restore module
2 parents 1762353 + 4d5e1c9 commit 45ca7c9

23 files changed

Lines changed: 7498 additions & 1560 deletions

data/ai_models.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@
3535
"github_asset": "denoise-nafnet.dtmodel",
3636
"default": false
3737
},
38+
{
39+
"id": "rawdenoise-nind",
40+
"name": "raw denoise nind",
41+
"description": "UtNet2 raw denoiser trained on RawNIND dataset",
42+
"task": "rawdenoise",
43+
"github_asset": "rawdenoise-nind.dtmodel",
44+
"default": true
45+
},
3846
{
3947
"id": "upscale-bsrgan",
4048
"name": "upscale bsrgan",

data/darktableconfig.xml.in

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3772,9 +3772,9 @@
37723772
<dtconfig>
37733773
<name>plugins/lighttable/neural_restore/detail_recovery_bands</name>
37743774
<type>string</type>
3775-
<default>0.5,0.3,0.1,0.05,0.02</default>
3775+
<default>0.25,0.15,0.05,0.02,0.01</default>
37763776
<shortdescription>detail recovery wavelet band thresholds</shortdescription>
3777-
<longdescription>comma-separated sigma multipliers for wavelet detail recovery bands (finest to coarsest). controls how much noise vs texture is recovered by the detail recovery slider</longdescription>
3777+
<longdescription>comma-separated sigma multipliers for wavelet detail recovery bands (finest to coarsest). controls how much noise vs texture passes through the DWT filter when strength is below 100</longdescription>
37783778
</dtconfig>
37793779
<dtconfig>
37803780
<name>plugins/lighttable/neural_restore/preview_height</name>

dev-doc/AI.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ src/ai/ ONNX Runtime backend (darktable_ai static lib)
1717
1818
src/common/ai/ higher-level AI modules (compiled in lib_darktable)
1919
segmentation.c/.h SAM/SegNext interactive masking
20-
restore.c/.h denoise/upscale tiled inference
20+
restore.c/.h generic env/ctx lifecycle + model loaders
21+
restore_common.h private struct defs shared by restore_*
22+
restore_rgb.c/.h RGB-path denoise + upscale (tiled inference,
23+
shadow boost, DWT detail recovery)
24+
restore_raw_bayer.c/.h RawNIND Bayer denoise (batch + piped preview)
25+
restore_raw_linear.c/.h RawNIND linear/X-Trans denoise
2126
2227
src/common/ai_models.c/.h model registry, download, preferences integration
2328
src/gui/preferences_ai.c AI preferences tab
@@ -402,6 +407,9 @@ FILE(GLOB SOURCE_FILES_AI
402407
"common/ai_models.c"
403408
"common/ai/segmentation.c"
404409
"common/ai/restore.c"
410+
"common/ai/restore_rgb.c"
411+
"common/ai/restore_raw_bayer.c"
412+
"common/ai/restore_raw_linear.c"
405413
"common/ai/your_task.c" # add here
406414
...
407415
)
@@ -455,8 +463,10 @@ dt_your_task_free(ctx);
455463
| Task | Key | API | Consumer |
456464
|------|-----|-----|----------|
457465
| Object Mask | `"mask"` | `src/common/ai/segmentation.h` | `src/develop/masks/object.c` |
458-
| Denoise | `"denoise"` | `src/common/ai/restore.h` | `src/libs/neural_restore.c` |
459-
| Upscale | `"upscale"` | `src/common/ai/restore.h` | `src/libs/neural_restore.c` |
466+
| Denoise | `"denoise"` | `src/common/ai/restore_rgb.h` | `src/libs/neural_restore.c` |
467+
| Upscale | `"upscale"` | `src/common/ai/restore_rgb.h` | `src/libs/neural_restore.c` |
468+
| Raw Denoise (Bayer) | `"rawdenoise"` | `src/common/ai/restore_raw_bayer.h` | `src/libs/neural_restore.c` |
469+
| Raw Denoise (Linear) | `"rawdenoise"` | `src/common/ai/restore_raw_linear.h` | `src/libs/neural_restore.c` |
460470
461471
For model requirements, I/O specifications, tiling strategies, color
462472
space conventions, ONNX export instructions, and config.json examples

dev-doc/AI_Tasks.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ repository. Requirements for the decoder export:
131131
Removes noise from developed images using neural network inference.
132132

133133
**Task key**: `"denoise"`
134-
**API**: `src/common/ai/restore.h` (`dt_restore_load_denoise`)
134+
**API**: `src/common/ai/restore.h` (loader: `dt_restore_load_denoise`), `src/common/ai/restore_rgb.h` (processing: `dt_restore_process_tiled`)
135135
**Consumer**: `src/libs/neural_restore.c`
136136

137137
### How It Works
@@ -222,7 +222,7 @@ torch.onnx.export(model, dummy_input, "model.onnx",
222222
Super-resolution upscaling of developed images (2x or 4x).
223223

224224
**Task key**: `"upscale"`
225-
**API**: `src/common/ai/restore.h` (`dt_restore_load_upscale_x2`, `dt_restore_load_upscale_x4`)
225+
**API**: `src/common/ai/restore.h` (loaders: `dt_restore_load_upscale_x2`, `dt_restore_load_upscale_x4`), `src/common/ai/restore_rgb.h` (processing: `dt_restore_process_tiled`)
226226
**Consumer**: `src/libs/neural_restore.c`
227227

228228
### How It Works

src/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ FILE(GLOB SOURCE_FILES
160160
"gui/welcome.c"
161161
"gui/styles_dialog.c"
162162
"imageio/imageio.c"
163+
"imageio/imageio_dng.c"
163164
"imageio/imageio_jpeg.c"
164165
"imageio/imageio_module.c"
165166
"imageio/imageio_pfm.c"
@@ -462,6 +463,9 @@ if(USE_AI)
462463
"common/ai_models.c"
463464
"common/ai/segmentation.c"
464465
"common/ai/restore.c"
466+
"common/ai/restore_rgb.c"
467+
"common/ai/restore_raw_bayer.c"
468+
"common/ai/restore_raw_linear.c"
465469
"develop/masks/object.c"
466470
"gui/preferences_ai.c"
467471
)

src/ai/backend.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,14 @@ double dt_ai_model_attribute_double(const dt_ai_model_info_t *info,
166166
char *dt_ai_model_attribute_string(const dt_ai_model_info_t *info,
167167
const char *key);
168168

169+
/** Return a newly-allocated int array from the JSON-array attribute
170+
* named `key`. *out_count is set to the array length; NULL is returned
171+
* (and *out_count = 0) when the key is absent or not a JSON array.
172+
* Caller frees the returned array with g_free(). */
173+
int *dt_ai_model_attribute_int_array(const dt_ai_model_info_t *info,
174+
const char *key,
175+
int *out_count);
176+
169177
/* --- Discovery --- */
170178

171179
/**
@@ -267,7 +275,8 @@ dt_ai_context_t *dt_ai_load_model_ext(dt_ai_environment_t *env,
267275
dt_ai_provider_t provider,
268276
dt_ai_opt_level_t opt_level,
269277
const dt_ai_dim_override_t *dim_overrides,
270-
int n_overrides);
278+
int n_overrides,
279+
uint32_t ep_flags);
271280

272281
/**
273282
* @brief Tensor Data Types

src/ai/backend_common.c

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,8 @@ dt_ai_provider_t dt_ai_env_get_provider(dt_ai_environment_t *env)
357357
extern dt_ai_context_t *
358358
dt_ai_onnx_load_ext(const char *model_dir, const char *model_file,
359359
dt_ai_provider_t provider, dt_ai_opt_level_t opt_level,
360-
const dt_ai_dim_override_t *dim_overrides, int n_overrides);
360+
const dt_ai_dim_override_t *dim_overrides, int n_overrides,
361+
uint32_t ep_flags);
361362

362363
// model loading with backend dispatch
363364

@@ -367,7 +368,7 @@ dt_ai_context_t *dt_ai_load_model(dt_ai_environment_t *env,
367368
dt_ai_provider_t provider)
368369
{
369370
return dt_ai_load_model_ext(env, model_id, model_file, provider,
370-
DT_AI_OPT_ALL, NULL, 0);
371+
DT_AI_OPT_ALL, NULL, 0, 0);
371372
}
372373

373374
dt_ai_context_t *dt_ai_load_model_ext(dt_ai_environment_t *env,
@@ -376,7 +377,8 @@ dt_ai_context_t *dt_ai_load_model_ext(dt_ai_environment_t *env,
376377
dt_ai_provider_t provider,
377378
dt_ai_opt_level_t opt_level,
378379
const dt_ai_dim_override_t *dim_overrides,
379-
int n_overrides)
380+
int n_overrides,
381+
uint32_t ep_flags)
380382
{
381383
if(!env || !model_id)
382384
return NULL;
@@ -428,7 +430,7 @@ dt_ai_context_t *dt_ai_load_model_ext(dt_ai_environment_t *env,
428430
if(strcmp(backend_copy, "onnx") == 0)
429431
{
430432
ctx = dt_ai_onnx_load_ext(model_dir, model_file, provider, opt_level,
431-
dim_overrides, n_overrides);
433+
dim_overrides, n_overrides, ep_flags);
432434
}
433435
else
434436
{
@@ -448,6 +450,10 @@ dt_ai_context_t *dt_ai_load_model_ext(dt_ai_environment_t *env,
448450
// _attribute_node returns the parsed JsonParser plus a borrowed JsonNode*
449451
// for the named key; caller must g_object_unref the returned parser;
450452
// returns NULL parser if the attribute set is absent or the key is missing
453+
//
454+
// the key accepts a dotted path ("variants.bayer.onnx"): each segment
455+
// except the last must resolve to a JSON object; the final segment is
456+
// the leaf lookup and may hold any JSON value type
451457
static JsonParser *_attribute_node(const dt_ai_model_info_t *info,
452458
const char *key,
453459
JsonNode **out_node)
@@ -467,12 +473,26 @@ static JsonParser *_attribute_node(const dt_ai_model_info_t *info,
467473
return NULL;
468474
}
469475
JsonObject *obj = json_node_get_object(root);
470-
if(!json_object_has_member(obj, key))
476+
gchar **segments = g_strsplit(key, ".", -1);
477+
const int n = g_strv_length(segments);
478+
JsonNode *node = NULL;
479+
for(int i = 0; i < n; i++)
480+
{
481+
if(!json_object_has_member(obj, segments[i])) goto out;
482+
node = json_object_get_member(obj, segments[i]);
483+
if(i == n - 1) break;
484+
// intermediate segments must be objects to descend further
485+
if(!node || !JSON_NODE_HOLDS_OBJECT(node)) { node = NULL; goto out; }
486+
obj = json_node_get_object(node);
487+
}
488+
out:
489+
g_strfreev(segments);
490+
if(!node)
471491
{
472492
g_object_unref(parser);
473493
return NULL;
474494
}
475-
*out_node = json_object_get_member(obj, key);
495+
*out_node = node;
476496
return parser;
477497
}
478498

@@ -529,6 +549,30 @@ char *dt_ai_model_attribute_string(const dt_ai_model_info_t *info,
529549
return result;
530550
}
531551

552+
int *dt_ai_model_attribute_int_array(const dt_ai_model_info_t *info,
553+
const char *key,
554+
int *out_count)
555+
{
556+
if(out_count) *out_count = 0;
557+
JsonNode *v = NULL;
558+
JsonParser *p = _attribute_node(info, key, &v);
559+
int *result = NULL;
560+
if(v && JSON_NODE_HOLDS_ARRAY(v))
561+
{
562+
JsonArray *arr = json_node_get_array(v);
563+
const guint n = json_array_get_length(arr);
564+
if(n > 0)
565+
{
566+
result = g_new(int, n);
567+
for(guint i = 0; i < n; i++)
568+
result[i] = (int)json_array_get_int_element(arr, i);
569+
if(out_count) *out_count = (int)n;
570+
}
571+
}
572+
if(p) g_object_unref(p);
573+
return result;
574+
}
575+
532576
// provider string conversion
533577

534578
const char *dt_ai_provider_to_string(dt_ai_provider_t provider)

src/ai/backend_onnx.c

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,8 @@ static float _half_to_float(uint16_t h)
801801
static gboolean _try_provider(OrtSessionOptions *session_opts,
802802
const char *symbol_name,
803803
const char *provider_name,
804-
const char *device_type)
804+
const char *device_type,
805+
uint32_t flags)
805806
{
806807
OrtStatus *status = NULL;
807808
gboolean ok = FALSE;
@@ -851,7 +852,7 @@ static gboolean _try_provider(OrtSessionOptions *session_opts,
851852
// integer-argument providers (CUDA, CoreML, DML, MIGraphX, ROCm)
852853
typedef OrtStatus *(*ProviderAppenderInt)(OrtSessionOptions *, uint32_t);
853854
ProviderAppenderInt appender = (ProviderAppenderInt)func_ptr;
854-
status = appender(session_opts, 0);
855+
status = appender(session_opts, flags);
855856
}
856857
if(!status)
857858
{
@@ -880,7 +881,9 @@ static gboolean _try_provider(OrtSessionOptions *session_opts,
880881
}
881882

882883
static void
883-
_enable_acceleration(OrtSessionOptions *session_opts, dt_ai_provider_t provider)
884+
_enable_acceleration(OrtSessionOptions *session_opts,
885+
dt_ai_provider_t provider,
886+
uint32_t coreml_flags)
884887
{
885888
switch(provider)
886889
{
@@ -894,36 +897,36 @@ _enable_acceleration(OrtSessionOptions *session_opts, dt_ai_provider_t provider)
894897
_try_provider(
895898
session_opts,
896899
"OrtSessionOptionsAppendExecutionProvider_CoreML",
897-
"Apple CoreML", NULL);
900+
"Apple CoreML", NULL, coreml_flags);
898901
#else
899902
dt_print(DT_DEBUG_AI, "[darktable_ai] apple CoreML not available on this platform");
900903
#endif
901904
break;
902905

903906
case DT_AI_PROVIDER_CUDA:
904-
_try_provider(session_opts, "OrtSessionOptionsAppendExecutionProvider_CUDA", "NVIDIA CUDA", NULL);
907+
_try_provider(session_opts, "OrtSessionOptionsAppendExecutionProvider_CUDA", "NVIDIA CUDA", NULL, 0);
905908
break;
906909

907910
case DT_AI_PROVIDER_MIGRAPHX:
908911
// MIGraphX reads its cache env vars once at provider library
909912
// load time, so they must be set before CreateEnv() — see
910913
// _setup_amd_caches() above. OpenVINO (below) takes options
911914
// per-session, so its cache path is passed inline here
912-
if(!_try_provider(session_opts, "OrtSessionOptionsAppendExecutionProvider_MIGraphX", "AMD MIGraphX", NULL))
913-
_try_provider(session_opts, "OrtSessionOptionsAppendExecutionProvider_ROCM", "AMD ROCm (legacy)", NULL);
915+
if(!_try_provider(session_opts, "OrtSessionOptionsAppendExecutionProvider_MIGraphX", "AMD MIGraphX", NULL, 0))
916+
_try_provider(session_opts, "OrtSessionOptionsAppendExecutionProvider_ROCM", "AMD ROCm (legacy)", NULL, 0);
914917
break;
915918

916919
case DT_AI_PROVIDER_OPENVINO:
917920
if(!_try_openvino_with_cache(session_opts))
918-
_try_provider(session_opts, "OrtSessionOptionsAppendExecutionProvider_OpenVINO", "Intel OpenVINO", "AUTO");
921+
_try_provider(session_opts, "OrtSessionOptionsAppendExecutionProvider_OpenVINO", "Intel OpenVINO", "AUTO", 0);
919922
break;
920923

921924
case DT_AI_PROVIDER_DIRECTML:
922925
#if defined(_WIN32)
923926
_try_provider(
924927
session_opts,
925928
"OrtSessionOptionsAppendExecutionProvider_DML",
926-
"Windows DirectML", NULL);
929+
"Windows DirectML", NULL, 0);
927930
#else
928931
dt_print(DT_DEBUG_AI, "[darktable_ai] windows DirectML not available on this platform");
929932
#endif
@@ -936,27 +939,27 @@ _enable_acceleration(OrtSessionOptions *session_opts, dt_ai_provider_t provider)
936939
_try_provider(
937940
session_opts,
938941
"OrtSessionOptionsAppendExecutionProvider_CoreML",
939-
"Apple CoreML", NULL);
942+
"Apple CoreML", NULL, coreml_flags);
940943
#elif defined(_WIN32)
941944
_try_provider(
942945
session_opts,
943946
"OrtSessionOptionsAppendExecutionProvider_DML",
944-
"Windows DirectML", NULL);
947+
"Windows DirectML", NULL, 0);
945948
#elif defined(__linux__)
946949
// try CUDA first, then MIGraphX (cache configured at env init)
947950
if(!_try_provider(
948951
session_opts,
949952
"OrtSessionOptionsAppendExecutionProvider_CUDA",
950-
"NVIDIA CUDA", NULL))
953+
"NVIDIA CUDA", NULL, 0))
951954
{
952955
if(!_try_provider(
953956
session_opts,
954957
"OrtSessionOptionsAppendExecutionProvider_MIGraphX",
955-
"AMD MIGraphX", NULL))
958+
"AMD MIGraphX", NULL, 0))
956959
_try_provider(
957960
session_opts,
958961
"OrtSessionOptionsAppendExecutionProvider_ROCM",
959-
"AMD ROCm (legacy)", NULL);
962+
"AMD ROCm (legacy)", NULL, 0);
960963
}
961964
#endif
962965
break;
@@ -996,20 +999,20 @@ int dt_ai_probe_provider(dt_ai_provider_t provider)
996999
switch(provider)
9971000
{
9981001
case DT_AI_PROVIDER_COREML:
999-
ok = _try_provider(opts, "OrtSessionOptionsAppendExecutionProvider_CoreML", "Apple CoreML", NULL);
1002+
ok = _try_provider(opts, "OrtSessionOptionsAppendExecutionProvider_CoreML", "Apple CoreML", NULL, 0);
10001003
break;
10011004
case DT_AI_PROVIDER_CUDA:
1002-
ok = _try_provider(opts, "OrtSessionOptionsAppendExecutionProvider_CUDA", "NVIDIA CUDA", NULL);
1005+
ok = _try_provider(opts, "OrtSessionOptionsAppendExecutionProvider_CUDA", "NVIDIA CUDA", NULL, 0);
10031006
break;
10041007
case DT_AI_PROVIDER_MIGRAPHX:
1005-
ok = _try_provider(opts, "OrtSessionOptionsAppendExecutionProvider_MIGraphX", "AMD MIGraphX", NULL)
1006-
|| _try_provider(opts, "OrtSessionOptionsAppendExecutionProvider_ROCM", "AMD ROCm (legacy)", NULL);
1008+
ok = _try_provider(opts, "OrtSessionOptionsAppendExecutionProvider_MIGraphX", "AMD MIGraphX", NULL, 0)
1009+
|| _try_provider(opts, "OrtSessionOptionsAppendExecutionProvider_ROCM", "AMD ROCm (legacy)", NULL, 0);
10071010
break;
10081011
case DT_AI_PROVIDER_OPENVINO:
1009-
ok = _try_provider(opts, "OrtSessionOptionsAppendExecutionProvider_OpenVINO", "Intel OpenVINO", "AUTO");
1012+
ok = _try_provider(opts, "OrtSessionOptionsAppendExecutionProvider_OpenVINO", "Intel OpenVINO", "AUTO", 0);
10101013
break;
10111014
case DT_AI_PROVIDER_DIRECTML:
1012-
ok = _try_provider(opts, "OrtSessionOptionsAppendExecutionProvider_DML", "Windows DirectML", NULL);
1015+
ok = _try_provider(opts, "OrtSessionOptionsAppendExecutionProvider_DML", "Windows DirectML", NULL, 0);
10131016
break;
10141017
default:
10151018
break;
@@ -1026,7 +1029,8 @@ int dt_ai_probe_provider(dt_ai_provider_t provider)
10261029
dt_ai_context_t *
10271030
dt_ai_onnx_load_ext(const char *model_dir, const char *model_file,
10281031
dt_ai_provider_t provider, dt_ai_opt_level_t opt_level,
1029-
const dt_ai_dim_override_t *dim_overrides, int n_overrides)
1032+
const dt_ai_dim_override_t *dim_overrides, int n_overrides,
1033+
uint32_t ep_flags)
10301034
{
10311035
if(!model_dir)
10321036
return NULL;
@@ -1111,7 +1115,7 @@ dt_ai_onnx_load_ext(const char *model_dir, const char *model_file,
11111115
}
11121116

11131117
// optimize: enable hardware acceleration (AMD caches set at env init)
1114-
_enable_acceleration(session_opts, provider);
1118+
_enable_acceleration(session_opts, provider, ep_flags);
11151119

11161120
#ifdef _WIN32
11171121
// on windows, CreateSession expects a wide character string
@@ -1176,7 +1180,7 @@ dt_ai_onnx_load_ext(const char *model_dir, const char *model_file,
11761180
if(s) g_ort->ReleaseStatus(s);
11771181
}
11781182
if(fallbacks[fb].prov != DT_AI_PROVIDER_CPU)
1179-
_enable_acceleration(session_opts, fallbacks[fb].prov);
1183+
_enable_acceleration(session_opts, fallbacks[fb].prov, ep_flags);
11801184
#ifdef _WIN32
11811185
status = g_ort->CreateSession(g_env, onnx_path_wide, session_opts, &ctx->session);
11821186
#else

0 commit comments

Comments
 (0)