Skip to content

Commit b94e59e

Browse files
committed
Merge branch 'master' into CT_ICP
2 parents 3eed79f + d6be208 commit b94e59e

23 files changed

+284
-189
lines changed

README.md

+23-14
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
This codebase proposes modular light python and pytorch implementations of several LiDAR Odometry methods,
44
which can easily be evaluated and compared on a set of public Datasets.
5+
![pyLiDAR-SLAM LiDAR Odometry modules](docs/data/overview.png)
56

67
It heavily relies on [omegaconf](https://omegaconf.readthedocs.io/en/2.0_branch/) and [hydra](https://hydra.cc/),
78
which allows us to easily test the different modules and parameters with few but structured configuration files.
@@ -36,8 +37,10 @@ The goal for the future is to gradually add functionalities to pyLIDAR-SLAM (Loo
3637
├─ models # PoseNet model code
3738
├─ odometry # Odometry modules
3839
├─ training # Modules for training PoseNet models
39-
├─ backend # Work in Progress
40-
├─ loop_closure # Work in Progress
40+
├─ initialization.py # Modules for Initial motion estimate
41+
├─ preprocessing.py # Modules for Preprocessing the point cloud
42+
├─ backend.py # Work in Progress
43+
├─ loop_closure.py # Work in Progress
4144
└─ viz # Tools for visualization
4245

4346
├─ tests
@@ -73,9 +76,9 @@ The configuration hierarchy in this project follows the hierarchy of folder `con
7376

7477
>- **Group** [`dataset`](slam/dataset/dataset.py):
7578
> - configurations: [`kitti`](slam/dataset/kitti_dataset.py), [`nclt`](slam/dataset/nclt_dataset.py), [`ford_campus`](slam/dataset/ford_campus.py)
76-
>- **Group** [`odometry/initialization`](slam/odometry/initialization.py) (Initialization module for the Frame To Model):
77-
> - configurations: [`EI`](slam/odometry/initialization.py), [`CV`](slam/odometry/initialization.py), [`PoseNet`](slam/odometry/initialization.py)
78-
>- **Group** [`odometry/local_map`](slam/odometry/local_map.py) (The local map Implementation):
79+
>- **Group** [`slam/initialization`](slam/initialization.py) (Initialization module for the Frame To Model):
80+
> - configurations: [`EI`](slam/initialization.py), [`CV`](slam/initialization.py), [`PoseNet`](slam/initialization.py)
81+
>- **Group** [`slam/odometry/local_map`](slam/odometry/local_map.py) (The local map Implementation):
7982
> - configurations [`projective`](slam/odometry/local_map.py), [`kdtree`](slam/odometry/local_map.py),
8083
8184
```bash
@@ -89,13 +92,19 @@ The configuration hierarchy in this project follows the hierarchy of folder `con
8992
├─ odometry
9093
├─ alignment # Alignment Group (will be expended in the future)
9194
└─ point_to_plane_GN.yaml # Point-to-Plane alignment for the Frame-to-Model
92-
├─ initialization # Initialization Group
93-
├─ CV.yaml # Configuration for the constant velocity model
94-
├─ PoseNet.yaml # Configuration for using PoseNet as initialization
95-
└─ EI.yaml # Elevation Image 2D registration configuration
9695
└─ local_map # The Local Map Group
9796
├─ projective # The projective Frame-to-Model proposed
98-
└─ kdtree # The kdtree based Frame-to-Model alignemnt
97+
└─ kdtree # The kdtree based Frame-to-Model alignmeeent
98+
├─ preprocessing # Preprocessing Group
99+
├─ grid_sample
100+
├─ voxelization
101+
...
102+
103+
├─ initialization # Initialization Group
104+
├─ CV.yaml # Configuration for the constant velocity model
105+
├─ PoseNet.yaml # Configuration for using PoseNet as initialization
106+
└─ EI.yaml # Elevation Image 2D registration configuration
107+
99108
├─ loop_closure # Loop Closure Group
100109
└─ backend # Backend Group
101110

@@ -156,7 +165,7 @@ export KITTI_ODOM_ROOT=<path-to-kitti-odometry-root-directory> # The path to
156165

157166
# Run the script
158167
python run.py dataset=kitti num_workers=4 device=cuda:0 slam/odometry/local_map=projective \
159-
slam/odometry/initialization=CV slam.odometry.local_map.num_neighbors_normals=15
168+
slam/initialization=CV slam.odometry.local_map.num_neighbors_normals=15
160169
```
161170

162171
The output files (configuration files, logs, and optionally metrics on the trajectory) will be saved by default at location :
@@ -209,9 +218,9 @@ In [benchmark.md](docs/benchmark.md) we present the results of *pyLIDAR-SLAM* on
209218

210219
### System Tested
211220

212-
| OS | CUDA | pytorch | python |
213-
| --- | --- | --- | --- |
214-
| Ubuntu 18.04 | 10.2 | 1.7.1 | 3.8.8 |
221+
| OS | CUDA | pytorch | python | hydra |
222+
| --- | --- | --- | --- | --- |
223+
| Ubuntu 18.04 | 10.2 | 1.7.1 | 3.8.8 | 1.0 |
215224

216225
### Author
217226
This is a work realised in the context of Pierre Dellenbach PhD thesis under supervision of [Bastien Jacquet](https://www.linkedin.com/in/bastienjacquet/?originalSubdomain=fr) ([Kitware](https://www.kitware.com/computer-vision/)),

config/slam.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ defaults:
2424
- slam/odometry: icp_odometry
2525
- dataset: kitti
2626
- slam/preprocessing: none
27+
- slam/initialization: CV
2728
- slam/loop_closure: none
2829
- slam/backend: none
2930
- hydra/output: slam
File renamed without changes.
File renamed without changes.

config/slam/preprocessing/cv_distortion.yaml

-11
This file was deleted.
+11-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
# @package _group_
22

33
filters:
4+
45
"1":
6+
filter_name: distortion
7+
force: false # Whether to fail if timestamps cannot be found
8+
activate: true # Distortion is only activated if this activate=true
9+
pointcloud_key: numpy_pc # The point cloud key in the dict
10+
timestamps_key: numpy_pc_timestamps # The timestamps key in the dict
11+
output_key: distorted
12+
13+
"2":
514
filter_name: grid_sample
615
voxel_size: 0.3
16+
pointcloud_key: distorted
717

8-
"2":
18+
"3":
919
filter_name: to_tensor
1020
keys:
1121
sample_points: input_data

docs/toolbox.md

+7-7
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ For each component, multiple algorithms are implemented, (more will be added in
2626
> - **EI** (for Elevation Images): the motion is predicted using a 2D image feature based registration.
2727
> - **NI** (for No Initialization)
2828
29-
> To select the initialization module, pass in argument `slam/odometry/initialization=<init_name>` where `init_name` is one of `CV,EI,PoseNet,NI`.
29+
> To select the initialization module, pass in argument `slam/initialization=<init_name>` where `init_name` is one of `CV,EI,PoseNet,NI`.
3030
31-
> All initialization models can be found in [initialization.py](../slam/odometry/initialization.py).
31+
> All initialization models can be found in [initialization.py](../slam/initialization.py).
3232
#### Constant Velocity
3333
> This model is typically the default model for a LiDAR mounted on a car (which has enough inertia for this model to hold).
3434
> For other acquisition methods (hand-held or mounted on a segway or a drone which lead to abrupt rotations, this might not be a good enough model)
3535
36-
- **Command arguments**: `slam/odometry/initialization=CV`
36+
- **Command arguments**: `slam/initialization=CV`
3737

3838
#### Elevation Images
3939
> The relative motion of a sensor is often strictly planar. Our **Elevation Images** based initializes the motion by estimating this 2D motion using Image Registration.
@@ -48,9 +48,9 @@ For each component, multiple algorithms are implemented, (more will be added in
4848
4949
> **Note:** This method requires the OpenCV package `cv2` to be installed (e.g. via `pip install opencv-python`).
5050
>
51-
- **Command arguments**: `slam/odometry/initialization=EI` (to visualize the 2D alignments with opencv :`slam.odometry.initialization.debug=true`)
51+
- **Command arguments**: `slam/initialization=EI` (to visualize the 2D alignments with opencv :`slam.odometry.initialization.debug=true`)
5252

53-
> See [`EIConfig`](../slam/odometry/initialization.py) and [`ElevationImageRegistration`](../slam/common/registration.py) for more details on the configuration options.
53+
> See [`EIConfig`](../slam/initialization.py) and [`ElevationImageRegistration`](../slam/common/registration.py) for more details on the configuration options.
5454
5555
#### PoseNet
5656

@@ -80,7 +80,7 @@ export KITTI_ODOM_ROOT=<path-to-kitti-odometry-root-directory> # The path to
8080
export TRAIN_DIR=<absolute-path-to-the-desired-train-dir> # Path to the output models
8181

8282
# Launches the Training of PoseNet
83-
python run.py +device=cuda:0 +num_workers=4 dataset=kitti slam/odometry/initialization=PoseNet
83+
python run.py +device=cuda:0 +num_workers=4 dataset=kitti slam/initialization=PoseNet
8484
```
8585

8686
> See our paper [What's In My LiDAR Odometry Toolbox](https://arxiv.org/abs/2103.09708) for an in depth discussion of the relevance/interest of PoseNet. To summarize: beware of its lack of generalization capacities.
@@ -95,7 +95,7 @@ python run.py +device=cuda:0 +num_workers=4 dataset=kitti slam/odometry/initiali
9595
> - **none** by default
9696
9797

98-
> **Command argument**: `slam/preprocessing=<module name>`. See [preprocessing.py](../slam/preprocessing/preprocessing.py) for more details.
98+
> **Command argument**: `slam/preprocessing=<module name>`. See [preprocessing.py](../slam/preprocessing.py) for more details.
9999
100100
# Odometry
101101

File renamed without changes.

slam/backend/__init__.py

-1
This file was deleted.

slam/dataset/kitti_360_dataset.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,7 @@ def __getitem__(self, idx) -> dict:
176176
data_dict = {}
177177

178178
xyz_r = kitti_read_scan(str(self.lidar_path / f"{idx:010}.bin"))
179-
data_dict["numpy_pc"] = self._correct_scan(xyz_r[:, :3]) # xyz_r[:, :3]
180-
179+
data_dict["numpy_pc"] = KITTIOdometrySequence.correct_scan(xyz_r[:, :3])
181180
data_dict["numpy_reflectance"] = xyz_r[:, 3:]
182181
data_dict["numpy_pc_timestamps"] = estimate_timestamps(xyz_r[:, :3], phi_0=np.pi, clockwise=True)
183182
if self.gt_poses is not None:

slam/dataset/kitti_dataset.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ def __len__(self):
199199
return self.size
200200

201201
@staticmethod
202-
def _correct_scan(scan: np.ndarray):
202+
def correct_scan(scan: np.ndarray):
203203
"""
204204
Corrects the calibration of KITTI's HDL-64 scan
205205
"""
@@ -242,7 +242,7 @@ def __getitem__(self, idx) -> dict:
242242
assert_debug(scan_path.exists() and scan_path.is_file(), f"The file {scan_path} does not exist")
243243
scan = kitti_read_scan(str(scan_path))
244244
# Apply Rectification on the scan
245-
scan = self._correct_scan(scan)
245+
scan = self.correct_scan(scan)
246246
if self._with_numpy_pc:
247247
data_dict["numpy_pc"] = scan.copy()
248248
scan = torch.from_numpy(scan[:, :3]).unsqueeze(0)

slam/dataset/rosbag_dataset.py

+27-19
Original file line numberDiff line numberDiff line change
@@ -59,36 +59,42 @@ class RosbagDataset(IterableDataset):
5959
topic_mapping (dict): The mapping topic name to key in the data_dict
6060
"""
6161

62+
def _lazy_initialization(self):
63+
if not self.initialized:
64+
logging.info(f"[RosbagDataset]Loading ROSBAG {self.file_path}. May take some time")
65+
self.rosbag = rosbag.Bag(self.file_path, "r")
66+
logging.info(f"Done.")
67+
68+
topic_info = self.rosbag.get_type_and_topic_info()
69+
for topic in self.topic_mapping:
70+
assert_debug(topic in topic_info.topics,
71+
f"{topic} is not a topic of the rosbag "
72+
f"(existing topics : {list(topic_info.topics.keys())}")
73+
self._len = self.rosbag.get_message_count(self.main_topic) // self._frame_size
74+
self.initialized = True
75+
6276
def __init__(self, config: RosbagConfig, file_path: str, main_topic: str, frame_size: int,
6377
topic_mapping: Optional[dict] = None):
6478
self.config = config
6579
self.rosbag = None
80+
self.initialized = False
6681
assert_debug(Path(file_path).exists(), f"The path to {file_path} does not exist.")
67-
logging.info(f"Loading ROSBAG {file_path}")
68-
self.rosbag = rosbag.Bag(file_path, "r")
69-
logging.info(f"Done.")
70-
82+
self.file_path = file_path
7183
self.topic_mapping = topic_mapping if topic_mapping is not None else {}
7284
if main_topic not in self.topic_mapping:
7385
self.topic_mapping[main_topic] = "numpy_pc"
74-
75-
topic_info = self.rosbag.get_type_and_topic_info()
76-
for topic in self.topic_mapping:
77-
assert_debug(topic in topic_info.topics,
78-
f"{topic} is not a topic of the rosbag "
79-
f"(existing topics : {list(topic_info.topics.keys())}")
80-
81-
self.main_topic = main_topic
86+
self.main_topic: str = main_topic
87+
self.frame_size = frame_size
8288
self._frame_size: int = frame_size if self.config.accumulate_scans else 1
83-
self._len = self.rosbag.get_message_count(self.main_topic) // self._frame_size
84-
85-
self.__idx = 0
89+
self._len = -1 #
90+
self._idx = 0
8691
self._topics = list(topic_mapping.keys())
8792
self.__iter = None
8893

8994
def __iter__(self):
95+
self._lazy_initialization()
9096
self.__iter = self.rosbag.read_messages(self._topics)
91-
self.__idx = 0
97+
self._idx = 0
9298
return self
9399

94100
@staticmethod
@@ -120,7 +126,8 @@ def _save_topic(self, data_dict, key, topic, msg, t, **kwargs):
120126
data_dict[timestamps_key].append(timestamps)
121127

122128
def __getitem__(self, index) -> dict:
123-
assert_debug(index == self.__idx, "A RosbagDataset does not support Random access")
129+
self._lazy_initialization()
130+
assert_debug(index == self._idx, "A RosbagDataset does not support Random access")
124131
assert isinstance(self.config, RosbagConfig)
125132
if self.__iter is None:
126133
self.__iter__()
@@ -134,15 +141,16 @@ def __getitem__(self, index) -> dict:
134141
_key = self.topic_mapping[topic]
135142
self._save_topic(data_dict, _key, topic, msg, t, frame_index=index)
136143

137-
self.__idx += 1
144+
self._idx += 1
138145
# Aggregate data
139146
data_dict = self.aggregate_messages(data_dict)
140147
return data_dict
141148

142149
def __next__(self):
143-
return self[self.__idx]
150+
return self[self._idx]
144151

145152
def __len__(self):
153+
self._lazy_initialization()
146154
return self._len
147155

148156
def __del__(self):

0 commit comments

Comments
 (0)