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}
118
119impl StreamConfig {
120 pub fn new(src: PathBuf, out: PathBuf) -> Self {
122 Self {
123 src,
124 out,
125 span: 12,
126 overlap: 2,
127 stride: 3,
128 incremental_triangulation: false,
129 depth_cache: false,
130 fixed_enu_origin: false,
131 pm_backend: "cuda".into(),
132 mvs_profile: "fast".into(),
133 sift_backend: "cpu".into(),
134 match_backend: "cpu".into(),
135 undistort_backend: "cpu".into(),
136 threads: 0,
137 max_positions: None,
138 save_models: false,
139 serialize_dense: false,
140 dense_max_image_size: 960,
141 number_views: 10,
142 max_num_features: 8192,
143 sift_max_image_size: 3200,
144 echo: true,
145 no_dense: false,
146 stable_display: false,
147 settle_tol: 0.01,
148 settle_max_zone: 3,
149 intrinsics_prior: None,
150 intrinsics_prior_sigma: crate::intrinsics::DEFAULT_FOCAL_SIGMA,
151 intrinsics_prior_dist_sigma: crate::intrinsics::DEFAULT_DIST_SIGMA,
152 seed: None,
153 max_levels: None,
154 fusion: Fusion::default(),
155 live_stride: crate::post::LiveOptions::default().stride,
156 }
157 }
158}
159
160#[derive(Clone, Debug)]
162pub struct Layout {
163 pub frames: Vec<u32>,
165 pub pos_of: HashMap<String, usize>,
167}
168
169impl Layout {
170 pub fn discover(src: &Path, stride: usize) -> Result<Self, String> {
172 Self::discover_in(&src.join("images"), &CAMS, stride)
173 }
174
175 pub fn discover_in<S: AsRef<str>>(images: &Path, cams: &[S], stride: usize) -> Result<Self, String> {
179 let first = cams.first().ok_or("카메라 폴더 목록이 비어 있음")?.as_ref();
180 let dir = images.join(first);
181 let rd = std::fs::read_dir(&dir).map_err(|e| format!("{}: {e}", dir.display()))?;
182 let mut frames = Vec::new();
183 for e in rd.flatten() {
184 let n = e.file_name().to_string_lossy().to_string();
185 let Some(stem) = n.strip_prefix(&format!("{first}_")).and_then(|s| s.strip_suffix(".jpg")) else { continue };
186 if let Ok(i) = stem.parse::<u32>() {
187 if (i as usize).is_multiple_of(stride.max(1)) {
188 frames.push(i);
189 }
190 }
191 }
192 frames.sort_unstable();
193 frames.dedup();
194 let mut l = Self { frames, pos_of: HashMap::new() };
195 for p in 0..l.frames.len() {
196 for c in cams {
197 let n = l.name(c.as_ref(), p);
198 l.pos_of.insert(n, p);
199 }
200 }
201 Ok(l)
202 }
203
204 pub fn truncate(&mut self, n: usize) {
206 self.frames.truncate(n);
207 let frames = self.frames.len();
208 self.pos_of.retain(|_, p| *p < frames);
209 }
210
211 pub fn name(&self, cam: &str, p: usize) -> String {
213 format!("{cam}/{cam}_{:04}.jpg", self.frames[p])
214 }
215
216 pub fn position(&self, name: &str) -> Option<usize> {
218 self.pos_of.get(name).copied()
219 }
220}
221
222pub fn frame_set(layout: &Layout, p: usize) -> FrameSet {
224 frame_set_of(layout, &CAMS, p)
225}
226
227pub fn frame_set_of<S: AsRef<str>>(layout: &Layout, cams: &[S], p: usize) -> FrameSet {
229 FrameSet::new(cams.iter().map(|c| (c.as_ref().to_string(), layout.name(c.as_ref(), p))))
230}
231
232pub fn pairs_for_position(layout: &Layout, p: usize) -> Vec<(String, String)> {
234 let frames: Vec<Vec<FrameImage>> = (0..=p).map(|q| frame_set(layout, q).images).collect();
235 pairs_for(&frames, p)
236}
237
238pub fn regions(npos: usize, span: usize, ovl: usize) -> Vec<(usize, usize, usize)> {
240 zones_upto(npos, span, ovl).into_iter().map(|z| (z.zone, z.lo, z.hi)).collect()
241}
242
243pub fn session_config(cfg: &StreamConfig, npos: usize, gps: Vec<GpsRecord>, dense: Option<DenseConfig>) -> Result<SessionConfig, String> {
245 let mut s = SessionConfig::new(cfg.src.join("images"));
246 s.gps = gps;
247 s.span = cfg.span;
248 s.overlap = cfg.overlap;
249 s.total_positions = Some(npos);
250 s.incremental_triangulation = cfg.incremental_triangulation;
251 s.stable_display = cfg.stable_display;
252 s.settle_tol = cfg.settle_tol;
253 s.settle_max_zone = cfg.settle_max_zone;
254 if let Some(p) = &cfg.intrinsics_prior {
255 s.intrinsics_prior = Some(Arc::new(IntrinsicsPrior::load(p, cfg.intrinsics_prior_sigma, cfg.intrinsics_prior_dist_sigma)?));
256 }
257 s.fixed_enu_origin = cfg.fixed_enu_origin;
258 s.seed = cfg.seed;
259 s.extraction.sift.max_num_features = cfg.max_num_features;
260 s.extraction.reader.max_image_size = cfg.sift_max_image_size;
261 s.sift = make_sift_backend(&cfg.sift_backend)?;
262 s.matcher = Arc::from(make_match_backend(&cfg.match_backend)?);
263 s.dense = dense;
264 s.history = 0;
266 Ok(s)
267}
268
269pub fn run_stream(cfg: StreamConfig) -> Result<(), String> {
274 let recon = Recon::from_plan(Plan::from_stream(&cfg)).build().map_err(|e| e.to_string())?;
275 recon.run().map(|_| ())
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281
282 #[test]
283 fn regions_match_script() {
284 assert_eq!(regions(26, 12, 2), vec![(0, 0, 14), (1, 10, 26), (2, 22, 26)]);
286 assert_eq!(regions(40, 12, 2), vec![(0, 0, 14), (1, 10, 26), (2, 22, 38), (3, 34, 40)]);
287 }
288
289 #[test]
290 fn pairs_rule() {
291 let frames: Vec<u32> = (0..20).map(|i| i * 3).collect();
292 let mut l = Layout { frames, pos_of: HashMap::new() };
293 l.pos_of.insert("x".into(), 0);
294 let p0 = pairs_for_position(&l, 0);
295 assert_eq!(p0.len(), 3);
297 let p17 = pairs_for_position(&l, 17);
298 assert_eq!(p17.len(), 21 + 27);
300 assert!(p17.contains(&("camF/camF_0003.jpg".to_string(), "camF/camF_0051.jpg".to_string())));
301 }
302}