The Ouster python SDK provides a simple interface for getting started running SLAM on live and recorded data. The following is an example of programming SLAM using the API. More information about the API can be found in the docs: SLAM Quickstart — Ouster Sensor SDK 0.13.1 documentation
SLAM visualized live in the SDK SimpleViz
For those looking to run slam without diving into code, check out ouster’s command-line-interface (CLI) toolset: Start mapping with the Ouster-CLI — Ouster Sensor SDK 0.13.1 documentation
Make sure you have installed the required imports:
from functools import partial
import numpy as np
import argparse
from ouster.sdk.client import ChanField, SensorInfo, LidarScan, XYZLut, ScanSource, last_valid_column_pose, last_valid_column_ts
from ouster.sdk.viz import SimpleViz, ScansAccumulator
from ouster.sdk.open_source import open_source
from ouster.sdk.mapping.slam import KissBackend
The SDK batches lidar data into frames that are called LidarScans which mimic a camera-like frame-by-frame processing interface. Many of the SDK’s libraries, including the SLAMBackend class, operate on LidarScans. LidarScans are streamed one at a time by the ScanSource class.
Here we create a ScanIterator class that mimics the built in ScanSource so that it can be passed the SDK’s SimpleViz viewer.
class ScanIterator:
def __init__(self, scans: ScanSource):
self._metadata: SensorInfo = scans.metadata
self._xyzlut = XYZLut(scans.metadata) # Create the XYZ lookup table
# Instantiate the slam object. The SDK will eventually contain multiple SLAMBackends. Currently it offers a backend built on the KISS-ICP project.
# Generally smaller voxel sizes give better results and run slower
self._slam = KissBackend(self._metadata, voxel_size=0.25)
# Map the self._update function on to the scans iterator
# the iterator will now run the self._update command before emitting the modified scan
self._scans = map(partial(self._update), scans) # Play on repeat
@property
def metadata(self) -> SensorInfo:
return self._metadata
# Return the scans iterator when instantiating the class
def __iter__(self):
return self._scans
def _update(self, scan: LidarScan) -> LidarScan:
# Run the slam update command to calculate column poses for the scan
scan = self._slam.update(scan)
# scan.pose now contains an (N, 4, 4) array of pose transformations. One for each column in the LidarScan
return scan
And here is the main function where we parse inputs and pass our ScanIterator object to SimpleViz for realtime viewing with poses added by SLAM.
if __name__ == "__main__":
# parse the command arguments
parser = argparse.ArgumentParser(prog='sdk slam demo',
description='Runs a minimal demo of SDK slam post-processing')
parser.add_argument('source', type=str, help='Sensor hostname or path to a sensor PCAP or OSF file')
parser.add_argument('--accum-num', type=int, help='Number of scans to vizualize', default=50)
args = parser.parse_args()
# Run some post-processing on the data
scans = ScanIterator(open_source(source_url=args.source, sensor_idx=0, cycle=True))
# Create a scan accumuator vizualization object
scan_accum = ScansAccumulator(scans.metadata, accum_max_num=args.accum_num)
# Pass the scans iterator to SimpleViz. The function mapping will
SimpleViz(scans.metadata, rate=0, scans_accum=scan_accum).run(scans)
Copying the code above in a slam.py file, you will be able to run the script from the command line:
python slam.py [SENSOR_HOSTNAME | PATH_TO_PCAP]