euler-preprocess 3.5.0__tar.gz → 3.6.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.5.0 → euler_preprocess-3.6.0}/PKG-INFO +62 -3
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/README.md +61 -2
- euler_preprocess-3.6.0/euler_preprocess/common/color.py +50 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/fog/capture.py +1034 -99
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/fog/models.py +172 -15
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/fog/pipeline.py +30 -2
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess.egg-info/PKG-INFO +62 -3
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess.egg-info/SOURCES.txt +6 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/pyproject.toml +1 -1
- euler_preprocess-3.6.0/tests/test_dense_gloomy_daylight_config.py +76 -0
- euler_preprocess-3.6.0/tests/test_fog_aware_auto_exposure.py +96 -0
- euler_preprocess-3.6.0/tests/test_scene_illumination.py +96 -0
- euler_preprocess-3.6.0/tests/test_sensor_identity.py +108 -0
- euler_preprocess-3.6.0/tests/test_tone_map_lut.py +37 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/__init__.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/cli.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/common/__init__.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/common/dataset.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/common/device.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/common/intrinsics.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/common/io.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/common/logging.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/common/noise.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/common/normalize.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/common/output.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/common/sampling.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/common/transform.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/fog/__init__.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/fog/airlight_from_sky.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/fog/atmospheric_light.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/fog/augmentations.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/fog/dcp_airlight.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/fog/dcp_airlight_torch.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/fog/dcp_heuristic_airlight.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/fog/dcp_heuristic_airlight_torch.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/fog/foggify.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/fog/foggify_logging.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/fog/inference.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/fog/logging.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/fog/transform.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/radial/__init__.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/radial/transform.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/sky_depth/__init__.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess/sky_depth/transform.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess.egg-info/dependency_links.txt +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess.egg-info/entry_points.txt +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess.egg-info/requires.txt +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/euler_preprocess.egg-info/top_level.txt +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/setup.cfg +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/tests/test_airlight_fallback.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/tests/test_cli_sample_selection.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/tests/test_dcp_heuristic_airlight.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/tests/test_fog_aux_outputs.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/tests/test_foggify_integration.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/tests/test_radial.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/tests/test_sky_depth.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.0}/tests/test_source_backed_output.py +0 -0
- {euler_preprocess-3.5.0 → euler_preprocess-3.6.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.6.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
|
+
"render_input_space": "srgb",
|
|
151
152
|
"mode": "sample",
|
|
152
153
|
"device": "cpu",
|
|
153
154
|
"gpu_batch_size": 4,
|
|
@@ -166,6 +167,7 @@ Controls the fog simulation.
|
|
|
166
167
|
| `depth_scale` | Multiplier applied to depth values after loading. |
|
|
167
168
|
| `resize_depth` | Resize the depth map to match the RGB resolution (bilinear). |
|
|
168
169
|
| `contrast_threshold` | Threshold *C_t* used in the visibility-to-attenuation conversion (default `0.05`). |
|
|
170
|
+
| `render_input_space` | Colour space of the RGB supplied to the fog renderer. Use `"srgb"` for display-encoded dataset images so fog and airlight are mixed in scene-linear RGB; use `"linear"` for legacy configs or already-linear radiance. |
|
|
169
171
|
| `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. |
|
|
170
172
|
| `device` | `"cpu"`, `"cuda"`, `"mps"`, or `"gpu"` (alias for cuda). |
|
|
171
173
|
| `gpu_batch_size` | Batch size when running on GPU. Uniform-model samples are batched; heterogeneous samples are processed individually. |
|
|
@@ -310,6 +312,22 @@ the exposure; `resolve_iso` can raise ISO from the metering pressure, dark pixel
|
|
|
310
312
|
fraction, and fog opacity. When auto exposure is enabled, `exposure_gain` still
|
|
311
313
|
applies as scenario-specific exposure compensation.
|
|
312
314
|
|
|
315
|
+
Fog-aware metering modes use `CaptureContext.depth_m`, `k_map`, fog opacity, and
|
|
316
|
+
`attributes.sky_mask` when available. Use `"metering":
|
|
317
|
+
"fog_aware_center_weighted"` or `"sky_aware_center_weighted"` and tune
|
|
318
|
+
`sky_suppression`, `fog_meter_suppression`, `depth_meter_decay_m`, and
|
|
319
|
+
`min_meter_weight` to keep bright sky or dense far-field airlight from dominating
|
|
320
|
+
the exposure meter. Legacy metering modes are unchanged unless these suppression
|
|
321
|
+
keys are present.
|
|
322
|
+
|
|
323
|
+
Set `sensor.sensor_identity.enabled` for persistent sensor structure across
|
|
324
|
+
frames. The identity cache is deterministic for the same `sensor_id`, `seed`,
|
|
325
|
+
image shape, and Bayer pattern. `prnu_sigma` adds multiplicative pixel-response
|
|
326
|
+
non-uniformity before shot noise; `dsnu_sigma`, `persistent_row_sigma`, and
|
|
327
|
+
`persistent_column_sigma` add fixed raw-domain offsets; persistent hot/dead pixel
|
|
328
|
+
probabilities create stable bad-pixel masks that combine with the existing
|
|
329
|
+
per-image bad-pixel probabilities.
|
|
330
|
+
|
|
313
331
|
Set `sensor.shadow_recovery_noise.enabled` to add extra post-demosaic luma and
|
|
314
332
|
chroma corruption only where the pre-exposure rendered luminance was low. This
|
|
315
333
|
is useful for reducing broad global grain while keeping lifted shadows visibly
|
|
@@ -361,6 +379,20 @@ gain, read noise, banding, and dark/fog noise modulation should move together:
|
|
|
361
379
|
}
|
|
362
380
|
```
|
|
363
381
|
|
|
382
|
+
`isp.tone_map` supports `"reinhard"`, `"aces"`, `"clip"`, and `"lut"`. The LUT
|
|
383
|
+
mode uses a cheap interpolated 1D camera-response curve:
|
|
384
|
+
|
|
385
|
+
```json
|
|
386
|
+
{
|
|
387
|
+
"type": "isp",
|
|
388
|
+
"tone_map": "lut",
|
|
389
|
+
"tone_map_strength": 1.0,
|
|
390
|
+
"tone_map_lut": [0.0, 0.006, 0.014, 0.028, 0.052, 0.090, 0.145, 0.220, 0.320, 0.450, 0.610, 0.780, 0.900, 0.965, 0.995, 1.0],
|
|
391
|
+
"tone_map_lut_domain": "linear",
|
|
392
|
+
"gamma": "srgb"
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
364
396
|
Top-level `scenario_profiles` sample one latent scene/camera condition before
|
|
365
397
|
rendering. The selected scenario is merged over the root config, so it can drive
|
|
366
398
|
fog density, atmospheric light, camera profile, capture-stage overrides, ISP, and
|
|
@@ -390,11 +422,30 @@ compression together:
|
|
|
390
422
|
"airlight_method": "dcp_heuristic",
|
|
391
423
|
"models": {
|
|
392
424
|
"heterogeneous_k_ls": {
|
|
393
|
-
"visibility_m": {"dist": "uniform", "min": 18.0, "max": 55.0}
|
|
425
|
+
"visibility_m": {"dist": "uniform", "min": 18.0, "max": 55.0},
|
|
426
|
+
"scene_illumination": {
|
|
427
|
+
"enabled": true,
|
|
428
|
+
"global_ev": {"dist": "uniform", "min": 0.25, "max": 0.85},
|
|
429
|
+
"near_ev": {"dist": "uniform", "min": 0.35, "max": 1.20},
|
|
430
|
+
"near_decay_depth_m": {"dist": "uniform", "min": 10.0, "max": 22.0},
|
|
431
|
+
"fog_coupled_ev": {"dist": "uniform", "min": 0.10, "max": 0.45},
|
|
432
|
+
"sky_weight": 0.0
|
|
433
|
+
}
|
|
394
434
|
}
|
|
395
435
|
},
|
|
396
436
|
"capture_overrides": {
|
|
397
|
-
"sensor": {
|
|
437
|
+
"sensor": {
|
|
438
|
+
"condition_profile": "underexposed_noisy",
|
|
439
|
+
"auto_exposure": {
|
|
440
|
+
"enabled": true,
|
|
441
|
+
"metering": "fog_aware_center_weighted",
|
|
442
|
+
"target_luminance": {"dist": "uniform", "min": 0.13, "max": 0.20},
|
|
443
|
+
"highlight_protection": 0.78,
|
|
444
|
+
"manual_gain_weight": 0.0,
|
|
445
|
+
"sky_suppression": 0.85,
|
|
446
|
+
"fog_meter_suppression": 0.65
|
|
447
|
+
}
|
|
448
|
+
},
|
|
398
449
|
"transport": {"jpeg": {"quality": {"dist": "uniform", "min": 54, "max": 78}}}
|
|
399
450
|
}
|
|
400
451
|
}
|
|
@@ -436,6 +487,14 @@ where:
|
|
|
436
487
|
|
|
437
488
|
Distant objects are attenuated more (`t` approaches 0) and replaced by airlight, just as in real fog.
|
|
438
489
|
|
|
490
|
+
For gloomy conditions, add `scene_illumination` inside a fog model config. This
|
|
491
|
+
darkens pre-fog scene radiance `I(x)` before the atmospheric scattering equation,
|
|
492
|
+
so near objects can become plausibly overcast or storm-lit instead of passing
|
|
493
|
+
through unchanged. `global_ev` applies to the whole non-sky scene, `near_ev` adds
|
|
494
|
+
extra near-field darkening with `near_decay_depth_m`, `fog_coupled_ev` adds a
|
|
495
|
+
term proportional to local fog opacity, and `sky_weight: 0.0` preserves sky
|
|
496
|
+
pixels when a sky mask is available.
|
|
497
|
+
|
|
439
498
|
### How Each Modality is Used
|
|
440
499
|
|
|
441
500
|
**RGB** — The clean scene image. Normalised to float32 in [0, 1]. This is the *I(x)* term in the fog equation -- it gets blended with the airlight according to transmittance.
|
|
@@ -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
|
+
"render_input_space": "srgb",
|
|
137
138
|
"mode": "sample",
|
|
138
139
|
"device": "cpu",
|
|
139
140
|
"gpu_batch_size": 4,
|
|
@@ -152,6 +153,7 @@ Controls the fog simulation.
|
|
|
152
153
|
| `depth_scale` | Multiplier applied to depth values after loading. |
|
|
153
154
|
| `resize_depth` | Resize the depth map to match the RGB resolution (bilinear). |
|
|
154
155
|
| `contrast_threshold` | Threshold *C_t* used in the visibility-to-attenuation conversion (default `0.05`). |
|
|
156
|
+
| `render_input_space` | Colour space of the RGB supplied to the fog renderer. Use `"srgb"` for display-encoded dataset images so fog and airlight are mixed in scene-linear RGB; use `"linear"` for legacy configs or already-linear radiance. |
|
|
155
157
|
| `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. |
|
|
156
158
|
| `device` | `"cpu"`, `"cuda"`, `"mps"`, or `"gpu"` (alias for cuda). |
|
|
157
159
|
| `gpu_batch_size` | Batch size when running on GPU. Uniform-model samples are batched; heterogeneous samples are processed individually. |
|
|
@@ -296,6 +298,22 @@ the exposure; `resolve_iso` can raise ISO from the metering pressure, dark pixel
|
|
|
296
298
|
fraction, and fog opacity. When auto exposure is enabled, `exposure_gain` still
|
|
297
299
|
applies as scenario-specific exposure compensation.
|
|
298
300
|
|
|
301
|
+
Fog-aware metering modes use `CaptureContext.depth_m`, `k_map`, fog opacity, and
|
|
302
|
+
`attributes.sky_mask` when available. Use `"metering":
|
|
303
|
+
"fog_aware_center_weighted"` or `"sky_aware_center_weighted"` and tune
|
|
304
|
+
`sky_suppression`, `fog_meter_suppression`, `depth_meter_decay_m`, and
|
|
305
|
+
`min_meter_weight` to keep bright sky or dense far-field airlight from dominating
|
|
306
|
+
the exposure meter. Legacy metering modes are unchanged unless these suppression
|
|
307
|
+
keys are present.
|
|
308
|
+
|
|
309
|
+
Set `sensor.sensor_identity.enabled` for persistent sensor structure across
|
|
310
|
+
frames. The identity cache is deterministic for the same `sensor_id`, `seed`,
|
|
311
|
+
image shape, and Bayer pattern. `prnu_sigma` adds multiplicative pixel-response
|
|
312
|
+
non-uniformity before shot noise; `dsnu_sigma`, `persistent_row_sigma`, and
|
|
313
|
+
`persistent_column_sigma` add fixed raw-domain offsets; persistent hot/dead pixel
|
|
314
|
+
probabilities create stable bad-pixel masks that combine with the existing
|
|
315
|
+
per-image bad-pixel probabilities.
|
|
316
|
+
|
|
299
317
|
Set `sensor.shadow_recovery_noise.enabled` to add extra post-demosaic luma and
|
|
300
318
|
chroma corruption only where the pre-exposure rendered luminance was low. This
|
|
301
319
|
is useful for reducing broad global grain while keeping lifted shadows visibly
|
|
@@ -347,6 +365,20 @@ gain, read noise, banding, and dark/fog noise modulation should move together:
|
|
|
347
365
|
}
|
|
348
366
|
```
|
|
349
367
|
|
|
368
|
+
`isp.tone_map` supports `"reinhard"`, `"aces"`, `"clip"`, and `"lut"`. The LUT
|
|
369
|
+
mode uses a cheap interpolated 1D camera-response curve:
|
|
370
|
+
|
|
371
|
+
```json
|
|
372
|
+
{
|
|
373
|
+
"type": "isp",
|
|
374
|
+
"tone_map": "lut",
|
|
375
|
+
"tone_map_strength": 1.0,
|
|
376
|
+
"tone_map_lut": [0.0, 0.006, 0.014, 0.028, 0.052, 0.090, 0.145, 0.220, 0.320, 0.450, 0.610, 0.780, 0.900, 0.965, 0.995, 1.0],
|
|
377
|
+
"tone_map_lut_domain": "linear",
|
|
378
|
+
"gamma": "srgb"
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
350
382
|
Top-level `scenario_profiles` sample one latent scene/camera condition before
|
|
351
383
|
rendering. The selected scenario is merged over the root config, so it can drive
|
|
352
384
|
fog density, atmospheric light, camera profile, capture-stage overrides, ISP, and
|
|
@@ -376,11 +408,30 @@ compression together:
|
|
|
376
408
|
"airlight_method": "dcp_heuristic",
|
|
377
409
|
"models": {
|
|
378
410
|
"heterogeneous_k_ls": {
|
|
379
|
-
"visibility_m": {"dist": "uniform", "min": 18.0, "max": 55.0}
|
|
411
|
+
"visibility_m": {"dist": "uniform", "min": 18.0, "max": 55.0},
|
|
412
|
+
"scene_illumination": {
|
|
413
|
+
"enabled": true,
|
|
414
|
+
"global_ev": {"dist": "uniform", "min": 0.25, "max": 0.85},
|
|
415
|
+
"near_ev": {"dist": "uniform", "min": 0.35, "max": 1.20},
|
|
416
|
+
"near_decay_depth_m": {"dist": "uniform", "min": 10.0, "max": 22.0},
|
|
417
|
+
"fog_coupled_ev": {"dist": "uniform", "min": 0.10, "max": 0.45},
|
|
418
|
+
"sky_weight": 0.0
|
|
419
|
+
}
|
|
380
420
|
}
|
|
381
421
|
},
|
|
382
422
|
"capture_overrides": {
|
|
383
|
-
"sensor": {
|
|
423
|
+
"sensor": {
|
|
424
|
+
"condition_profile": "underexposed_noisy",
|
|
425
|
+
"auto_exposure": {
|
|
426
|
+
"enabled": true,
|
|
427
|
+
"metering": "fog_aware_center_weighted",
|
|
428
|
+
"target_luminance": {"dist": "uniform", "min": 0.13, "max": 0.20},
|
|
429
|
+
"highlight_protection": 0.78,
|
|
430
|
+
"manual_gain_weight": 0.0,
|
|
431
|
+
"sky_suppression": 0.85,
|
|
432
|
+
"fog_meter_suppression": 0.65
|
|
433
|
+
}
|
|
434
|
+
},
|
|
384
435
|
"transport": {"jpeg": {"quality": {"dist": "uniform", "min": 54, "max": 78}}}
|
|
385
436
|
}
|
|
386
437
|
}
|
|
@@ -422,6 +473,14 @@ where:
|
|
|
422
473
|
|
|
423
474
|
Distant objects are attenuated more (`t` approaches 0) and replaced by airlight, just as in real fog.
|
|
424
475
|
|
|
476
|
+
For gloomy conditions, add `scene_illumination` inside a fog model config. This
|
|
477
|
+
darkens pre-fog scene radiance `I(x)` before the atmospheric scattering equation,
|
|
478
|
+
so near objects can become plausibly overcast or storm-lit instead of passing
|
|
479
|
+
through unchanged. `global_ev` applies to the whole non-sky scene, `near_ev` adds
|
|
480
|
+
extra near-field darkening with `near_decay_depth_m`, `fog_coupled_ev` adds a
|
|
481
|
+
term proportional to local fog opacity, and `sky_weight: 0.0` preserves sky
|
|
482
|
+
pixels when a sky mask is available.
|
|
483
|
+
|
|
425
484
|
### How Each Modality is Used
|
|
426
485
|
|
|
427
486
|
**RGB** — The clean scene image. Normalised to float32 in [0, 1]. This is the *I(x)* term in the fog equation -- it gets blended with the airlight according to transmittance.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
import torch
|
|
7
|
+
except ImportError: # pragma: no cover - torch is optional
|
|
8
|
+
torch = None
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def srgb_to_linear(image: np.ndarray) -> np.ndarray:
|
|
12
|
+
"""Convert display-encoded sRGB values in ``[0, 1]`` to scene-linear RGB."""
|
|
13
|
+
img = np.clip(np.asarray(image, dtype=np.float32), 0.0, 1.0)
|
|
14
|
+
return np.where(
|
|
15
|
+
img <= 0.04045,
|
|
16
|
+
img / 12.92,
|
|
17
|
+
((img + 0.055) / 1.055) ** 2.4,
|
|
18
|
+
).astype(np.float32, copy=False)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def linear_to_srgb(image: np.ndarray) -> np.ndarray:
|
|
22
|
+
"""Convert scene-linear RGB values in ``[0, 1]`` to display sRGB."""
|
|
23
|
+
img = np.clip(np.asarray(image, dtype=np.float32), 0.0, 1.0)
|
|
24
|
+
return np.where(
|
|
25
|
+
img <= 0.0031308,
|
|
26
|
+
img * 12.92,
|
|
27
|
+
1.055 * np.power(img, 1.0 / 2.4) - 0.055,
|
|
28
|
+
).astype(np.float32, copy=False)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def srgb_to_linear_torch(image):
|
|
32
|
+
if torch is None: # pragma: no cover - defensive
|
|
33
|
+
raise RuntimeError("Torch color conversion requested but torch is unavailable")
|
|
34
|
+
img = torch.clamp(image, 0.0, 1.0)
|
|
35
|
+
return torch.where(
|
|
36
|
+
img <= 0.04045,
|
|
37
|
+
img / 12.92,
|
|
38
|
+
torch.pow((img + 0.055) / 1.055, 2.4),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def linear_to_srgb_torch(image):
|
|
43
|
+
if torch is None: # pragma: no cover - defensive
|
|
44
|
+
raise RuntimeError("Torch color conversion requested but torch is unavailable")
|
|
45
|
+
img = torch.clamp(image, 0.0, 1.0)
|
|
46
|
+
return torch.where(
|
|
47
|
+
img <= 0.0031308,
|
|
48
|
+
img * 12.92,
|
|
49
|
+
1.055 * torch.pow(img, 1.0 / 2.4) - 0.055,
|
|
50
|
+
)
|