euler-preprocess 2.3.0__tar.gz → 3.0.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.3.0 → euler_preprocess-3.0.0}/PKG-INFO +198 -9
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/README.md +197 -8
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/common/output.py +22 -4
- euler_preprocess-3.0.0/euler_preprocess/fog/atmospheric_light.py +306 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/augmentations.py +4 -0
- euler_preprocess-3.0.0/euler_preprocess/fog/capture.py +1806 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/models.py +502 -4
- euler_preprocess-3.0.0/euler_preprocess/fog/pipeline.py +151 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/transform.py +210 -284
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess.egg-info/PKG-INFO +198 -9
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess.egg-info/SOURCES.txt +3 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/pyproject.toml +1 -1
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/tests/test_dcp_heuristic_airlight.py +2 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/tests/test_fog_aux_outputs.py +610 -3
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/tests/test_source_backed_output.py +11 -4
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/__init__.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/cli.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/common/__init__.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/common/dataset.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/common/device.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/common/intrinsics.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/common/io.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/common/logging.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/common/noise.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/common/normalize.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/common/sampling.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/common/transform.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/__init__.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/airlight_from_sky.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/dcp_airlight.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/dcp_airlight_torch.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/dcp_heuristic_airlight.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/dcp_heuristic_airlight_torch.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/foggify.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/foggify_logging.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/logging.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/radial/__init__.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/radial/transform.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/sky_depth/__init__.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess/sky_depth/transform.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess.egg-info/dependency_links.txt +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess.egg-info/entry_points.txt +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess.egg-info/requires.txt +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/euler_preprocess.egg-info/top_level.txt +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/setup.cfg +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/tests/test_airlight_fallback.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/tests/test_cli_sample_selection.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/tests/test_foggify_integration.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/tests/test_radial.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.0}/tests/test_sky_depth.py +0 -0
- {euler_preprocess-2.3.0 → euler_preprocess-3.0.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
|
+
Version: 3.0.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
|
|
@@ -98,7 +98,7 @@ starting at index 10 with stride 5.
|
|
|
98
98
|
|
|
99
99
|
| Transform | `modalities` | `hierarchical_modalities` |
|
|
100
100
|
|---|---|---|
|
|
101
|
-
| `fog` | `rgb`, `depth`, `semantic_segmentation` |
|
|
101
|
+
| `fog` | `rgb`, `depth`, `semantic_segmentation` | `intrinsics` when available; used for radial depth conversion and camera-profile optics |
|
|
102
102
|
| `sky-depth` | `depth`, `semantic_segmentation` | — |
|
|
103
103
|
| `radial` | `depth` | `intrinsics` |
|
|
104
104
|
|
|
@@ -150,6 +150,8 @@ Controls the fog simulation.
|
|
|
150
150
|
"contrast_threshold": 0.05,
|
|
151
151
|
"device": "cpu",
|
|
152
152
|
"gpu_batch_size": 4,
|
|
153
|
+
"capture": { "preset": "camera" },
|
|
154
|
+
"camera_profile": "dashcam",
|
|
153
155
|
"augmentations": { ... },
|
|
154
156
|
"selection": { ... },
|
|
155
157
|
"models": { ... }
|
|
@@ -165,8 +167,130 @@ Controls the fog simulation.
|
|
|
165
167
|
| `contrast_threshold` | Threshold *C_t* used in the visibility-to-attenuation conversion (default `0.05`). |
|
|
166
168
|
| `device` | `"cpu"`, `"cuda"`, `"mps"`, or `"gpu"` (alias for cuda). |
|
|
167
169
|
| `gpu_batch_size` | Batch size when running on GPU. Uniform-model samples are batched; heterogeneous samples are processed individually. |
|
|
170
|
+
| `capture` / `capture_artifacts` | Optional post-fog camera artifact pipeline. Omit it or set `{"stages": []}` for the legacy no-op path. Set `true`, `{"preset": "camera"}`, or a custom `stages` list to enable optics, raw sensor, ISP, and compression artifacts. |
|
|
171
|
+
| `camera_profile` | Optional named or inline camera profile whose stage defaults are merged into the capture stack before per-stage overrides. Built-ins are `"default"`, `"generic"`, `"dashcam"`, and `"low_light_fog"`. |
|
|
172
|
+
| `camera_profiles` | Optional map of project-specific named profiles. Use this for calibrated lens, sensor, ISP, and transport settings. |
|
|
168
173
|
| `augmentations` | Optional stepped augmentation set. When present, every input sample produces every configured augmentation and uses the file-id hierarchy output layout described below. |
|
|
169
174
|
|
|
175
|
+
### Processing Pipeline
|
|
176
|
+
|
|
177
|
+
Fog generation is split into two phases:
|
|
178
|
+
|
|
179
|
+
1. Ideal scene rendering: physics-based fog and auxiliary `scattering_coefficient`
|
|
180
|
+
/ `atmospheric_light` maps are computed.
|
|
181
|
+
2. Capture artifacts: camera-specific effects are applied to the rendered RGB
|
|
182
|
+
only. Physical fog maps stay stable while the RGB output can receive
|
|
183
|
+
exposure shifts, lens blur, vignetting, raw sensor noise, Bayer/demosaicing
|
|
184
|
+
artifacts, ISP tone/gamma/sharpening, and JPEG/resize/quantization effects.
|
|
185
|
+
|
|
186
|
+
This keeps physical fog maps stable while making the RGB output extensible for
|
|
187
|
+
real-camera simulation.
|
|
188
|
+
|
|
189
|
+
### Capture Artifact Stack
|
|
190
|
+
|
|
191
|
+
Enable the recommended camera stack with:
|
|
192
|
+
|
|
193
|
+
```json
|
|
194
|
+
"capture": { "preset": "camera" }
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
or equivalently:
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
"capture": true
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
For tighter control, provide explicit stages in camera order:
|
|
204
|
+
|
|
205
|
+
```json
|
|
206
|
+
"camera_profiles": {
|
|
207
|
+
"real_drive_front": {
|
|
208
|
+
"optics": {
|
|
209
|
+
"lens_distortion": -0.012,
|
|
210
|
+
"vignetting_strength": 0.18,
|
|
211
|
+
"windshield_haze": {"enabled": true, "probability": 0.55}
|
|
212
|
+
},
|
|
213
|
+
"sensor": {
|
|
214
|
+
"bayer_pattern": "RGGB",
|
|
215
|
+
"iso": {"dist": "choice", "values": [200, 400, 800]},
|
|
216
|
+
"base_iso": 100,
|
|
217
|
+
"full_well_electrons": [14000, 12000, 13000],
|
|
218
|
+
"read_noise_electrons": {"dist": "uniform", "min": 2.0, "max": 6.0},
|
|
219
|
+
"black_level": [0.003, 0.0035, 0.003],
|
|
220
|
+
"white_level": [1.0, 0.995, 1.0],
|
|
221
|
+
"adc_bit_depth": 12,
|
|
222
|
+
"post_demosaic_bit_depth": 12
|
|
223
|
+
},
|
|
224
|
+
"isp": {
|
|
225
|
+
"tone_map": "reinhard",
|
|
226
|
+
"gamma": "srgb",
|
|
227
|
+
"denoise_sigma": 0.2,
|
|
228
|
+
"sharpen_amount": 0.2,
|
|
229
|
+
"saturation": 0.9
|
|
230
|
+
},
|
|
231
|
+
"transport": {
|
|
232
|
+
"jpeg": {"enabled": true, "quality": {"dist": "uniform", "min": 65, "max": 92}},
|
|
233
|
+
"bit_depth": 8
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
"camera_profile": "real_drive_front",
|
|
238
|
+
"capture": {
|
|
239
|
+
"stages": [
|
|
240
|
+
{
|
|
241
|
+
"type": "optics",
|
|
242
|
+
"blur_sigma": {"dist": "uniform", "min": 0.2, "max": 0.8},
|
|
243
|
+
"vignetting_strength": 0.15,
|
|
244
|
+
"windshield_haze": {"enabled": true, "probability": 0.4},
|
|
245
|
+
"droplets": {"enabled": false}
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"type": "sensor",
|
|
249
|
+
"input_space": "srgb",
|
|
250
|
+
"exposure_gain": {"dist": "uniform", "min": 0.85, "max": 1.2},
|
|
251
|
+
"row_noise_sigma": 0.003
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
"type": "isp",
|
|
255
|
+
"tone_map": "reinhard",
|
|
256
|
+
"gamma": "srgb",
|
|
257
|
+
"denoise_sigma": 0.2,
|
|
258
|
+
"sharpen_amount": 0.2,
|
|
259
|
+
"saturation": 0.9
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"type": "transport",
|
|
263
|
+
"jpeg": {"enabled": true, "quality": {"dist": "uniform", "min": 65, "max": 92}},
|
|
264
|
+
"bit_depth": 8
|
|
265
|
+
}
|
|
266
|
+
]
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Supported stage types:
|
|
271
|
+
|
|
272
|
+
| Stage | Main effects |
|
|
273
|
+
|---|---|
|
|
274
|
+
| `optics` | Defocus/MTF blur, motion blur, bloom, veiling glare, vignetting, chromatic aberration, lens distortion, windshield haze, optional droplets. |
|
|
275
|
+
| `sensor` | Exposure, white balance, camera matrix, Bayer mosaic, shot/read noise, fixed-pattern noise, row/column banding, hot/dead pixels, bilinear demosaic. |
|
|
276
|
+
| `isp` | Denoising, color correction, tone mapping, sRGB/gamma, local contrast, sharpening halos, saturation shifts. |
|
|
277
|
+
| `transport` | Crop/resize, bit-depth quantization, JPEG round-trip compression. |
|
|
278
|
+
| `exposure` | Lightweight standalone exposure and white-balance stage for simple custom chains. |
|
|
279
|
+
|
|
280
|
+
Any stage can define `condition_profiles` to sample coherent per-image settings
|
|
281
|
+
before the stage runs. This is useful for exposure states where ISO, exposure
|
|
282
|
+
gain, read noise, banding, and dark/fog noise modulation should move together:
|
|
283
|
+
|
|
284
|
+
```json
|
|
285
|
+
{
|
|
286
|
+
"type": "sensor",
|
|
287
|
+
"condition_profiles": [
|
|
288
|
+
{"name": "clean_daylight", "weight": 0.25, "exposure_gain": 1.0, "iso": 100},
|
|
289
|
+
{"name": "underexposed_noisy", "weight": 0.25, "exposure_gain": 0.65, "iso": 1600}
|
|
290
|
+
]
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
170
294
|
### Fog Model
|
|
171
295
|
|
|
172
296
|
The core equation is the **Koschmieder model** (atmospheric scattering):
|
|
@@ -224,6 +348,39 @@ When `airlight` is `"dcp_heuristic"`, you can optionally add:
|
|
|
224
348
|
- `white_bias + cool_bias` must be `<= 1`.
|
|
225
349
|
- The tint bias preserves the estimated airlight luminance, so it shifts colour without silently changing fog density.
|
|
226
350
|
|
|
351
|
+
### Airlight Intensity Dampening
|
|
352
|
+
|
|
353
|
+
Estimated airlight is dampened by default as fog density increases. This keeps
|
|
354
|
+
strong fog closer to the low, grey lighting seen in real in-car fog footage
|
|
355
|
+
instead of letting DCP-style estimates wash dense fog toward white.
|
|
356
|
+
|
|
357
|
+
Each fog model can override the dampening curve:
|
|
358
|
+
|
|
359
|
+
```json
|
|
360
|
+
"airlight_dampening": {
|
|
361
|
+
"enabled": true,
|
|
362
|
+
"apply_to": "estimated",
|
|
363
|
+
"reference_visibility_m": 80.0,
|
|
364
|
+
"min_factor": 0.45,
|
|
365
|
+
"max_factor": 1.0,
|
|
366
|
+
"strength": 1.0
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
The factor is:
|
|
371
|
+
`min_factor + (max_factor - min_factor) / (1 + strength * beta / reference_beta)`.
|
|
372
|
+
`reference_beta` is either `reference_scattering_coefficient` /
|
|
373
|
+
`reference_beta`, or it is derived from `reference_visibility_m` using the
|
|
374
|
+
model's contrast threshold. The default applies only when `atmospheric_light`
|
|
375
|
+
uses an estimated airlight method (`"from_sky"`, `"dcp"`, or
|
|
376
|
+
`"dcp_heuristic"`); literal RGB `atmospheric_light` values stay exact unless
|
|
377
|
+
`apply_to` is set to `"all"`. Set `"enabled": false` or `apply_to: "none"` to
|
|
378
|
+
preserve the previous undampened behavior.
|
|
379
|
+
|
|
380
|
+
For `heterogeneous_ls` and `heterogeneous_k_ls`, the Perlin atmospheric-light
|
|
381
|
+
field is sampled around the dampened base airlight, so the spatial variation
|
|
382
|
+
does not reintroduce the old over-illuminated look.
|
|
383
|
+
|
|
227
384
|
### Model Selection
|
|
228
385
|
|
|
229
386
|
Each image is assigned a fog model via the `selection` block:
|
|
@@ -264,7 +421,10 @@ Each model specifies a `visibility_m` distribution from which a visibility dista
|
|
|
264
421
|
| `lognormal` | `mean`, `sigma`, optional `min`/`max` | Log-normal. |
|
|
265
422
|
| `choice` | `values`, optional `weights` | Discrete weighted choice. |
|
|
266
423
|
|
|
267
|
-
The sampled visibility *V* is converted to the attenuation coefficient:
|
|
424
|
+
The sampled visibility *V* is converted to the attenuation coefficient:
|
|
425
|
+
**k = -ln(C_t) / V**. This happens once per output image. For
|
|
426
|
+
`heterogeneous_k` and `heterogeneous_k_ls`, that sampled value is the base
|
|
427
|
+
coefficient that is then spatially modulated by the noise field.
|
|
268
428
|
|
|
269
429
|
### Stepped Augmentations
|
|
270
430
|
|
|
@@ -355,7 +515,31 @@ The noise field (values in [0, 1]) is mapped to a factor field:
|
|
|
355
515
|
`contrast < 1` compresses the noise around 0.5 before this mapping, avoiding
|
|
356
516
|
extreme local fog density. When `normalize_to_mean` is `true`, the factor field
|
|
357
517
|
is rescaled so its spatial mean equals 1.0, preserving the overall fog density
|
|
358
|
-
while introducing spatial variation.
|
|
518
|
+
while introducing spatial variation. In other words, with heterogeneous `k`:
|
|
519
|
+
`k(x) = k_sampled * factor(x)`. If `visibility_m` / MOR was sampled from a
|
|
520
|
+
distribution, `k_sampled` is the coefficient derived from that one sampled MOR.
|
|
521
|
+
With `normalize_to_mean: true`, the arithmetic mean of the per-pixel `k` map
|
|
522
|
+
equals `k_sampled`; the median is not forced to match. With
|
|
523
|
+
`normalize_to_mean: false`, the map mean shifts by the mean of the factor field.
|
|
524
|
+
|
|
525
|
+
For `heterogeneous_ls` / `heterogeneous_k_ls`, `ls_hetero` can also include a
|
|
526
|
+
weak view-direction illumination prior. This modulates the atmospheric-light
|
|
527
|
+
field itself, so the rendered effect is still gated by fog transmittance:
|
|
528
|
+
|
|
529
|
+
```json
|
|
530
|
+
"ls_hetero": {
|
|
531
|
+
"ls_gradient": {
|
|
532
|
+
"enabled": true,
|
|
533
|
+
"probability": 0.65,
|
|
534
|
+
"axis": "vertical",
|
|
535
|
+
"top_factor": {"dist": "uniform", "min": 1.03, "max": 1.14},
|
|
536
|
+
"bottom_factor": {"dist": "uniform", "min": 0.88, "max": 0.99},
|
|
537
|
+
"gamma": {"dist": "uniform", "min": 0.85, "max": 1.6},
|
|
538
|
+
"normalize_to_mean": true,
|
|
539
|
+
"fog_opacity_weight": 0.65
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
```
|
|
359
543
|
|
|
360
544
|
| Parameter | Effect |
|
|
361
545
|
|---|---|
|
|
@@ -366,16 +550,19 @@ while introducing spatial variation.
|
|
|
366
550
|
| `octaves` / `lacunarity` / `max_scale` | Control how many increasingly broad Perlin components are mixed. |
|
|
367
551
|
| `contrast` | Compress or expand the Perlin range before mapping to factors. Values below 1 are recommended. |
|
|
368
552
|
| `smooth_sigma` / `smooth_sigma_fraction` | Optional final Gaussian blur in pixels or as a fraction of the shorter image side. |
|
|
553
|
+
| `ls_gradient` | Optional `L_s` top-to-bottom or left-to-right factor field. Keep it weak and probabilistic to avoid a deterministic image-position shortcut. |
|
|
369
554
|
|
|
370
555
|
### Fog Output
|
|
371
556
|
|
|
372
557
|
CLI runs write a source-backed RGB dataset. The output keeps the source RGB
|
|
373
|
-
dataset's relative paths, basenames, extensions, and
|
|
374
|
-
|
|
558
|
+
dataset's relative paths, basenames, extensions, and ds-crawler metadata so the
|
|
559
|
+
result stays loadable by `euler-loading`:
|
|
375
560
|
|
|
376
561
|
```
|
|
377
562
|
<output_path>/
|
|
378
|
-
.ds_crawler/
|
|
563
|
+
.ds_crawler/dataset-head.json
|
|
564
|
+
.ds_crawler/ds-crawler.json
|
|
565
|
+
.ds_crawler/index.json
|
|
379
566
|
Scene01/
|
|
380
567
|
Camera_0/
|
|
381
568
|
00000.png
|
|
@@ -390,7 +577,9 @@ the source file id instead:
|
|
|
390
577
|
|
|
391
578
|
```
|
|
392
579
|
<output_path>/
|
|
393
|
-
.ds_crawler/
|
|
580
|
+
.ds_crawler/dataset-head.json
|
|
581
|
+
.ds_crawler/ds-crawler.json
|
|
582
|
+
.ds_crawler/index.json
|
|
394
583
|
Scene01/
|
|
395
584
|
Camera_0/
|
|
396
585
|
00000/
|
|
@@ -446,6 +635,6 @@ No special parameters are required. The transform reads intrinsics from the `int
|
|
|
446
635
|
### Radial Output
|
|
447
636
|
|
|
448
637
|
CLI runs write a source-backed depth dataset mirroring the input depth
|
|
449
|
-
modality's layout and writer metadata. The emitted `
|
|
638
|
+
modality's layout and writer metadata. The emitted `index.json` also flips
|
|
450
639
|
`meta.radial_depth` to `true`. Standalone/direct `RadialTransform(...)` usage
|
|
451
640
|
keeps the legacy `.npy` output behavior.
|
|
@@ -84,7 +84,7 @@ starting at index 10 with stride 5.
|
|
|
84
84
|
|
|
85
85
|
| Transform | `modalities` | `hierarchical_modalities` |
|
|
86
86
|
|---|---|---|
|
|
87
|
-
| `fog` | `rgb`, `depth`, `semantic_segmentation` |
|
|
87
|
+
| `fog` | `rgb`, `depth`, `semantic_segmentation` | `intrinsics` when available; used for radial depth conversion and camera-profile optics |
|
|
88
88
|
| `sky-depth` | `depth`, `semantic_segmentation` | — |
|
|
89
89
|
| `radial` | `depth` | `intrinsics` |
|
|
90
90
|
|
|
@@ -136,6 +136,8 @@ Controls the fog simulation.
|
|
|
136
136
|
"contrast_threshold": 0.05,
|
|
137
137
|
"device": "cpu",
|
|
138
138
|
"gpu_batch_size": 4,
|
|
139
|
+
"capture": { "preset": "camera" },
|
|
140
|
+
"camera_profile": "dashcam",
|
|
139
141
|
"augmentations": { ... },
|
|
140
142
|
"selection": { ... },
|
|
141
143
|
"models": { ... }
|
|
@@ -151,8 +153,130 @@ Controls the fog simulation.
|
|
|
151
153
|
| `contrast_threshold` | Threshold *C_t* used in the visibility-to-attenuation conversion (default `0.05`). |
|
|
152
154
|
| `device` | `"cpu"`, `"cuda"`, `"mps"`, or `"gpu"` (alias for cuda). |
|
|
153
155
|
| `gpu_batch_size` | Batch size when running on GPU. Uniform-model samples are batched; heterogeneous samples are processed individually. |
|
|
156
|
+
| `capture` / `capture_artifacts` | Optional post-fog camera artifact pipeline. Omit it or set `{"stages": []}` for the legacy no-op path. Set `true`, `{"preset": "camera"}`, or a custom `stages` list to enable optics, raw sensor, ISP, and compression artifacts. |
|
|
157
|
+
| `camera_profile` | Optional named or inline camera profile whose stage defaults are merged into the capture stack before per-stage overrides. Built-ins are `"default"`, `"generic"`, `"dashcam"`, and `"low_light_fog"`. |
|
|
158
|
+
| `camera_profiles` | Optional map of project-specific named profiles. Use this for calibrated lens, sensor, ISP, and transport settings. |
|
|
154
159
|
| `augmentations` | Optional stepped augmentation set. When present, every input sample produces every configured augmentation and uses the file-id hierarchy output layout described below. |
|
|
155
160
|
|
|
161
|
+
### Processing Pipeline
|
|
162
|
+
|
|
163
|
+
Fog generation is split into two phases:
|
|
164
|
+
|
|
165
|
+
1. Ideal scene rendering: physics-based fog and auxiliary `scattering_coefficient`
|
|
166
|
+
/ `atmospheric_light` maps are computed.
|
|
167
|
+
2. Capture artifacts: camera-specific effects are applied to the rendered RGB
|
|
168
|
+
only. Physical fog maps stay stable while the RGB output can receive
|
|
169
|
+
exposure shifts, lens blur, vignetting, raw sensor noise, Bayer/demosaicing
|
|
170
|
+
artifacts, ISP tone/gamma/sharpening, and JPEG/resize/quantization effects.
|
|
171
|
+
|
|
172
|
+
This keeps physical fog maps stable while making the RGB output extensible for
|
|
173
|
+
real-camera simulation.
|
|
174
|
+
|
|
175
|
+
### Capture Artifact Stack
|
|
176
|
+
|
|
177
|
+
Enable the recommended camera stack with:
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
"capture": { "preset": "camera" }
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
or equivalently:
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
"capture": true
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
For tighter control, provide explicit stages in camera order:
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
"camera_profiles": {
|
|
193
|
+
"real_drive_front": {
|
|
194
|
+
"optics": {
|
|
195
|
+
"lens_distortion": -0.012,
|
|
196
|
+
"vignetting_strength": 0.18,
|
|
197
|
+
"windshield_haze": {"enabled": true, "probability": 0.55}
|
|
198
|
+
},
|
|
199
|
+
"sensor": {
|
|
200
|
+
"bayer_pattern": "RGGB",
|
|
201
|
+
"iso": {"dist": "choice", "values": [200, 400, 800]},
|
|
202
|
+
"base_iso": 100,
|
|
203
|
+
"full_well_electrons": [14000, 12000, 13000],
|
|
204
|
+
"read_noise_electrons": {"dist": "uniform", "min": 2.0, "max": 6.0},
|
|
205
|
+
"black_level": [0.003, 0.0035, 0.003],
|
|
206
|
+
"white_level": [1.0, 0.995, 1.0],
|
|
207
|
+
"adc_bit_depth": 12,
|
|
208
|
+
"post_demosaic_bit_depth": 12
|
|
209
|
+
},
|
|
210
|
+
"isp": {
|
|
211
|
+
"tone_map": "reinhard",
|
|
212
|
+
"gamma": "srgb",
|
|
213
|
+
"denoise_sigma": 0.2,
|
|
214
|
+
"sharpen_amount": 0.2,
|
|
215
|
+
"saturation": 0.9
|
|
216
|
+
},
|
|
217
|
+
"transport": {
|
|
218
|
+
"jpeg": {"enabled": true, "quality": {"dist": "uniform", "min": 65, "max": 92}},
|
|
219
|
+
"bit_depth": 8
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
"camera_profile": "real_drive_front",
|
|
224
|
+
"capture": {
|
|
225
|
+
"stages": [
|
|
226
|
+
{
|
|
227
|
+
"type": "optics",
|
|
228
|
+
"blur_sigma": {"dist": "uniform", "min": 0.2, "max": 0.8},
|
|
229
|
+
"vignetting_strength": 0.15,
|
|
230
|
+
"windshield_haze": {"enabled": true, "probability": 0.4},
|
|
231
|
+
"droplets": {"enabled": false}
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"type": "sensor",
|
|
235
|
+
"input_space": "srgb",
|
|
236
|
+
"exposure_gain": {"dist": "uniform", "min": 0.85, "max": 1.2},
|
|
237
|
+
"row_noise_sigma": 0.003
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"type": "isp",
|
|
241
|
+
"tone_map": "reinhard",
|
|
242
|
+
"gamma": "srgb",
|
|
243
|
+
"denoise_sigma": 0.2,
|
|
244
|
+
"sharpen_amount": 0.2,
|
|
245
|
+
"saturation": 0.9
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"type": "transport",
|
|
249
|
+
"jpeg": {"enabled": true, "quality": {"dist": "uniform", "min": 65, "max": 92}},
|
|
250
|
+
"bit_depth": 8
|
|
251
|
+
}
|
|
252
|
+
]
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Supported stage types:
|
|
257
|
+
|
|
258
|
+
| Stage | Main effects |
|
|
259
|
+
|---|---|
|
|
260
|
+
| `optics` | Defocus/MTF blur, motion blur, bloom, veiling glare, vignetting, chromatic aberration, lens distortion, windshield haze, optional droplets. |
|
|
261
|
+
| `sensor` | Exposure, white balance, camera matrix, Bayer mosaic, shot/read noise, fixed-pattern noise, row/column banding, hot/dead pixels, bilinear demosaic. |
|
|
262
|
+
| `isp` | Denoising, color correction, tone mapping, sRGB/gamma, local contrast, sharpening halos, saturation shifts. |
|
|
263
|
+
| `transport` | Crop/resize, bit-depth quantization, JPEG round-trip compression. |
|
|
264
|
+
| `exposure` | Lightweight standalone exposure and white-balance stage for simple custom chains. |
|
|
265
|
+
|
|
266
|
+
Any stage can define `condition_profiles` to sample coherent per-image settings
|
|
267
|
+
before the stage runs. This is useful for exposure states where ISO, exposure
|
|
268
|
+
gain, read noise, banding, and dark/fog noise modulation should move together:
|
|
269
|
+
|
|
270
|
+
```json
|
|
271
|
+
{
|
|
272
|
+
"type": "sensor",
|
|
273
|
+
"condition_profiles": [
|
|
274
|
+
{"name": "clean_daylight", "weight": 0.25, "exposure_gain": 1.0, "iso": 100},
|
|
275
|
+
{"name": "underexposed_noisy", "weight": 0.25, "exposure_gain": 0.65, "iso": 1600}
|
|
276
|
+
]
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
156
280
|
### Fog Model
|
|
157
281
|
|
|
158
282
|
The core equation is the **Koschmieder model** (atmospheric scattering):
|
|
@@ -210,6 +334,39 @@ When `airlight` is `"dcp_heuristic"`, you can optionally add:
|
|
|
210
334
|
- `white_bias + cool_bias` must be `<= 1`.
|
|
211
335
|
- The tint bias preserves the estimated airlight luminance, so it shifts colour without silently changing fog density.
|
|
212
336
|
|
|
337
|
+
### Airlight Intensity Dampening
|
|
338
|
+
|
|
339
|
+
Estimated airlight is dampened by default as fog density increases. This keeps
|
|
340
|
+
strong fog closer to the low, grey lighting seen in real in-car fog footage
|
|
341
|
+
instead of letting DCP-style estimates wash dense fog toward white.
|
|
342
|
+
|
|
343
|
+
Each fog model can override the dampening curve:
|
|
344
|
+
|
|
345
|
+
```json
|
|
346
|
+
"airlight_dampening": {
|
|
347
|
+
"enabled": true,
|
|
348
|
+
"apply_to": "estimated",
|
|
349
|
+
"reference_visibility_m": 80.0,
|
|
350
|
+
"min_factor": 0.45,
|
|
351
|
+
"max_factor": 1.0,
|
|
352
|
+
"strength": 1.0
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
The factor is:
|
|
357
|
+
`min_factor + (max_factor - min_factor) / (1 + strength * beta / reference_beta)`.
|
|
358
|
+
`reference_beta` is either `reference_scattering_coefficient` /
|
|
359
|
+
`reference_beta`, or it is derived from `reference_visibility_m` using the
|
|
360
|
+
model's contrast threshold. The default applies only when `atmospheric_light`
|
|
361
|
+
uses an estimated airlight method (`"from_sky"`, `"dcp"`, or
|
|
362
|
+
`"dcp_heuristic"`); literal RGB `atmospheric_light` values stay exact unless
|
|
363
|
+
`apply_to` is set to `"all"`. Set `"enabled": false` or `apply_to: "none"` to
|
|
364
|
+
preserve the previous undampened behavior.
|
|
365
|
+
|
|
366
|
+
For `heterogeneous_ls` and `heterogeneous_k_ls`, the Perlin atmospheric-light
|
|
367
|
+
field is sampled around the dampened base airlight, so the spatial variation
|
|
368
|
+
does not reintroduce the old over-illuminated look.
|
|
369
|
+
|
|
213
370
|
### Model Selection
|
|
214
371
|
|
|
215
372
|
Each image is assigned a fog model via the `selection` block:
|
|
@@ -250,7 +407,10 @@ Each model specifies a `visibility_m` distribution from which a visibility dista
|
|
|
250
407
|
| `lognormal` | `mean`, `sigma`, optional `min`/`max` | Log-normal. |
|
|
251
408
|
| `choice` | `values`, optional `weights` | Discrete weighted choice. |
|
|
252
409
|
|
|
253
|
-
The sampled visibility *V* is converted to the attenuation coefficient:
|
|
410
|
+
The sampled visibility *V* is converted to the attenuation coefficient:
|
|
411
|
+
**k = -ln(C_t) / V**. This happens once per output image. For
|
|
412
|
+
`heterogeneous_k` and `heterogeneous_k_ls`, that sampled value is the base
|
|
413
|
+
coefficient that is then spatially modulated by the noise field.
|
|
254
414
|
|
|
255
415
|
### Stepped Augmentations
|
|
256
416
|
|
|
@@ -341,7 +501,31 @@ The noise field (values in [0, 1]) is mapped to a factor field:
|
|
|
341
501
|
`contrast < 1` compresses the noise around 0.5 before this mapping, avoiding
|
|
342
502
|
extreme local fog density. When `normalize_to_mean` is `true`, the factor field
|
|
343
503
|
is rescaled so its spatial mean equals 1.0, preserving the overall fog density
|
|
344
|
-
while introducing spatial variation.
|
|
504
|
+
while introducing spatial variation. In other words, with heterogeneous `k`:
|
|
505
|
+
`k(x) = k_sampled * factor(x)`. If `visibility_m` / MOR was sampled from a
|
|
506
|
+
distribution, `k_sampled` is the coefficient derived from that one sampled MOR.
|
|
507
|
+
With `normalize_to_mean: true`, the arithmetic mean of the per-pixel `k` map
|
|
508
|
+
equals `k_sampled`; the median is not forced to match. With
|
|
509
|
+
`normalize_to_mean: false`, the map mean shifts by the mean of the factor field.
|
|
510
|
+
|
|
511
|
+
For `heterogeneous_ls` / `heterogeneous_k_ls`, `ls_hetero` can also include a
|
|
512
|
+
weak view-direction illumination prior. This modulates the atmospheric-light
|
|
513
|
+
field itself, so the rendered effect is still gated by fog transmittance:
|
|
514
|
+
|
|
515
|
+
```json
|
|
516
|
+
"ls_hetero": {
|
|
517
|
+
"ls_gradient": {
|
|
518
|
+
"enabled": true,
|
|
519
|
+
"probability": 0.65,
|
|
520
|
+
"axis": "vertical",
|
|
521
|
+
"top_factor": {"dist": "uniform", "min": 1.03, "max": 1.14},
|
|
522
|
+
"bottom_factor": {"dist": "uniform", "min": 0.88, "max": 0.99},
|
|
523
|
+
"gamma": {"dist": "uniform", "min": 0.85, "max": 1.6},
|
|
524
|
+
"normalize_to_mean": true,
|
|
525
|
+
"fog_opacity_weight": 0.65
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
```
|
|
345
529
|
|
|
346
530
|
| Parameter | Effect |
|
|
347
531
|
|---|---|
|
|
@@ -352,16 +536,19 @@ while introducing spatial variation.
|
|
|
352
536
|
| `octaves` / `lacunarity` / `max_scale` | Control how many increasingly broad Perlin components are mixed. |
|
|
353
537
|
| `contrast` | Compress or expand the Perlin range before mapping to factors. Values below 1 are recommended. |
|
|
354
538
|
| `smooth_sigma` / `smooth_sigma_fraction` | Optional final Gaussian blur in pixels or as a fraction of the shorter image side. |
|
|
539
|
+
| `ls_gradient` | Optional `L_s` top-to-bottom or left-to-right factor field. Keep it weak and probabilistic to avoid a deterministic image-position shortcut. |
|
|
355
540
|
|
|
356
541
|
### Fog Output
|
|
357
542
|
|
|
358
543
|
CLI runs write a source-backed RGB dataset. The output keeps the source RGB
|
|
359
|
-
dataset's relative paths, basenames, extensions, and
|
|
360
|
-
|
|
544
|
+
dataset's relative paths, basenames, extensions, and ds-crawler metadata so the
|
|
545
|
+
result stays loadable by `euler-loading`:
|
|
361
546
|
|
|
362
547
|
```
|
|
363
548
|
<output_path>/
|
|
364
|
-
.ds_crawler/
|
|
549
|
+
.ds_crawler/dataset-head.json
|
|
550
|
+
.ds_crawler/ds-crawler.json
|
|
551
|
+
.ds_crawler/index.json
|
|
365
552
|
Scene01/
|
|
366
553
|
Camera_0/
|
|
367
554
|
00000.png
|
|
@@ -376,7 +563,9 @@ the source file id instead:
|
|
|
376
563
|
|
|
377
564
|
```
|
|
378
565
|
<output_path>/
|
|
379
|
-
.ds_crawler/
|
|
566
|
+
.ds_crawler/dataset-head.json
|
|
567
|
+
.ds_crawler/ds-crawler.json
|
|
568
|
+
.ds_crawler/index.json
|
|
380
569
|
Scene01/
|
|
381
570
|
Camera_0/
|
|
382
571
|
00000/
|
|
@@ -432,6 +621,6 @@ No special parameters are required. The transform reads intrinsics from the `int
|
|
|
432
621
|
### Radial Output
|
|
433
622
|
|
|
434
623
|
CLI runs write a source-backed depth dataset mirroring the input depth
|
|
435
|
-
modality's layout and writer metadata. The emitted `
|
|
624
|
+
modality's layout and writer metadata. The emitted `index.json` also flips
|
|
436
625
|
`meta.radial_depth` to `true`. Standalone/direct `RadialTransform(...)` usage
|
|
437
626
|
keeps the legacy `.npy` output behavior.
|
|
@@ -9,6 +9,11 @@ from pathlib import Path
|
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
11
|
from ds_crawler import DatasetWriter, ZipDatasetWriter
|
|
12
|
+
from ds_crawler.zip_utils import (
|
|
13
|
+
METADATA_DIR,
|
|
14
|
+
OUTPUT_FILENAME as DS_CRAWLER_INDEX_FILENAME,
|
|
15
|
+
get_metadata_entry_name,
|
|
16
|
+
)
|
|
12
17
|
from euler_loading import MultiModalDataset
|
|
13
18
|
from euler_loading.dataset import create_dataset_writer_from_index
|
|
14
19
|
from euler_loading.loaders._writer_utils import supports_stream_target
|
|
@@ -216,12 +221,18 @@ def _merge_top_level_mapping(target: dict[str, Any], overlay: dict[str, Any]) ->
|
|
|
216
221
|
def _patch_output_index(
|
|
217
222
|
dataset_writer: DatasetWriter | ZipDatasetWriter,
|
|
218
223
|
overrides: dict[str, Any],
|
|
224
|
+
*,
|
|
225
|
+
filename: str = DS_CRAWLER_INDEX_FILENAME,
|
|
219
226
|
) -> None:
|
|
220
227
|
if not overrides:
|
|
221
228
|
return
|
|
222
229
|
|
|
230
|
+
metadata_scope = getattr(dataset_writer, "_metadata_scope", None)
|
|
223
231
|
if isinstance(dataset_writer, ZipDatasetWriter):
|
|
224
|
-
entry_name =
|
|
232
|
+
entry_name = get_metadata_entry_name(
|
|
233
|
+
filename,
|
|
234
|
+
metadata_scope=metadata_scope,
|
|
235
|
+
)
|
|
225
236
|
archive_path = Path(dataset_writer.root)
|
|
226
237
|
replacement_name = archive_path.with_suffix(archive_path.suffix + ".tmp")
|
|
227
238
|
with zipfile.ZipFile(archive_path, "r") as source_zip:
|
|
@@ -238,7 +249,10 @@ def _patch_output_index(
|
|
|
238
249
|
replacement_name.replace(archive_path)
|
|
239
250
|
return
|
|
240
251
|
|
|
241
|
-
output_path = Path(dataset_writer.root) /
|
|
252
|
+
output_path = Path(dataset_writer.root) / METADATA_DIR
|
|
253
|
+
if metadata_scope is not None:
|
|
254
|
+
output_path = output_path / metadata_scope
|
|
255
|
+
output_path = output_path / filename
|
|
242
256
|
payload = json.loads(output_path.read_text(encoding="utf-8"))
|
|
243
257
|
_merge_top_level_mapping(payload, overrides)
|
|
244
258
|
output_path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
|
@@ -508,8 +522,12 @@ class SourceBackedOutputBackend:
|
|
|
508
522
|
)
|
|
509
523
|
|
|
510
524
|
def finalize(self) -> None:
|
|
511
|
-
self.dataset_writer.save_index()
|
|
512
|
-
_patch_output_index(
|
|
525
|
+
self.dataset_writer.save_index(filename=DS_CRAWLER_INDEX_FILENAME)
|
|
526
|
+
_patch_output_index(
|
|
527
|
+
self.dataset_writer,
|
|
528
|
+
self.index_overrides,
|
|
529
|
+
filename=DS_CRAWLER_INDEX_FILENAME,
|
|
530
|
+
)
|
|
513
531
|
if self.pipeline_manifest_path and self.pipeline_manifest_targets:
|
|
514
532
|
_write_pipeline_outputs_manifest(
|
|
515
533
|
self.pipeline_manifest_path,
|