kornia_3d/io/colmap/
text.rs1use std::{
2 fs::File,
3 io::{BufRead, BufReader},
4 path::Path,
5};
6
7use super::{CameraModelId, ColmapCamera, ColmapImage, ColmapPoint3d};
8
9#[derive(Debug, thiserror::Error)]
11pub enum ColmapError {
12 #[error("error reading or writing file")]
14 IoError(#[from] std::io::Error),
15
16 #[error("Invalid number of camera parameters")]
18 InvalidNumCameraParams(usize),
19
20 #[error("Parse error {0}")]
22 ParseError(String),
23}
24
25pub fn read_cameras_txt(path: impl AsRef<Path>) -> Result<Vec<ColmapCamera>, ColmapError> {
35 let file = File::open(path)?;
37 let reader = BufReader::new(file);
38
39 let cameras = reader
41 .lines()
42 .skip(3)
43 .map(|line| -> Result<ColmapCamera, ColmapError> {
44 let line = line.map_err(ColmapError::from)?;
45 parse_camera_line(&line)
46 })
47 .collect::<Result<Vec<_>, _>>()?;
48
49 Ok(cameras)
50}
51
52pub fn read_points3d_txt(path: impl AsRef<Path>) -> Result<Vec<ColmapPoint3d>, ColmapError> {
62 let file = File::open(path)?;
64 let reader = BufReader::new(file);
65
66 let points = reader
69 .lines()
70 .skip(3)
71 .map(|line| -> Result<ColmapPoint3d, ColmapError> {
72 let line = line.map_err(ColmapError::from)?;
73 parse_point3d_line(&line)
74 })
75 .collect::<Result<Vec<_>, _>>()?;
76
77 Ok(points)
78}
79
80pub fn read_images_txt(path: impl AsRef<Path>) -> Result<Vec<ColmapImage>, ColmapError> {
90 let file = File::open(path)?;
92 let reader = BufReader::new(file);
93
94 let images = reader
95 .lines()
96 .skip(4)
97 .collect::<Result<Vec<_>, _>>()?
98 .chunks(2)
99 .map(|chunk| match chunk {
100 [line1, line2] => parse_image_line(line1, line2),
101 _ => Err(ColmapError::ParseError(
102 "Invalid number of lines".to_string(),
103 )),
104 })
105 .collect::<Result<Vec<_>, _>>()?;
106
107 Ok(images)
108}
109
110fn parse_part<T: std::str::FromStr>(s: &str) -> Result<T, ColmapError>
112where
113 T::Err: std::fmt::Display,
114{
115 s.parse::<T>()
116 .map_err(|e| ColmapError::ParseError(format!("{s}: {e}")))
117}
118
119fn parse_camera_line(line: &str) -> Result<ColmapCamera, ColmapError> {
123 let parts = line.split_whitespace().collect::<Vec<_>>();
125
126 if parts.len() < 5 {
127 return Err(ColmapError::ParseError(format!(
128 "Invalid number of parts: {parts_len}",
129 parts_len = parts.len()
130 )));
131 }
132
133 Ok(ColmapCamera {
134 camera_id: parse_part(parts[0])?,
135 model_id: parse_camera_model_id(parts[1])?,
136 width: parse_part(parts[2])?,
137 height: parse_part(parts[3])?,
138 params: parts[4..]
139 .iter()
140 .map(|s| parse_part(s))
141 .collect::<Result<Vec<_>, _>>()?,
142 })
143}
144
145fn parse_camera_model_id(model_id: &str) -> Result<CameraModelId, ColmapError> {
146 match model_id {
147 "SIMPLE_PINHOLE" => Ok(CameraModelId::CameraModelSimplePinhole),
148 "PINHOLE" => Ok(CameraModelId::CameraModelPinhole),
149 "SIMPLE_RADIAL" => Ok(CameraModelId::CameraModelSimplifiedRadial),
150 "RADIAL" => Ok(CameraModelId::CameraModelRadial),
151 "OPENCV" => Ok(CameraModelId::CameraModelOpenCV),
152 "OPENCV_FISHEYE" => Ok(CameraModelId::CameraModelOpenCVFisheye),
153 "FULL_OPENCV" => Ok(CameraModelId::CameraModelFullOpenCV),
154 "FOV" => Ok(CameraModelId::CameraModelFOV),
155 "SIMPLE_RADIAL_FISHEYE" => Ok(CameraModelId::CameraModelSimpleRadialFisheye),
156 "RADIAL_FISHEYE" => Ok(CameraModelId::CameraModelRadialFisheye),
157 "THIN_PRISM_FISHEYE" => Ok(CameraModelId::CameraModelThinPrismFisheye),
158 _ => Err(ColmapError::ParseError(format!(
159 "Invalid camera model id: {model_id}"
160 ))),
161 }
162}
163
164fn parse_point3d_line(line: &str) -> Result<ColmapPoint3d, ColmapError> {
168 let parts = line.split_whitespace().collect::<Vec<_>>();
170
171 if parts.len() < 8 {
173 return Err(ColmapError::ParseError(format!(
174 "Invalid number of parts: {}",
175 parts.len()
176 )));
177 }
178
179 Ok(ColmapPoint3d {
180 point3d_id: parse_part(parts[0])?,
181 xyz: parts[1..4]
182 .iter()
183 .map(|s| parse_part(s))
184 .collect::<Result<Vec<_>, _>>()?
185 .try_into()
186 .map_err(|_| {
187 ColmapError::ParseError("Invalid number of xyz coordinates".to_string())
188 })?,
189 rgb: parts[4..7]
190 .iter()
191 .map(|s| parse_part(s))
192 .collect::<Result<Vec<_>, _>>()?
193 .try_into()
194 .map_err(|_| {
195 ColmapError::ParseError("Invalid number of rgb coordinates".to_string())
196 })?,
197 error: parse_part(parts[7])?,
198 track: parts[8..]
199 .chunks_exact(2)
200 .map(|chunk| -> Result<(u32, u32), ColmapError> {
201 Ok((parse_part(chunk[0])?, parse_part(chunk[1])?))
202 })
203 .collect::<Result<Vec<_>, _>>()?,
204 })
205}
206
207fn parse_image_line(line1: &str, line2: &str) -> Result<ColmapImage, ColmapError> {
211 let parts1 = line1.split_whitespace().collect::<Vec<_>>();
213 let parts2 = line2.split_whitespace().collect::<Vec<_>>();
214
215 Ok(ColmapImage {
216 image_id: parse_part(parts1[0])?,
217 rotation: parts1[1..5]
218 .iter()
219 .map(|s| parse_part(s))
220 .collect::<Result<Vec<_>, _>>()?
221 .try_into()
222 .map_err(|_| {
223 ColmapError::ParseError("Invalid number of rotation coordinates".to_string())
224 })?,
225 translation: parts1[5..8]
226 .iter()
227 .map(|s| parse_part(s))
228 .collect::<Result<Vec<_>, _>>()?
229 .try_into()
230 .map_err(|_| {
231 ColmapError::ParseError("Invalid number of translation coordinates".to_string())
232 })?,
233 camera_id: parse_part(parts1[8])?,
234 name: parts1[9].to_string(),
235 points2d: parts2
236 .chunks_exact(3)
237 .map(|chunk| -> Result<(f64, f64, i64), ColmapError> {
238 Ok((
239 parse_part(chunk[0])?,
240 parse_part(chunk[1])?,
241 parse_part(chunk[2])?,
242 ))
243 })
244 .collect::<Result<Vec<_>, _>>()?,
245 })
246}