1use crate::declare::{Fusion, Plan, Recon};
37use crate::densewrap::{make_match_backend, make_sift_backend, DenseConfig};
38use crate::intrinsics::IntrinsicsPrior;
39use crate::session::{pairs_for, zones_upto, FrameImage, FrameSet, SessionConfig};
40use cumulus3d_core::io::GpsRecord;
41use std::collections::HashMap;
42use std::path::{Path, PathBuf};
43use std::sync::Arc;
44
45pub const CAMS: [&str; 3] = ["camF", "camR", "camL"];
47
48#[derive(Clone, Debug)]
50pub struct StreamConfig {
51 pub src: PathBuf,
53 pub out: PathBuf,
55 pub span: usize,
57 pub overlap: usize,
59 pub stride: usize,
61 pub incremental_triangulation: bool,
63 pub depth_cache: bool,
65 pub fixed_enu_origin: bool,
67 pub pm_backend: String,
69 pub mvs_profile: String,
71 pub sift_backend: String,
73 pub match_backend: String,
75 pub undistort_backend: String,
77 pub threads: usize,
79 pub max_positions: Option<usize>,
81 pub save_models: bool,
83 pub serialize_dense: bool,
85 pub dense_max_image_size: i64,
87 pub number_views: usize,
89 pub max_num_features: usize,
91 pub sift_max_image_size: usize,
93 pub echo: bool,
95 pub no_dense: bool,
97 pub stable_display: bool,
99 pub settle_tol: f64,
101 pub settle_max_zone: usize,
103 pub intrinsics_prior: Option<PathBuf>,
105 pub intrinsics_prior_sigma: f64,
107 pub intrinsics_prior_dist_sigma: f64,
109 pub seed: Option<u64>,
111 pub max_levels: Option<usize>,
113 pub fusion: Fusion,
115 pub live_stride: usize,
117 pub lookahead: usize,
119 pub preview: bool,
121}
122
123impl StreamConfig {
124 pub fn new(src: PathBuf, out: PathBuf) -> Self {
126 Self {
127 src,
128 out,
129 span: 12,
130 overlap: 2,
131 stride: 3,
132 incremental_triangulation: false,
133 depth_cache: false,
134 fixed_enu_origin: false,
135 pm_backend: "cuda".into(),
136 mvs_profile: "fast".into(),
137 sift_backend: "cpu".into(),
138 match_backend: "cpu".into(),
139 undistort_backend: "cpu".into(),
140 threads: 0,
141 max_positions: None,
142 save_models: false,
143 serialize_dense: false,
144 dense_max_image_size: 960,
145 number_views: 10,
146 max_num_features: 8192,
147 sift_max_image_size: 3200,
148 echo: true,
149 no_dense: false,
150 stable_display: false,
151 settle_tol: 0.01,
152 settle_max_zone: 3,
153 intrinsics_prior: None,
154 intrinsics_prior_sigma: crate::intrinsics::DEFAULT_FOCAL_SIGMA,
155 intrinsics_prior_dist_sigma: crate::intrinsics::DEFAULT_DIST_SIGMA,
156 seed: None,
157 max_levels: None,
158 fusion: Fusion::default(),
159 live_stride: crate::post::LiveOptions::default().stride,
160 lookahead: 0,
161 preview: false,
162 }
163 }
164}
165
166#[derive(Clone, Debug)]
168pub struct Layout {
169 pub frames: Vec<u32>,
171 pub pos_of: HashMap<String, usize>,
173}
174
175impl Layout {
176 pub fn discover(src: &Path, stride: usize) -> Result<Self, String> {
178 Self::discover_in(&src.join("images"), &CAMS, stride)
179 }
180
181 pub fn discover_in<S: AsRef<str>>(images: &Path, cams: &[S], stride: usize) -> Result<Self, String> {
185 let first = cams.first().ok_or("카메라 폴더 목록이 비어 있음")?.as_ref();
186 let dir = images.join(first);
187 let rd = std::fs::read_dir(&dir).map_err(|e| format!("{}: {e}", dir.display()))?;
188 let mut frames = Vec::new();
189 for e in rd.flatten() {
190 let n = e.file_name().to_string_lossy().to_string();
191 let Some(stem) = n.strip_prefix(&format!("{first}_")).and_then(|s| s.strip_suffix(".jpg")) else { continue };
192 if let Ok(i) = stem.parse::<u32>() {
193 if (i as usize).is_multiple_of(stride.max(1)) {
194 frames.push(i);
195 }
196 }
197 }
198 frames.sort_unstable();
199 frames.dedup();
200 let mut l = Self { frames, pos_of: HashMap::new() };
201 for p in 0..l.frames.len() {
202 for c in cams {
203 let n = l.name(c.as_ref(), p);
204 l.pos_of.insert(n, p);
205 }
206 }
207 Ok(l)
208 }
209
210 pub fn truncate(&mut self, n: usize) {
212 self.frames.truncate(n);
213 let frames = self.frames.len();
214 self.pos_of.retain(|_, p| *p < frames);
215 }
216
217 pub fn name(&self, cam: &str, p: usize) -> String {
219 format!("{cam}/{cam}_{:04}.jpg", self.frames[p])
220 }
221
222 pub fn position(&self, name: &str) -> Option<usize> {
224 self.pos_of.get(name).copied()
225 }
226}
227
228pub fn frame_set(layout: &Layout, p: usize) -> FrameSet {
230 frame_set_of(layout, &CAMS, p)
231}
232
233pub fn frame_set_of<S: AsRef<str>>(layout: &Layout, cams: &[S], p: usize) -> FrameSet {
235 FrameSet::new(cams.iter().map(|c| (c.as_ref().to_string(), layout.name(c.as_ref(), p))))
236}
237
238pub fn pairs_for_position(layout: &Layout, p: usize) -> Vec<(String, String)> {
240 let frames: Vec<Vec<FrameImage>> = (0..=p).map(|q| frame_set(layout, q).images).collect();
241 pairs_for(&frames, p)
242}
243
244pub fn regions(npos: usize, span: usize, ovl: usize) -> Vec<(usize, usize, usize)> {
246 zones_upto(npos, span, ovl).into_iter().map(|z| (z.zone, z.lo, z.hi)).collect()
247}
248
249pub fn session_config(cfg: &StreamConfig, npos: usize, gps: Vec<GpsRecord>, dense: Option<DenseConfig>) -> Result<SessionConfig, String> {
251 let mut s = SessionConfig::new(cfg.src.join("images"));
252 s.gps = gps;
253 s.span = cfg.span;
254 s.overlap = cfg.overlap;
255 s.total_positions = Some(npos);
256 s.incremental_triangulation = cfg.incremental_triangulation;
257 s.stable_display = cfg.stable_display;
258 s.settle_tol = cfg.settle_tol;
259 s.settle_max_zone = cfg.settle_max_zone;
260 s.lookahead = cfg.lookahead;
261 s.preview = cfg.preview;
262 if let Some(p) = &cfg.intrinsics_prior {
263 s.intrinsics_prior = Some(Arc::new(IntrinsicsPrior::load(p, cfg.intrinsics_prior_sigma, cfg.intrinsics_prior_dist_sigma)?));
264 }
265 s.fixed_enu_origin = cfg.fixed_enu_origin;
266 s.seed = cfg.seed;
267 s.extraction.sift.max_num_features = cfg.max_num_features;
268 s.extraction.reader.max_image_size = cfg.sift_max_image_size;
269 s.sift = make_sift_backend(&cfg.sift_backend)?;
270 s.matcher = Arc::from(make_match_backend(&cfg.match_backend)?);
271 s.dense = dense;
272 s.history = 0;
274 Ok(s)
275}
276
277pub fn run_stream(cfg: StreamConfig) -> Result<(), String> {
282 let recon = Recon::from_plan(Plan::from_stream(&cfg)).build().map_err(|e| e.to_string())?;
283 recon.run().map(|_| ())
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn regions_match_script() {
292 assert_eq!(regions(26, 12, 2), vec![(0, 0, 14), (1, 10, 26), (2, 22, 26)]);
294 assert_eq!(regions(40, 12, 2), vec![(0, 0, 14), (1, 10, 26), (2, 22, 38), (3, 34, 40)]);
295 }
296
297 #[test]
298 fn pairs_rule() {
299 let frames: Vec<u32> = (0..20).map(|i| i * 3).collect();
300 let mut l = Layout { frames, pos_of: HashMap::new() };
301 l.pos_of.insert("x".into(), 0);
302 let p0 = pairs_for_position(&l, 0);
303 assert_eq!(p0.len(), 3);
305 let p17 = pairs_for_position(&l, 17);
306 assert_eq!(p17.len(), 21 + 27);
308 assert!(p17.contains(&("camF/camF_0003.jpg".to_string(), "camF/camF_0051.jpg".to_string())));
309 }
310}