1use crate::densewrap::{make_match_backend, make_pm_backend, make_sift_backend, parse_profile, DenseConfig};
6use crate::pipeline::Pipeline;
7use crate::session::{pairs_for, zones_upto, FrameImage, FrameSet, Input, Session, SessionConfig};
8use crate::sinks::{DefaultSinks, SinkOptions};
9use cumulus3d_core::io::{read_gps_file, GpsRecord};
10use cumulus3d_dense::DepthMapCache;
11use std::collections::{BTreeMap, HashMap};
12use std::path::{Path, PathBuf};
13use std::sync::{Arc, Mutex};
14
15pub const CAMS: [&str; 3] = ["camF", "camR", "camL"];
17
18#[derive(Clone, Debug)]
20pub struct StreamConfig {
21 pub src: PathBuf,
23 pub out: PathBuf,
25 pub span: usize,
27 pub overlap: usize,
29 pub stride: usize,
31 pub incremental_triangulation: bool,
33 pub depth_cache: bool,
35 pub fixed_enu_origin: bool,
37 pub pm_backend: String,
39 pub mvs_profile: String,
41 pub sift_backend: String,
43 pub match_backend: String,
45 pub threads: usize,
47 pub max_positions: Option<usize>,
49 pub save_models: bool,
51 pub serialize_dense: bool,
53 pub dense_max_image_size: i64,
55 pub number_views: usize,
57 pub max_num_features: usize,
59 pub sift_max_image_size: usize,
61 pub echo: bool,
63 pub no_dense: bool,
65 pub seed: Option<u64>,
67}
68
69impl StreamConfig {
70 pub fn new(src: PathBuf, out: PathBuf) -> Self {
72 Self {
73 src,
74 out,
75 span: 12,
76 overlap: 2,
77 stride: 3,
78 incremental_triangulation: false,
79 depth_cache: false,
80 fixed_enu_origin: false,
81 pm_backend: "cuda".into(),
82 mvs_profile: "fast".into(),
83 sift_backend: "cpu".into(),
84 match_backend: "cpu".into(),
85 threads: 0,
86 max_positions: None,
87 save_models: false,
88 serialize_dense: false,
89 dense_max_image_size: 960,
90 number_views: 10,
91 max_num_features: 8192,
92 sift_max_image_size: 3200,
93 echo: true,
94 no_dense: false,
95 seed: None,
96 }
97 }
98}
99
100#[derive(Clone, Debug)]
102pub struct Layout {
103 pub frames: Vec<u32>,
105 pub pos_of: HashMap<String, usize>,
107}
108
109impl Layout {
110 pub fn discover(src: &Path, stride: usize) -> Result<Self, String> {
112 let dir = src.join("images").join(CAMS[0]);
113 let rd = std::fs::read_dir(&dir).map_err(|e| format!("{}: {e}", dir.display()))?;
114 let mut frames = Vec::new();
115 for e in rd.flatten() {
116 let n = e.file_name().to_string_lossy().to_string();
117 let Some(stem) = n.strip_prefix(&format!("{}_", CAMS[0])).and_then(|s| s.strip_suffix(".jpg")) else { continue };
118 if let Ok(i) = stem.parse::<u32>() {
119 if (i as usize).is_multiple_of(stride.max(1)) {
120 frames.push(i);
121 }
122 }
123 }
124 frames.sort_unstable();
125 frames.dedup();
126 let mut l = Self { frames, pos_of: HashMap::new() };
127 for p in 0..l.frames.len() {
128 for c in CAMS {
129 let n = l.name(c, p);
130 l.pos_of.insert(n, p);
131 }
132 }
133 Ok(l)
134 }
135
136 pub fn name(&self, cam: &str, p: usize) -> String {
138 format!("{cam}/{cam}_{:04}.jpg", self.frames[p])
139 }
140
141 pub fn position(&self, name: &str) -> Option<usize> {
143 self.pos_of.get(name).copied()
144 }
145}
146
147pub fn frame_set(layout: &Layout, p: usize) -> FrameSet {
149 FrameSet::new(CAMS.iter().map(|c| (c.to_string(), layout.name(c, p))))
150}
151
152pub fn pairs_for_position(layout: &Layout, p: usize) -> Vec<(String, String)> {
154 let frames: Vec<Vec<FrameImage>> = (0..=p).map(|q| frame_set(layout, q).images).collect();
155 pairs_for(&frames, p)
156}
157
158pub fn regions(npos: usize, span: usize, ovl: usize) -> Vec<(usize, usize, usize)> {
160 zones_upto(npos, span, ovl).into_iter().map(|z| (z.zone, z.lo, z.hi)).collect()
161}
162
163pub fn session_config(cfg: &StreamConfig, npos: usize, gps: Vec<GpsRecord>, dense: Option<DenseConfig>) -> Result<SessionConfig, String> {
165 let mut s = SessionConfig::new(cfg.src.join("images"));
166 s.gps = gps;
167 s.span = cfg.span;
168 s.overlap = cfg.overlap;
169 s.total_positions = Some(npos);
170 s.incremental_triangulation = cfg.incremental_triangulation;
171 s.fixed_enu_origin = cfg.fixed_enu_origin;
172 s.seed = cfg.seed;
173 s.extraction.sift.max_num_features = cfg.max_num_features;
174 s.extraction.reader.max_image_size = cfg.sift_max_image_size;
175 s.sift = make_sift_backend(&cfg.sift_backend)?;
176 s.matcher = Arc::from(make_match_backend(&cfg.match_backend)?);
177 s.dense = dense;
178 s.history = 0;
180 Ok(s)
181}
182
183pub fn run_stream(cfg: StreamConfig) -> Result<(), String> {
185 if cfg.threads > 0 {
186 let _ = rayon::ThreadPoolBuilder::new().num_threads(cfg.threads).build_global();
188 }
189 let backend = make_pm_backend(&cfg.pm_backend);
191 let profile = parse_profile(&cfg.mvs_profile)?;
192 let out = cfg.out.clone();
193 if out.exists() {
194 std::fs::remove_dir_all(&out).map_err(|e| format!("{}: {e}", out.display()))?;
195 }
196 let sinks = DefaultSinks::new(&out, &SinkOptions { echo: cfg.echo, save_models: cfg.save_models }).map_err(|e| e.to_string())?;
197
198 let mut layout = Layout::discover(&cfg.src, cfg.stride)?;
199 if let Some(m) = cfg.max_positions {
200 layout.frames.truncate(m);
201 let frames = layout.frames.len();
202 layout.pos_of.retain(|_, p| *p < frames);
203 }
204 let npos = layout.frames.len();
205 let gps_path = cfg.src.join("gps_ref.txt");
206 let gps_all: Vec<GpsRecord> = read_gps_file(&gps_path)
207 .map_err(|e| format!("{}: {e}", gps_path.display()))?
208 .into_iter()
209 .filter(|g| layout.pos_of.contains_key(&g.name))
210 .collect();
211
212 let dense = (!cfg.no_dense).then(|| {
213 let mut dense = DenseConfig::new(backend, profile);
214 dense.undistort.max_image_size = cfg.dense_max_image_size;
215 dense.densify.neighbors.num_views = cfg.number_views;
216 if cfg.serialize_dense {
217 dense.lock = Some(Arc::new(Mutex::new(())));
218 }
219 if cfg.depth_cache {
220 dense.depth_cache = Some(Arc::new(DepthMapCache::new()));
221 }
222 dense
223 });
224 let session = Session::new(session_config(&cfg, npos, gps_all, dense)?);
225 let mut pl = Pipeline::new(session).on_any(sinks.into_hook());
227
228 let (span, ovl) = (cfg.span, cfg.overlap);
229 let mut reg_end: BTreeMap<usize, Vec<(usize, usize, usize)>> = BTreeMap::new();
230 for (k, lo, hi) in regions(npos, span, ovl) {
231 reg_end.entry(hi - 1).or_default().push((k, lo, hi));
232 }
233 pl.push(Input::Log(format!(
234 "regions: {}",
235 reg_end
236 .iter()
237 .map(|(e, v)| format!("{e} ->{}", v.iter().map(|(k, lo, hi)| format!(" {k}:{lo}:{hi}")).collect::<String>()))
238 .collect::<Vec<_>>()
239 .join(", ")
240 )));
241 pl.push(Input::Log(format!(
242 "설정: span {span} overlap {ovl} stride {} 증분삼각측량 {} 깊이캐시 {} 고정원점 {} pm {}({})/sift {}/match {} 직렬조밀화 {} 왜곡보정최대 {} 뷰 {} 스레드 {}",
243 cfg.stride,
244 cfg.incremental_triangulation,
245 cfg.depth_cache,
246 cfg.fixed_enu_origin,
247 cfg.pm_backend,
248 cfg.mvs_profile,
249 cfg.sift_backend,
250 cfg.match_backend,
251 cfg.serialize_dense,
252 cfg.dense_max_image_size,
253 cfg.number_views,
254 rayon::current_num_threads()
255 )));
256 for p in 0..npos {
257 pl.push(frame_set(&layout, p).into());
258 if let Some(e) = pl.reducer().failed() {
259 return Err(e.to_string());
261 }
262 }
263 let _ = pl.finish();
264 Ok(())
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270
271 #[test]
272 fn regions_match_script() {
273 assert_eq!(regions(26, 12, 2), vec![(0, 0, 14), (1, 10, 26), (2, 22, 26)]);
275 assert_eq!(regions(40, 12, 2), vec![(0, 0, 14), (1, 10, 26), (2, 22, 38), (3, 34, 40)]);
276 }
277
278 #[test]
279 fn pairs_rule() {
280 let frames: Vec<u32> = (0..20).map(|i| i * 3).collect();
281 let mut l = Layout { frames, pos_of: HashMap::new() };
282 l.pos_of.insert("x".into(), 0);
283 let p0 = pairs_for_position(&l, 0);
284 assert_eq!(p0.len(), 3);
286 let p17 = pairs_for_position(&l, 17);
287 assert_eq!(p17.len(), 21 + 27);
289 assert!(p17.contains(&("camF/camF_0003.jpg".to_string(), "camF/camF_0051.jpg".to_string())));
290 }
291}