use std::{
fs::File,
io::{BufRead, BufReader},
path::Path,
};
use super::{CameraModelId, ColmapCamera, ColmapImage, ColmapPoint3d};
#[derive(Debug, thiserror::Error)]
pub enum ColmapError {
#[error("error reading or writing file")]
IoError(#[from] std::io::Error),
#[error("Invalid number of camera parameters")]
InvalidNumCameraParams(usize),
#[error("Parse error {0}")]
ParseError(String),
}
pub fn read_cameras_txt(path: impl AsRef<Path>) -> Result<Vec<ColmapCamera>, ColmapError> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let cameras = reader
.lines()
.skip(3)
.map(|line| -> Result<ColmapCamera, ColmapError> {
let line = line.map_err(ColmapError::from)?;
parse_camera_line(&line)
})
.collect::<Result<Vec<_>, _>>()?;
Ok(cameras)
}
pub fn read_points3d_txt(path: impl AsRef<Path>) -> Result<Vec<ColmapPoint3d>, ColmapError> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let points = reader
.lines()
.skip(3)
.map(|line| -> Result<ColmapPoint3d, ColmapError> {
let line = line.map_err(ColmapError::from)?;
parse_point3d_line(&line)
})
.collect::<Result<Vec<_>, _>>()?;
Ok(points)
}
pub fn read_images_txt(path: impl AsRef<Path>) -> Result<Vec<ColmapImage>, ColmapError> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let images = reader
.lines()
.skip(4)
.collect::<Result<Vec<_>, _>>()?
.chunks(2)
.map(|chunk| match chunk {
[line1, line2] => parse_image_line(line1, line2),
_ => Err(ColmapError::ParseError(
"Invalid number of lines".to_string(),
)),
})
.collect::<Result<Vec<_>, _>>()?;
Ok(images)
}
fn parse_part<T: std::str::FromStr>(s: &str) -> Result<T, ColmapError>
where
T::Err: std::fmt::Display,
{
s.parse::<T>()
.map_err(|e| ColmapError::ParseError(format!("{s}: {e}")))
}
fn parse_camera_line(line: &str) -> Result<ColmapCamera, ColmapError> {
let parts = line.split_whitespace().collect::<Vec<_>>();
if parts.len() < 5 {
return Err(ColmapError::ParseError(format!(
"Invalid number of parts: {parts_len}",
parts_len = parts.len()
)));
}
Ok(ColmapCamera {
camera_id: parse_part(parts[0])?,
model_id: parse_camera_model_id(parts[1])?,
width: parse_part(parts[2])?,
height: parse_part(parts[3])?,
params: parts[4..]
.iter()
.map(|s| parse_part(s))
.collect::<Result<Vec<_>, _>>()?,
})
}
fn parse_camera_model_id(model_id: &str) -> Result<CameraModelId, ColmapError> {
match model_id {
"SIMPLE_PINHOLE" => Ok(CameraModelId::CameraModelSimplePinhole),
"PINHOLE" => Ok(CameraModelId::CameraModelPinhole),
"SIMPLE_RADIAL" => Ok(CameraModelId::CameraModelSimplifiedRadial),
"RADIAL" => Ok(CameraModelId::CameraModelRadial),
"OPENCV" => Ok(CameraModelId::CameraModelOpenCV),
"OPENCV_FISHEYE" => Ok(CameraModelId::CameraModelOpenCVFisheye),
"FULL_OPENCV" => Ok(CameraModelId::CameraModelFullOpenCV),
"FOV" => Ok(CameraModelId::CameraModelFOV),
"SIMPLE_RADIAL_FISHEYE" => Ok(CameraModelId::CameraModelSimpleRadialFisheye),
"RADIAL_FISHEYE" => Ok(CameraModelId::CameraModelRadialFisheye),
"THIN_PRISM_FISHEYE" => Ok(CameraModelId::CameraModelThinPrismFisheye),
_ => Err(ColmapError::ParseError(format!(
"Invalid camera model id: {model_id}"
))),
}
}
fn parse_point3d_line(line: &str) -> Result<ColmapPoint3d, ColmapError> {
let parts = line.split_whitespace().collect::<Vec<_>>();
if parts.len() < 8 {
return Err(ColmapError::ParseError(format!(
"Invalid number of parts: {}",
parts.len()
)));
}
Ok(ColmapPoint3d {
point3d_id: parse_part(parts[0])?,
xyz: parts[1..4]
.iter()
.map(|s| parse_part(s))
.collect::<Result<Vec<_>, _>>()?
.try_into()
.map_err(|_| {
ColmapError::ParseError("Invalid number of xyz coordinates".to_string())
})?,
rgb: parts[4..7]
.iter()
.map(|s| parse_part(s))
.collect::<Result<Vec<_>, _>>()?
.try_into()
.map_err(|_| {
ColmapError::ParseError("Invalid number of rgb coordinates".to_string())
})?,
error: parse_part(parts[7])?,
track: parts[8..]
.chunks_exact(2)
.map(|chunk| -> Result<(u32, u32), ColmapError> {
Ok((parse_part(chunk[0])?, parse_part(chunk[1])?))
})
.collect::<Result<Vec<_>, _>>()?,
})
}
fn parse_image_line(line1: &str, line2: &str) -> Result<ColmapImage, ColmapError> {
let parts1 = line1.split_whitespace().collect::<Vec<_>>();
let parts2 = line2.split_whitespace().collect::<Vec<_>>();
Ok(ColmapImage {
image_id: parse_part(parts1[0])?,
rotation: parts1[1..5]
.iter()
.map(|s| parse_part(s))
.collect::<Result<Vec<_>, _>>()?
.try_into()
.map_err(|_| {
ColmapError::ParseError("Invalid number of rotation coordinates".to_string())
})?,
translation: parts1[5..8]
.iter()
.map(|s| parse_part(s))
.collect::<Result<Vec<_>, _>>()?
.try_into()
.map_err(|_| {
ColmapError::ParseError("Invalid number of translation coordinates".to_string())
})?,
camera_id: parse_part(parts1[8])?,
name: parts1[9].to_string(),
points2d: parts2
.chunks_exact(3)
.map(|chunk| -> Result<(f64, f64, i64), ColmapError> {
Ok((
parse_part(chunk[0])?,
parse_part(chunk[1])?,
parse_part(chunk[2])?,
))
})
.collect::<Result<Vec<_>, _>>()?,
})
}