euler-preprocess 3.11.0__tar.gz → 3.13.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.13.0/LICENSE +21 -0
- {euler_preprocess-3.11.0/euler_preprocess.egg-info → euler_preprocess-3.13.0}/PKG-INFO +43 -10
- euler_preprocess-3.11.0/PKG-INFO → euler_preprocess-3.13.0/README.md +39 -24
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/fog/capture.py +64 -7
- euler_preprocess-3.11.0/README.md → euler_preprocess-3.13.0/euler_preprocess.egg-info/PKG-INFO +56 -9
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess.egg-info/SOURCES.txt +1 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/pyproject.toml +10 -1
- euler_preprocess-3.13.0/tests/test_tone_map_lut.py +139 -0
- euler_preprocess-3.11.0/tests/test_tone_map_lut.py +0 -37
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/MANIFEST.in +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/__init__.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/cli.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/common/__init__.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/common/color.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/common/dataset.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/common/device.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/common/intrinsics.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/common/io.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/common/logging.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/common/noise.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/common/normalize.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/common/output.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/common/sampling.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/common/transform.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/fog/__init__.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/fog/airlight_from_sky.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/fog/atmospheric_light.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/fog/augmentations.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/fog/dcp_airlight.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/fog/dcp_airlight_torch.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/fog/dcp_heuristic_airlight.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/fog/dcp_heuristic_airlight_torch.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/fog/inference.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/fog/logging.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/fog/models.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/fog/pipeline.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/fog/transform.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/radial/__init__.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/radial/transform.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/sky_depth/__init__.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/sky_depth/transform.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess.egg-info/dependency_links.txt +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess.egg-info/entry_points.txt +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess.egg-info/requires.txt +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess.egg-info/top_level.txt +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/setup.cfg +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/tests/test_airlight_fallback.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/tests/test_cli_sample_selection.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/tests/test_dcp_heuristic_airlight.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/tests/test_dense_gloomy_daylight_config.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/tests/test_fog_aux_outputs.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/tests/test_fog_aware_auto_exposure.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/tests/test_fog_cpu_gpu_parity.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/tests/test_fog_integration.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/tests/test_radial.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/tests/test_real_drive_sim_scenario_profiles.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/tests/test_scene_illumination.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/tests/test_sensor_identity.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/tests/test_sky_depth.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/tests/test_source_backed_output.py +0 -0
- {euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/tests/test_zip_output.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Daniel Rothenpieler
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: euler-preprocess
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.13.0
|
|
4
4
|
Summary: Physics-based preprocessing (fog, etc.) for RGB+depth datasets
|
|
5
|
+
Author-email: Daniel Rothenpieler <rothenpielerdaniel@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
5
7
|
Requires-Python: >=3.9
|
|
6
8
|
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
7
10
|
Requires-Dist: numpy
|
|
8
11
|
Requires-Dist: Pillow
|
|
9
12
|
Requires-Dist: euler-loading
|
|
@@ -11,12 +14,29 @@ Provides-Extra: gpu
|
|
|
11
14
|
Requires-Dist: torch; extra == "gpu"
|
|
12
15
|
Provides-Extra: progress
|
|
13
16
|
Requires-Dist: tqdm; extra == "progress"
|
|
17
|
+
Dynamic: license-file
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
<!-- euler header — shared across the euler packages.
|
|
20
|
+
Per package, change only: the <h1>, the tagline, and the badge URLs. -->
|
|
21
|
+
<p align="center">
|
|
22
|
+
<img src="https://files.chronodle.com/icons/euler.svg" alt="euler" width="96" height="96">
|
|
23
|
+
</p>
|
|
16
24
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
<h1 align="center">euler-preprocess</h1>
|
|
26
|
+
|
|
27
|
+
<p align="center">
|
|
28
|
+
<em>Physics-based preprocessing for multi-modal RGB+depth datasets — synthetic fog, sky-depth normalisation, and planar-to-radial depth conversion.</em>
|
|
29
|
+
</p>
|
|
30
|
+
|
|
31
|
+
<p align="center">
|
|
32
|
+
<a href="https://pypi.org/project/euler-preprocess/"><img alt="PyPI" src="https://img.shields.io/pypi/v/euler-preprocess.svg"></a>
|
|
33
|
+
<a href="https://pypi.org/project/euler-preprocess/"><img alt="Python 3.9+" src="https://img.shields.io/badge/python-3.9%2B-blue.svg"></a>
|
|
34
|
+
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
|
|
35
|
+
</p>
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
Built on [euler-loading](https://github.com/d-rothen/euler-loading) and
|
|
20
40
|
[ds-crawler](https://github.com/d-rothen/ds-crawler).
|
|
21
41
|
|
|
22
42
|

|
|
@@ -25,12 +45,13 @@ fog, sky-depth normalisation, and planar-to-radial depth conversion. Built on
|
|
|
25
45
|
(`configs/fog_stepped_config.json`). Fog density follows the depth map, so the
|
|
26
46
|
attenuation is scene-consistent rather than a flat overlay.*
|
|
27
47
|
|
|
28
|
-

|
|
29
49
|
|
|
30
|
-
*The same frame through
|
|
50
|
+
*The same frame through all six `scenario_profiles` in
|
|
31
51
|
`configs/dense_gloomy_daylight_fog_camera.json`. Each profile samples fog density,
|
|
32
52
|
airlight, exposure, sensor noise and compression together, so weather and camera
|
|
33
|
-
response stay correlated
|
|
53
|
+
response stay correlated. The last two are deliberately extreme low-light stress
|
|
54
|
+
cases — their auto-exposure targets a mean luminance of ~0.1.*
|
|
34
55
|
|
|
35
56
|
Both sets were rendered with `tools/run_fog_qualitative_samples.py`, which runs a
|
|
36
57
|
folder of RGB/depth/segmentation samples through the transform and writes images for
|
|
@@ -470,8 +491,14 @@ and dark/fog modulation should move together:
|
|
|
470
491
|
|
|
471
492
|
**Tone mapping.** `isp.tone_map` supports `"reinhard"`, `"aces"`, `"clip"`, and
|
|
472
493
|
`"lut"`. The LUT mode interpolates a cheap 1D camera-response curve given by
|
|
473
|
-
`tone_map_lut
|
|
474
|
-
|
|
494
|
+
`tone_map_lut` and interpreted in `tone_map_lut_domain`.
|
|
495
|
+
|
|
496
|
+
For `"lut"` and `"aces"`, `tone_map_strength` is an **exponent on the curve's
|
|
497
|
+
response**, not a linear blend: `0` leaves the image unchanged, `1` applies the
|
|
498
|
+
curve exactly, and values above `1` apply it more strongly. Because the blend is
|
|
499
|
+
geometric, output radiance stays strictly non-negative at any strength. For
|
|
500
|
+
`"reinhard"` the strength is a curve parameter (`x / (1 + strength * x)`) as
|
|
501
|
+
before.
|
|
475
502
|
|
|
476
503
|
### Scenario Profiles
|
|
477
504
|
|
|
@@ -653,3 +680,9 @@ The transform config takes no parameters (`{}`); intrinsics are read from the
|
|
|
653
680
|
CLI runs write a source-backed depth dataset mirroring the input depth modality's layout
|
|
654
681
|
and writer metadata, with `meta.radial_depth` set to `true` in the emitted `index.json`.
|
|
655
682
|
Standalone `RadialTransform(...)` usage keeps the legacy `.npy` output behaviour.
|
|
683
|
+
|
|
684
|
+
---
|
|
685
|
+
|
|
686
|
+
## License
|
|
687
|
+
|
|
688
|
+
[MIT](LICENSE) © Daniel Rothenpieler
|
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
<!-- euler header — shared across the euler packages.
|
|
2
|
+
Per package, change only: the <h1>, the tagline, and the badge URLs. -->
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://files.chronodle.com/icons/euler.svg" alt="euler" width="96" height="96">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<h1 align="center">euler-preprocess</h1>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<em>Physics-based preprocessing for multi-modal RGB+depth datasets — synthetic fog, sky-depth normalisation, and planar-to-radial depth conversion.</em>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<a href="https://pypi.org/project/euler-preprocess/"><img alt="PyPI" src="https://img.shields.io/pypi/v/euler-preprocess.svg"></a>
|
|
15
|
+
<a href="https://pypi.org/project/euler-preprocess/"><img alt="Python 3.9+" src="https://img.shields.io/badge/python-3.9%2B-blue.svg"></a>
|
|
16
|
+
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
Built on [euler-loading](https://github.com/d-rothen/euler-loading) and
|
|
20
22
|
[ds-crawler](https://github.com/d-rothen/ds-crawler).
|
|
21
23
|
|
|
22
24
|

|
|
@@ -25,12 +27,13 @@ fog, sky-depth normalisation, and planar-to-radial depth conversion. Built on
|
|
|
25
27
|
(`configs/fog_stepped_config.json`). Fog density follows the depth map, so the
|
|
26
28
|
attenuation is scene-consistent rather than a flat overlay.*
|
|
27
29
|
|
|
28
|
-

|
|
29
31
|
|
|
30
|
-
*The same frame through
|
|
32
|
+
*The same frame through all six `scenario_profiles` in
|
|
31
33
|
`configs/dense_gloomy_daylight_fog_camera.json`. Each profile samples fog density,
|
|
32
34
|
airlight, exposure, sensor noise and compression together, so weather and camera
|
|
33
|
-
response stay correlated
|
|
35
|
+
response stay correlated. The last two are deliberately extreme low-light stress
|
|
36
|
+
cases — their auto-exposure targets a mean luminance of ~0.1.*
|
|
34
37
|
|
|
35
38
|
Both sets were rendered with `tools/run_fog_qualitative_samples.py`, which runs a
|
|
36
39
|
folder of RGB/depth/segmentation samples through the transform and writes images for
|
|
@@ -470,8 +473,14 @@ and dark/fog modulation should move together:
|
|
|
470
473
|
|
|
471
474
|
**Tone mapping.** `isp.tone_map` supports `"reinhard"`, `"aces"`, `"clip"`, and
|
|
472
475
|
`"lut"`. The LUT mode interpolates a cheap 1D camera-response curve given by
|
|
473
|
-
`tone_map_lut
|
|
474
|
-
|
|
476
|
+
`tone_map_lut` and interpreted in `tone_map_lut_domain`.
|
|
477
|
+
|
|
478
|
+
For `"lut"` and `"aces"`, `tone_map_strength` is an **exponent on the curve's
|
|
479
|
+
response**, not a linear blend: `0` leaves the image unchanged, `1` applies the
|
|
480
|
+
curve exactly, and values above `1` apply it more strongly. Because the blend is
|
|
481
|
+
geometric, output radiance stays strictly non-negative at any strength. For
|
|
482
|
+
`"reinhard"` the strength is a curve parameter (`x / (1 + strength * x)`) as
|
|
483
|
+
before.
|
|
475
484
|
|
|
476
485
|
### Scenario Profiles
|
|
477
486
|
|
|
@@ -653,3 +662,9 @@ The transform config takes no parameters (`{}`); intrinsics are read from the
|
|
|
653
662
|
CLI runs write a source-backed depth dataset mirroring the input depth modality's layout
|
|
654
663
|
and writer metadata, with `meta.radial_depth` set to `true` in the emitted `index.json`.
|
|
655
664
|
Standalone `RadialTransform(...)` usage keeps the legacy `.npy` output behaviour.
|
|
665
|
+
|
|
666
|
+
---
|
|
667
|
+
|
|
668
|
+
## License
|
|
669
|
+
|
|
670
|
+
[MIT](LICENSE) © Daniel Rothenpieler
|
|
@@ -4960,6 +4960,48 @@ def _apply_bad_pixels_torch(
|
|
|
4960
4960
|
return out
|
|
4961
4961
|
|
|
4962
4962
|
|
|
4963
|
+
# Radiance floor used when blending tone curves in log space, and the bounds
|
|
4964
|
+
# applied to the log-space result so extreme strengths cannot overflow ``exp``.
|
|
4965
|
+
_TONE_CURVE_FLOOR = 1e-8
|
|
4966
|
+
_TONE_CURVE_LOG_MIN = -80.0
|
|
4967
|
+
_TONE_CURVE_LOG_MAX = 20.0
|
|
4968
|
+
|
|
4969
|
+
|
|
4970
|
+
def _blend_tone_curve_np(
|
|
4971
|
+
image: np.ndarray,
|
|
4972
|
+
mapped: np.ndarray,
|
|
4973
|
+
strength: float,
|
|
4974
|
+
) -> np.ndarray:
|
|
4975
|
+
"""Apply a tone curve with ``strength`` acting as an exponent on its response.
|
|
4976
|
+
|
|
4977
|
+
The blend is geometric rather than linear: ``strength`` exponentiates the
|
|
4978
|
+
per-pixel response ratio ``mapped / image``, so ``0`` returns the input
|
|
4979
|
+
unchanged, ``1`` reproduces the curve exactly, and values above ``1`` apply
|
|
4980
|
+
the curve more strongly. A linear blend extrapolates through zero once
|
|
4981
|
+
``strength`` exceeds ``1 / (1 - toe_slope)``, which drives every shadow
|
|
4982
|
+
pixel negative and clips whole frames to black; the geometric form stays
|
|
4983
|
+
strictly non-negative for any strength.
|
|
4984
|
+
"""
|
|
4985
|
+
if strength == 1.0:
|
|
4986
|
+
return np.asarray(mapped, dtype=np.float32)
|
|
4987
|
+
if strength == 0.0:
|
|
4988
|
+
return np.asarray(image, dtype=np.float32)
|
|
4989
|
+
|
|
4990
|
+
image = np.asarray(image, dtype=np.float32)
|
|
4991
|
+
mapped = np.asarray(mapped, dtype=np.float32)
|
|
4992
|
+
log_image = np.log(np.maximum(image, _TONE_CURVE_FLOOR))
|
|
4993
|
+
log_mapped = np.log(np.maximum(mapped, _TONE_CURVE_FLOOR))
|
|
4994
|
+
log_blend = np.clip(
|
|
4995
|
+
log_image + strength * (log_mapped - log_image),
|
|
4996
|
+
_TONE_CURVE_LOG_MIN,
|
|
4997
|
+
_TONE_CURVE_LOG_MAX,
|
|
4998
|
+
)
|
|
4999
|
+
return np.where(image > 0.0, np.exp(log_blend), 0.0).astype(
|
|
5000
|
+
np.float32,
|
|
5001
|
+
copy=False,
|
|
5002
|
+
)
|
|
5003
|
+
|
|
5004
|
+
|
|
4963
5005
|
def _apply_tone_map_np(
|
|
4964
5006
|
image: np.ndarray,
|
|
4965
5007
|
mode: str,
|
|
@@ -4977,18 +5019,33 @@ def _apply_tone_map_np(
|
|
|
4977
5019
|
d = 0.59
|
|
4978
5020
|
e = 0.14
|
|
4979
5021
|
mapped = (img * (a * img + b)) / (img * (c * img + d) + e)
|
|
4980
|
-
return img
|
|
5022
|
+
return _blend_tone_curve_np(img, mapped, strength)
|
|
4981
5023
|
if mode == "clip":
|
|
4982
5024
|
return _clip01(img)
|
|
4983
5025
|
if mode == "lut":
|
|
4984
5026
|
mapped = _apply_tone_map_lut_np(img, config or {})
|
|
4985
|
-
return (img
|
|
4986
|
-
np.float32,
|
|
4987
|
-
copy=False,
|
|
4988
|
-
)
|
|
5027
|
+
return _blend_tone_curve_np(img, mapped, strength)
|
|
4989
5028
|
raise ValueError(f"Unsupported tone_map: {mode}")
|
|
4990
5029
|
|
|
4991
5030
|
|
|
5031
|
+
def _blend_tone_curve_torch(image, mapped, strength: float):
|
|
5032
|
+
"""Torch counterpart of :func:`_blend_tone_curve_np`."""
|
|
5033
|
+
if strength == 1.0:
|
|
5034
|
+
return mapped
|
|
5035
|
+
if strength == 0.0:
|
|
5036
|
+
return image
|
|
5037
|
+
|
|
5038
|
+
log_image = torch.log(torch.clamp(image, min=_TONE_CURVE_FLOOR))
|
|
5039
|
+
log_mapped = torch.log(torch.clamp(mapped, min=_TONE_CURVE_FLOOR))
|
|
5040
|
+
log_blend = torch.clamp(
|
|
5041
|
+
log_image + strength * (log_mapped - log_image),
|
|
5042
|
+
min=_TONE_CURVE_LOG_MIN,
|
|
5043
|
+
max=_TONE_CURVE_LOG_MAX,
|
|
5044
|
+
)
|
|
5045
|
+
blended = torch.exp(log_blend)
|
|
5046
|
+
return torch.where(image > 0.0, blended, torch.zeros_like(blended))
|
|
5047
|
+
|
|
5048
|
+
|
|
4992
5049
|
def _apply_tone_map_torch(
|
|
4993
5050
|
image,
|
|
4994
5051
|
mode: str,
|
|
@@ -5006,12 +5063,12 @@ def _apply_tone_map_torch(
|
|
|
5006
5063
|
d = 0.59
|
|
5007
5064
|
e = 0.14
|
|
5008
5065
|
mapped = (img * (a * img + b)) / (img * (c * img + d) + e)
|
|
5009
|
-
return img
|
|
5066
|
+
return _blend_tone_curve_torch(img, mapped, strength)
|
|
5010
5067
|
if mode == "clip":
|
|
5011
5068
|
return _clip01_torch(img)
|
|
5012
5069
|
if mode == "lut":
|
|
5013
5070
|
mapped = _apply_tone_map_lut_torch(img, config or {})
|
|
5014
|
-
return img
|
|
5071
|
+
return _blend_tone_curve_torch(img, mapped, strength)
|
|
5015
5072
|
raise ValueError(f"Unsupported tone_map: {mode}")
|
|
5016
5073
|
|
|
5017
5074
|
|
euler_preprocess-3.11.0/README.md → euler_preprocess-3.13.0/euler_preprocess.egg-info/PKG-INFO
RENAMED
|
@@ -1,8 +1,42 @@
|
|
|
1
|
-
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: euler-preprocess
|
|
3
|
+
Version: 3.13.0
|
|
4
|
+
Summary: Physics-based preprocessing (fog, etc.) for RGB+depth datasets
|
|
5
|
+
Author-email: Daniel Rothenpieler <rothenpielerdaniel@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: numpy
|
|
11
|
+
Requires-Dist: Pillow
|
|
12
|
+
Requires-Dist: euler-loading
|
|
13
|
+
Provides-Extra: gpu
|
|
14
|
+
Requires-Dist: torch; extra == "gpu"
|
|
15
|
+
Provides-Extra: progress
|
|
16
|
+
Requires-Dist: tqdm; extra == "progress"
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
<!-- euler header — shared across the euler packages.
|
|
20
|
+
Per package, change only: the <h1>, the tagline, and the badge URLs. -->
|
|
21
|
+
<p align="center">
|
|
22
|
+
<img src="https://files.chronodle.com/icons/euler.svg" alt="euler" width="96" height="96">
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
<h1 align="center">euler-preprocess</h1>
|
|
26
|
+
|
|
27
|
+
<p align="center">
|
|
28
|
+
<em>Physics-based preprocessing for multi-modal RGB+depth datasets — synthetic fog, sky-depth normalisation, and planar-to-radial depth conversion.</em>
|
|
29
|
+
</p>
|
|
30
|
+
|
|
31
|
+
<p align="center">
|
|
32
|
+
<a href="https://pypi.org/project/euler-preprocess/"><img alt="PyPI" src="https://img.shields.io/pypi/v/euler-preprocess.svg"></a>
|
|
33
|
+
<a href="https://pypi.org/project/euler-preprocess/"><img alt="Python 3.9+" src="https://img.shields.io/badge/python-3.9%2B-blue.svg"></a>
|
|
34
|
+
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
|
|
35
|
+
</p>
|
|
2
36
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
[euler-loading](https://github.com/d-rothen/euler-loading) and
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
Built on [euler-loading](https://github.com/d-rothen/euler-loading) and
|
|
6
40
|
[ds-crawler](https://github.com/d-rothen/ds-crawler).
|
|
7
41
|
|
|
8
42
|

|
|
@@ -11,12 +45,13 @@ fog, sky-depth normalisation, and planar-to-radial depth conversion. Built on
|
|
|
11
45
|
(`configs/fog_stepped_config.json`). Fog density follows the depth map, so the
|
|
12
46
|
attenuation is scene-consistent rather than a flat overlay.*
|
|
13
47
|
|
|
14
|
-

|
|
15
49
|
|
|
16
|
-
*The same frame through
|
|
50
|
+
*The same frame through all six `scenario_profiles` in
|
|
17
51
|
`configs/dense_gloomy_daylight_fog_camera.json`. Each profile samples fog density,
|
|
18
52
|
airlight, exposure, sensor noise and compression together, so weather and camera
|
|
19
|
-
response stay correlated
|
|
53
|
+
response stay correlated. The last two are deliberately extreme low-light stress
|
|
54
|
+
cases — their auto-exposure targets a mean luminance of ~0.1.*
|
|
20
55
|
|
|
21
56
|
Both sets were rendered with `tools/run_fog_qualitative_samples.py`, which runs a
|
|
22
57
|
folder of RGB/depth/segmentation samples through the transform and writes images for
|
|
@@ -456,8 +491,14 @@ and dark/fog modulation should move together:
|
|
|
456
491
|
|
|
457
492
|
**Tone mapping.** `isp.tone_map` supports `"reinhard"`, `"aces"`, `"clip"`, and
|
|
458
493
|
`"lut"`. The LUT mode interpolates a cheap 1D camera-response curve given by
|
|
459
|
-
`tone_map_lut
|
|
460
|
-
|
|
494
|
+
`tone_map_lut` and interpreted in `tone_map_lut_domain`.
|
|
495
|
+
|
|
496
|
+
For `"lut"` and `"aces"`, `tone_map_strength` is an **exponent on the curve's
|
|
497
|
+
response**, not a linear blend: `0` leaves the image unchanged, `1` applies the
|
|
498
|
+
curve exactly, and values above `1` apply it more strongly. Because the blend is
|
|
499
|
+
geometric, output radiance stays strictly non-negative at any strength. For
|
|
500
|
+
`"reinhard"` the strength is a curve parameter (`x / (1 + strength * x)`) as
|
|
501
|
+
before.
|
|
461
502
|
|
|
462
503
|
### Scenario Profiles
|
|
463
504
|
|
|
@@ -639,3 +680,9 @@ The transform config takes no parameters (`{}`); intrinsics are read from the
|
|
|
639
680
|
CLI runs write a source-backed depth dataset mirroring the input depth modality's layout
|
|
640
681
|
and writer metadata, with `meta.radial_depth` set to `true` in the emitted `index.json`.
|
|
641
682
|
Standalone `RadialTransform(...)` usage keeps the legacy `.npy` output behaviour.
|
|
683
|
+
|
|
684
|
+
---
|
|
685
|
+
|
|
686
|
+
## License
|
|
687
|
+
|
|
688
|
+
[MIT](LICENSE) © Daniel Rothenpieler
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
1
5
|
[project]
|
|
2
6
|
name = "euler-preprocess"
|
|
3
|
-
version = "3.
|
|
7
|
+
version = "3.13.0"
|
|
4
8
|
description = "Physics-based preprocessing (fog, etc.) for RGB+depth datasets"
|
|
5
9
|
readme = "README.md"
|
|
6
10
|
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Daniel Rothenpieler", email = "rothenpielerdaniel@gmail.com" },
|
|
15
|
+
]
|
|
7
16
|
dependencies = [
|
|
8
17
|
"numpy",
|
|
9
18
|
"Pillow",
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from euler_preprocess.fog.capture import (
|
|
7
|
+
CaptureArtifactPipeline,
|
|
8
|
+
CaptureContext,
|
|
9
|
+
_apply_tone_map_np,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import torch
|
|
14
|
+
|
|
15
|
+
from euler_preprocess.fog.capture import _apply_tone_map_torch
|
|
16
|
+
except ImportError:
|
|
17
|
+
torch = None
|
|
18
|
+
|
|
19
|
+
# A camera-response curve whose toe compresses shadows ~11x, matching the
|
|
20
|
+
# profiles in configs/dense_gloomy_daylight_fog_camera.json.
|
|
21
|
+
TOE_LUT = [
|
|
22
|
+
0.0, 0.006, 0.014, 0.028, 0.052, 0.090, 0.145, 0.220,
|
|
23
|
+
0.320, 0.450, 0.610, 0.780, 0.900, 0.965, 0.995, 1.0,
|
|
24
|
+
]
|
|
25
|
+
LUT_CONFIG = {"tone_map_lut": TOE_LUT, "tone_map_lut_domain": "linear"}
|
|
26
|
+
# Shadow values below the LUT's linear toe, where a linear strength blend used
|
|
27
|
+
# to extrapolate through zero at strength ~= 1/(1 - 0.09) = 1.0989.
|
|
28
|
+
SHADOWS = np.asarray(
|
|
29
|
+
[[[0.005, 0.005, 0.005], [0.0145, 0.0145, 0.0145], [0.03, 0.03, 0.03]]],
|
|
30
|
+
dtype=np.float32,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _isp_pipeline(**overrides):
|
|
35
|
+
stage = {
|
|
36
|
+
"type": "isp",
|
|
37
|
+
"denoise_sigma": 0.0,
|
|
38
|
+
"color_matrix": [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
|
|
39
|
+
"tone_map": "lut",
|
|
40
|
+
"tone_map_strength": 1.0,
|
|
41
|
+
"tone_map_lut": TOE_LUT,
|
|
42
|
+
"gamma": "linear",
|
|
43
|
+
"local_contrast_strength": 0.0,
|
|
44
|
+
"sharpen_amount": 0.0,
|
|
45
|
+
"saturation": 1.0,
|
|
46
|
+
}
|
|
47
|
+
stage.update(overrides)
|
|
48
|
+
return CaptureArtifactPipeline.from_config({"capture": {"stages": [stage]}})
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_isp_tone_map_lut_interpolates_1d_curve() -> None:
|
|
52
|
+
pipeline = _isp_pipeline(tone_map_lut=[0.0, 0.25, 1.0])
|
|
53
|
+
image = np.asarray([[[0.0, 0.5, 1.0]]], dtype=np.float32)
|
|
54
|
+
|
|
55
|
+
result = pipeline.apply_np(image, CaptureContext(rng=np.random.default_rng(9)))
|
|
56
|
+
|
|
57
|
+
np.testing.assert_allclose(result, [[[0.0, 0.25, 1.0]]], atol=1e-6)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_tone_map_strength_zero_is_identity() -> None:
|
|
61
|
+
result = _apply_tone_map_np(SHADOWS, "lut", 0.0, LUT_CONFIG)
|
|
62
|
+
|
|
63
|
+
np.testing.assert_allclose(result, SHADOWS, atol=1e-7)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_tone_map_strength_one_reproduces_curve_exactly() -> None:
|
|
67
|
+
result = _apply_tone_map_np(SHADOWS, "lut", 1.0, LUT_CONFIG)
|
|
68
|
+
|
|
69
|
+
expected = np.interp(
|
|
70
|
+
SHADOWS,
|
|
71
|
+
np.linspace(0.0, 1.0, len(TOE_LUT)),
|
|
72
|
+
np.asarray(TOE_LUT, dtype=np.float32),
|
|
73
|
+
)
|
|
74
|
+
np.testing.assert_allclose(result, expected, atol=1e-6)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@pytest.mark.parametrize("strength", [1.05, 1.0989, 1.119, 1.22, 2.0, 8.0])
|
|
78
|
+
def test_tone_map_strength_above_one_never_goes_negative(strength: float) -> None:
|
|
79
|
+
"""A linear blend crossed zero here and clipped whole frames to black."""
|
|
80
|
+
result = _apply_tone_map_np(SHADOWS, "lut", strength, LUT_CONFIG)
|
|
81
|
+
|
|
82
|
+
assert np.isfinite(result).all()
|
|
83
|
+
assert float(result.min()) > 0.0
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_isp_does_not_black_out_shadows_at_strength_above_one() -> None:
|
|
87
|
+
"""End-to-end guard: the ISP must not crush a dark frame to zero."""
|
|
88
|
+
pipeline = _isp_pipeline(tone_map_strength=1.22, gamma="srgb")
|
|
89
|
+
|
|
90
|
+
result = pipeline.apply_np(SHADOWS, CaptureContext(rng=np.random.default_rng(9)))
|
|
91
|
+
|
|
92
|
+
assert float(result.min()) > 0.0
|
|
93
|
+
assert float(np.median(result)) > 1e-4
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_tone_map_strength_is_monotonic_and_bounded() -> None:
|
|
97
|
+
previous = None
|
|
98
|
+
for strength in np.linspace(0.0, 4.0, 41):
|
|
99
|
+
result = _apply_tone_map_np(SHADOWS, "lut", float(strength), LUT_CONFIG)
|
|
100
|
+
assert np.isfinite(result).all()
|
|
101
|
+
assert float(result.min()) >= 0.0
|
|
102
|
+
if previous is not None:
|
|
103
|
+
# A compressive curve only darkens further as strength rises.
|
|
104
|
+
assert (result <= previous + 1e-6).all()
|
|
105
|
+
previous = result
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_tone_map_preserves_black_and_survives_extreme_strength() -> None:
|
|
109
|
+
image = np.zeros((1, 1, 3), dtype=np.float32)
|
|
110
|
+
assert float(_apply_tone_map_np(image, "lut", 3.0, LUT_CONFIG).max()) == 0.0
|
|
111
|
+
|
|
112
|
+
result = _apply_tone_map_np(SHADOWS, "lut", 1e6, LUT_CONFIG)
|
|
113
|
+
assert np.isfinite(result).all()
|
|
114
|
+
assert float(result.min()) >= 0.0
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@pytest.mark.parametrize("strength", [0.0, 1.0, 1.3, 5.0])
|
|
118
|
+
def test_aces_tone_map_stays_non_negative(strength: float) -> None:
|
|
119
|
+
result = _apply_tone_map_np(SHADOWS, "aces", strength, None)
|
|
120
|
+
|
|
121
|
+
assert np.isfinite(result).all()
|
|
122
|
+
assert float(result.min()) >= 0.0
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@pytest.mark.skipif(torch is None, reason="torch not installed")
|
|
126
|
+
@pytest.mark.parametrize("mode", ["lut", "aces"])
|
|
127
|
+
@pytest.mark.parametrize("strength", [0.0, 0.5, 1.0, 1.119, 2.0])
|
|
128
|
+
def test_torch_tone_map_matches_numpy(mode: str, strength: float) -> None:
|
|
129
|
+
config = LUT_CONFIG if mode == "lut" else None
|
|
130
|
+
expected = _apply_tone_map_np(SHADOWS, mode, strength, config)
|
|
131
|
+
|
|
132
|
+
actual = _apply_tone_map_torch(
|
|
133
|
+
torch.from_numpy(SHADOWS.copy()),
|
|
134
|
+
mode,
|
|
135
|
+
strength,
|
|
136
|
+
config,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
np.testing.assert_allclose(actual.numpy(), expected, atol=1e-6)
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
|
|
5
|
-
from euler_preprocess.fog.capture import CaptureArtifactPipeline, CaptureContext
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def test_isp_tone_map_lut_interpolates_1d_curve() -> None:
|
|
9
|
-
pipeline = CaptureArtifactPipeline.from_config(
|
|
10
|
-
{
|
|
11
|
-
"capture": {
|
|
12
|
-
"stages": [
|
|
13
|
-
{
|
|
14
|
-
"type": "isp",
|
|
15
|
-
"denoise_sigma": 0.0,
|
|
16
|
-
"color_matrix": [
|
|
17
|
-
[1.0, 0.0, 0.0],
|
|
18
|
-
[0.0, 1.0, 0.0],
|
|
19
|
-
[0.0, 0.0, 1.0],
|
|
20
|
-
],
|
|
21
|
-
"tone_map": "lut",
|
|
22
|
-
"tone_map_strength": 1.0,
|
|
23
|
-
"tone_map_lut": [0.0, 0.25, 1.0],
|
|
24
|
-
"gamma": "linear",
|
|
25
|
-
"local_contrast_strength": 0.0,
|
|
26
|
-
"sharpen_amount": 0.0,
|
|
27
|
-
"saturation": 1.0,
|
|
28
|
-
}
|
|
29
|
-
]
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
)
|
|
33
|
-
image = np.asarray([[[0.0, 0.5, 1.0]]], dtype=np.float32)
|
|
34
|
-
|
|
35
|
-
result = pipeline.apply_np(image, CaptureContext(rng=np.random.default_rng(9)))
|
|
36
|
-
|
|
37
|
-
np.testing.assert_allclose(result, [[[0.0, 0.25, 1.0]]], atol=1e-6)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/fog/airlight_from_sky.py
RENAMED
|
File without changes
|
{euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/fog/atmospheric_light.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/fog/dcp_airlight_torch.py
RENAMED
|
File without changes
|
{euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess/fog/dcp_heuristic_airlight.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/euler_preprocess.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/tests/test_dense_gloomy_daylight_config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{euler_preprocess-3.11.0 → euler_preprocess-3.13.0}/tests/test_real_drive_sim_scenario_profiles.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|