euler-preprocess 3.2.0__tar.gz → 3.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/PKG-INFO +34 -1
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/README.md +33 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/fog/capture.py +335 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/fog/transform.py +670 -48
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess.egg-info/PKG-INFO +34 -1
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/pyproject.toml +1 -1
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/tests/test_fog_aux_outputs.py +208 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/__init__.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/cli.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/common/__init__.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/common/dataset.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/common/device.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/common/intrinsics.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/common/io.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/common/logging.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/common/noise.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/common/normalize.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/common/output.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/common/sampling.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/common/transform.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/fog/__init__.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/fog/airlight_from_sky.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/fog/atmospheric_light.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/fog/augmentations.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/fog/dcp_airlight.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/fog/dcp_airlight_torch.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/fog/dcp_heuristic_airlight.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/fog/dcp_heuristic_airlight_torch.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/fog/foggify.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/fog/foggify_logging.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/fog/inference.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/fog/logging.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/fog/models.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/fog/pipeline.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/radial/__init__.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/radial/transform.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/sky_depth/__init__.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess/sky_depth/transform.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess.egg-info/SOURCES.txt +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess.egg-info/dependency_links.txt +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess.egg-info/entry_points.txt +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess.egg-info/requires.txt +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/euler_preprocess.egg-info/top_level.txt +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/setup.cfg +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/tests/test_airlight_fallback.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/tests/test_cli_sample_selection.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/tests/test_dcp_heuristic_airlight.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/tests/test_foggify_integration.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/tests/test_radial.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/tests/test_sky_depth.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/tests/test_source_backed_output.py +0 -0
- {euler_preprocess-3.2.0 → euler_preprocess-3.4.0}/tests/test_zip_output.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: euler-preprocess
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.0
|
|
4
4
|
Summary: Physics-based preprocessing (fog, etc.) for RGB+depth datasets
|
|
5
5
|
Requires-Python: >=3.9
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -148,6 +148,7 @@ Controls the fog simulation.
|
|
|
148
148
|
"depth_scale": 1.0,
|
|
149
149
|
"resize_depth": true,
|
|
150
150
|
"contrast_threshold": 0.05,
|
|
151
|
+
"mode": "sample",
|
|
151
152
|
"device": "cpu",
|
|
152
153
|
"gpu_batch_size": 4,
|
|
153
154
|
"capture": { "preset": "camera" },
|
|
@@ -165,6 +166,7 @@ Controls the fog simulation.
|
|
|
165
166
|
| `depth_scale` | Multiplier applied to depth values after loading. |
|
|
166
167
|
| `resize_depth` | Resize the depth map to match the RGB resolution (bilinear). |
|
|
167
168
|
| `contrast_threshold` | Threshold *C_t* used in the visibility-to-attenuation conversion (default `0.05`). |
|
|
169
|
+
| `mode` | Optional scenario mode. Omit it or use `"sample"` for current one-scenario-per-image behavior; use `"progressive"` to render every scenario step for every image. |
|
|
168
170
|
| `device` | `"cpu"`, `"cuda"`, `"mps"`, or `"gpu"` (alias for cuda). |
|
|
169
171
|
| `gpu_batch_size` | Batch size when running on GPU. Uniform-model samples are batched; heterogeneous samples are processed individually. |
|
|
170
172
|
| `capture` / `capture_artifacts` | Optional post-fog camera artifact pipeline. Omit it or set `{"stages": []}` for the legacy no-op path. Set `true`, `{"preset": "camera"}`, or a custom `stages` list to enable optics, raw sensor, ISP, and compression artifacts. |
|
|
@@ -323,6 +325,28 @@ noise is fine-grained rather than blocky. `black_noise_floor` with
|
|
|
323
325
|
noise in near-clipped black regions, so the strongest visible noise sits in
|
|
324
326
|
dim-but-readable shadows.
|
|
325
327
|
|
|
328
|
+
Set `sensor.noise_adjustment` for relative, scenario-level noise controls on
|
|
329
|
+
top of the selected camera/condition profile. `level: 1.0` leaves the authored
|
|
330
|
+
profile unchanged; lower values suppress read/static/chroma noise and higher
|
|
331
|
+
values amplify it. `static_chroma_bias` ranges from `-1.0` for more fixed
|
|
332
|
+
pattern, row/column, banding, and bad-pixel noise to `1.0` for more
|
|
333
|
+
chromatic/high-ISO-looking shadow noise:
|
|
334
|
+
|
|
335
|
+
```json
|
|
336
|
+
{
|
|
337
|
+
"capture_overrides": {
|
|
338
|
+
"sensor": {
|
|
339
|
+
"condition_profile": "nominal_gloom",
|
|
340
|
+
"noise_adjustment": {
|
|
341
|
+
"enabled": true,
|
|
342
|
+
"level": 1.25,
|
|
343
|
+
"static_chroma_bias": 0.35
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
326
350
|
Any stage can define `condition_profiles` to sample coherent per-image settings
|
|
327
351
|
before the stage runs. This is useful for exposure states where ISO, exposure
|
|
328
352
|
gain, read noise, banding, and dark/fog noise modulation should move together:
|
|
@@ -382,6 +406,15 @@ compression together:
|
|
|
382
406
|
`condition_profiles`; if omitted, the stage continues sampling its own profile
|
|
383
407
|
weights locally.
|
|
384
408
|
|
|
409
|
+
Set top-level `"mode": "progressive"` to emit every configured scenario for
|
|
410
|
+
every input image instead of sampling one scenario. Each scenario accepts
|
|
411
|
+
`"steps"` and `"weight"`; the transform writes steps from weight `0` through
|
|
412
|
+
the scenario's configured weight, and weight `1` matches the original scenario.
|
|
413
|
+
Fog density is progressed in scattering-coefficient space, while numeric
|
|
414
|
+
camera/config values blend from the base config toward the scenario config.
|
|
415
|
+
Source-backed outputs are written as `fog_progression` variants under each
|
|
416
|
+
source file id.
|
|
417
|
+
|
|
385
418
|
### Fog Model
|
|
386
419
|
|
|
387
420
|
The core equation is the **Koschmieder model** (atmospheric scattering):
|
|
@@ -134,6 +134,7 @@ Controls the fog simulation.
|
|
|
134
134
|
"depth_scale": 1.0,
|
|
135
135
|
"resize_depth": true,
|
|
136
136
|
"contrast_threshold": 0.05,
|
|
137
|
+
"mode": "sample",
|
|
137
138
|
"device": "cpu",
|
|
138
139
|
"gpu_batch_size": 4,
|
|
139
140
|
"capture": { "preset": "camera" },
|
|
@@ -151,6 +152,7 @@ Controls the fog simulation.
|
|
|
151
152
|
| `depth_scale` | Multiplier applied to depth values after loading. |
|
|
152
153
|
| `resize_depth` | Resize the depth map to match the RGB resolution (bilinear). |
|
|
153
154
|
| `contrast_threshold` | Threshold *C_t* used in the visibility-to-attenuation conversion (default `0.05`). |
|
|
155
|
+
| `mode` | Optional scenario mode. Omit it or use `"sample"` for current one-scenario-per-image behavior; use `"progressive"` to render every scenario step for every image. |
|
|
154
156
|
| `device` | `"cpu"`, `"cuda"`, `"mps"`, or `"gpu"` (alias for cuda). |
|
|
155
157
|
| `gpu_batch_size` | Batch size when running on GPU. Uniform-model samples are batched; heterogeneous samples are processed individually. |
|
|
156
158
|
| `capture` / `capture_artifacts` | Optional post-fog camera artifact pipeline. Omit it or set `{"stages": []}` for the legacy no-op path. Set `true`, `{"preset": "camera"}`, or a custom `stages` list to enable optics, raw sensor, ISP, and compression artifacts. |
|
|
@@ -309,6 +311,28 @@ noise is fine-grained rather than blocky. `black_noise_floor` with
|
|
|
309
311
|
noise in near-clipped black regions, so the strongest visible noise sits in
|
|
310
312
|
dim-but-readable shadows.
|
|
311
313
|
|
|
314
|
+
Set `sensor.noise_adjustment` for relative, scenario-level noise controls on
|
|
315
|
+
top of the selected camera/condition profile. `level: 1.0` leaves the authored
|
|
316
|
+
profile unchanged; lower values suppress read/static/chroma noise and higher
|
|
317
|
+
values amplify it. `static_chroma_bias` ranges from `-1.0` for more fixed
|
|
318
|
+
pattern, row/column, banding, and bad-pixel noise to `1.0` for more
|
|
319
|
+
chromatic/high-ISO-looking shadow noise:
|
|
320
|
+
|
|
321
|
+
```json
|
|
322
|
+
{
|
|
323
|
+
"capture_overrides": {
|
|
324
|
+
"sensor": {
|
|
325
|
+
"condition_profile": "nominal_gloom",
|
|
326
|
+
"noise_adjustment": {
|
|
327
|
+
"enabled": true,
|
|
328
|
+
"level": 1.25,
|
|
329
|
+
"static_chroma_bias": 0.35
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
312
336
|
Any stage can define `condition_profiles` to sample coherent per-image settings
|
|
313
337
|
before the stage runs. This is useful for exposure states where ISO, exposure
|
|
314
338
|
gain, read noise, banding, and dark/fog noise modulation should move together:
|
|
@@ -368,6 +392,15 @@ compression together:
|
|
|
368
392
|
`condition_profiles`; if omitted, the stage continues sampling its own profile
|
|
369
393
|
weights locally.
|
|
370
394
|
|
|
395
|
+
Set top-level `"mode": "progressive"` to emit every configured scenario for
|
|
396
|
+
every input image instead of sampling one scenario. Each scenario accepts
|
|
397
|
+
`"steps"` and `"weight"`; the transform writes steps from weight `0` through
|
|
398
|
+
the scenario's configured weight, and weight `1` matches the original scenario.
|
|
399
|
+
Fog density is progressed in scattering-coefficient space, while numeric
|
|
400
|
+
camera/config values blend from the base config toward the scenario config.
|
|
401
|
+
Source-backed outputs are written as `fog_progression` variants under each
|
|
402
|
+
source file id.
|
|
403
|
+
|
|
371
404
|
### Fog Model
|
|
372
405
|
|
|
373
406
|
The core equation is the **Koschmieder model** (atmospheric scattering):
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from copy import deepcopy
|
|
3
4
|
import io
|
|
4
5
|
import math
|
|
5
6
|
from dataclasses import dataclass, field
|
|
@@ -522,6 +523,7 @@ class SensorStage(ConfiguredCaptureStage):
|
|
|
522
523
|
rng,
|
|
523
524
|
manual_exposure_gain=exposure,
|
|
524
525
|
)
|
|
526
|
+
config = _apply_sensor_noise_adjustment(config, rng)
|
|
525
527
|
wb = _sample_triplet(config.get("white_balance", [1.0, 1.0, 1.0]), rng)
|
|
526
528
|
wb_jitter = _sample_float(config, "white_balance_jitter", 0.0, rng)
|
|
527
529
|
if wb_jitter > 0.0:
|
|
@@ -717,6 +719,7 @@ class SensorStage(ConfiguredCaptureStage):
|
|
|
717
719
|
rng,
|
|
718
720
|
manual_exposure_gain=exposure,
|
|
719
721
|
)
|
|
722
|
+
config = _apply_sensor_noise_adjustment(config, rng)
|
|
720
723
|
wb = _sample_triplet(config.get("white_balance", [1.0, 1.0, 1.0]), rng)
|
|
721
724
|
wb_jitter = _sample_float(config, "white_balance_jitter", 0.0, rng)
|
|
722
725
|
if wb_jitter > 0.0:
|
|
@@ -2152,6 +2155,338 @@ def _resolve_electron_capacity_torch(
|
|
|
2152
2155
|
)
|
|
2153
2156
|
|
|
2154
2157
|
|
|
2158
|
+
def _apply_sensor_noise_adjustment(
|
|
2159
|
+
config: Mapping[str, Any],
|
|
2160
|
+
rng: np.random.Generator,
|
|
2161
|
+
) -> Mapping[str, Any]:
|
|
2162
|
+
cfg = _config_block(config, "noise_adjustment")
|
|
2163
|
+
if not cfg or not _bool_value(cfg.get("enabled", True)):
|
|
2164
|
+
return config
|
|
2165
|
+
|
|
2166
|
+
level = max(
|
|
2167
|
+
_sample_float_from_keys(
|
|
2168
|
+
cfg,
|
|
2169
|
+
("level", "amount", "factor", "noise_level"),
|
|
2170
|
+
1.0,
|
|
2171
|
+
rng,
|
|
2172
|
+
),
|
|
2173
|
+
0.0,
|
|
2174
|
+
)
|
|
2175
|
+
bias = float(
|
|
2176
|
+
np.clip(
|
|
2177
|
+
_sample_float_from_keys(
|
|
2178
|
+
cfg,
|
|
2179
|
+
("static_chroma_bias", "character_bias", "chroma_bias"),
|
|
2180
|
+
0.0,
|
|
2181
|
+
rng,
|
|
2182
|
+
),
|
|
2183
|
+
-1.0,
|
|
2184
|
+
1.0,
|
|
2185
|
+
)
|
|
2186
|
+
)
|
|
2187
|
+
groups = _config_block(cfg, "groups")
|
|
2188
|
+
limits = _config_block(cfg, "limits")
|
|
2189
|
+
|
|
2190
|
+
min_factor = max(_sample_float(limits, "min_factor", 0.0, rng), 0.0)
|
|
2191
|
+
max_factor = max(_sample_float(limits, "max_factor", 8.0, rng), min_factor)
|
|
2192
|
+
|
|
2193
|
+
def group_factor(name: str, base: float) -> float:
|
|
2194
|
+
factor = base * max(_sample_float(groups, name, 1.0, rng), 0.0)
|
|
2195
|
+
return float(np.clip(factor, min_factor, max_factor))
|
|
2196
|
+
|
|
2197
|
+
read_factor = group_factor("read", level * math.pow(2.0, 0.35 * bias))
|
|
2198
|
+
static_factor = group_factor("static", level * math.pow(2.0, -bias))
|
|
2199
|
+
banding_factor = group_factor("banding", static_factor)
|
|
2200
|
+
bad_pixel_factor = group_factor("bad_pixels", static_factor)
|
|
2201
|
+
chroma_factor = group_factor("chroma", level * math.pow(2.0, bias))
|
|
2202
|
+
shadow_luma_factor = group_factor("shadow_luma", level)
|
|
2203
|
+
modulation_factor = group_factor("modulation", 1.0 + (level - 1.0) * 0.75)
|
|
2204
|
+
|
|
2205
|
+
if (
|
|
2206
|
+
abs(read_factor - 1.0) < 1e-9
|
|
2207
|
+
and abs(static_factor - 1.0) < 1e-9
|
|
2208
|
+
and abs(banding_factor - 1.0) < 1e-9
|
|
2209
|
+
and abs(bad_pixel_factor - 1.0) < 1e-9
|
|
2210
|
+
and abs(chroma_factor - 1.0) < 1e-9
|
|
2211
|
+
and abs(shadow_luma_factor - 1.0) < 1e-9
|
|
2212
|
+
and abs(modulation_factor - 1.0) < 1e-9
|
|
2213
|
+
):
|
|
2214
|
+
return config
|
|
2215
|
+
|
|
2216
|
+
adjusted = deepcopy(dict(config))
|
|
2217
|
+
|
|
2218
|
+
_scale_config_path(adjusted, ("read_noise_electrons",), read_factor, min_value=0.0)
|
|
2219
|
+
_scale_config_path(adjusted, ("read_noise_sigma",), read_factor, min_value=0.0)
|
|
2220
|
+
|
|
2221
|
+
_scale_config_path(adjusted, ("fixed_pattern_sigma",), static_factor, min_value=0.0)
|
|
2222
|
+
_scale_config_path(adjusted, ("row_noise_sigma",), banding_factor, min_value=0.0)
|
|
2223
|
+
_scale_config_path(adjusted, ("column_noise_sigma",), banding_factor, min_value=0.0)
|
|
2224
|
+
_scale_config_path(
|
|
2225
|
+
adjusted,
|
|
2226
|
+
("banding_modulation",),
|
|
2227
|
+
banding_factor,
|
|
2228
|
+
min_value=0.0,
|
|
2229
|
+
max_value=1.0,
|
|
2230
|
+
)
|
|
2231
|
+
|
|
2232
|
+
max_bad_pixel_probability = _sample_float(
|
|
2233
|
+
limits,
|
|
2234
|
+
"max_bad_pixel_probability",
|
|
2235
|
+
0.05,
|
|
2236
|
+
rng,
|
|
2237
|
+
)
|
|
2238
|
+
_scale_config_path(
|
|
2239
|
+
adjusted,
|
|
2240
|
+
("hot_pixel_probability",),
|
|
2241
|
+
bad_pixel_factor,
|
|
2242
|
+
min_value=0.0,
|
|
2243
|
+
max_value=max_bad_pixel_probability,
|
|
2244
|
+
)
|
|
2245
|
+
_scale_config_path(
|
|
2246
|
+
adjusted,
|
|
2247
|
+
("dead_pixel_probability",),
|
|
2248
|
+
bad_pixel_factor,
|
|
2249
|
+
min_value=0.0,
|
|
2250
|
+
max_value=max_bad_pixel_probability,
|
|
2251
|
+
)
|
|
2252
|
+
|
|
2253
|
+
for key in ("dark_gain", "depth_gain", "fog_gain"):
|
|
2254
|
+
_scale_config_path(
|
|
2255
|
+
adjusted,
|
|
2256
|
+
("noise_modulation", key),
|
|
2257
|
+
modulation_factor,
|
|
2258
|
+
min_value=0.0,
|
|
2259
|
+
)
|
|
2260
|
+
_scale_config_path(
|
|
2261
|
+
adjusted,
|
|
2262
|
+
("noise_modulation", "max_gain"),
|
|
2263
|
+
modulation_factor,
|
|
2264
|
+
anchor=1.0,
|
|
2265
|
+
min_value=1.0,
|
|
2266
|
+
)
|
|
2267
|
+
|
|
2268
|
+
_scale_config_path(
|
|
2269
|
+
adjusted,
|
|
2270
|
+
("shadow_recovery_noise", "luma_sigma"),
|
|
2271
|
+
shadow_luma_factor,
|
|
2272
|
+
min_value=0.0,
|
|
2273
|
+
)
|
|
2274
|
+
_scale_config_path(
|
|
2275
|
+
adjusted,
|
|
2276
|
+
("shadow_recovery_noise", "chroma_sigma"),
|
|
2277
|
+
chroma_factor,
|
|
2278
|
+
min_value=0.0,
|
|
2279
|
+
)
|
|
2280
|
+
_scale_config_path(
|
|
2281
|
+
adjusted,
|
|
2282
|
+
("shadow_recovery_noise", "blotch_sigma"),
|
|
2283
|
+
chroma_factor,
|
|
2284
|
+
min_value=0.0,
|
|
2285
|
+
)
|
|
2286
|
+
_scale_config_path(
|
|
2287
|
+
adjusted,
|
|
2288
|
+
("shadow_recovery_noise", "red_chroma_gain"),
|
|
2289
|
+
chroma_factor,
|
|
2290
|
+
anchor=1.0,
|
|
2291
|
+
min_value=0.0,
|
|
2292
|
+
)
|
|
2293
|
+
_scale_config_path(
|
|
2294
|
+
adjusted,
|
|
2295
|
+
("shadow_recovery_noise", "blue_chroma_gain"),
|
|
2296
|
+
chroma_factor,
|
|
2297
|
+
anchor=1.0,
|
|
2298
|
+
min_value=0.0,
|
|
2299
|
+
)
|
|
2300
|
+
_scale_config_path(
|
|
2301
|
+
adjusted,
|
|
2302
|
+
("shadow_recovery_noise", "chroma_axis_correlation"),
|
|
2303
|
+
chroma_factor,
|
|
2304
|
+
min_value=-0.95,
|
|
2305
|
+
max_value=0.95,
|
|
2306
|
+
)
|
|
2307
|
+
return adjusted
|
|
2308
|
+
|
|
2309
|
+
|
|
2310
|
+
def _sample_float_from_keys(
|
|
2311
|
+
config: Mapping[str, Any],
|
|
2312
|
+
keys: tuple[str, ...],
|
|
2313
|
+
default: float,
|
|
2314
|
+
rng: np.random.Generator,
|
|
2315
|
+
) -> float:
|
|
2316
|
+
for key in keys:
|
|
2317
|
+
if config.get(key) is not None:
|
|
2318
|
+
return _sample_float(config, key, default, rng)
|
|
2319
|
+
return float(default)
|
|
2320
|
+
|
|
2321
|
+
|
|
2322
|
+
def _scale_config_path(
|
|
2323
|
+
config: dict[str, Any],
|
|
2324
|
+
path: tuple[str, ...],
|
|
2325
|
+
factor: float,
|
|
2326
|
+
*,
|
|
2327
|
+
anchor: float = 0.0,
|
|
2328
|
+
min_value: float | None = None,
|
|
2329
|
+
max_value: float | None = None,
|
|
2330
|
+
) -> None:
|
|
2331
|
+
parent: dict[str, Any] = config
|
|
2332
|
+
for key in path[:-1]:
|
|
2333
|
+
value = parent.get(key)
|
|
2334
|
+
if not isinstance(value, dict):
|
|
2335
|
+
return
|
|
2336
|
+
parent = value
|
|
2337
|
+
key = path[-1]
|
|
2338
|
+
if key not in parent or parent[key] is None:
|
|
2339
|
+
return
|
|
2340
|
+
parent[key] = _scale_numeric_spec(
|
|
2341
|
+
parent[key],
|
|
2342
|
+
factor,
|
|
2343
|
+
anchor=anchor,
|
|
2344
|
+
min_value=min_value,
|
|
2345
|
+
max_value=max_value,
|
|
2346
|
+
)
|
|
2347
|
+
|
|
2348
|
+
|
|
2349
|
+
def _scale_numeric_spec(
|
|
2350
|
+
spec: Any,
|
|
2351
|
+
factor: float,
|
|
2352
|
+
*,
|
|
2353
|
+
anchor: float = 0.0,
|
|
2354
|
+
min_value: float | None = None,
|
|
2355
|
+
max_value: float | None = None,
|
|
2356
|
+
) -> Any:
|
|
2357
|
+
if isinstance(spec, bool) or spec is None:
|
|
2358
|
+
return spec
|
|
2359
|
+
if isinstance(spec, (int, float)):
|
|
2360
|
+
return _clamp_scaled_number(
|
|
2361
|
+
anchor + (float(spec) - anchor) * factor,
|
|
2362
|
+
min_value,
|
|
2363
|
+
max_value,
|
|
2364
|
+
)
|
|
2365
|
+
if isinstance(spec, list):
|
|
2366
|
+
return [
|
|
2367
|
+
_scale_numeric_spec(
|
|
2368
|
+
value,
|
|
2369
|
+
factor,
|
|
2370
|
+
anchor=anchor,
|
|
2371
|
+
min_value=min_value,
|
|
2372
|
+
max_value=max_value,
|
|
2373
|
+
)
|
|
2374
|
+
for value in spec
|
|
2375
|
+
]
|
|
2376
|
+
if isinstance(spec, tuple):
|
|
2377
|
+
return tuple(
|
|
2378
|
+
_scale_numeric_spec(
|
|
2379
|
+
value,
|
|
2380
|
+
factor,
|
|
2381
|
+
anchor=anchor,
|
|
2382
|
+
min_value=min_value,
|
|
2383
|
+
max_value=max_value,
|
|
2384
|
+
)
|
|
2385
|
+
for value in spec
|
|
2386
|
+
)
|
|
2387
|
+
if not isinstance(spec, dict):
|
|
2388
|
+
return spec
|
|
2389
|
+
|
|
2390
|
+
scaled = dict(spec)
|
|
2391
|
+
dist = scaled.get("dist")
|
|
2392
|
+
if dist is None:
|
|
2393
|
+
if "value" in scaled:
|
|
2394
|
+
scaled["value"] = _scale_numeric_spec(
|
|
2395
|
+
scaled["value"],
|
|
2396
|
+
factor,
|
|
2397
|
+
anchor=anchor,
|
|
2398
|
+
min_value=min_value,
|
|
2399
|
+
max_value=max_value,
|
|
2400
|
+
)
|
|
2401
|
+
return scaled
|
|
2402
|
+
|
|
2403
|
+
if dist == "constant":
|
|
2404
|
+
scaled["value"] = _scale_numeric_spec(
|
|
2405
|
+
scaled.get("value", 0.0),
|
|
2406
|
+
factor,
|
|
2407
|
+
anchor=anchor,
|
|
2408
|
+
min_value=min_value,
|
|
2409
|
+
max_value=max_value,
|
|
2410
|
+
)
|
|
2411
|
+
elif dist == "uniform":
|
|
2412
|
+
scaled["min"] = _scale_numeric_spec(
|
|
2413
|
+
scaled["min"],
|
|
2414
|
+
factor,
|
|
2415
|
+
anchor=anchor,
|
|
2416
|
+
min_value=min_value,
|
|
2417
|
+
max_value=max_value,
|
|
2418
|
+
)
|
|
2419
|
+
scaled["max"] = _scale_numeric_spec(
|
|
2420
|
+
scaled["max"],
|
|
2421
|
+
factor,
|
|
2422
|
+
anchor=anchor,
|
|
2423
|
+
min_value=min_value,
|
|
2424
|
+
max_value=max_value,
|
|
2425
|
+
)
|
|
2426
|
+
if scaled["min"] > scaled["max"]:
|
|
2427
|
+
scaled["min"], scaled["max"] = scaled["max"], scaled["min"]
|
|
2428
|
+
elif dist == "normal":
|
|
2429
|
+
scaled["mean"] = _scale_numeric_spec(
|
|
2430
|
+
scaled["mean"],
|
|
2431
|
+
factor,
|
|
2432
|
+
anchor=anchor,
|
|
2433
|
+
min_value=min_value,
|
|
2434
|
+
max_value=max_value,
|
|
2435
|
+
)
|
|
2436
|
+
scaled["std"] = max(float(scaled.get("std", 0.0)) * abs(factor), 0.0)
|
|
2437
|
+
for key in ("min", "max"):
|
|
2438
|
+
if key in scaled and scaled[key] is not None:
|
|
2439
|
+
scaled[key] = _scale_numeric_spec(
|
|
2440
|
+
scaled[key],
|
|
2441
|
+
factor,
|
|
2442
|
+
anchor=anchor,
|
|
2443
|
+
min_value=min_value,
|
|
2444
|
+
max_value=max_value,
|
|
2445
|
+
)
|
|
2446
|
+
if scaled.get("min") is not None and scaled.get("max") is not None:
|
|
2447
|
+
if scaled["min"] > scaled["max"]:
|
|
2448
|
+
scaled["min"], scaled["max"] = scaled["max"], scaled["min"]
|
|
2449
|
+
elif dist == "lognormal":
|
|
2450
|
+
if anchor == 0.0 and factor > 0.0:
|
|
2451
|
+
scaled["mean"] = float(scaled.get("mean", 0.0)) + math.log(factor)
|
|
2452
|
+
for key in ("min", "max"):
|
|
2453
|
+
if key in scaled and scaled[key] is not None:
|
|
2454
|
+
scaled[key] = _scale_numeric_spec(
|
|
2455
|
+
scaled[key],
|
|
2456
|
+
factor,
|
|
2457
|
+
anchor=anchor,
|
|
2458
|
+
min_value=min_value,
|
|
2459
|
+
max_value=max_value,
|
|
2460
|
+
)
|
|
2461
|
+
if scaled.get("min") is not None and scaled.get("max") is not None:
|
|
2462
|
+
if scaled["min"] > scaled["max"]:
|
|
2463
|
+
scaled["min"], scaled["max"] = scaled["max"], scaled["min"]
|
|
2464
|
+
elif dist == "choice":
|
|
2465
|
+
scaled["values"] = [
|
|
2466
|
+
_scale_numeric_spec(
|
|
2467
|
+
value,
|
|
2468
|
+
factor,
|
|
2469
|
+
anchor=anchor,
|
|
2470
|
+
min_value=min_value,
|
|
2471
|
+
max_value=max_value,
|
|
2472
|
+
)
|
|
2473
|
+
for value in scaled.get("values", [])
|
|
2474
|
+
]
|
|
2475
|
+
return scaled
|
|
2476
|
+
|
|
2477
|
+
|
|
2478
|
+
def _clamp_scaled_number(
|
|
2479
|
+
value: float,
|
|
2480
|
+
min_value: float | None,
|
|
2481
|
+
max_value: float | None,
|
|
2482
|
+
) -> float:
|
|
2483
|
+
if min_value is not None:
|
|
2484
|
+
value = max(float(min_value), value)
|
|
2485
|
+
if max_value is not None:
|
|
2486
|
+
value = min(float(max_value), value)
|
|
2487
|
+
return float(value)
|
|
2488
|
+
|
|
2489
|
+
|
|
2155
2490
|
def _sensor_noise_modulation(
|
|
2156
2491
|
signal: np.ndarray,
|
|
2157
2492
|
context: CaptureContext,
|