euler-preprocess 3.18.0__tar.gz → 3.20.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 (87) hide show
  1. euler_preprocess-3.20.0/CHANGELOG.md +73 -0
  2. {euler_preprocess-3.18.0/euler_preprocess.egg-info → euler_preprocess-3.20.0}/PKG-INFO +138 -3
  3. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/README.md +135 -0
  4. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/docs/plans/coherent-cropping.md +198 -40
  5. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/cli.py +44 -2
  6. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/common/intrinsics.py +26 -6
  7. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/common/output.py +157 -0
  8. euler_preprocess-3.20.0/euler_preprocess/crop/__init__.py +1 -0
  9. euler_preprocess-3.20.0/euler_preprocess/crop/calibration.py +103 -0
  10. euler_preprocess-3.20.0/euler_preprocess/crop/files.py +84 -0
  11. euler_preprocess-3.20.0/euler_preprocess/crop/label_files.py +183 -0
  12. euler_preprocess-3.20.0/euler_preprocess/crop/roles.py +208 -0
  13. euler_preprocess-3.20.0/euler_preprocess/crop/run.py +915 -0
  14. euler_preprocess-3.20.0/euler_preprocess/crop/spec.py +444 -0
  15. euler_preprocess-3.20.0/euler_preprocess/crop/verify.py +109 -0
  16. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0/euler_preprocess.egg-info}/PKG-INFO +138 -3
  17. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess.egg-info/SOURCES.txt +14 -0
  18. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess.egg-info/requires.txt +2 -2
  19. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/pyproject.toml +3 -3
  20. euler_preprocess-3.20.0/tests/test_crop.py +423 -0
  21. euler_preprocess-3.20.0/tests/test_crop_labels.py +255 -0
  22. euler_preprocess-3.20.0/tests/test_crop_muses.py +267 -0
  23. euler_preprocess-3.20.0/tests/test_crop_roles.py +84 -0
  24. euler_preprocess-3.20.0/tests/test_crop_spec.py +195 -0
  25. euler_preprocess-3.20.0/tests/test_intrinsics.py +30 -0
  26. euler_preprocess-3.18.0/CHANGELOG.md +0 -14
  27. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/LICENSE +0 -0
  28. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/MANIFEST.in +0 -0
  29. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/docs/spatial.md +0 -0
  30. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/__init__.py +0 -0
  31. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/common/__init__.py +0 -0
  32. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/common/color.py +0 -0
  33. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/common/dataset.py +0 -0
  34. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/common/device.py +0 -0
  35. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/common/io.py +0 -0
  36. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/common/logging.py +0 -0
  37. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/common/noise.py +0 -0
  38. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/common/normalize.py +0 -0
  39. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/common/sampling.py +0 -0
  40. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/common/transform.py +0 -0
  41. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/fog/__init__.py +0 -0
  42. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/fog/airlight_from_sky.py +0 -0
  43. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/fog/atmospheric_light.py +0 -0
  44. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/fog/augmentations.py +0 -0
  45. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/fog/capture.py +0 -0
  46. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/fog/dcp_airlight.py +0 -0
  47. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/fog/dcp_airlight_torch.py +0 -0
  48. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/fog/dcp_heuristic_airlight.py +0 -0
  49. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/fog/dcp_heuristic_airlight_torch.py +0 -0
  50. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/fog/inference.py +0 -0
  51. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/fog/logging.py +0 -0
  52. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/fog/models.py +0 -0
  53. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/fog/pipeline.py +0 -0
  54. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/fog/transform.py +0 -0
  55. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/lidar_motion/__init__.py +0 -0
  56. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/lidar_motion/compensation.py +0 -0
  57. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/lidar_motion/transform.py +0 -0
  58. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/radial/__init__.py +0 -0
  59. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/radial/transform.py +0 -0
  60. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/sky_depth/__init__.py +0 -0
  61. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/sky_depth/transform.py +0 -0
  62. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess/spatial.py +0 -0
  63. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess.egg-info/dependency_links.txt +0 -0
  64. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess.egg-info/entry_points.txt +0 -0
  65. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/euler_preprocess.egg-info/top_level.txt +0 -0
  66. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/examples/spatial-five-field.json +0 -0
  67. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/setup.cfg +0 -0
  68. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/tests/test_airlight_fallback.py +0 -0
  69. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/tests/test_cli_sample_selection.py +0 -0
  70. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/tests/test_dcp_heuristic_airlight.py +0 -0
  71. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/tests/test_dense_gloomy_daylight_config.py +0 -0
  72. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/tests/test_fog_aux_outputs.py +0 -0
  73. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/tests/test_fog_aware_auto_exposure.py +0 -0
  74. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/tests/test_fog_cpu_gpu_parity.py +0 -0
  75. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/tests/test_fog_input_normalization.py +0 -0
  76. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/tests/test_fog_integration.py +0 -0
  77. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/tests/test_lidar_motion.py +0 -0
  78. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/tests/test_modality_roles.py +0 -0
  79. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/tests/test_radial.py +0 -0
  80. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/tests/test_real_drive_sim_scenario_profiles.py +0 -0
  81. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/tests/test_scene_illumination.py +0 -0
  82. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/tests/test_sensor_identity.py +0 -0
  83. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/tests/test_sky_depth.py +0 -0
  84. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/tests/test_source_backed_output.py +0 -0
  85. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/tests/test_spatial.py +0 -0
  86. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/tests/test_tone_map_lut.py +0 -0
  87. {euler_preprocess-3.18.0 → euler_preprocess-3.20.0}/tests/test_zip_output.py +0 -0
@@ -0,0 +1,73 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ - `crop` crops label rasters in the stored file: semantic segmentation (the
6
+ ground truth that also holds the sky), instance and panoptic segmentation,
7
+ and sky masks.
8
+ - PNG, TIFF and BMP keep their mode, palette, bit depth and every channel;
9
+ `.npy`/`.npz` keep their dtype and other arrays.
10
+ - The result must decode exactly as the crop of the source labels.
11
+ - So no class is lost to a writer that re-encodes only what one loader
12
+ function returns: Real Drive Sim's non-red channels, or every class but
13
+ the sky for a dataset read through `sky_mask`. Label modalities no longer
14
+ need a writer.
15
+ - Files that cannot be kept (JPEG, 16-bit colour PNGs) are re-encoded with
16
+ the writer, with a warning.
17
+ - Labels can only be resized with `nearest`; another `resize.interpolation`
18
+ for them is an error.
19
+
20
+ - `crop` supports MUSES (with euler-loading 2.35.0's MUSES writers):
21
+ - `calib.json` holds the frame camera's `K` and every extrinsic. The
22
+ `intrinsics` modality patches it once, and every other modality indexing
23
+ the same file, or a byte-identical copy (`camera_extrinsics`,
24
+ `lidar_to_gnss`), is written with the patched bytes, so the bundle holds
25
+ one `K`. What each of them reads from the patched file must stay as it was;
26
+ two intrinsics modalities patching one file differently are refused.
27
+ - GNSS records (`gnss`) and `meta.json` timing (`sensor_timing`) are copied
28
+ unchanged by default.
29
+ - `configs/crop_muses_hood_config.json` cuts the engine hood off the
30
+ 1920×1080 frame camera images (bottom 160 rows, 1920×920 stay), with
31
+ `configs/crop_muses_dataset_config.json` as the dataset config.
32
+ - Require euler-loading 2.35.0 for the MUSES writers.
33
+
34
+ - Add `euler-preprocess lidar-motion`, which compensates scanning-lidar points for
35
+ the vehicle's motion during the sweep. It follows the MUSES SDK, moving each
36
+ point to the frame camera's mid-exposure using the `gnss`, `sensor_timing`
37
+ and `lidar_to_gnss` modalities.
38
+ - Require euler-loading 2.32.0, the first release with `muses.gnss`,
39
+ `muses.sensor_timing`, `muses.read_lidar_to_gnss` and `muses.write_sparse_depth`.
40
+ - Add `euler-preprocess crop`, which crops RGB, depth, intrinsics and
41
+ extrinsics coherently. The box is given as fractions or pixels, either removed
42
+ per edge or kept with an anchor, relative to a reference raster. Rasters are
43
+ sliced, pinhole intrinsics get `cx -= left`, `cy -= top` merged into a copy of
44
+ their file, and lidar point clouds and extrinsics are copied unchanged. Each
45
+ modality's treatment comes from its loader annotation, not its name. Shared
46
+ calibration files are written once, and samples with different crops cannot
47
+ share one. The outputs are reopened and checked against the crop.
48
+ `configs/crop_stf_valid_region_config.json` removes SeeingThroughFog's black
49
+ rectification border.
50
+ - `crop` phase 3:
51
+ - `point_clouds: "prune_to_crop"` keeps only the lidar points that project
52
+ into the output image (`z > 0`, `rint(K'·T·X)` inside), the projection
53
+ consumers such as dmd3c-euler use;
54
+ - `resize` (`size` or `scale`) after the crop, with `K' = S·C·K`; colour
55
+ images are interpolated bilinearly, all other rasters take the nearest
56
+ pixel;
57
+ - `"output_layout": "scoped"` writes one root (directory or zip) with one
58
+ metadata scope per modality, as dmd3c-euler's `--dataset-root` reads;
59
+ - every encoded raster or pruned cloud is decoded again and must reproduce
60
+ the values;
61
+ - `euler-preprocess crop-box` prints the pixel box and the matching
62
+ `spatial` preprocessing block.
63
+ - `crop` uses the new public euler-loading accessors (`get_loader_meta`,
64
+ `get_hierarchical_files`) and ds-crawler's `set_modality_meta` / `path_prefix`.
65
+ Requires euler-loading 2.33.0 and ds-crawler 2.14.0.
66
+ - `extract_intrinsics` (radial, fog) also uses a hierarchical intrinsics file
67
+ that is not named `intrinsics` when it is the only one, such as SeeingThroughFog's
68
+ `calib_cam_stereo_left`. Several matching files now raise instead of being
69
+ ignored.
70
+
71
+ ## 3.14.0 (unreleased)
72
+
73
+ Consume shared preprocessing config and add the opt-in spatial producer, explicit variants/mappings and captured source-backed outputs. Preserve ordinary pipeline APIs.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: euler-preprocess
3
- Version: 3.18.0
3
+ Version: 3.20.0
4
4
  Summary: Physics-based preprocessing (fog, etc.) for RGB+depth datasets
5
5
  Author-email: Daniel Rothenpieler <rothenpielerdaniel@gmail.com>
6
6
  License-Expression: MIT
@@ -9,8 +9,8 @@ Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
10
  Requires-Dist: numpy
11
11
  Requires-Dist: Pillow
12
- Requires-Dist: euler-loading>=2.32.0
13
- Requires-Dist: ds-crawler>=2.11.0
12
+ Requires-Dist: euler-loading>=2.35.0
13
+ Requires-Dist: ds-crawler>=2.14.0
14
14
  Requires-Dist: euler-dataset-contract>=0.5.0
15
15
  Provides-Extra: gpu
16
16
  Requires-Dist: torch; extra == "gpu"
@@ -65,6 +65,8 @@ qualitative review.
65
65
  | `euler-preprocess sky-depth` | Override depth values in sky regions with a constant |
66
66
  | `euler-preprocess radial` | Convert planar (z-buffer) depth to radial (Euclidean) depth |
67
67
  | `euler-preprocess lidar-motion` | Compensate scanning-lidar points for the vehicle's motion during a sweep |
68
+ | `euler-preprocess crop` | Crop RGB, depth, intrinsics and extrinsics coherently |
69
+ | `euler-preprocess crop-box` | Print the pixel box a crop config gives for an image size |
68
70
 
69
71
  ## Installation
70
72
 
@@ -79,6 +81,7 @@ euler-preprocess fog -c configs/example_dataset_config.json
79
81
  euler-preprocess sky-depth -c configs/sky_depth_dataset_config.json
80
82
  euler-preprocess radial -c configs/radial_dataset_config.json
81
83
  euler-preprocess lidar-motion -c configs/lidar_motion_dataset_config.json
84
+ euler-preprocess crop -c configs/crop_stf_dataset_config.json
82
85
  ```
83
86
 
84
87
  ## Dataset Config
@@ -737,6 +740,138 @@ holds the target time and the speed and rates used. Point
737
740
 
738
741
  ---
739
742
 
743
+ ## Coherent Crop
744
+
745
+ `crop` cuts the same box out of every image-shaped modality and corrects
746
+ everything that refers to the pixel grid, so a cropped bundle projects exactly
747
+ like the original:
748
+
749
+ | Modality kind | Crop |
750
+ |---|---|
751
+ | Rasters: RGB, dense depth | Sliced; no resampling, so values are unchanged |
752
+ | Label rasters: semantic segmentation (the ground truth that also holds the sky), instance and panoptic segmentation, sky masks | Cropped in the stored file, so every class, channel, palette and bit depth is kept; resized only by nearest |
753
+ | Pinhole intrinsics `K` (or `P` for rectified images) | `cx -= left`, `cy -= top`; `fx`, `fy`, skew unchanged |
754
+ | Lidar point clouds | Copied byte for byte, or pruned to the points that project into the crop |
755
+ | Extrinsics | Copied byte for byte |
756
+
757
+ The extrinsics need no change, because `C·K·[R|t]·X = K'·[R|t]·X` with
758
+ `C = [[1,0,-left],[0,1,-top],[0,0,1]]`: the whole correction lives in the
759
+ camera matrix, which is also what the official SeeingThroughFog tools do. An
760
+ optional resize afterwards adds `S` (an `align_corners=False` rescale), so
761
+ `K' = S·C·K`, still with untouched extrinsics.
762
+ Which kind a modality is comes from its euler-loading loader annotation
763
+ (`shape`, `type`), never from its name.
764
+
765
+ The dataset config lists every modality to crop; whether a modality is
766
+ per-sample or hierarchical (shared calibration) is inferred from its index:
767
+
768
+ ```json
769
+ {
770
+ "transform_config_path": "crop_stf_valid_region_config.json",
771
+ "output_path": "/data/stf_crop",
772
+ "modalities": {
773
+ "rgb": "/data/stf/cam_stereo_left_lut",
774
+ "sparse_depth": "/data/stf/lidar_hdl64_strongest",
775
+ "intrinsics": "/data/stf/calib_cam_stereo_left",
776
+ "camera_extrinsics": "/data/stf/calib_tf_tree"
777
+ },
778
+ "samples": {"start": 0, "count": 100}
779
+ }
780
+ ```
781
+
782
+ Each modality is written to `<output_path>/<name>`, or to its
783
+ `pipeline.output_targets[]` entry (`slot` = the modality name; directory or zip).
784
+ With pipeline targets, every modality needs one and the outputs manifest lists
785
+ them all. `"output_layout": "scoped"` writes one root at `output_path` instead
786
+ (a directory or `.zip`): one ds-crawler metadata scope per modality, named after
787
+ it, with its files below `<name>/`. That is the layout dmd3c-euler's
788
+ `--dataset-root` reads. The dataset config must not contain `preprocessing`.
789
+
790
+ The crop config (`configs/crop_config.json`) describes the box relative to the
791
+ `reference` raster:
792
+
793
+ ```json
794
+ {
795
+ "reference": "rgb",
796
+ "crop": {"remove": {"top": 0.20, "bottom": 0.05, "left": 0.15, "right": 0.19}},
797
+ "point_clouds": "keep",
798
+ "verify": "all"
799
+ }
800
+ ```
801
+
802
+ | Key | Meaning |
803
+ |---|---|
804
+ | `crop.remove` | What to cut off each edge (`top`, `bottom`, `left`, `right`) |
805
+ | `crop.keep` + `crop.anchor` | The kept `height` / `width`, placed at `center` (default), `top`, `bottom`, `left`, `right`, `top_left`, `top_right`, `bottom_left` or `bottom_right` |
806
+ | `crop.unit` | `fraction` (default) or `pixel`. Fractions lie in `[0, 1)` (`keep`: `(0, 1]`) and round half up per edge: `floor(f · size + 0.5)` |
807
+ | `crop.multiple_of` | Trim the kept size down to a multiple, e.g. a network stride; the extra rows and columns come off both sides evenly |
808
+ | `resize` | Optional, after the crop: `{"size": [h, w]}` or `{"scale": 0.5}` (or `[sy, sx]`); `interpolation` maps modality names to `nearest`, `bilinear` or `bicubic` |
809
+ | `point_clouds` | `keep` (default): copy every point, consumers clip to the image. `prune_to_crop`: keep only the points with `z > 0` whose pixel `rint(K'·T·X)` lies in the output image, as consumers such as dmd3c-euler project them |
810
+ | `projection` | For `prune_to_crop`: the `intrinsics` and `extrinsics` modalities to project with, when the dataset has more than one candidate |
811
+ | `verify` | `all` (default), `none`, or a sample count: reopen the outputs and check every file against the crop (rasters and pruned clouds as encoded, intrinsics to 1e-3 px, copies byte-identical) |
812
+ | `roles` | Override a modality's kind with `raster` or `copy`, for loaders whose annotation the crop does not know, or to copy an intrinsics file of another camera (e.g. MUSES `sensor: event`) instead of cropping it |
813
+
814
+ Pixel units reproduce the official boxes exactly, e.g. `RGB2Gatedv2` is
815
+ `{"remove": {"top": 202, "bottom": 54, "left": 280, "right": 360}, "unit": "pixel"}`.
816
+ `configs/crop_stf_valid_region_config.json` removes the black border the
817
+ SeeingThroughFog rectification leaves on `_lut` frames (top 34, bottom 46,
818
+ left 16 px), keeping a fully valid 1904×944 image.
819
+
820
+ MUSES keeps the frame camera's `K` and every extrinsic in one `calib.json`,
821
+ indexed as `intrinsics`, `camera_extrinsics` (and `lidar_to_gnss`). The crop
822
+ patches `intrinsics.rgb.K` once and writes that file for every modality that
823
+ indexes it, also when it is indexed as separate byte-identical copies, so the
824
+ bundle holds one `K`. `D`, the event camera and all extrinsics stay as they
825
+ are. `configs/crop_muses_hood_config.json` cuts the engine hood off the
826
+ 1920×1080 frame camera images: the bottom 160 rows, leaving 1920×920. Rows come
827
+ off the bottom only, so `K` and every `calib.json` stay byte-identical. Run
828
+ `lidar-motion` first and crop its output (`configs/crop_muses_dataset_config.json`).
829
+ GNSS records and `meta.json` sensor timing are copied unchanged, like the
830
+ extrinsics. MUSES label images (`gt_semantic`, `gt_panoptic`) are cropped in place like
831
+ any label raster.
832
+
833
+ Label rasters (loader types `semantic_segmentation`, `semantic_segmentation_color`,
834
+ `instance_segmentation`, `panoptic_segmentation`, `sky_mask`) are cropped in the
835
+ file itself: PNG, TIFF and BMP through Pillow in their own mode and palette,
836
+ `.npy`/`.npz` through NumPy. A semantic segmentation is the ground truth for
837
+ every class including the sky, and consumers read it through different
838
+ functions (`class_segmentation`, or `sky_mask` for sky-depth). Re-encoding the
839
+ decoded labels would drop what one function does not return: Real Drive Sim's
840
+ non-red channels, an indexed PNG's palette, or every class but the sky when a
841
+ dataset only has a `sky_mask` function. The in-place crop must decode exactly
842
+ as the crop of the source labels. Files it cannot keep (JPEG, 16-bit colour
843
+ PNGs, ambiguous array layouts) fall back to the writer with a warning. Labels
844
+ are never interpolated: a `resize.interpolation` other than `nearest` for them
845
+ is an error.
846
+
847
+ Other rasters are re-encoded with the dataset's euler-loading writer, so a raster
848
+ modality needs one (SeeingThroughFog's raw `rccb` TIFFs have none). Each
849
+ encoded file is decoded again right away and must give back the cropped values:
850
+ exactly, or for an interpolated resize within half an 8-bit step. A resize
851
+ interpolates colour images (`rgb`, `rccb`) bilinearly and takes the nearest
852
+ pixel for everything else, so depth and label values stay real values;
853
+ sparse-depth rasters are not resized. Pruned point clouds need a writer too
854
+ (`princeton_dense.write_sparse_depth`, `muses.write_sparse_depth`). Intrinsics
855
+ are written by copying the source file and letting its writer move the matrices
856
+ onto the new grid: for a ROS CameraInfo file such as
857
+ `calib_cam_stereo_left.json`, `K` and `P` shift by exact integers, `width` and
858
+ `height` become the crop size, and `D`, `R`, `roi` and the header are kept. A
859
+ calibration file that records another image size than the reference rasters
860
+ is refused. A hierarchical file is written once, so every sample sharing it
861
+ must get the same box; mixed image sizes under one calibration file are an
862
+ error. Every output head records `meta.crop` (reference, spec and boxes), and
863
+ every file its own `attributes.crop` box; pruned clouds also record
864
+ `attributes.point_clouds` (source and kept point counts).
865
+
866
+ To reproduce a crop with `euler-preprocess spatial`, print its pixel box and
867
+ the matching `preprocessing` block for a reference size:
868
+
869
+ ```bash
870
+ euler-preprocess crop-box -c configs/crop_config.json --size 1024 1920
871
+ ```
872
+
873
+ ---
874
+
740
875
  ## License
741
876
 
742
877
  [MIT](LICENSE) © Daniel Rothenpieler
@@ -45,6 +45,8 @@ qualitative review.
45
45
  | `euler-preprocess sky-depth` | Override depth values in sky regions with a constant |
46
46
  | `euler-preprocess radial` | Convert planar (z-buffer) depth to radial (Euclidean) depth |
47
47
  | `euler-preprocess lidar-motion` | Compensate scanning-lidar points for the vehicle's motion during a sweep |
48
+ | `euler-preprocess crop` | Crop RGB, depth, intrinsics and extrinsics coherently |
49
+ | `euler-preprocess crop-box` | Print the pixel box a crop config gives for an image size |
48
50
 
49
51
  ## Installation
50
52
 
@@ -59,6 +61,7 @@ euler-preprocess fog -c configs/example_dataset_config.json
59
61
  euler-preprocess sky-depth -c configs/sky_depth_dataset_config.json
60
62
  euler-preprocess radial -c configs/radial_dataset_config.json
61
63
  euler-preprocess lidar-motion -c configs/lidar_motion_dataset_config.json
64
+ euler-preprocess crop -c configs/crop_stf_dataset_config.json
62
65
  ```
63
66
 
64
67
  ## Dataset Config
@@ -717,6 +720,138 @@ holds the target time and the speed and rates used. Point
717
720
 
718
721
  ---
719
722
 
723
+ ## Coherent Crop
724
+
725
+ `crop` cuts the same box out of every image-shaped modality and corrects
726
+ everything that refers to the pixel grid, so a cropped bundle projects exactly
727
+ like the original:
728
+
729
+ | Modality kind | Crop |
730
+ |---|---|
731
+ | Rasters: RGB, dense depth | Sliced; no resampling, so values are unchanged |
732
+ | Label rasters: semantic segmentation (the ground truth that also holds the sky), instance and panoptic segmentation, sky masks | Cropped in the stored file, so every class, channel, palette and bit depth is kept; resized only by nearest |
733
+ | Pinhole intrinsics `K` (or `P` for rectified images) | `cx -= left`, `cy -= top`; `fx`, `fy`, skew unchanged |
734
+ | Lidar point clouds | Copied byte for byte, or pruned to the points that project into the crop |
735
+ | Extrinsics | Copied byte for byte |
736
+
737
+ The extrinsics need no change, because `C·K·[R|t]·X = K'·[R|t]·X` with
738
+ `C = [[1,0,-left],[0,1,-top],[0,0,1]]`: the whole correction lives in the
739
+ camera matrix, which is also what the official SeeingThroughFog tools do. An
740
+ optional resize afterwards adds `S` (an `align_corners=False` rescale), so
741
+ `K' = S·C·K`, still with untouched extrinsics.
742
+ Which kind a modality is comes from its euler-loading loader annotation
743
+ (`shape`, `type`), never from its name.
744
+
745
+ The dataset config lists every modality to crop; whether a modality is
746
+ per-sample or hierarchical (shared calibration) is inferred from its index:
747
+
748
+ ```json
749
+ {
750
+ "transform_config_path": "crop_stf_valid_region_config.json",
751
+ "output_path": "/data/stf_crop",
752
+ "modalities": {
753
+ "rgb": "/data/stf/cam_stereo_left_lut",
754
+ "sparse_depth": "/data/stf/lidar_hdl64_strongest",
755
+ "intrinsics": "/data/stf/calib_cam_stereo_left",
756
+ "camera_extrinsics": "/data/stf/calib_tf_tree"
757
+ },
758
+ "samples": {"start": 0, "count": 100}
759
+ }
760
+ ```
761
+
762
+ Each modality is written to `<output_path>/<name>`, or to its
763
+ `pipeline.output_targets[]` entry (`slot` = the modality name; directory or zip).
764
+ With pipeline targets, every modality needs one and the outputs manifest lists
765
+ them all. `"output_layout": "scoped"` writes one root at `output_path` instead
766
+ (a directory or `.zip`): one ds-crawler metadata scope per modality, named after
767
+ it, with its files below `<name>/`. That is the layout dmd3c-euler's
768
+ `--dataset-root` reads. The dataset config must not contain `preprocessing`.
769
+
770
+ The crop config (`configs/crop_config.json`) describes the box relative to the
771
+ `reference` raster:
772
+
773
+ ```json
774
+ {
775
+ "reference": "rgb",
776
+ "crop": {"remove": {"top": 0.20, "bottom": 0.05, "left": 0.15, "right": 0.19}},
777
+ "point_clouds": "keep",
778
+ "verify": "all"
779
+ }
780
+ ```
781
+
782
+ | Key | Meaning |
783
+ |---|---|
784
+ | `crop.remove` | What to cut off each edge (`top`, `bottom`, `left`, `right`) |
785
+ | `crop.keep` + `crop.anchor` | The kept `height` / `width`, placed at `center` (default), `top`, `bottom`, `left`, `right`, `top_left`, `top_right`, `bottom_left` or `bottom_right` |
786
+ | `crop.unit` | `fraction` (default) or `pixel`. Fractions lie in `[0, 1)` (`keep`: `(0, 1]`) and round half up per edge: `floor(f · size + 0.5)` |
787
+ | `crop.multiple_of` | Trim the kept size down to a multiple, e.g. a network stride; the extra rows and columns come off both sides evenly |
788
+ | `resize` | Optional, after the crop: `{"size": [h, w]}` or `{"scale": 0.5}` (or `[sy, sx]`); `interpolation` maps modality names to `nearest`, `bilinear` or `bicubic` |
789
+ | `point_clouds` | `keep` (default): copy every point, consumers clip to the image. `prune_to_crop`: keep only the points with `z > 0` whose pixel `rint(K'·T·X)` lies in the output image, as consumers such as dmd3c-euler project them |
790
+ | `projection` | For `prune_to_crop`: the `intrinsics` and `extrinsics` modalities to project with, when the dataset has more than one candidate |
791
+ | `verify` | `all` (default), `none`, or a sample count: reopen the outputs and check every file against the crop (rasters and pruned clouds as encoded, intrinsics to 1e-3 px, copies byte-identical) |
792
+ | `roles` | Override a modality's kind with `raster` or `copy`, for loaders whose annotation the crop does not know, or to copy an intrinsics file of another camera (e.g. MUSES `sensor: event`) instead of cropping it |
793
+
794
+ Pixel units reproduce the official boxes exactly, e.g. `RGB2Gatedv2` is
795
+ `{"remove": {"top": 202, "bottom": 54, "left": 280, "right": 360}, "unit": "pixel"}`.
796
+ `configs/crop_stf_valid_region_config.json` removes the black border the
797
+ SeeingThroughFog rectification leaves on `_lut` frames (top 34, bottom 46,
798
+ left 16 px), keeping a fully valid 1904×944 image.
799
+
800
+ MUSES keeps the frame camera's `K` and every extrinsic in one `calib.json`,
801
+ indexed as `intrinsics`, `camera_extrinsics` (and `lidar_to_gnss`). The crop
802
+ patches `intrinsics.rgb.K` once and writes that file for every modality that
803
+ indexes it, also when it is indexed as separate byte-identical copies, so the
804
+ bundle holds one `K`. `D`, the event camera and all extrinsics stay as they
805
+ are. `configs/crop_muses_hood_config.json` cuts the engine hood off the
806
+ 1920×1080 frame camera images: the bottom 160 rows, leaving 1920×920. Rows come
807
+ off the bottom only, so `K` and every `calib.json` stay byte-identical. Run
808
+ `lidar-motion` first and crop its output (`configs/crop_muses_dataset_config.json`).
809
+ GNSS records and `meta.json` sensor timing are copied unchanged, like the
810
+ extrinsics. MUSES label images (`gt_semantic`, `gt_panoptic`) are cropped in place like
811
+ any label raster.
812
+
813
+ Label rasters (loader types `semantic_segmentation`, `semantic_segmentation_color`,
814
+ `instance_segmentation`, `panoptic_segmentation`, `sky_mask`) are cropped in the
815
+ file itself: PNG, TIFF and BMP through Pillow in their own mode and palette,
816
+ `.npy`/`.npz` through NumPy. A semantic segmentation is the ground truth for
817
+ every class including the sky, and consumers read it through different
818
+ functions (`class_segmentation`, or `sky_mask` for sky-depth). Re-encoding the
819
+ decoded labels would drop what one function does not return: Real Drive Sim's
820
+ non-red channels, an indexed PNG's palette, or every class but the sky when a
821
+ dataset only has a `sky_mask` function. The in-place crop must decode exactly
822
+ as the crop of the source labels. Files it cannot keep (JPEG, 16-bit colour
823
+ PNGs, ambiguous array layouts) fall back to the writer with a warning. Labels
824
+ are never interpolated: a `resize.interpolation` other than `nearest` for them
825
+ is an error.
826
+
827
+ Other rasters are re-encoded with the dataset's euler-loading writer, so a raster
828
+ modality needs one (SeeingThroughFog's raw `rccb` TIFFs have none). Each
829
+ encoded file is decoded again right away and must give back the cropped values:
830
+ exactly, or for an interpolated resize within half an 8-bit step. A resize
831
+ interpolates colour images (`rgb`, `rccb`) bilinearly and takes the nearest
832
+ pixel for everything else, so depth and label values stay real values;
833
+ sparse-depth rasters are not resized. Pruned point clouds need a writer too
834
+ (`princeton_dense.write_sparse_depth`, `muses.write_sparse_depth`). Intrinsics
835
+ are written by copying the source file and letting its writer move the matrices
836
+ onto the new grid: for a ROS CameraInfo file such as
837
+ `calib_cam_stereo_left.json`, `K` and `P` shift by exact integers, `width` and
838
+ `height` become the crop size, and `D`, `R`, `roi` and the header are kept. A
839
+ calibration file that records another image size than the reference rasters
840
+ is refused. A hierarchical file is written once, so every sample sharing it
841
+ must get the same box; mixed image sizes under one calibration file are an
842
+ error. Every output head records `meta.crop` (reference, spec and boxes), and
843
+ every file its own `attributes.crop` box; pruned clouds also record
844
+ `attributes.point_clouds` (source and kept point counts).
845
+
846
+ To reproduce a crop with `euler-preprocess spatial`, print its pixel box and
847
+ the matching `preprocessing` block for a reference size:
848
+
849
+ ```bash
850
+ euler-preprocess crop-box -c configs/crop_config.json --size 1024 1920
851
+ ```
852
+
853
+ ---
854
+
720
855
  ## License
721
856
 
722
857
  [MIT](LICENSE) © Daniel Rothenpieler