euler-preprocess 2.2.0__tar.gz → 2.3.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-2.2.0 → euler_preprocess-2.3.0}/PKG-INFO +34 -15
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/README.md +33 -14
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/common/output.py +25 -3
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/fog/models.py +277 -16
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/fog/transform.py +80 -1
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess.egg-info/PKG-INFO +34 -15
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/pyproject.toml +1 -1
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/tests/test_fog_aux_outputs.py +69 -1
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/__init__.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/cli.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/common/__init__.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/common/dataset.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/common/device.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/common/intrinsics.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/common/io.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/common/logging.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/common/noise.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/common/normalize.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/common/sampling.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/common/transform.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/fog/__init__.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/fog/airlight_from_sky.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/fog/augmentations.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/fog/dcp_airlight.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/fog/dcp_airlight_torch.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/fog/dcp_heuristic_airlight.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/fog/dcp_heuristic_airlight_torch.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/fog/foggify.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/fog/foggify_logging.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/fog/logging.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/radial/__init__.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/radial/transform.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/sky_depth/__init__.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/sky_depth/transform.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess.egg-info/SOURCES.txt +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess.egg-info/dependency_links.txt +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess.egg-info/entry_points.txt +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess.egg-info/requires.txt +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess.egg-info/top_level.txt +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/setup.cfg +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/tests/test_airlight_fallback.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/tests/test_cli_sample_selection.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/tests/test_dcp_heuristic_airlight.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/tests/test_foggify_integration.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/tests/test_radial.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/tests/test_sky_depth.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/tests/test_source_backed_output.py +0 -0
- {euler_preprocess-2.2.0 → euler_preprocess-2.3.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: 2.
|
|
3
|
+
Version: 2.3.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
|
|
@@ -232,10 +232,10 @@ Each image is assigned a fog model via the `selection` block:
|
|
|
232
232
|
"selection": {
|
|
233
233
|
"mode": "weighted",
|
|
234
234
|
"weights": {
|
|
235
|
-
"uniform":
|
|
236
|
-
"heterogeneous_k": 0.
|
|
237
|
-
"heterogeneous_ls": 0.
|
|
238
|
-
"heterogeneous_k_ls": 0.
|
|
235
|
+
"uniform": 0.25,
|
|
236
|
+
"heterogeneous_k": 0.35,
|
|
237
|
+
"heterogeneous_ls": 0.25,
|
|
238
|
+
"heterogeneous_k_ls": 0.15
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
241
|
```
|
|
@@ -309,9 +309,12 @@ variants:
|
|
|
309
309
|
"scattering_coefficient": 0.15,
|
|
310
310
|
"atmospheric_light": [1.0, 1.0, 1.0],
|
|
311
311
|
"k_hetero": {
|
|
312
|
-
"scales": "
|
|
313
|
-
"
|
|
314
|
-
"
|
|
312
|
+
"scales": "smooth_auto",
|
|
313
|
+
"correlation_length_fraction": 0.25,
|
|
314
|
+
"octaves": 3,
|
|
315
|
+
"min_factor": 0.65,
|
|
316
|
+
"max_factor": 1.45,
|
|
317
|
+
"contrast": 0.65,
|
|
315
318
|
"normalize_to_mean": true
|
|
316
319
|
}
|
|
317
320
|
}
|
|
@@ -327,26 +330,42 @@ MOR/beta descriptors when available. euler-loading exposes these as
|
|
|
327
330
|
|
|
328
331
|
### Heterogeneous Noise Fields
|
|
329
332
|
|
|
330
|
-
Both `k_hetero` and `ls_hetero` use Perlin FBM (fractional Brownian
|
|
333
|
+
Both `k_hetero` and `ls_hetero` use Perlin FBM (fractional Brownian
|
|
334
|
+
motion) to generate spatially-varying factor fields. For realistic fog,
|
|
335
|
+
prefer the smooth mode: it keeps Perlin wavelengths tied to the image size,
|
|
336
|
+
then optionally reduces noise contrast and applies a final blur before mapping
|
|
337
|
+
the noise to physical factors.
|
|
331
338
|
|
|
332
339
|
```json
|
|
333
340
|
"k_hetero": {
|
|
334
|
-
"scales": "
|
|
335
|
-
"
|
|
341
|
+
"scales": "smooth_auto",
|
|
342
|
+
"correlation_length_fraction": 0.25,
|
|
343
|
+
"octaves": 3,
|
|
336
344
|
"max_scale": null,
|
|
337
|
-
"min_factor": 0.
|
|
338
|
-
"max_factor": 1.
|
|
345
|
+
"min_factor": 0.65,
|
|
346
|
+
"max_factor": 1.45,
|
|
347
|
+
"contrast": 0.65,
|
|
348
|
+
"smooth_sigma_fraction": 0.0,
|
|
339
349
|
"normalize_to_mean": true
|
|
340
350
|
}
|
|
341
351
|
```
|
|
342
352
|
|
|
343
|
-
The noise field (values in [0, 1]) is mapped to a factor field:
|
|
353
|
+
The noise field (values in [0, 1]) is mapped to a factor field:
|
|
354
|
+
`factor(x) = min_factor + (max_factor - min_factor) * noise(x)`.
|
|
355
|
+
`contrast < 1` compresses the noise around 0.5 before this mapping, avoiding
|
|
356
|
+
extreme local fog density. When `normalize_to_mean` is `true`, the factor field
|
|
357
|
+
is rescaled so its spatial mean equals 1.0, preserving the overall fog density
|
|
358
|
+
while introducing spatial variation.
|
|
344
359
|
|
|
345
360
|
| Parameter | Effect |
|
|
346
361
|
|---|---|
|
|
347
362
|
| `min_factor` / `max_factor` | Range of the multiplicative factor. |
|
|
348
363
|
| `normalize_to_mean` | Rescale factors so the image-wide mean equals the base value. Recommended for `k_hetero`. |
|
|
349
|
-
| `scales`
|
|
364
|
+
| `scales: "smooth_auto"` | Build low-frequency Perlin scales from the image size. |
|
|
365
|
+
| `correlation_length_fraction` | Approximate smallest fog feature size as a fraction of the shorter image side. Larger values create smoother gradients. |
|
|
366
|
+
| `octaves` / `lacunarity` / `max_scale` | Control how many increasingly broad Perlin components are mixed. |
|
|
367
|
+
| `contrast` | Compress or expand the Perlin range before mapping to factors. Values below 1 are recommended. |
|
|
368
|
+
| `smooth_sigma` / `smooth_sigma_fraction` | Optional final Gaussian blur in pixels or as a fraction of the shorter image side. |
|
|
350
369
|
|
|
351
370
|
### Fog Output
|
|
352
371
|
|
|
@@ -218,10 +218,10 @@ Each image is assigned a fog model via the `selection` block:
|
|
|
218
218
|
"selection": {
|
|
219
219
|
"mode": "weighted",
|
|
220
220
|
"weights": {
|
|
221
|
-
"uniform":
|
|
222
|
-
"heterogeneous_k": 0.
|
|
223
|
-
"heterogeneous_ls": 0.
|
|
224
|
-
"heterogeneous_k_ls": 0.
|
|
221
|
+
"uniform": 0.25,
|
|
222
|
+
"heterogeneous_k": 0.35,
|
|
223
|
+
"heterogeneous_ls": 0.25,
|
|
224
|
+
"heterogeneous_k_ls": 0.15
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
```
|
|
@@ -295,9 +295,12 @@ variants:
|
|
|
295
295
|
"scattering_coefficient": 0.15,
|
|
296
296
|
"atmospheric_light": [1.0, 1.0, 1.0],
|
|
297
297
|
"k_hetero": {
|
|
298
|
-
"scales": "
|
|
299
|
-
"
|
|
300
|
-
"
|
|
298
|
+
"scales": "smooth_auto",
|
|
299
|
+
"correlation_length_fraction": 0.25,
|
|
300
|
+
"octaves": 3,
|
|
301
|
+
"min_factor": 0.65,
|
|
302
|
+
"max_factor": 1.45,
|
|
303
|
+
"contrast": 0.65,
|
|
301
304
|
"normalize_to_mean": true
|
|
302
305
|
}
|
|
303
306
|
}
|
|
@@ -313,26 +316,42 @@ MOR/beta descriptors when available. euler-loading exposes these as
|
|
|
313
316
|
|
|
314
317
|
### Heterogeneous Noise Fields
|
|
315
318
|
|
|
316
|
-
Both `k_hetero` and `ls_hetero` use Perlin FBM (fractional Brownian
|
|
319
|
+
Both `k_hetero` and `ls_hetero` use Perlin FBM (fractional Brownian
|
|
320
|
+
motion) to generate spatially-varying factor fields. For realistic fog,
|
|
321
|
+
prefer the smooth mode: it keeps Perlin wavelengths tied to the image size,
|
|
322
|
+
then optionally reduces noise contrast and applies a final blur before mapping
|
|
323
|
+
the noise to physical factors.
|
|
317
324
|
|
|
318
325
|
```json
|
|
319
326
|
"k_hetero": {
|
|
320
|
-
"scales": "
|
|
321
|
-
"
|
|
327
|
+
"scales": "smooth_auto",
|
|
328
|
+
"correlation_length_fraction": 0.25,
|
|
329
|
+
"octaves": 3,
|
|
322
330
|
"max_scale": null,
|
|
323
|
-
"min_factor": 0.
|
|
324
|
-
"max_factor": 1.
|
|
331
|
+
"min_factor": 0.65,
|
|
332
|
+
"max_factor": 1.45,
|
|
333
|
+
"contrast": 0.65,
|
|
334
|
+
"smooth_sigma_fraction": 0.0,
|
|
325
335
|
"normalize_to_mean": true
|
|
326
336
|
}
|
|
327
337
|
```
|
|
328
338
|
|
|
329
|
-
The noise field (values in [0, 1]) is mapped to a factor field:
|
|
339
|
+
The noise field (values in [0, 1]) is mapped to a factor field:
|
|
340
|
+
`factor(x) = min_factor + (max_factor - min_factor) * noise(x)`.
|
|
341
|
+
`contrast < 1` compresses the noise around 0.5 before this mapping, avoiding
|
|
342
|
+
extreme local fog density. When `normalize_to_mean` is `true`, the factor field
|
|
343
|
+
is rescaled so its spatial mean equals 1.0, preserving the overall fog density
|
|
344
|
+
while introducing spatial variation.
|
|
330
345
|
|
|
331
346
|
| Parameter | Effect |
|
|
332
347
|
|---|---|
|
|
333
348
|
| `min_factor` / `max_factor` | Range of the multiplicative factor. |
|
|
334
349
|
| `normalize_to_mean` | Rescale factors so the image-wide mean equals the base value. Recommended for `k_hetero`. |
|
|
335
|
-
| `scales`
|
|
350
|
+
| `scales: "smooth_auto"` | Build low-frequency Perlin scales from the image size. |
|
|
351
|
+
| `correlation_length_fraction` | Approximate smallest fog feature size as a fraction of the shorter image side. Larger values create smoother gradients. |
|
|
352
|
+
| `octaves` / `lacunarity` / `max_scale` | Control how many increasingly broad Perlin components are mixed. |
|
|
353
|
+
| `contrast` | Compress or expand the Perlin range before mapping to factors. Values below 1 are recommended. |
|
|
354
|
+
| `smooth_sigma` / `smooth_sigma_fraction` | Optional final Gaussian blur in pixels or as a fraction of the shorter image side. |
|
|
336
355
|
|
|
337
356
|
### Fog Output
|
|
338
357
|
|
|
@@ -447,13 +447,22 @@ class SourceBackedOutputBackend:
|
|
|
447
447
|
entry_attributes = None
|
|
448
448
|
if attributes:
|
|
449
449
|
entry_attributes = {**(entry_attributes or {}), **attributes}
|
|
450
|
+
source_entry_for_writer = dict(source_meta_copy)
|
|
451
|
+
if output_full_id is not None or output_basename is not None:
|
|
452
|
+
# The caller is intentionally writing a new logical layout
|
|
453
|
+
# (e.g. source sample -> augmentation). Let DatasetWriter derive
|
|
454
|
+
# properties from the new full_id/basename instead of copying the
|
|
455
|
+
# source file's old path and basename captures.
|
|
456
|
+
source_entry_for_writer.pop("path_properties", None)
|
|
457
|
+
source_entry_for_writer.pop("basename_properties", None)
|
|
458
|
+
source_entry_for_writer.pop("attributes", None)
|
|
450
459
|
|
|
451
460
|
if isinstance(self.dataset_writer, ZipDatasetWriter):
|
|
452
461
|
if supports_stream_target(self.modality_writer):
|
|
453
462
|
with self.dataset_writer.open(
|
|
454
463
|
full_id,
|
|
455
464
|
basename,
|
|
456
|
-
source_entry=
|
|
465
|
+
source_entry=source_entry_for_writer,
|
|
457
466
|
attributes=entry_attributes,
|
|
458
467
|
) as stream:
|
|
459
468
|
_set_stream_name(stream, basename)
|
|
@@ -466,7 +475,7 @@ class SourceBackedOutputBackend:
|
|
|
466
475
|
full_id,
|
|
467
476
|
basename,
|
|
468
477
|
temp_path.read_bytes(),
|
|
469
|
-
source_entry=
|
|
478
|
+
source_entry=source_entry_for_writer,
|
|
470
479
|
attributes=entry_attributes,
|
|
471
480
|
)
|
|
472
481
|
return Path(f"{self.dataset_writer.root}::{relative_path}")
|
|
@@ -474,12 +483,25 @@ class SourceBackedOutputBackend:
|
|
|
474
483
|
target_path = self.dataset_writer.get_path(
|
|
475
484
|
full_id,
|
|
476
485
|
basename,
|
|
477
|
-
source_entry=
|
|
486
|
+
source_entry=source_entry_for_writer,
|
|
478
487
|
attributes=entry_attributes,
|
|
479
488
|
)
|
|
480
489
|
self.modality_writer(str(target_path), value, self.modality_meta)
|
|
481
490
|
return target_path
|
|
482
491
|
|
|
492
|
+
def set_hierarchy_separator(self, separator: str) -> None:
|
|
493
|
+
"""Set the writer hierarchy separator used for future entries."""
|
|
494
|
+
setattr(self.dataset_writer, "_separator", separator)
|
|
495
|
+
|
|
496
|
+
def add_head_addon(self, name: str, payload: dict[str, Any]) -> None:
|
|
497
|
+
"""Add a dataset-head addon before the writer saves its artifacts."""
|
|
498
|
+
head = getattr(self.dataset_writer, "_dataset_head", None)
|
|
499
|
+
addons = getattr(head, "addons", None)
|
|
500
|
+
if not isinstance(addons, dict):
|
|
501
|
+
raise RuntimeError("Unsupported dataset writer head object")
|
|
502
|
+
addons[name] = dict(payload)
|
|
503
|
+
self.index_overrides[name] = dict(payload)
|
|
504
|
+
|
|
483
505
|
def write_json(self, path: Path, data: dict[str, Any]) -> None:
|
|
484
506
|
raise RuntimeError(
|
|
485
507
|
"Source-backed outputs do not support auxiliary JSON sidecars."
|
|
@@ -28,11 +28,13 @@ DEFAULT_MODEL_CONFIGS = {
|
|
|
28
28
|
"visibility_m": {"dist": "constant", "value": 80.0},
|
|
29
29
|
"atmospheric_light": "from_sky",
|
|
30
30
|
"k_hetero": {
|
|
31
|
-
"scales": "
|
|
32
|
-
"
|
|
31
|
+
"scales": "smooth_auto",
|
|
32
|
+
"correlation_length_fraction": 0.25,
|
|
33
|
+
"octaves": 3,
|
|
33
34
|
"max_scale": None,
|
|
34
|
-
"min_factor": 0.
|
|
35
|
-
"max_factor": 1.
|
|
35
|
+
"min_factor": 0.65,
|
|
36
|
+
"max_factor": 1.45,
|
|
37
|
+
"contrast": 0.65,
|
|
36
38
|
"normalize_to_mean": True,
|
|
37
39
|
},
|
|
38
40
|
},
|
|
@@ -40,11 +42,13 @@ DEFAULT_MODEL_CONFIGS = {
|
|
|
40
42
|
"visibility_m": {"dist": "constant", "value": 80.0},
|
|
41
43
|
"atmospheric_light": "from_sky",
|
|
42
44
|
"ls_hetero": {
|
|
43
|
-
"scales": "
|
|
44
|
-
"
|
|
45
|
+
"scales": "smooth_auto",
|
|
46
|
+
"correlation_length_fraction": 0.35,
|
|
47
|
+
"octaves": 3,
|
|
45
48
|
"max_scale": None,
|
|
46
|
-
"min_factor": 0.
|
|
47
|
-
"max_factor": 1.
|
|
49
|
+
"min_factor": 0.85,
|
|
50
|
+
"max_factor": 1.08,
|
|
51
|
+
"contrast": 0.55,
|
|
48
52
|
"normalize_to_mean": False,
|
|
49
53
|
},
|
|
50
54
|
},
|
|
@@ -52,19 +56,23 @@ DEFAULT_MODEL_CONFIGS = {
|
|
|
52
56
|
"visibility_m": {"dist": "constant", "value": 80.0},
|
|
53
57
|
"atmospheric_light": "from_sky",
|
|
54
58
|
"k_hetero": {
|
|
55
|
-
"scales": "
|
|
56
|
-
"
|
|
59
|
+
"scales": "smooth_auto",
|
|
60
|
+
"correlation_length_fraction": 0.25,
|
|
61
|
+
"octaves": 3,
|
|
57
62
|
"max_scale": None,
|
|
58
|
-
"min_factor": 0.
|
|
59
|
-
"max_factor": 1.
|
|
63
|
+
"min_factor": 0.65,
|
|
64
|
+
"max_factor": 1.45,
|
|
65
|
+
"contrast": 0.65,
|
|
60
66
|
"normalize_to_mean": True,
|
|
61
67
|
},
|
|
62
68
|
"ls_hetero": {
|
|
63
|
-
"scales": "
|
|
64
|
-
"
|
|
69
|
+
"scales": "smooth_auto",
|
|
70
|
+
"correlation_length_fraction": 0.35,
|
|
71
|
+
"octaves": 3,
|
|
65
72
|
"max_scale": None,
|
|
66
|
-
"min_factor": 0.
|
|
67
|
-
"max_factor": 1.
|
|
73
|
+
"min_factor": 0.85,
|
|
74
|
+
"max_factor": 1.08,
|
|
75
|
+
"contrast": 0.55,
|
|
68
76
|
"normalize_to_mean": False,
|
|
69
77
|
},
|
|
70
78
|
},
|
|
@@ -166,6 +174,8 @@ def resolve_scales(
|
|
|
166
174
|
scales_spec = hetero_cfg.get("scales", "auto")
|
|
167
175
|
scales_spec = sample_value(scales_spec, rng)
|
|
168
176
|
if isinstance(scales_spec, str):
|
|
177
|
+
if scales_spec == "smooth_auto":
|
|
178
|
+
return _resolve_smooth_auto_scales(hetero_cfg, height, width, rng)
|
|
169
179
|
if scales_spec != "auto":
|
|
170
180
|
raise ValueError(f"Unsupported scales value: {scales_spec}")
|
|
171
181
|
min_scale = int(sample_value(hetero_cfg.get("min_scale", 2), rng))
|
|
@@ -186,6 +196,255 @@ def resolve_scales(
|
|
|
186
196
|
raise ValueError(f"Unsupported scales spec: {scales_spec}")
|
|
187
197
|
|
|
188
198
|
|
|
199
|
+
def _resolve_smooth_auto_scales(
|
|
200
|
+
hetero_cfg: dict,
|
|
201
|
+
height: int,
|
|
202
|
+
width: int,
|
|
203
|
+
rng: np.random.Generator,
|
|
204
|
+
) -> list[int]:
|
|
205
|
+
"""Resolve low-frequency Perlin scales for realistic fog gradients."""
|
|
206
|
+
min_dimension = max(1, min(height, width))
|
|
207
|
+
max_dimension = max(1, max(height, width))
|
|
208
|
+
|
|
209
|
+
base_scale = _resolve_scale_alias(
|
|
210
|
+
hetero_cfg,
|
|
211
|
+
rng,
|
|
212
|
+
absolute_keys=("correlation_length", "base_scale", "min_scale"),
|
|
213
|
+
fraction_keys=(
|
|
214
|
+
"correlation_length_fraction",
|
|
215
|
+
"base_scale_fraction",
|
|
216
|
+
"min_scale_fraction",
|
|
217
|
+
),
|
|
218
|
+
fraction_basis=min_dimension,
|
|
219
|
+
default=max(4, int(round(min_dimension * 0.25))),
|
|
220
|
+
)
|
|
221
|
+
max_scale = _resolve_scale_alias(
|
|
222
|
+
hetero_cfg,
|
|
223
|
+
rng,
|
|
224
|
+
absolute_keys=("max_scale",),
|
|
225
|
+
fraction_keys=("max_scale_fraction",),
|
|
226
|
+
fraction_basis=max_dimension,
|
|
227
|
+
default=max_dimension,
|
|
228
|
+
allow_none=True,
|
|
229
|
+
)
|
|
230
|
+
max_scale = max(base_scale, max_scale)
|
|
231
|
+
|
|
232
|
+
octaves = max(
|
|
233
|
+
1,
|
|
234
|
+
int(round(_sample_float(hetero_cfg.get("octaves", 3), rng, "octaves"))),
|
|
235
|
+
)
|
|
236
|
+
lacunarity = _sample_float(hetero_cfg.get("lacunarity", 2.0), rng, "lacunarity")
|
|
237
|
+
if lacunarity <= 1.0:
|
|
238
|
+
raise ValueError(f"lacunarity must be > 1.0, got {lacunarity}")
|
|
239
|
+
|
|
240
|
+
scales: list[int] = []
|
|
241
|
+
scale = float(base_scale)
|
|
242
|
+
for _ in range(octaves):
|
|
243
|
+
scales.append(max(1, int(round(scale))))
|
|
244
|
+
if scale >= max_scale:
|
|
245
|
+
break
|
|
246
|
+
scale = min(scale * lacunarity, float(max_scale))
|
|
247
|
+
return _unique_positive_scales(scales)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _resolve_scale_alias(
|
|
251
|
+
hetero_cfg: dict,
|
|
252
|
+
rng: np.random.Generator,
|
|
253
|
+
*,
|
|
254
|
+
absolute_keys: tuple[str, ...],
|
|
255
|
+
fraction_keys: tuple[str, ...],
|
|
256
|
+
fraction_basis: int,
|
|
257
|
+
default: int,
|
|
258
|
+
allow_none: bool = False,
|
|
259
|
+
) -> int:
|
|
260
|
+
for key in absolute_keys:
|
|
261
|
+
if key not in hetero_cfg:
|
|
262
|
+
continue
|
|
263
|
+
raw_value = hetero_cfg[key]
|
|
264
|
+
if raw_value is None and allow_none:
|
|
265
|
+
break
|
|
266
|
+
return _scale_pixels(raw_value, rng, key)
|
|
267
|
+
for key in fraction_keys:
|
|
268
|
+
if key not in hetero_cfg:
|
|
269
|
+
continue
|
|
270
|
+
fraction = _sample_float(hetero_cfg[key], rng, key)
|
|
271
|
+
if fraction <= 0:
|
|
272
|
+
raise ValueError(f"{key} must be > 0, got {fraction}")
|
|
273
|
+
return max(1, int(round(float(fraction_basis) * fraction)))
|
|
274
|
+
return max(1, int(default))
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _scale_pixels(value, rng: np.random.Generator, name: str) -> int:
|
|
278
|
+
scale = _sample_float(value, rng, name)
|
|
279
|
+
if scale <= 0:
|
|
280
|
+
raise ValueError(f"{name} must be > 0, got {scale}")
|
|
281
|
+
return max(1, int(round(scale)))
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _sample_float(value, rng: np.random.Generator, name: str) -> float:
|
|
285
|
+
sampled = sample_value(value, rng)
|
|
286
|
+
try:
|
|
287
|
+
return float(sampled)
|
|
288
|
+
except (TypeError, ValueError) as exc:
|
|
289
|
+
raise ValueError(f"{name} must resolve to a number, got {sampled!r}") from exc
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _unique_positive_scales(scales: list[int]) -> list[int]:
|
|
293
|
+
unique: list[int] = []
|
|
294
|
+
seen: set[int] = set()
|
|
295
|
+
for scale in scales:
|
|
296
|
+
scale = int(scale)
|
|
297
|
+
if scale <= 0 or scale in seen:
|
|
298
|
+
continue
|
|
299
|
+
seen.add(scale)
|
|
300
|
+
unique.append(scale)
|
|
301
|
+
return unique or [1]
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def prepare_noise_field(
|
|
305
|
+
noise: np.ndarray,
|
|
306
|
+
hetero_cfg: dict,
|
|
307
|
+
rng: np.random.Generator,
|
|
308
|
+
) -> np.ndarray:
|
|
309
|
+
"""Apply optional smoothing and contrast control to a Perlin noise field."""
|
|
310
|
+
noise = np.asarray(noise, dtype=np.float32)
|
|
311
|
+
sigma = resolve_smoothing_sigma(hetero_cfg, noise.shape[0], noise.shape[1], rng)
|
|
312
|
+
if sigma > 0.0:
|
|
313
|
+
noise = _gaussian_blur_np(noise, sigma)
|
|
314
|
+
noise = _normalize_noise_np(noise)
|
|
315
|
+
contrast = resolve_noise_contrast(hetero_cfg, rng)
|
|
316
|
+
if contrast != 1.0:
|
|
317
|
+
noise = 0.5 + (noise - 0.5) * contrast
|
|
318
|
+
return np.clip(noise, 0.0, 1.0).astype(np.float32, copy=False)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def prepare_noise_field_torch(
|
|
322
|
+
noise: "torch.Tensor",
|
|
323
|
+
hetero_cfg: dict,
|
|
324
|
+
rng: np.random.Generator,
|
|
325
|
+
) -> "torch.Tensor":
|
|
326
|
+
"""Torch equivalent of :func:`prepare_noise_field`."""
|
|
327
|
+
height = int(noise.shape[-2])
|
|
328
|
+
width = int(noise.shape[-1])
|
|
329
|
+
sigma = resolve_smoothing_sigma(hetero_cfg, height, width, rng)
|
|
330
|
+
if sigma > 0.0:
|
|
331
|
+
noise = _gaussian_blur_torch(noise, sigma)
|
|
332
|
+
noise = _normalize_noise_torch(noise)
|
|
333
|
+
contrast = resolve_noise_contrast(hetero_cfg, rng)
|
|
334
|
+
if contrast != 1.0:
|
|
335
|
+
noise = 0.5 + (noise - 0.5) * contrast
|
|
336
|
+
return torch.clamp(noise, 0.0, 1.0)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def resolve_smoothing_sigma(
|
|
340
|
+
hetero_cfg: dict,
|
|
341
|
+
height: int,
|
|
342
|
+
width: int,
|
|
343
|
+
rng: np.random.Generator,
|
|
344
|
+
) -> float:
|
|
345
|
+
for key in ("smooth_sigma", "smoothing_sigma", "blur_sigma"):
|
|
346
|
+
if key in hetero_cfg:
|
|
347
|
+
sigma = _sample_float(hetero_cfg[key], rng, key)
|
|
348
|
+
if sigma < 0:
|
|
349
|
+
raise ValueError(f"{key} must be >= 0, got {sigma}")
|
|
350
|
+
return sigma
|
|
351
|
+
for key in (
|
|
352
|
+
"smooth_sigma_fraction",
|
|
353
|
+
"smoothing_sigma_fraction",
|
|
354
|
+
"blur_sigma_fraction",
|
|
355
|
+
):
|
|
356
|
+
if key in hetero_cfg:
|
|
357
|
+
fraction = _sample_float(hetero_cfg[key], rng, key)
|
|
358
|
+
if fraction < 0:
|
|
359
|
+
raise ValueError(f"{key} must be >= 0, got {fraction}")
|
|
360
|
+
return fraction * float(max(1, min(height, width)))
|
|
361
|
+
return 0.0
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def resolve_noise_contrast(hetero_cfg: dict, rng: np.random.Generator) -> float:
|
|
365
|
+
raw = hetero_cfg.get("contrast", hetero_cfg.get("noise_contrast", 1.0))
|
|
366
|
+
contrast = _sample_float(raw, rng, "contrast")
|
|
367
|
+
if contrast < 0:
|
|
368
|
+
raise ValueError(f"contrast must be >= 0, got {contrast}")
|
|
369
|
+
return contrast
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _normalize_noise_np(noise: np.ndarray) -> np.ndarray:
|
|
373
|
+
min_val = float(np.min(noise))
|
|
374
|
+
max_val = float(np.max(noise))
|
|
375
|
+
denom = max_val - min_val
|
|
376
|
+
if denom <= 1e-8:
|
|
377
|
+
return np.full_like(noise, 0.5, dtype=np.float32)
|
|
378
|
+
return ((noise - min_val) / denom).astype(np.float32, copy=False)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _normalize_noise_torch(noise: "torch.Tensor") -> "torch.Tensor":
|
|
382
|
+
min_val = noise.amin()
|
|
383
|
+
max_val = noise.amax()
|
|
384
|
+
denom = max_val - min_val
|
|
385
|
+
if float(denom.item()) <= 1e-8:
|
|
386
|
+
return torch.full_like(noise, 0.5)
|
|
387
|
+
return (noise - min_val) / denom
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _gaussian_kernel_np(sigma: float) -> np.ndarray:
|
|
391
|
+
radius = max(1, int(math.ceil(3.0 * sigma)))
|
|
392
|
+
offsets = np.arange(-radius, radius + 1, dtype=np.float32)
|
|
393
|
+
kernel = np.exp(-0.5 * (offsets / float(sigma)) ** 2)
|
|
394
|
+
kernel /= float(kernel.sum())
|
|
395
|
+
return kernel.astype(np.float32)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def _convolve_axis_np(
|
|
399
|
+
values: np.ndarray,
|
|
400
|
+
kernel: np.ndarray,
|
|
401
|
+
axis: int,
|
|
402
|
+
) -> np.ndarray:
|
|
403
|
+
radius = kernel.shape[0] // 2
|
|
404
|
+
padding = [(0, 0)] * values.ndim
|
|
405
|
+
padding[axis] = (radius, radius)
|
|
406
|
+
padded = np.pad(values, padding, mode="edge")
|
|
407
|
+
result = np.zeros_like(values, dtype=np.float32)
|
|
408
|
+
for offset, weight in enumerate(kernel):
|
|
409
|
+
slices = [slice(None)] * values.ndim
|
|
410
|
+
slices[axis] = slice(offset, offset + values.shape[axis])
|
|
411
|
+
result += float(weight) * padded[tuple(slices)]
|
|
412
|
+
return result
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _gaussian_blur_np(noise: np.ndarray, sigma: float) -> np.ndarray:
|
|
416
|
+
if sigma <= 0.0:
|
|
417
|
+
return noise
|
|
418
|
+
kernel = _gaussian_kernel_np(sigma)
|
|
419
|
+
blurred = _convolve_axis_np(noise, kernel, axis=1)
|
|
420
|
+
return _convolve_axis_np(blurred, kernel, axis=0)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _gaussian_blur_torch(noise: "torch.Tensor", sigma: float) -> "torch.Tensor":
|
|
424
|
+
if sigma <= 0.0:
|
|
425
|
+
return noise
|
|
426
|
+
radius = max(1, int(math.ceil(3.0 * sigma)))
|
|
427
|
+
offsets = torch.arange(
|
|
428
|
+
-radius,
|
|
429
|
+
radius + 1,
|
|
430
|
+
device=noise.device,
|
|
431
|
+
dtype=torch.float32,
|
|
432
|
+
)
|
|
433
|
+
kernel = torch.exp(-0.5 * (offsets / float(sigma)) ** 2)
|
|
434
|
+
kernel = kernel / kernel.sum()
|
|
435
|
+
x = noise.to(dtype=torch.float32).view(
|
|
436
|
+
1,
|
|
437
|
+
1,
|
|
438
|
+
int(noise.shape[-2]),
|
|
439
|
+
int(noise.shape[-1]),
|
|
440
|
+
)
|
|
441
|
+
x = torch.nn.functional.pad(x, (radius, radius, 0, 0), mode="replicate")
|
|
442
|
+
x = torch.nn.functional.conv2d(x, kernel.view(1, 1, 1, -1))
|
|
443
|
+
x = torch.nn.functional.pad(x, (0, 0, radius, radius), mode="replicate")
|
|
444
|
+
x = torch.nn.functional.conv2d(x, kernel.view(1, 1, -1, 1))
|
|
445
|
+
return x.view(noise.shape)
|
|
446
|
+
|
|
447
|
+
|
|
189
448
|
def modulate_with_noise(
|
|
190
449
|
mean_value: np.ndarray,
|
|
191
450
|
noise: np.ndarray,
|
|
@@ -349,6 +608,7 @@ def apply_model(
|
|
|
349
608
|
k_cfg = model_cfg.get("k_hetero", {})
|
|
350
609
|
k_scales = resolve_scales(k_cfg, height, width, rng)
|
|
351
610
|
k_noise = perlin_fbm(height, width, k_scales, rng)
|
|
611
|
+
k_noise = prepare_noise_field(k_noise, k_cfg, rng)
|
|
352
612
|
min_factor = float(sample_value(k_cfg.get("min_factor", 1.0), rng))
|
|
353
613
|
max_factor = float(sample_value(k_cfg.get("max_factor", 1.0), rng))
|
|
354
614
|
k_field = modulate_with_noise(
|
|
@@ -365,6 +625,7 @@ def apply_model(
|
|
|
365
625
|
ls_cfg = model_cfg.get("ls_hetero", {})
|
|
366
626
|
ls_scales = resolve_scales(ls_cfg, height, width, rng)
|
|
367
627
|
ls_noise = perlin_fbm(height, width, ls_scales, rng)
|
|
628
|
+
ls_noise = prepare_noise_field(ls_noise, ls_cfg, rng)
|
|
368
629
|
min_factor = float(sample_value(ls_cfg.get("min_factor", 1.0), rng))
|
|
369
630
|
max_factor = float(sample_value(ls_cfg.get("max_factor", 1.0), rng))
|
|
370
631
|
ls_field = modulate_with_noise(
|
|
@@ -40,6 +40,7 @@ from euler_preprocess.fog.models import (
|
|
|
40
40
|
estimate_airlight_torch,
|
|
41
41
|
modulate_with_noise_torch,
|
|
42
42
|
normalize_atmospheric_light_torch,
|
|
43
|
+
prepare_noise_field_torch,
|
|
43
44
|
resolve_model_config,
|
|
44
45
|
resolve_scattering_coefficient,
|
|
45
46
|
resolve_scales,
|
|
@@ -51,6 +52,33 @@ from euler_loading.loaders.cpu.generic import (
|
|
|
51
52
|
write_map_3d as _write_map_3d,
|
|
52
53
|
)
|
|
53
54
|
|
|
55
|
+
try:
|
|
56
|
+
from ds_crawler import EULER_LAYOUT_ADDON, build_layout_addon
|
|
57
|
+
except ImportError: # pragma: no cover - compatibility with older ds-crawler
|
|
58
|
+
EULER_LAYOUT_ADDON = "euler_layout"
|
|
59
|
+
|
|
60
|
+
def build_layout_addon(**kwargs):
|
|
61
|
+
payload: dict[str, Any] = {
|
|
62
|
+
"version": kwargs.get("version", "1.0"),
|
|
63
|
+
"sample_axis": {
|
|
64
|
+
"name": kwargs["sample_axis_name"],
|
|
65
|
+
"location": kwargs["sample_axis_location"],
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
family = kwargs.get("family")
|
|
69
|
+
if family is not None:
|
|
70
|
+
payload["family"] = family
|
|
71
|
+
variant_axis_name = kwargs.get("variant_axis_name")
|
|
72
|
+
if variant_axis_name is not None:
|
|
73
|
+
payload["variant_axis"] = {
|
|
74
|
+
"name": variant_axis_name,
|
|
75
|
+
"location": kwargs.get("variant_axis_location", "file_id"),
|
|
76
|
+
}
|
|
77
|
+
derived_from = kwargs.get("derived_from")
|
|
78
|
+
if derived_from is not None:
|
|
79
|
+
payload["derived_from"] = dict(derived_from)
|
|
80
|
+
return payload
|
|
81
|
+
|
|
54
82
|
|
|
55
83
|
SCATTERING_COEFFICIENT_SLOT = "scattering_coefficient"
|
|
56
84
|
ATMOSPHERIC_LIGHT_SLOT = "atmospheric_light"
|
|
@@ -174,6 +202,7 @@ class FogTransform(Transform):
|
|
|
174
202
|
self.config
|
|
175
203
|
)
|
|
176
204
|
self.augmentation_specs = list(self.augmentation_config.specs)
|
|
205
|
+
self._configure_output_layout_metadata()
|
|
177
206
|
self._written_configs: set[str] = set()
|
|
178
207
|
self.torch_device = None
|
|
179
208
|
self.use_gpu = False
|
|
@@ -374,9 +403,57 @@ class FogTransform(Transform):
|
|
|
374
403
|
return suffix
|
|
375
404
|
return ".png"
|
|
376
405
|
|
|
406
|
+
def _layout_family(self) -> str | None:
|
|
407
|
+
raw = self.config.get("dataset_family")
|
|
408
|
+
return raw if isinstance(raw, str) and raw else None
|
|
409
|
+
|
|
410
|
+
def _augmentation_hierarchy_separator(self, backend: Any) -> str:
|
|
411
|
+
separator = getattr(getattr(backend, "dataset_writer", None), "_separator", None)
|
|
412
|
+
if isinstance(separator, str) and separator and separator != "+":
|
|
413
|
+
return separator
|
|
414
|
+
return ":"
|
|
415
|
+
|
|
416
|
+
def _configure_output_layout_metadata(self) -> None:
|
|
417
|
+
"""Declare fog outputs as variants grouped by source sample id."""
|
|
418
|
+
if not self.augmentation_specs:
|
|
419
|
+
return
|
|
420
|
+
|
|
421
|
+
sample_axis_name = self.augmentation_config.file_id_hierarchy_name
|
|
422
|
+
if not sample_axis_name:
|
|
423
|
+
return
|
|
424
|
+
|
|
425
|
+
for backend in self.output_backends.values():
|
|
426
|
+
if not getattr(backend, "is_source_backed", False):
|
|
427
|
+
continue
|
|
428
|
+
|
|
429
|
+
separator = self._augmentation_hierarchy_separator(backend)
|
|
430
|
+
set_separator = getattr(backend, "set_hierarchy_separator", None)
|
|
431
|
+
if callable(set_separator):
|
|
432
|
+
set_separator(separator)
|
|
433
|
+
|
|
434
|
+
layout = build_layout_addon(
|
|
435
|
+
family=self._layout_family(),
|
|
436
|
+
sample_axis_name=sample_axis_name,
|
|
437
|
+
sample_axis_location="hierarchy",
|
|
438
|
+
variant_axis_name=self.augmentation_config.attribute_key,
|
|
439
|
+
variant_axis_location="file_id",
|
|
440
|
+
derived_from={
|
|
441
|
+
"source_modality": getattr(backend, "source_modality", "rgb"),
|
|
442
|
+
"source_id_attribute": (
|
|
443
|
+
f"{self.augmentation_config.attribute_key}.source_id"
|
|
444
|
+
),
|
|
445
|
+
"source_full_id_attribute": (
|
|
446
|
+
f"{self.augmentation_config.attribute_key}.source_full_id"
|
|
447
|
+
),
|
|
448
|
+
},
|
|
449
|
+
)
|
|
450
|
+
add_head_addon = getattr(backend, "add_head_addon", None)
|
|
451
|
+
if callable(add_head_addon):
|
|
452
|
+
add_head_addon(EULER_LAYOUT_ADDON, layout)
|
|
453
|
+
|
|
377
454
|
def _file_id_hierarchy_key(self, sample_id: str, backend: Any) -> str:
|
|
378
455
|
name = self.augmentation_config.file_id_hierarchy_name
|
|
379
|
-
separator =
|
|
456
|
+
separator = self._augmentation_hierarchy_separator(backend)
|
|
380
457
|
if name and separator:
|
|
381
458
|
return f"{name}{separator}{sample_id}"
|
|
382
459
|
return sample_id
|
|
@@ -635,6 +712,7 @@ class FogTransform(Transform):
|
|
|
635
712
|
torch_gen,
|
|
636
713
|
self.torch_device,
|
|
637
714
|
)
|
|
715
|
+
k_noise = prepare_noise_field_torch(k_noise, k_cfg, rng)
|
|
638
716
|
min_factor = float(sample_value(k_cfg.get("min_factor", 1.0), rng))
|
|
639
717
|
max_factor = float(sample_value(k_cfg.get("max_factor", 1.0), rng))
|
|
640
718
|
k_field = modulate_with_noise_torch(
|
|
@@ -657,6 +735,7 @@ class FogTransform(Transform):
|
|
|
657
735
|
torch_gen,
|
|
658
736
|
self.torch_device,
|
|
659
737
|
)
|
|
738
|
+
ls_noise = prepare_noise_field_torch(ls_noise, ls_cfg, rng)
|
|
660
739
|
min_factor = float(sample_value(ls_cfg.get("min_factor", 1.0), rng))
|
|
661
740
|
max_factor = float(sample_value(ls_cfg.get("max_factor", 1.0), rng))
|
|
662
741
|
ls_field = modulate_with_noise_torch(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: euler-preprocess
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.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
|
|
@@ -232,10 +232,10 @@ Each image is assigned a fog model via the `selection` block:
|
|
|
232
232
|
"selection": {
|
|
233
233
|
"mode": "weighted",
|
|
234
234
|
"weights": {
|
|
235
|
-
"uniform":
|
|
236
|
-
"heterogeneous_k": 0.
|
|
237
|
-
"heterogeneous_ls": 0.
|
|
238
|
-
"heterogeneous_k_ls": 0.
|
|
235
|
+
"uniform": 0.25,
|
|
236
|
+
"heterogeneous_k": 0.35,
|
|
237
|
+
"heterogeneous_ls": 0.25,
|
|
238
|
+
"heterogeneous_k_ls": 0.15
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
241
|
```
|
|
@@ -309,9 +309,12 @@ variants:
|
|
|
309
309
|
"scattering_coefficient": 0.15,
|
|
310
310
|
"atmospheric_light": [1.0, 1.0, 1.0],
|
|
311
311
|
"k_hetero": {
|
|
312
|
-
"scales": "
|
|
313
|
-
"
|
|
314
|
-
"
|
|
312
|
+
"scales": "smooth_auto",
|
|
313
|
+
"correlation_length_fraction": 0.25,
|
|
314
|
+
"octaves": 3,
|
|
315
|
+
"min_factor": 0.65,
|
|
316
|
+
"max_factor": 1.45,
|
|
317
|
+
"contrast": 0.65,
|
|
315
318
|
"normalize_to_mean": true
|
|
316
319
|
}
|
|
317
320
|
}
|
|
@@ -327,26 +330,42 @@ MOR/beta descriptors when available. euler-loading exposes these as
|
|
|
327
330
|
|
|
328
331
|
### Heterogeneous Noise Fields
|
|
329
332
|
|
|
330
|
-
Both `k_hetero` and `ls_hetero` use Perlin FBM (fractional Brownian
|
|
333
|
+
Both `k_hetero` and `ls_hetero` use Perlin FBM (fractional Brownian
|
|
334
|
+
motion) to generate spatially-varying factor fields. For realistic fog,
|
|
335
|
+
prefer the smooth mode: it keeps Perlin wavelengths tied to the image size,
|
|
336
|
+
then optionally reduces noise contrast and applies a final blur before mapping
|
|
337
|
+
the noise to physical factors.
|
|
331
338
|
|
|
332
339
|
```json
|
|
333
340
|
"k_hetero": {
|
|
334
|
-
"scales": "
|
|
335
|
-
"
|
|
341
|
+
"scales": "smooth_auto",
|
|
342
|
+
"correlation_length_fraction": 0.25,
|
|
343
|
+
"octaves": 3,
|
|
336
344
|
"max_scale": null,
|
|
337
|
-
"min_factor": 0.
|
|
338
|
-
"max_factor": 1.
|
|
345
|
+
"min_factor": 0.65,
|
|
346
|
+
"max_factor": 1.45,
|
|
347
|
+
"contrast": 0.65,
|
|
348
|
+
"smooth_sigma_fraction": 0.0,
|
|
339
349
|
"normalize_to_mean": true
|
|
340
350
|
}
|
|
341
351
|
```
|
|
342
352
|
|
|
343
|
-
The noise field (values in [0, 1]) is mapped to a factor field:
|
|
353
|
+
The noise field (values in [0, 1]) is mapped to a factor field:
|
|
354
|
+
`factor(x) = min_factor + (max_factor - min_factor) * noise(x)`.
|
|
355
|
+
`contrast < 1` compresses the noise around 0.5 before this mapping, avoiding
|
|
356
|
+
extreme local fog density. When `normalize_to_mean` is `true`, the factor field
|
|
357
|
+
is rescaled so its spatial mean equals 1.0, preserving the overall fog density
|
|
358
|
+
while introducing spatial variation.
|
|
344
359
|
|
|
345
360
|
| Parameter | Effect |
|
|
346
361
|
|---|---|
|
|
347
362
|
| `min_factor` / `max_factor` | Range of the multiplicative factor. |
|
|
348
363
|
| `normalize_to_mean` | Rescale factors so the image-wide mean equals the base value. Recommended for `k_hetero`. |
|
|
349
|
-
| `scales`
|
|
364
|
+
| `scales: "smooth_auto"` | Build low-frequency Perlin scales from the image size. |
|
|
365
|
+
| `correlation_length_fraction` | Approximate smallest fog feature size as a fraction of the shorter image side. Larger values create smoother gradients. |
|
|
366
|
+
| `octaves` / `lacunarity` / `max_scale` | Control how many increasingly broad Perlin components are mixed. |
|
|
367
|
+
| `contrast` | Compress or expand the Perlin range before mapping to factors. Values below 1 are recommended. |
|
|
368
|
+
| `smooth_sigma` / `smooth_sigma_fraction` | Optional final Gaussian blur in pixels or as a fraction of the shorter image side. |
|
|
350
369
|
|
|
351
370
|
### Fog Output
|
|
352
371
|
|
|
@@ -314,15 +314,29 @@ def test_stepped_augmentations_write_file_id_layout_and_attributes(
|
|
|
314
314
|
(pipeline_root / "foggy_rgb" / ".ds_crawler" / "output.json").read_text()
|
|
315
315
|
)
|
|
316
316
|
node = output_index["dataset"]["children"]["Scene01"]["children"]["Camera_0"]
|
|
317
|
-
file_id_node = node["children"]["00001"]
|
|
317
|
+
file_id_node = node["children"]["file_id:00001"]
|
|
318
318
|
entries = {entry["id"]: entry for entry in file_id_node["files"]}
|
|
319
319
|
assert set(entries) == {"mor_10m", "mor_20m"}
|
|
320
|
+
assert entries["mor_10m"]["path_properties"]["file_id"] == "00001"
|
|
321
|
+
assert entries["mor_10m"]["basename_properties"]["ext"] == "png"
|
|
320
322
|
attrs = entries["mor_10m"]["attributes"]["fog_augmentation"]
|
|
321
323
|
assert attrs["id"] == "mor_10m"
|
|
322
324
|
assert attrs["source_id"] == "00001"
|
|
323
325
|
assert attrs["meteorological_visibility_m"] == 10.0
|
|
324
326
|
assert attrs["model"] == "uniform"
|
|
325
327
|
np.testing.assert_allclose(attrs["atmospheric_light"], [0.4, 0.5, 0.6])
|
|
328
|
+
assert output_index["euler_layout"]["sample_axis"] == {
|
|
329
|
+
"name": "file_id",
|
|
330
|
+
"location": "hierarchy",
|
|
331
|
+
}
|
|
332
|
+
assert output_index["euler_layout"]["variant_axis"] == {
|
|
333
|
+
"name": "fog_augmentation",
|
|
334
|
+
"location": "file_id",
|
|
335
|
+
}
|
|
336
|
+
output_head = json.loads(
|
|
337
|
+
(pipeline_root / "foggy_rgb" / ".ds_crawler" / "dataset-head.json").read_text()
|
|
338
|
+
)
|
|
339
|
+
assert output_head["addons"]["euler_layout"] == output_index["euler_layout"]
|
|
326
340
|
|
|
327
341
|
|
|
328
342
|
def test_only_scattering_target_writes_only_scattering(tmp_path: Path) -> None:
|
|
@@ -547,6 +561,60 @@ def test_apply_model_returns_spatial_fields_for_heterogeneous() -> None:
|
|
|
547
561
|
assert float(k_map.std()) > 0.0
|
|
548
562
|
|
|
549
563
|
|
|
564
|
+
def test_smooth_auto_scales_are_image_relative_low_frequency() -> None:
|
|
565
|
+
"""smooth_auto should avoid the pixel-scale octaves that make fog speckly."""
|
|
566
|
+
from euler_preprocess.fog.models import resolve_scales
|
|
567
|
+
|
|
568
|
+
rng = np.random.default_rng(0)
|
|
569
|
+
cfg = {
|
|
570
|
+
"scales": "smooth_auto",
|
|
571
|
+
"correlation_length_fraction": 0.25,
|
|
572
|
+
"octaves": 4,
|
|
573
|
+
"max_scale_fraction": 1.0,
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
assert resolve_scales(cfg, height=100, width=200, rng=rng) == [25, 50, 100, 200]
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def test_smooth_noise_contrast_keeps_heterogeneous_beta_near_mean() -> None:
|
|
580
|
+
"""Low noise contrast keeps spatial fog gradients subtle around the base beta."""
|
|
581
|
+
from euler_preprocess.fog.models import apply_model
|
|
582
|
+
|
|
583
|
+
rng = np.random.default_rng(123)
|
|
584
|
+
rgb = np.full((80, 120, 3), 0.5, dtype=np.float32)
|
|
585
|
+
depth = np.full((80, 120), 50.0, dtype=np.float32)
|
|
586
|
+
estimated = np.array([0.8, 0.8, 0.9], dtype=np.float32)
|
|
587
|
+
cfg = {
|
|
588
|
+
"visibility_m": {"dist": "constant", "value": 80.0},
|
|
589
|
+
"atmospheric_light": "from_sky",
|
|
590
|
+
"k_hetero": {
|
|
591
|
+
"scales": "smooth_auto",
|
|
592
|
+
"correlation_length_fraction": 0.25,
|
|
593
|
+
"octaves": 3,
|
|
594
|
+
"min_factor": 0.5,
|
|
595
|
+
"max_factor": 1.5,
|
|
596
|
+
"contrast": 0.2,
|
|
597
|
+
"normalize_to_mean": True,
|
|
598
|
+
},
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
_, k_mean, _, k_map, _ = apply_model(
|
|
602
|
+
rgb,
|
|
603
|
+
depth,
|
|
604
|
+
"heterogeneous_k",
|
|
605
|
+
cfg,
|
|
606
|
+
rng,
|
|
607
|
+
contrast_threshold_default=0.05,
|
|
608
|
+
estimated_airlight=estimated,
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
factors = k_map / k_mean
|
|
612
|
+
assert float(factors.std()) > 0.0
|
|
613
|
+
assert float(factors.min()) >= 0.75
|
|
614
|
+
assert float(factors.max()) <= 1.25
|
|
615
|
+
np.testing.assert_allclose(float(factors.mean()), 1.0, rtol=1e-6)
|
|
616
|
+
|
|
617
|
+
|
|
550
618
|
def test_apply_model_accepts_direct_scattering_coefficient() -> None:
|
|
551
619
|
"""Stepped configs may specify beta directly instead of MOR/visibility."""
|
|
552
620
|
from euler_preprocess.fog.models import apply_model
|
|
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-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess/fog/dcp_airlight_torch.py
RENAMED
|
File without changes
|
{euler_preprocess-2.2.0 → euler_preprocess-2.3.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
|
{euler_preprocess-2.2.0 → euler_preprocess-2.3.0}/euler_preprocess.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{euler_preprocess-2.2.0 → euler_preprocess-2.3.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|