apairo-preprocess 0.2.0__py3-none-any.whl

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.
@@ -0,0 +1,181 @@
1
+ """Trajectory-based traversability ground truth.
2
+
3
+ For each scan, a point is labelled traversable (1) if it lies within the
4
+ robot's 2-D footprint projected along any **future** pose — i.e. positions
5
+ the robot has not yet visited.
6
+
7
+ Poses are read from the dataset via ``poses_key`` (declared in
8
+ ``input_keys``). The full trajectory is available in ``__call__`` because
9
+ :class:`SequencePreprocessor` receives an iterator over all frames at once.
10
+
11
+ Poses must be ``(4, 4)`` float64 homogeneous matrices. Apply
12
+ ``ds = ds.transform(poses_key, PoseTo4x4())`` from ``apairo_transform`` beforehand
13
+ if your poses are stored in another format (quaternion, Euler, compact 3×4).
14
+
15
+ Typical usage::
16
+
17
+ Goose3DDataset.run_preprocess(
18
+ TraversabilityFromTrajectory(poses_key="kissicp_poses", robot_radius=0.75),
19
+ split_dir,
20
+ )
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from typing import ClassVar, Iterator
26
+
27
+ import numpy as np
28
+ from scipy.spatial import KDTree
29
+
30
+ from apairo.core.preprocessor import SequencePreprocessor
31
+ from apairo.core.sample import Sample
32
+ from apairo_transform import RangeFilter
33
+
34
+ class TraversabilityFromTrajectory(SequencePreprocessor):
35
+ """Label each point traversable if it lies in the robot's forward footprint.
36
+
37
+ Poses and point clouds are loaded from the dataset via ``input_keys``.
38
+ Sequence boundary detection and look-ahead are computed in ``__call__``
39
+ where the full trajectory is available.
40
+
41
+ Args:
42
+ lidar_key: Input channel for point cloud data.
43
+ poses_key: Input channel for per-frame poses — must be ``(4, 4)``
44
+ float64. Apply ``PoseTo4x4()`` from ``apairo_transform``
45
+ if needed.
46
+ robot_radius: Half-width of the robot footprint in XY (metres).
47
+ height_min: Minimum point height relative to the nearest robot
48
+ position to be traversable (metres, ≤ 0).
49
+ height_max: Maximum point height relative to the nearest robot
50
+ position (metres, ≥ 0).
51
+ forward_window: Maximum number of future poses to look ahead.
52
+ ``None`` (default) uses the entire remaining trajectory.
53
+ forward_distance: Maximum *path length* (metres) of future trajectory
54
+ to consider. Caps the look-ahead where the path
55
+ loops back near already-seen terrain: without it, a
56
+ point can be labelled traversable because a much
57
+ later part of the trajectory passes nearby.
58
+ ``None`` (default) does not cap by distance.
59
+ sequence_gap: Distance threshold (metres) to detect sequence
60
+ boundaries and avoid look-ahead across discontinuous
61
+ sessions.
62
+ near_exclusion_radius: Sensor-frame distance below which a point is never
63
+ labelled traversable, suppressing dynamic objects
64
+ (e.g. humans) close to the robot. The shape of the
65
+ exclusion zone is controlled by ``near_exclusion_norm``.
66
+ ``0.0`` disables the exclusion (default).
67
+ near_exclusion_norm: Norm order for the exclusion distance, forwarded to
68
+ :class:`~apairo_transform.RangeFilter`. Use
69
+ ``np.inf`` (L∞) for a cube, ``2`` for a sphere.
70
+ Defaults to ``np.inf``.
71
+ output_key: Override the default output channel name ``"trav_traj"``.
72
+ """
73
+
74
+ output_key: ClassVar[str] = "trav_traj"
75
+ input_keys: ClassVar[list[str]] = ["lidar", "poses"]
76
+ timestamps_from: ClassVar[str] = "lidar"
77
+ sources: ClassVar[list[str]] = ["lidar", "poses"]
78
+
79
+ def __init__(
80
+ self,
81
+ lidar_key: str = "lidar",
82
+ poses_key: str = "poses",
83
+ robot_radius: float = 0.75,
84
+ height_min: float = -0.3,
85
+ height_max: float = 0.5,
86
+ forward_window: int | None = None,
87
+ forward_distance: float | None = None,
88
+ sequence_gap: float = 5.0,
89
+ near_exclusion_radius: float = 0.0,
90
+ near_exclusion_norm: float = np.inf,
91
+ output_key: str | None = None,
92
+ ) -> None:
93
+ self._lidar_key = lidar_key
94
+ self._poses_key = poses_key
95
+ self._robot_radius = robot_radius
96
+ self._height_min = height_min
97
+ self._height_max = height_max
98
+ self._forward_window = forward_window
99
+ self._forward_distance = forward_distance
100
+ self._sequence_gap = sequence_gap
101
+ self._near_filter = (
102
+ RangeFilter(min=near_exclusion_radius, max=None, norm=near_exclusion_norm)
103
+ if near_exclusion_radius > 0
104
+ else None
105
+ )
106
+
107
+ self.input_keys = [lidar_key, poses_key]
108
+ self.sources = [lidar_key, poses_key]
109
+ if output_key is not None:
110
+ self.output_key = output_key
111
+
112
+ def __call__(self, frames: Iterator[Sample]) -> np.ndarray:
113
+ all_samples = list(frames)
114
+ n = len(all_samples)
115
+
116
+ poses = np.asarray(
117
+ np.stack([s.data[self._poses_key] for s in all_samples]), dtype=np.float64
118
+ )
119
+ if poses.shape[1:] != (4, 4):
120
+ raise ValueError(
121
+ f"poses must be (N, 4, 4) — apply PoseTo4x4() from apairo_transform first. "
122
+ f"Got {poses.shape}"
123
+ )
124
+
125
+ # Sequence boundary detection — done here where poses are available.
126
+ positions = poses[:, :3, 3]
127
+ dists = np.linalg.norm(np.diff(positions, axis=0), axis=1)
128
+ boundaries = np.where(dists > self._sequence_gap)[0]
129
+ ends = np.concatenate([boundaries, [n - 1]])
130
+ seq_end = ends[np.searchsorted(ends, np.arange(n))]
131
+
132
+ # Cumulative path length, for the forward_distance cap.
133
+ path = np.concatenate([[0.0], np.cumsum(dists)])
134
+
135
+ results = []
136
+ for idx, sample in enumerate(all_samples):
137
+ pc = np.asarray(sample.data[self._lidar_key])
138
+ xyz_sensor = pc[:, :3].astype(np.float64)
139
+ n_pts = len(xyz_sensor)
140
+
141
+ T = poses[idx]
142
+ xyz_h = np.column_stack([xyz_sensor, np.ones(n_pts)])
143
+ xyz_world = (T @ xyz_h.T).T[:, :3]
144
+
145
+ end = int(seq_end[idx]) + 1
146
+ if self._forward_window is not None:
147
+ end = min(end, idx + 1 + self._forward_window)
148
+ if self._forward_distance is not None:
149
+ end = min(
150
+ end,
151
+ int(np.searchsorted(path, path[idx] + self._forward_distance, side="right")),
152
+ )
153
+ future_pos = poses[idx + 1 : end, :3, 3]
154
+
155
+ if len(future_pos) == 0:
156
+ results.append(np.zeros(n_pts, dtype=np.uint8))
157
+ continue
158
+
159
+ tree = KDTree(future_pos[:, :2])
160
+ # workers=-1 deadlocks when called from a ThreadPoolExecutor (e.g. viewer)
161
+ dist_xy, nn_idx = tree.query(xyz_world[:, :2], k=1)
162
+ dz = xyz_world[:, 2] - future_pos[nn_idx, 2]
163
+
164
+ trav = (
165
+ (dist_xy < self._robot_radius)
166
+ & (dz >= self._height_min)
167
+ & (dz <= self._height_max)
168
+ )
169
+ if self._near_filter is not None:
170
+ trav &= self._near_filter.compute_mask(xyz_sensor)
171
+
172
+ results.append(trav.astype(np.uint8))
173
+
174
+ # Uniform scans stack to (N, P) as before; ragged scans (real lidar,
175
+ # variable return count) become an object array of per-frame rows,
176
+ # which the per-frame runner and direct callers iterate identically.
177
+ if len({len(r) for r in results}) <= 1:
178
+ return np.stack(results)
179
+ out = np.empty(n, dtype=object)
180
+ out[:] = results
181
+ return out
@@ -0,0 +1,255 @@
1
+ Metadata-Version: 2.4
2
+ Name: apairo-preprocess
3
+ Version: 0.2.0
4
+ Summary: Preprocessing for apairo datasets: odometry, ground segmentation, projection, traversability labels
5
+ Author-email: Augustin Bresset <augustin.bresset@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://apairo-robotics.github.io/
8
+ Project-URL: Repository, https://github.com/apairo-robotics/apairo_preprocess
9
+ Project-URL: Issues, https://github.com/apairo-robotics/apairo_preprocess/issues
10
+ Keywords: apairo,lidar,point-cloud,traversability,ground-segmentation,robotics
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: Topic :: Scientific/Engineering
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: numpy>=1.26
23
+ Requires-Dist: scipy
24
+ Requires-Dist: pyyaml
25
+ Requires-Dist: tqdm
26
+ Requires-Dist: cloth-simulation-filter
27
+ Requires-Dist: apairo>=0.9
28
+ Requires-Dist: apairo-transform>=0.3
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # apairo-preprocess
34
+
35
+ Preprocessing pipelines for [apairo](https://github.com/apairo-robotics/apairo) datasets — LiDAR odometry, ground segmentation, camera projection, and traversability ground truth generation.
36
+
37
+ ---
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install apairo-preprocess
43
+ ```
44
+
45
+ It brings `apairo` (≥ 0.9) and `apairo-transform` (≥ 0.3).
46
+
47
+ ### Optional dependencies
48
+
49
+ | Preprocessor | Dependency | Install |
50
+ |---|---|---|
51
+ | `KissICPOdometry` | KISS-ICP | `pip install kiss-icp` |
52
+ | `GICPOdometry` | Open3D | `pip install open3d` |
53
+ | `GroundSegmentationCSF` / `GroundHeightFromLabels` (CSF backend) | CSF | `pip install cloth-simulation-filter` |
54
+
55
+ Requires Python ≥ 3.11.
56
+
57
+ ---
58
+
59
+ ## Preprocessors
60
+
61
+ ### Odometry
62
+
63
+ | Class | Output channel | Backend | Output |
64
+ |---|---|---|---|
65
+ | `KissICPOdometry` | `kissicp_poses` | [KISS-ICP](https://github.com/PRBonn/kiss-icp) | `(4, 4)` float64 pose per scan |
66
+ | `GICPOdometry` | `gicp_poses` | Open3D GICP | `(4, 4)` float64 pose per scan |
67
+
68
+ ### Ground segmentation
69
+
70
+ Binary ground/non-ground labels (0 = ground, 1 = non-ground). All three algorithms share the same label convention and can be compared directly.
71
+
72
+ | Class | Output channel | Method | Extra dep |
73
+ |---|---|---|---|
74
+ | `GroundSegmentationCSF` | `ground_csf` | Cloth Simulation Filter — accurate on uneven terrain | `CSF` |
75
+ | `GroundSegmentationRANSAC` | `ground_ransac` | RANSAC plane fitting — fast, assumes flat ground | — |
76
+ | `GroundSegmentationFromLabels` | `ground_labels` | Maps existing semantic class IDs to ground/non-ground | — |
77
+
78
+ ### Priors
79
+
80
+ Scalar per-point features used as traversability priors. Depend on previously computed channels.
81
+
82
+ | Class | Output channel | Input channels | Output |
83
+ |---|---|---|---|
84
+ | `GroundHeightFromLabels` | `ground_height` | any ground segmentation + `voxelised` | float32 height above nearest ground point (m) |
85
+ | `TrajectoryDistance` | `trajectory_distance` | `voxelised` + `poses` | float32 distance to nearest trajectory waypoint (m) |
86
+
87
+ ### Traversability
88
+
89
+ Binary traversability labels (1 = traversable, 0 = not).
90
+
91
+ | Class | Output channel | Method |
92
+ |---|---|---|
93
+ | `TraversabilityFromLabels` | `trav_label` | Maps semantic class IDs to binary traversable/non-traversable |
94
+ | `TraversabilityFromTrajectory` | `trav_traj` | Labels points inside the robot's forward footprint along the trajectory |
95
+
96
+ ### Camera projection
97
+
98
+ Atomic bridges between the point cloud and image spaces. All outputs are
99
+ **row-aligned** with the lidar scan, so any per-point channel composes with
100
+ the projection by row index.
101
+
102
+ | Class | Output channel | Input channels | Output |
103
+ |---|---|---|---|
104
+ | `LidarCameraProjection` | `lidar_uv` | lidar only (extrinsics/intrinsics are static) | `(N, 3)` float32 `[u, v, depth]`, NaN outside the frustum |
105
+ | `PointFeaturesFromImage` | `point_features` | `lidar_uv` + image | `(N, C)` image values per point (e.g. RGB) — image → cloud |
106
+ | `ImageMaskFromPointLabels` | `trav_mask` | `trav_traj` + `lidar_uv` | `(H, W)` uint8 label mask, 255 = no data — cloud → image |
107
+
108
+ Pixel conflicts resolve to the nearest point (depth ordering); an optional
109
+ occlusion filter (`occlusion_bin_px`) drops points hidden behind nearer
110
+ returns so ground behind an obstacle does not bleed onto its pixels.
111
+ Both halves of the projection come from the dataset calibration: the
112
+ extrinsic via `ds.calibration.get_tf(lidar_frame, camera_frame)`, the
113
+ intrinsics via `ds.calibration.get_intrinsics(camera_frame)`.
114
+
115
+ **Asynchronous datasets:** `LidarCameraProjection` streams a single channel
116
+ and runs anywhere. The two multi-channel preprocessors run via
117
+ `run_preprocess` on synchronous (profiled) datasets only; on an async dataset
118
+ (TartanDrive, raw rigs) run them over a `synchronize()` view and persist with
119
+ `ChannelWriter` — see
120
+ [`examples/traversability_image_mask.py`](examples/traversability_image_mask.py).
121
+
122
+ ---
123
+
124
+ ## Quickstart
125
+
126
+ ### Ground segmentation
127
+
128
+ ```python
129
+ from apairo.dataset.rellis import Rellis3DDataset
130
+ from apairo_preprocess import GroundSegmentationCSF, GroundSegmentationFromLabels, GroundSegmentationRANSAC
131
+
132
+ dataset_dir = "/data/Rellis-3D/00000"
133
+
134
+ Rellis3DDataset.run_preprocess(GroundSegmentationRANSAC(), dataset_dir)
135
+ Rellis3DDataset.run_preprocess(GroundSegmentationCSF(), dataset_dir) # requires: pip install CSF
136
+
137
+ # From existing semantic labels (default ground IDs are for RELLIS-3D)
138
+ Rellis3DDataset.run_preprocess(GroundSegmentationFromLabels(), dataset_dir)
139
+
140
+ # writes ground_ransac/, ground_csf/, ground_labels/ (uint8: 0=ground, 1=non-ground)
141
+ ```
142
+
143
+ ### Height above ground (prior)
144
+
145
+ Ground segmentation must be computed first.
146
+
147
+ ```python
148
+ from apairo_preprocess import GroundSegmentationCSF, GroundHeightFromLabels
149
+
150
+ Rellis3DDataset.run_preprocess(GroundSegmentationCSF(), dataset_dir)
151
+ Rellis3DDataset.run_preprocess(
152
+ GroundHeightFromLabels(ground_key="ground_csf"),
153
+ dataset_dir,
154
+ )
155
+ # writes ground_height/ (float32, metres above nearest ground point)
156
+ ```
157
+
158
+ `GroundHeightFromLabels` accepts any ground key — swap `"ground_csf"` for `"ground_ransac"` or `"ground_labels"` to change the backend without re-running CSF.
159
+
160
+ ### Traversability from semantic labels
161
+
162
+ ```python
163
+ from apairo_preprocess import TraversabilityFromLabels
164
+
165
+ # Default traversable IDs for RELLIS-3D: {dirt, grass, asphalt, concrete, puddle, mud}
166
+ Rellis3DDataset.run_preprocess(TraversabilityFromLabels(), dataset_dir)
167
+ # writes trav_label/ (uint8: 1=traversable, 0=not)
168
+ ```
169
+
170
+ Custom IDs for SemanticKITTI:
171
+
172
+ ```python
173
+ from apairo.dataset.semantic_kitti import SemanticKittiDataset
174
+ from apairo_preprocess import TraversabilityFromLabels
175
+
176
+ SemanticKittiDataset.run_preprocess(
177
+ TraversabilityFromLabels(traversable_ids=frozenset({40, 44, 48, 49, 60, 72})),
178
+ "/data/sequences/00",
179
+ )
180
+ ```
181
+
182
+ ### Traversability ground truth from trajectory
183
+
184
+ Requires poses to be computed first (e.g. with `KissICPOdometry`).
185
+
186
+ ```python
187
+ from apairo.dataset.goose import Goose3DDataset
188
+ from apairo_preprocess import KissICPOdometry, TraversabilityFromTrajectory
189
+
190
+ Goose3DDataset.run_preprocess(KissICPOdometry(voxel_size=1.0), "/data/goose/seq_001")
191
+ Goose3DDataset.run_preprocess(
192
+ TraversabilityFromTrajectory(poses_key="kissicp_poses", robot_radius=0.75),
193
+ "/data/goose/seq_001",
194
+ )
195
+ # writes trav_traj/ (uint8: 1=traversable, 0=not)
196
+ ```
197
+
198
+ ### Traversability mask in image space
199
+
200
+ Projects the trajectory ground truth into the camera: `trav_traj` labels the
201
+ points, `lidar_uv` places them in the image, `ImageMaskFromPointLabels`
202
+ paints the mask.
203
+
204
+ ```python
205
+ from apairo_preprocess import ImageMaskFromPointLabels, LidarCameraProjection
206
+
207
+ cal = Rellis3DDataset(dataset_dir, keys=["lidar"]).calibration
208
+ cam = cal.get_intrinsics("camera") # K, distortion, image size
209
+ T = cal.get_tf("lidar", "camera") # lidar -> camera optical frame
210
+
211
+ Rellis3DDataset.run_preprocess(
212
+ LidarCameraProjection(intrinsics=cam, extrinsics=T),
213
+ dataset_dir,
214
+ )
215
+ Rellis3DDataset.run_preprocess(
216
+ ImageMaskFromPointLabels(image_size=(cam.height, cam.width), radius=2, occlusion_bin_px=8),
217
+ dataset_dir,
218
+ )
219
+ # writes lidar_uv/ (float32 [u, v, depth] per point)
220
+ # and trav_mask/ (uint8 per pixel: 1=traversable, 0=not, 255=no data)
221
+ ```
222
+
223
+ To sanity-check calibration and projection first, colour the cloud with
224
+ camera RGB and inspect it in a 3-D viewer (projector, apairo_rr):
225
+
226
+ ```python
227
+ from apairo_preprocess import PointFeaturesFromImage
228
+
229
+ Rellis3DDataset.run_preprocess(
230
+ PointFeaturesFromImage(image_key="image", output_key="lidar_rgb"),
231
+ dataset_dir,
232
+ )
233
+ ```
234
+
235
+ ---
236
+
237
+ ## Examples
238
+
239
+ Ready-to-run scripts in [`examples/`](examples/):
240
+
241
+ ```bash
242
+ python examples/kissicp_odometry.py /data/Rellis-3D/00000 --dataset rellis
243
+ python examples/traversability_from_labels.py /data/Rellis-3D/00000
244
+ python examples/traversability_from_trajectory.py /data/goose/seq_001
245
+ python examples/traversability_image_mask.py /data/tartan/seq --lidar-frame velodyne --camera-frame multisense_left --rgb
246
+
247
+ # no dataset needed — synthetic scene, renders every pipeline stage (needs matplotlib)
248
+ python examples/projection_pipeline_demo.py --out-dir /tmp/projection_demo
249
+ ```
250
+
251
+ ---
252
+
253
+ ## License
254
+
255
+ MIT
@@ -0,0 +1,27 @@
1
+ apairo_preprocess/__init__.py,sha256=wEAn4dD45rqJAFfaWqEqk8UCjiR8bNpZ7ca5Fqv4DRQ,1133
2
+ apairo_preprocess/ground/__init__.py,sha256=3UvuSZhAdVvUqZZjFFNLH90LBs-ZSmhLmEXl1x2bjq4,322
3
+ apairo_preprocess/ground/csf.py,sha256=6DmUABDq6Of4Wi1JHdCAE8OH3iaYA9CNKdqVbkq5zfo,3157
4
+ apairo_preprocess/ground/from_labels.py,sha256=Bcj8bpB6_mRf_9MnYKx5-7Zl8lFhAyaAWvreRl8BFfs,1911
5
+ apairo_preprocess/ground/ransac.py,sha256=IlhK4oeo7JJSm_xe1SRcs1cSOcg-0tGdZ1XAh4VjUKo,3115
6
+ apairo_preprocess/odometry/__init__.py,sha256=erzuos15IRNHoBm0q3Hf-IqvP3lpnSLKNc5doKX2lks,167
7
+ apairo_preprocess/odometry/gicp.py,sha256=RM3NQjSTJMGpe9p8bPPVbubdTMk6HHtnIwVcHRnvTgk,3337
8
+ apairo_preprocess/odometry/kissicp.py,sha256=pqnbmmZ4Q7J2cYUOEP_RYZux851A_4roMMuZcjX_Lao,2884
9
+ apairo_preprocess/pointcloud/__init__.py,sha256=0p975u6q-KfblA6CJwI1U7sfsSDZVD35Gie1M7PQwP8,264
10
+ apairo_preprocess/pointcloud/robot_filter.py,sha256=ckh8eTE_0FtJNn-QEQHq_jPF1B8WAFJPUaZp-tXRGDU,4388
11
+ apairo_preprocess/pointcloud/voxelise.py,sha256=IVuzNjNbNMhZbJmlHeBZaF9yHcQTEx9IXMhchk_SnQo,10861
12
+ apairo_preprocess/priors/__init__.py,sha256=3_s47SMwLczKFM7LhO43wtW77Db-ZGBSXwPWANkcatI,221
13
+ apairo_preprocess/priors/ground_height.py,sha256=SKbVN0Yz5gYx5f9imOSTxXSmi2QU9tzTx61__XWKvd4,2264
14
+ apairo_preprocess/priors/trajectory_distance.py,sha256=4FmNxJ4KX-rl8iacuYPofWsYAQQOehMUv9iYscXWqxM,3576
15
+ apairo_preprocess/projection/__init__.py,sha256=RWy_75qtgOtvNQqGAdp4rMrstAvVTLe5tSaNw8UYUHY,465
16
+ apairo_preprocess/projection/bev.py,sha256=PUyc9HyU9UkVvHBjgwm5wRA7wpiT47fuP8oc75UdcCc,6711
17
+ apairo_preprocess/projection/image_mask.py,sha256=vH8TxwVbt5mPCTM07S6E0wQeX0YooF-qGoOaqREN3s0,7712
18
+ apairo_preprocess/projection/lidar_to_camera.py,sha256=oC-ZWEc5lJGFtTSSTN1sAmKflGU9_dMJrz5zKA0XXlM,7350
19
+ apairo_preprocess/projection/point_features.py,sha256=sjpJT-JMtHaZRkq-F0xNGucFZbNlq3of-4PlWm8oONo,4014
20
+ apairo_preprocess/traversability/__init__.py,sha256=PJXnR-IFpylesJBP3uJJhVae51yRPsF70R_tvvAIifk,255
21
+ apairo_preprocess/traversability/from_labels.py,sha256=Ko-Oa-xJgbYux61BbPcNeXJ-YiwgKJY9mP7o7_rkfew,1979
22
+ apairo_preprocess/traversability/from_trajectory.py,sha256=JhXbRCltlWGQ6GP08ErNP09305ILowzAZXoiCRtxbb8,7967
23
+ apairo_preprocess-0.2.0.dist-info/licenses/LICENSE,sha256=17AGWvla1McJcPrt-Tyos8l2V1W4rgNJiRujo1lKzTY,1073
24
+ apairo_preprocess-0.2.0.dist-info/METADATA,sha256=IQU6Q_SdQeWjlCOamkGucx-jSuqLTNUPd3-TAZNFBZY,9434
25
+ apairo_preprocess-0.2.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
26
+ apairo_preprocess-0.2.0.dist-info/top_level.txt,sha256=WT1v0QlQpiFhHPUuIHrZwbVGU2zxkvwUwSl19clk8WA,18
27
+ apairo_preprocess-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Augustin BRESSET
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ apairo_preprocess