Added support for Kitti dataset#20
Conversation
| } | ||
|
|
||
|
|
||
| impl PipelineConfig { |
There was a problem hiding this comment.
Maybe a better way is to define the pipeline config in the dataset specific file, in this case kitti.rs and by extension, same for euroc.rs
| // ── ORB detector (used externally before feeding Pipeline) ───────────── | ||
| let detector = kornia_imgproc::features::OrbDetector { | ||
| n_keypoints: 1000, | ||
| // KITTI generally needs a denser feature set to keep tracking stable. |
|
@jeffin07 which specific dataset from kitti did you run this on? |
There was a problem hiding this comment.
Pull request overview
Adds KITTI (Odometry grayscale) dataset support to the examples/orb_slam runner, including dataset selection via CLI and KITTI-tuned SLAM defaults.
Changes:
- Add a KITTI dataset reader and export it from the common datasets module.
- Extend the ORB-SLAM example CLI to select between
eurocandkitti, and tune feature density + pipeline config for KITTI. - Adjust bootstrap behavior and update dependency lockfiles for the kornia git branch used by the example.
Reviewed changes
Copilot reviewed 5 out of 7 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| examples/orb_slam/src/pipeline.rs | Tweaks bootstrap failure handling and triangulation call site behavior in map growth. |
| examples/orb_slam/src/main.rs | Adds --dataset flag, switches dataset loading, tunes ORB keypoints for KITTI, and uses dataset-specific pipeline config. |
| examples/orb_slam/src/config.rs | Introduces PipelineConfig::for_dataset() with KITTI-tuned parameters. |
| examples/common/datasets/mod.rs | Exposes the new kitti module and KittiDataset type. |
| examples/common/datasets/kitti.rs | Implements KITTI odometry grayscale reader (calibration, times, image listing). |
| examples/orb_slam/Cargo.lock | Locks example dependencies to the kornia git branch commit. |
| Cargo.lock | Locks workspace dependencies to the kornia git branch commit. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// CLI arguments. | ||
| #[derive(argh::FromArgs)] | ||
| #[argh(description = "Monocular visual odometry on EuRoC dataset")] | ||
| struct Args { | ||
|
|
||
| /// dataset type: "euroc", "kitti" | ||
| #[argh(option, default="String::from(\"euroc\")")] | ||
| dataset: String, | ||
|
|
||
| /// path to EuRoC dataset root (e.g. V1_01_easy/) | ||
| #[argh(option)] | ||
| data: String, |
There was a problem hiding this comment.
The CLI description still says it's for the EuRoC dataset only, but this PR adds --dataset kitti support. Please update the #[argh(description = ...)] text (and ideally the --data help string) so the usage/help output matches the new behavior.
| let (camera, samples): (PinholeCamera, Vec<datasets::euroc::DatasetSample>) = match dataset_kind.as_str() | ||
| { | ||
| "euroc" => { | ||
| println!("Euroc dataset unsupported"); |
There was a problem hiding this comment.
The message printed for the "euroc" branch says "Euroc dataset unsupported", but the code proceeds to open and run EuRoC. This is misleading for users; adjust the message to reflect what’s actually happening (or remove it).
| println!("Euroc dataset unsupported"); | |
| println!("EuRoC dataset"); |
| let dataset_kind = &args.dataset.to_lowercase(); | ||
| let (camera, samples): (PinholeCamera, Vec<datasets::euroc::DatasetSample>) = match dataset_kind.as_str() | ||
| { |
There was a problem hiding this comment.
samples is typed as Vec<datasets::euroc::DatasetSample> even when --dataset kitti is selected. Since KITTI reuses DatasetSample via super::euroc, this works but creates an odd cross-module coupling. Consider moving DatasetSample/DatasetError into a shared module (e.g. examples/common/datasets/types.rs re-exported by datasets/mod.rs) and using that type here instead of referencing datasets::euroc::....
| @@ -67,17 +92,18 @@ | |||
| args.start_frame + n_frames | |||
| ); | |||
|
|
|||
| // ── Camera (from EuRoC cam0 sensor.yaml) ─────────────────────────────── | |||
| let camera = dataset.camera(); | |||
| // // ── Camera (from EuRoC cam0 sensor.yaml) ─────────────────────────────── | |||
| // let camera = dataset.camera(); | |||
|
|
|||
There was a problem hiding this comment.
There are several commented-out lines left in place (old EuRoC-only dataset/samples and camera setup). Since this is example code, keeping dead/commented code makes it harder to read and maintain; please remove the commented-out blocks if they’re no longer needed.
| /// EuRoC `cam0` calibration loaded from `sensor.yaml`. | ||
| #[derive(Debug, Clone, Copy)] | ||
| pub struct KittiCameraCalibration { | ||
| /// Focal length in x. | ||
| pub fx: f64, | ||
| /// Focal length in y. | ||
| pub fy: f64, | ||
| /// Principal point x. | ||
| pub cx: f64, | ||
| /// Principal point y. | ||
| pub cy: f64, | ||
| /// Radial distortion coefficient. | ||
| pub k1: f64, | ||
| /// Radial distortion coefficient. | ||
| pub k2: f64, | ||
| /// Tangential distortion coefficient. | ||
| pub p1: f64, | ||
| /// Tangential distortion coefficient. | ||
| pub p2: f64, | ||
| } | ||
|
|
||
| impl KittiCameraCalibration{ | ||
|
|
||
| /// Converts the parsed EuRoC calibration into a `PinholeCamera`. | ||
| pub fn to_pinhole_camera(self) -> PinholeCamera { | ||
| PinholeCamera { | ||
| fx: self.fx, | ||
| fy: self.fy, | ||
| cx: self.cx, | ||
| cy: self.cy, | ||
| k1: self.k1, | ||
| k2: self.k2, | ||
| p1: self.p1, | ||
| p2: self.p2, | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| /// PNG images in `<root>/mav0/cam0/data/`. | ||
| #[derive(Debug, Clone)] | ||
| pub struct KittiDataset { |
There was a problem hiding this comment.
The module-level docs appear to be copy/pasted from the EuRoC reader (e.g., mentions of sensor.yaml and <root>/mav0/cam0/data/). Please update the docstrings to reflect KITTI’s actual structure (e.g., calib.txt, times.txt, image_0/).
| path::{Path, PathBuf}, | ||
| }; | ||
|
|
||
| use super::euroc::{DatasetError, DatasetSample, GroundTruthPose}; |
There was a problem hiding this comment.
GroundTruthPose is imported but not used (and the ground-truth field is commented out). This will trigger unused-import warnings; remove the import (and/or implement the ground-truth support if intended).
| use super::euroc::{DatasetError, DatasetSample, GroundTruthPose}; | |
| use super::euroc::{DatasetError, DatasetSample}; |
| // pub fn open(root: impl AsRef<Path>) -> Result<Self, DatasetError> | ||
| pub fn open(root: impl AsRef<Path>) -> Result<Self, DatasetError>{ | ||
|
|
||
| println!("Kitti dataset open method called"); |
There was a problem hiding this comment.
println!("Kitti dataset open method called") introduces unconditional stdout output from a dataset loader. This makes the example noisy and can interfere with tooling; prefer using eprintln! consistently with the rest of the example, or remove the message / gate it behind a verbosity flag.
| println!("Kitti dataset open method called"); | |
| eprintln!("Kitti dataset open method called"); |
| // let samples = Self::load_image(&root, ×tamp)?; | ||
|
|
||
| let samples = match Self::load_image(&root, ×tamp){ | ||
| Ok(s) => s, | ||
| Err(DatasetError) => return Err(DatasetError::FileNotFound(root)), | ||
| }; | ||
|
|
There was a problem hiding this comment.
This match arm pattern Err(DatasetError) is not a valid way to match a Result<_, DatasetError> and will not compile. Also, converting any error from load_image into FileNotFound(root) discards useful error context. Prefer let samples = Self::load_image(&root, ×tamp)?; or Err(e) => return Err(e) to preserve the actual failure (I/O vs parse, etc.).
| // let samples = Self::load_image(&root, ×tamp)?; | |
| let samples = match Self::load_image(&root, ×tamp){ | |
| Ok(s) => s, | |
| Err(DatasetError) => return Err(DatasetError::FileNotFound(root)), | |
| }; | |
| let samples = Self::load_image(&root, ×tamp)?; |
| fn load_times(root: &Path) -> Vec<f64>{ | ||
| let times_file = root.join("times.txt"); | ||
|
|
||
| let times = match fs::read_to_string(×_file){ | ||
| Ok(s) => s, | ||
| Err(_) => return Vec::new(), | ||
| }; | ||
|
|
||
| return times.lines().filter_map(|t| t.trim().parse::<f64>().ok()).collect(); |
There was a problem hiding this comment.
load_times silently returns an empty vector if times.txt can’t be read. That makes a missing/incorrect dataset root look like it worked and then falls back to synthetic timestamps. Since KITTI odometry sequences normally include times.txt, consider returning Result<Vec<f64>, DatasetError> and propagating the I/O/parse error from open() (and/or validating that times.txt exists up front).
| fn load_times(root: &Path) -> Vec<f64>{ | |
| let times_file = root.join("times.txt"); | |
| let times = match fs::read_to_string(×_file){ | |
| Ok(s) => s, | |
| Err(_) => return Vec::new(), | |
| }; | |
| return times.lines().filter_map(|t| t.trim().parse::<f64>().ok()).collect(); | |
| fn load_times(root: &Path) -> Result<Vec<f64>, DatasetError>{ | |
| let times_file = root.join("times.txt"); | |
| if !times_file.exists() { | |
| return Err(DatasetError::FileNotFound(times_file)); | |
| } | |
| let times = fs::read_to_string(×_file)?; | |
| let mut parsed_times = Vec::new(); | |
| for (line_idx, line) in times.lines().enumerate() { | |
| let trimmed = line.trim(); | |
| if trimmed.is_empty() { | |
| continue; | |
| } | |
| let timestamp = trimmed.parse::<f64>().map_err(|_| { | |
| DatasetError::Parse(format!( | |
| "Malformed timestamp at line {} in {}", | |
| line_idx + 1, | |
| times_file.display() | |
| )) | |
| })?; | |
| parsed_times.push(timestamp); | |
| } | |
| Ok(parsed_times) |
| impl KittiDataset{ | ||
| // pub fn open(root: impl AsRef<Path>) -> Result<Self, DatasetError> | ||
| pub fn open(root: impl AsRef<Path>) -> Result<Self, DatasetError>{ | ||
|
|
||
| println!("Kitti dataset open method called"); | ||
| let root = root.as_ref().to_path_buf(); | ||
|
|
||
| // let seq_dir = if root.join("image_0").is_dir() { | ||
| // root.clone() | ||
| // } | ||
|
|
||
|
|
||
| let image_dir = root.join("image_0"); | ||
|
|
||
| if !image_dir.is_dir(){ | ||
| return Err(DatasetError::FileNotFound(image_dir)); | ||
| } | ||
|
|
||
|
|
||
| // load the camera | ||
| let camera = Self::load_camera_from_calib(&root)?; | ||
| // load timestamps | ||
| let timestamp = Self::load_times(&root); |
There was a problem hiding this comment.
EurocDataset has unit tests for calibration parsing and failure cases (see examples/common/datasets/euroc.rs), but KittiDataset introduces similar parsing logic (calib.txt, times.txt, image discovery) without any tests. Please add basic tests (e.g., minimal temp directory tree with calib.txt, times.txt, and a couple of image_0/*.png files) to cover success + missing/malformed calibration/times cases.
|
@cjpurackal I used |
|
@cjpurackal Fixed comments. Please review |
|
tests failing ^^ @jeffin07 |
Added support for Kitti Dataset, Odometry Grascale. To Run use
cargo run --release --manifest-path examples/orb_slam/Cargo.toml -- --dataset kitti --data /path_to_kitti_dataset/Fixes #18