colmap-rs/src/sfm/mod.rs

176 lines
6.3 KiB
Rust

//! Incremental Structure-from-Motion (SfM).
//!
//! This module mirrors the high-level incremental mapping pipeline of
//! [PyCOLMAP](https://colmap.github.io/pycolmap/) (`pycolmap.incremental_mapping`
//! and the underlying `IncrementalMapper` / `IncrementalPipeline` controllers).
//!
//! It exposes the **option structs** and **driver function signatures** that the
//! C++ implementation uses, so that calling code can be written and type-checked
//! against the stable public surface. The heavy numerical core — robust two-view
//! initialization, absolute-pose registration, triangulation and bundle
//! adjustment scheduling — is not ported in this pure-Rust crate yet and the
//! driver functions therefore return [`crate::Error::Unimplemented`].
use std::path::Path;
use crate::scene::Reconstruction;
/// Options that control a single run of the incremental mapper.
///
/// These mirror a subset of COLMAP's `IncrementalMapperOptions`. Only the most
/// load-bearing knobs are surfaced here; the defaults match COLMAP's defaults.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct IncrementalMapperOptions {
/// Minimum number of two-view inliers required to initialize the model
/// from the seed image pair.
pub init_min_num_inliers: usize,
/// Minimum number of inliers required to accept an absolute-pose
/// (image registration) estimate.
pub abs_pose_min_num_inliers: usize,
/// Minimum number of feature matches an image pair must have to be used
/// at all during mapping.
pub min_num_matches: usize,
/// Whether to reconstruct multiple disjoint models from the remaining,
/// not-yet-registered images.
pub multiple_models: bool,
/// Upper bound on the number of distinct models to reconstruct when
/// [`multiple_models`](Self::multiple_models) is enabled.
pub max_num_models: usize,
}
impl Default for IncrementalMapperOptions {
fn default() -> Self {
Self {
init_min_num_inliers: 100,
abs_pose_min_num_inliers: 30,
min_num_matches: 15,
multiple_models: true,
max_num_models: 50,
}
}
}
/// Options that control the full incremental SfM pipeline.
///
/// Mirrors a subset of COLMAP's `IncrementalPipelineOptions`, which wraps an
/// [`IncrementalMapperOptions`] together with pipeline-level scheduling knobs
/// such as how often global bundle adjustment is triggered.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct IncrementalPipelineOptions {
/// Minimum number of feature matches an image pair must have to be used.
pub min_num_matches: usize,
/// The growth ratio of registered images that triggers a new round of
/// global bundle adjustment (e.g. `1.1` => re-run when the model grows
/// by 10%).
pub ba_global_images_ratio: f64,
/// Options forwarded to the underlying incremental mapper.
pub mapper: IncrementalMapperOptions,
}
impl Default for IncrementalPipelineOptions {
fn default() -> Self {
Self {
min_num_matches: 15,
ba_global_images_ratio: 1.1,
mapper: IncrementalMapperOptions::default(),
}
}
}
/// Run the full incremental Structure-from-Motion pipeline.
///
/// Reads features and matches from the COLMAP feature database at
/// `database_path`, registers images one by one, triangulates points and
/// bundle-adjusts, writing the resulting sparse model(s) under `output_path`.
/// Mirrors `pycolmap.incremental_mapping`.
///
/// # Arguments
///
/// * `database_path` — path to the SQLite feature database.
/// * `image_path` — root directory containing the source images.
/// * `output_path` — directory under which reconstructed model(s) are written.
/// * `options` — pipeline configuration.
///
/// # Errors
///
/// Returns [`crate::Error::Unimplemented`]; the numerical SfM core is not ported
/// in this pure-Rust crate.
pub fn incremental_mapping(
database_path: &Path,
image_path: &Path,
output_path: &Path,
options: &IncrementalPipelineOptions,
) -> crate::Result<Vec<Reconstruction>> {
let _ = (database_path, image_path, output_path, options);
Err(crate::Error::Unimplemented("incremental SfM mapping"))
}
/// Triangulate additional 3D points into an existing reconstruction.
///
/// Given an already-registered set of images (poses fixed), this completes and
/// merges feature tracks read from the database into new and existing 3D points.
/// Mirrors `pycolmap.triangulate_points`.
///
/// # Errors
///
/// Returns [`crate::Error::Unimplemented`]; triangulation is not ported in this
/// pure-Rust crate.
pub fn triangulate_points(
rec: &mut Reconstruction,
database_path: &Path,
) -> crate::Result<()> {
let _ = (rec, database_path);
Err(crate::Error::Unimplemented("point triangulation"))
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn mapper_options_defaults() {
let o = IncrementalMapperOptions::default();
assert_eq!(o.init_min_num_inliers, 100);
assert_eq!(o.abs_pose_min_num_inliers, 30);
assert_eq!(o.min_num_matches, 15);
assert!(o.multiple_models);
assert_eq!(o.max_num_models, 50);
}
#[test]
fn pipeline_options_defaults() {
let o = IncrementalPipelineOptions::default();
assert_eq!(o.min_num_matches, 15);
assert!((o.ba_global_images_ratio - 1.1).abs() < 1e-12);
assert_eq!(o.mapper, IncrementalMapperOptions::default());
}
#[test]
fn incremental_mapping_is_unimplemented() {
let err = incremental_mapping(
Path::new("db.db"),
Path::new("images"),
Path::new("out"),
&IncrementalPipelineOptions::default(),
)
.unwrap_err();
assert!(matches!(err, crate::Error::Unimplemented(_)));
}
#[test]
fn triangulate_points_is_unimplemented() {
let mut rec = Reconstruction {
rigs: Default::default(),
cameras: Default::default(),
frames: Default::default(),
images: Default::default(),
points3d: Default::default(),
};
let err = triangulate_points(&mut rec, Path::new("db.db")).unwrap_err();
assert!(matches!(err, crate::Error::Unimplemented(_)));
}
}