euler-preprocess 2.2.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.
Files changed (53) hide show
  1. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/PKG-INFO +230 -22
  2. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/README.md +229 -21
  3. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/common/output.py +47 -7
  4. euler_preprocess-3.0.0/euler_preprocess/fog/atmospheric_light.py +306 -0
  5. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/augmentations.py +4 -0
  6. euler_preprocess-3.0.0/euler_preprocess/fog/capture.py +1806 -0
  7. euler_preprocess-3.0.0/euler_preprocess/fog/models.py +1143 -0
  8. euler_preprocess-3.0.0/euler_preprocess/fog/pipeline.py +151 -0
  9. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/transform.py +290 -285
  10. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess.egg-info/PKG-INFO +230 -22
  11. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess.egg-info/SOURCES.txt +3 -0
  12. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/pyproject.toml +1 -1
  13. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/tests/test_dcp_heuristic_airlight.py +2 -0
  14. euler_preprocess-3.0.0/tests/test_fog_aux_outputs.py +1250 -0
  15. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/tests/test_source_backed_output.py +11 -4
  16. euler_preprocess-2.2.0/euler_preprocess/fog/models.py +0 -384
  17. euler_preprocess-2.2.0/tests/test_fog_aux_outputs.py +0 -575
  18. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/__init__.py +0 -0
  19. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/cli.py +0 -0
  20. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/common/__init__.py +0 -0
  21. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/common/dataset.py +0 -0
  22. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/common/device.py +0 -0
  23. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/common/intrinsics.py +0 -0
  24. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/common/io.py +0 -0
  25. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/common/logging.py +0 -0
  26. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/common/noise.py +0 -0
  27. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/common/normalize.py +0 -0
  28. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/common/sampling.py +0 -0
  29. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/common/transform.py +0 -0
  30. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/__init__.py +0 -0
  31. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/airlight_from_sky.py +0 -0
  32. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/dcp_airlight.py +0 -0
  33. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/dcp_airlight_torch.py +0 -0
  34. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/dcp_heuristic_airlight.py +0 -0
  35. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/dcp_heuristic_airlight_torch.py +0 -0
  36. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/foggify.py +0 -0
  37. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/foggify_logging.py +0 -0
  38. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/fog/logging.py +0 -0
  39. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/radial/__init__.py +0 -0
  40. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/radial/transform.py +0 -0
  41. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/sky_depth/__init__.py +0 -0
  42. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess/sky_depth/transform.py +0 -0
  43. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess.egg-info/dependency_links.txt +0 -0
  44. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess.egg-info/entry_points.txt +0 -0
  45. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess.egg-info/requires.txt +0 -0
  46. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/euler_preprocess.egg-info/top_level.txt +0 -0
  47. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/setup.cfg +0 -0
  48. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/tests/test_airlight_fallback.py +0 -0
  49. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/tests/test_cli_sample_selection.py +0 -0
  50. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/tests/test_foggify_integration.py +0 -0
  51. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/tests/test_radial.py +0 -0
  52. {euler_preprocess-2.2.0 → euler_preprocess-3.0.0}/tests/test_sky_depth.py +0 -0
  53. {euler_preprocess-2.2.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: 2.2.0
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` | — (intrinsics optional) |
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:
@@ -232,10 +389,10 @@ Each image is assigned a fog model via the `selection` block:
232
389
  "selection": {
233
390
  "mode": "weighted",
234
391
  "weights": {
235
- "uniform": 1.0,
236
- "heterogeneous_k": 0.0,
237
- "heterogeneous_ls": 0.0,
238
- "heterogeneous_k_ls": 0.0
392
+ "uniform": 0.25,
393
+ "heterogeneous_k": 0.35,
394
+ "heterogeneous_ls": 0.25,
395
+ "heterogeneous_k_ls": 0.15
239
396
  }
240
397
  }
241
398
  ```
@@ -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: **k = -ln(C_t) / V**.
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
 
@@ -309,9 +469,12 @@ variants:
309
469
  "scattering_coefficient": 0.15,
310
470
  "atmospheric_light": [1.0, 1.0, 1.0],
311
471
  "k_hetero": {
312
- "scales": "auto",
313
- "min_factor": 0.5,
314
- "max_factor": 1.5,
472
+ "scales": "smooth_auto",
473
+ "correlation_length_fraction": 0.25,
474
+ "octaves": 3,
475
+ "min_factor": 0.65,
476
+ "max_factor": 1.45,
477
+ "contrast": 0.65,
315
478
  "normalize_to_mean": true
316
479
  }
317
480
  }
@@ -327,36 +490,79 @@ MOR/beta descriptors when available. euler-loading exposes these as
327
490
 
328
491
  ### Heterogeneous Noise Fields
329
492
 
330
- Both `k_hetero` and `ls_hetero` use Perlin FBM (fractional Brownian motion) to generate spatially-varying factor fields:
493
+ Both `k_hetero` and `ls_hetero` use Perlin FBM (fractional Brownian
494
+ motion) to generate spatially-varying factor fields. For realistic fog,
495
+ prefer the smooth mode: it keeps Perlin wavelengths tied to the image size,
496
+ then optionally reduces noise contrast and applies a final blur before mapping
497
+ the noise to physical factors.
331
498
 
332
499
  ```json
333
500
  "k_hetero": {
334
- "scales": "auto",
335
- "min_scale": 2,
501
+ "scales": "smooth_auto",
502
+ "correlation_length_fraction": 0.25,
503
+ "octaves": 3,
336
504
  "max_scale": null,
337
- "min_factor": 0.0,
338
- "max_factor": 1.0,
505
+ "min_factor": 0.65,
506
+ "max_factor": 1.45,
507
+ "contrast": 0.65,
508
+ "smooth_sigma_fraction": 0.0,
339
509
  "normalize_to_mean": true
340
510
  }
341
511
  ```
342
512
 
343
- The noise field (values in [0, 1]) is mapped to a factor field: `factor(x) = min_factor + (max_factor - min_factor) * noise(x)`. When `normalize_to_mean` is `true`, the factor field is rescaled so its spatial mean equals 1.0, preserving the overall fog density while introducing spatial variation.
513
+ The noise field (values in [0, 1]) is mapped to a factor field:
514
+ `factor(x) = min_factor + (max_factor - min_factor) * noise(x)`.
515
+ `contrast < 1` compresses the noise around 0.5 before this mapping, avoiding
516
+ extreme local fog density. When `normalize_to_mean` is `true`, the factor field
517
+ is rescaled so its spatial mean equals 1.0, preserving the overall fog density
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
+ ```
344
543
 
345
544
  | Parameter | Effect |
346
545
  |---|---|
347
546
  | `min_factor` / `max_factor` | Range of the multiplicative factor. |
348
547
  | `normalize_to_mean` | Rescale factors so the image-wide mean equals the base value. Recommended for `k_hetero`. |
349
- | `scales` / `min_scale` / `max_scale` | Control spatial frequency content. |
548
+ | `scales: "smooth_auto"` | Build low-frequency Perlin scales from the image size. |
549
+ | `correlation_length_fraction` | Approximate smallest fog feature size as a fraction of the shorter image side. Larger values create smoother gradients. |
550
+ | `octaves` / `lacunarity` / `max_scale` | Control how many increasingly broad Perlin components are mixed. |
551
+ | `contrast` | Compress or expand the Perlin range before mapping to factors. Values below 1 are recommended. |
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. |
350
554
 
351
555
  ### Fog Output
352
556
 
353
557
  CLI runs write a source-backed RGB dataset. The output keeps the source RGB
354
- dataset's relative paths, basenames, extensions, and `output.json` metadata so
355
- the result stays loadable by `euler-loading`:
558
+ dataset's relative paths, basenames, extensions, and ds-crawler metadata so the
559
+ result stays loadable by `euler-loading`:
356
560
 
357
561
  ```
358
562
  <output_path>/
359
- .ds_crawler/output.json
563
+ .ds_crawler/dataset-head.json
564
+ .ds_crawler/ds-crawler.json
565
+ .ds_crawler/index.json
360
566
  Scene01/
361
567
  Camera_0/
362
568
  00000.png
@@ -371,7 +577,9 @@ the source file id instead:
371
577
 
372
578
  ```
373
579
  <output_path>/
374
- .ds_crawler/output.json
580
+ .ds_crawler/dataset-head.json
581
+ .ds_crawler/ds-crawler.json
582
+ .ds_crawler/index.json
375
583
  Scene01/
376
584
  Camera_0/
377
585
  00000/
@@ -427,6 +635,6 @@ No special parameters are required. The transform reads intrinsics from the `int
427
635
  ### Radial Output
428
636
 
429
637
  CLI runs write a source-backed depth dataset mirroring the input depth
430
- modality's layout and writer metadata. The emitted `output.json` also flips
638
+ modality's layout and writer metadata. The emitted `index.json` also flips
431
639
  `meta.radial_depth` to `true`. Standalone/direct `RadialTransform(...)` usage
432
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` | — (intrinsics optional) |
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:
@@ -218,10 +375,10 @@ Each image is assigned a fog model via the `selection` block:
218
375
  "selection": {
219
376
  "mode": "weighted",
220
377
  "weights": {
221
- "uniform": 1.0,
222
- "heterogeneous_k": 0.0,
223
- "heterogeneous_ls": 0.0,
224
- "heterogeneous_k_ls": 0.0
378
+ "uniform": 0.25,
379
+ "heterogeneous_k": 0.35,
380
+ "heterogeneous_ls": 0.25,
381
+ "heterogeneous_k_ls": 0.15
225
382
  }
226
383
  }
227
384
  ```
@@ -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: **k = -ln(C_t) / V**.
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
 
@@ -295,9 +455,12 @@ variants:
295
455
  "scattering_coefficient": 0.15,
296
456
  "atmospheric_light": [1.0, 1.0, 1.0],
297
457
  "k_hetero": {
298
- "scales": "auto",
299
- "min_factor": 0.5,
300
- "max_factor": 1.5,
458
+ "scales": "smooth_auto",
459
+ "correlation_length_fraction": 0.25,
460
+ "octaves": 3,
461
+ "min_factor": 0.65,
462
+ "max_factor": 1.45,
463
+ "contrast": 0.65,
301
464
  "normalize_to_mean": true
302
465
  }
303
466
  }
@@ -313,36 +476,79 @@ MOR/beta descriptors when available. euler-loading exposes these as
313
476
 
314
477
  ### Heterogeneous Noise Fields
315
478
 
316
- Both `k_hetero` and `ls_hetero` use Perlin FBM (fractional Brownian motion) to generate spatially-varying factor fields:
479
+ Both `k_hetero` and `ls_hetero` use Perlin FBM (fractional Brownian
480
+ motion) to generate spatially-varying factor fields. For realistic fog,
481
+ prefer the smooth mode: it keeps Perlin wavelengths tied to the image size,
482
+ then optionally reduces noise contrast and applies a final blur before mapping
483
+ the noise to physical factors.
317
484
 
318
485
  ```json
319
486
  "k_hetero": {
320
- "scales": "auto",
321
- "min_scale": 2,
487
+ "scales": "smooth_auto",
488
+ "correlation_length_fraction": 0.25,
489
+ "octaves": 3,
322
490
  "max_scale": null,
323
- "min_factor": 0.0,
324
- "max_factor": 1.0,
491
+ "min_factor": 0.65,
492
+ "max_factor": 1.45,
493
+ "contrast": 0.65,
494
+ "smooth_sigma_fraction": 0.0,
325
495
  "normalize_to_mean": true
326
496
  }
327
497
  ```
328
498
 
329
- The noise field (values in [0, 1]) is mapped to a factor field: `factor(x) = min_factor + (max_factor - min_factor) * noise(x)`. When `normalize_to_mean` is `true`, the factor field is rescaled so its spatial mean equals 1.0, preserving the overall fog density while introducing spatial variation.
499
+ The noise field (values in [0, 1]) is mapped to a factor field:
500
+ `factor(x) = min_factor + (max_factor - min_factor) * noise(x)`.
501
+ `contrast < 1` compresses the noise around 0.5 before this mapping, avoiding
502
+ extreme local fog density. When `normalize_to_mean` is `true`, the factor field
503
+ is rescaled so its spatial mean equals 1.0, preserving the overall fog density
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
+ ```
330
529
 
331
530
  | Parameter | Effect |
332
531
  |---|---|
333
532
  | `min_factor` / `max_factor` | Range of the multiplicative factor. |
334
533
  | `normalize_to_mean` | Rescale factors so the image-wide mean equals the base value. Recommended for `k_hetero`. |
335
- | `scales` / `min_scale` / `max_scale` | Control spatial frequency content. |
534
+ | `scales: "smooth_auto"` | Build low-frequency Perlin scales from the image size. |
535
+ | `correlation_length_fraction` | Approximate smallest fog feature size as a fraction of the shorter image side. Larger values create smoother gradients. |
536
+ | `octaves` / `lacunarity` / `max_scale` | Control how many increasingly broad Perlin components are mixed. |
537
+ | `contrast` | Compress or expand the Perlin range before mapping to factors. Values below 1 are recommended. |
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. |
336
540
 
337
541
  ### Fog Output
338
542
 
339
543
  CLI runs write a source-backed RGB dataset. The output keeps the source RGB
340
- dataset's relative paths, basenames, extensions, and `output.json` metadata so
341
- the result stays loadable by `euler-loading`:
544
+ dataset's relative paths, basenames, extensions, and ds-crawler metadata so the
545
+ result stays loadable by `euler-loading`:
342
546
 
343
547
  ```
344
548
  <output_path>/
345
- .ds_crawler/output.json
549
+ .ds_crawler/dataset-head.json
550
+ .ds_crawler/ds-crawler.json
551
+ .ds_crawler/index.json
346
552
  Scene01/
347
553
  Camera_0/
348
554
  00000.png
@@ -357,7 +563,9 @@ the source file id instead:
357
563
 
358
564
  ```
359
565
  <output_path>/
360
- .ds_crawler/output.json
566
+ .ds_crawler/dataset-head.json
567
+ .ds_crawler/ds-crawler.json
568
+ .ds_crawler/index.json
361
569
  Scene01/
362
570
  Camera_0/
363
571
  00000/
@@ -413,6 +621,6 @@ No special parameters are required. The transform reads intrinsics from the `int
413
621
  ### Radial Output
414
622
 
415
623
  CLI runs write a source-backed depth dataset mirroring the input depth
416
- modality's layout and writer metadata. The emitted `output.json` also flips
624
+ modality's layout and writer metadata. The emitted `index.json` also flips
417
625
  `meta.radial_depth` to `true`. Standalone/direct `RadialTransform(...)` usage
418
626
  keeps the legacy `.npy` output behavior.