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