Skip to main content

cumulus3d_core/
store.rs

1//! 메모리 특징 저장소(영상·카메라·키포인트·기술자·매칭·두 뷰 기하). 이진 파일로 저장·적재 가능.
2//!
3//! 스레드 안전(RwLock). 큰 자료(키포인트·기술자·매칭·기하)는 `Arc` 로 공유해 조회 시 복사가 없다.
4//! 짝 자료는 항상 "작은 id → 큰 id" 방향으로 저장하고, 반대 방향 조회 시 역변환해 돌려준다.
5
6use crate::camera::{Camera, CameraModelKind};
7use crate::error::{Error, Result};
8use crate::features::{Descriptors, FeatureMatch, Keypoint, TwoViewGeometry, TwoViewGeometryConfig};
9use crate::geometry::{Mat3, Rigid3, Vec3};
10use crate::ids::*;
11use crate::io::binary::{check_count, ReadLe, WriteLe};
12use std::collections::{BTreeMap, HashMap};
13use std::io::{BufReader, BufWriter, Read, Write};
14use std::path::Path;
15use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
16
17/// 저장소의 영상 행.
18#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct StoreImage {
20    /// 영상 id.
21    pub image_id: ImageId,
22    /// 영상 이름(고유).
23    pub name: String,
24    /// 카메라 id.
25    pub camera_id: CameraId,
26}
27
28/// 사전 위치(WGS84 위도·경도(도), 고도(m)).
29#[derive(Clone, Copy, Debug, PartialEq)]
30pub struct PosePrior {
31    /// (위도, 경도, 고도) 또는 좌표계에 따른 위치.
32    pub position: Vec3,
33    /// 0 = WGS84.
34    pub coordinate_system: i32,
35    /// 중력 방향(선택).
36    pub gravity: Option<Vec3>,
37}
38
39#[derive(Default)]
40struct Inner {
41    cameras: BTreeMap<CameraId, Camera>,
42    images: BTreeMap<ImageId, StoreImage>,
43    name_to_id: HashMap<String, ImageId>,
44    keypoints: HashMap<ImageId, Arc<Vec<Keypoint>>>,
45    descriptors: HashMap<ImageId, Arc<Descriptors>>,
46    pose_priors: HashMap<ImageId, PosePrior>,
47    matches: HashMap<PairId, Arc<Vec<FeatureMatch>>>,
48    two_view: HashMap<PairId, Arc<TwoViewGeometry>>,
49    /// 두 뷰 기하 기록 로그(증분 소비용). 같은 짝이 다시 기록되면 중복될 수 있다.
50    tvg_log: Vec<PairId>,
51}
52
53/// 메모리 특징 저장소.
54#[derive(Default)]
55pub struct FeatureStore {
56    inner: RwLock<Inner>,
57}
58
59fn swap_matches(m: &[FeatureMatch]) -> Vec<FeatureMatch> {
60    m.iter().map(|x| x.swapped()).collect()
61}
62
63impl FeatureStore {
64    /// 빈 저장소.
65    pub fn new() -> Self {
66        Self::default()
67    }
68
69    fn r(&self) -> RwLockReadGuard<'_, Inner> {
70        self.inner.read().unwrap_or_else(|e| e.into_inner())
71    }
72    fn w(&self) -> RwLockWriteGuard<'_, Inner> {
73        self.inner.write().unwrap_or_else(|e| e.into_inner())
74    }
75
76    // ---------- 카메라 ----------
77
78    /// 카메라 추가. `camera_id` 가 무효값이면 "최대 id + 1"(빈 저장소에서 1)을 발급.
79    pub fn add_camera(&self, mut camera: Camera) -> Result<CameraId> {
80        let mut g = self.w();
81        if camera.camera_id == INVALID_CAMERA_ID {
82            camera.camera_id = g.cameras.keys().next_back().map_or(1, |k| k + 1);
83        } else if g.cameras.contains_key(&camera.camera_id) {
84            return Err(Error::AlreadyExists(format!("카메라 {}", camera.camera_id)));
85        }
86        let id = camera.camera_id;
87        g.cameras.insert(id, camera);
88        Ok(id)
89    }
90    /// 기존 카메라 덮어쓰기(없으면 오류).
91    pub fn update_camera(&self, camera: Camera) -> Result<()> {
92        let mut g = self.w();
93        match g.cameras.get_mut(&camera.camera_id) {
94            Some(c) => {
95                *c = camera;
96                Ok(())
97            }
98            None => Err(Error::NotFound(format!("카메라 {}", camera.camera_id))),
99        }
100    }
101    /// 카메라 조회(복사).
102    pub fn camera(&self, id: CameraId) -> Option<Camera> {
103        self.r().cameras.get(&id).cloned()
104    }
105    /// 카메라 전체(id 순).
106    pub fn cameras(&self) -> Vec<Camera> {
107        self.r().cameras.values().cloned().collect()
108    }
109    /// 카메라 수.
110    pub fn num_cameras(&self) -> usize {
111        self.r().cameras.len()
112    }
113
114    // ---------- 영상 ----------
115
116    /// 영상 추가(이름 고유, 카메라 존재 필요). id 는 "최대 id + 1"(빈 저장소에서 1).
117    pub fn add_image(&self, name: &str, camera_id: CameraId) -> Result<ImageId> {
118        let mut g = self.w();
119        let id = g.images.keys().next_back().map_or(1, |k| k + 1);
120        Self::add_image_locked(&mut g, id, name, camera_id)
121    }
122    /// 지정 id 로 영상 추가.
123    pub fn add_image_with_id(&self, image_id: ImageId, name: &str, camera_id: CameraId) -> Result<ImageId> {
124        Self::add_image_locked(&mut self.w(), image_id, name, camera_id)
125    }
126    fn add_image_locked(g: &mut Inner, image_id: ImageId, name: &str, camera_id: CameraId) -> Result<ImageId> {
127        if image_id as u64 >= MAX_NUM_IMAGES {
128            return Err(Error::InvalidArgument(format!("영상 id {image_id} 가 상한 이상")));
129        }
130        if !g.cameras.contains_key(&camera_id) {
131            return Err(Error::NotFound(format!("카메라 {camera_id}")));
132        }
133        if g.name_to_id.contains_key(name) {
134            return Err(Error::AlreadyExists(format!("영상 이름 {name}")));
135        }
136        if g.images.contains_key(&image_id) {
137            return Err(Error::AlreadyExists(format!("영상 {image_id}")));
138        }
139        g.name_to_id.insert(name.to_string(), image_id);
140        g.images.insert(image_id, StoreImage { image_id, name: name.to_string(), camera_id });
141        Ok(image_id)
142    }
143    /// 영상 조회.
144    pub fn image(&self, id: ImageId) -> Option<StoreImage> {
145        self.r().images.get(&id).cloned()
146    }
147    /// 이름 → 영상 id.
148    pub fn image_id_by_name(&self, name: &str) -> Option<ImageId> {
149        self.r().name_to_id.get(name).copied()
150    }
151    /// 이름으로 영상 조회.
152    pub fn image_by_name(&self, name: &str) -> Option<StoreImage> {
153        let g = self.r();
154        g.name_to_id.get(name).and_then(|id| g.images.get(id)).cloned()
155    }
156    /// 모든 영상(id 오름차순).
157    pub fn images(&self) -> Vec<StoreImage> {
158        self.r().images.values().cloned().collect()
159    }
160    /// 영상 id 목록(오름차순).
161    pub fn image_ids(&self) -> Vec<ImageId> {
162        self.r().images.keys().copied().collect()
163    }
164    /// 영상 수.
165    pub fn num_images(&self) -> usize {
166        self.r().images.len()
167    }
168    /// 영상이 있는지.
169    pub fn exists_image(&self, id: ImageId) -> bool {
170        self.r().images.contains_key(&id)
171    }
172
173    /// 영상 사전 위치 설정.
174    pub fn set_pose_prior(&self, image_id: ImageId, prior: PosePrior) {
175        self.w().pose_priors.insert(image_id, prior);
176    }
177    /// 영상 사전 위치 조회.
178    pub fn pose_prior(&self, image_id: ImageId) -> Option<PosePrior> {
179        self.r().pose_priors.get(&image_id).copied()
180    }
181
182    // ---------- 특징 ----------
183
184    /// 키포인트 기록(덮어씀).
185    pub fn set_keypoints(&self, image_id: ImageId, kps: Vec<Keypoint>) {
186        self.w().keypoints.insert(image_id, Arc::new(kps));
187    }
188    /// 기술자 기록(덮어씀).
189    pub fn set_descriptors(&self, image_id: ImageId, d: Descriptors) {
190        self.w().descriptors.insert(image_id, Arc::new(d));
191    }
192    /// 키포인트 조회(공유).
193    pub fn keypoints(&self, image_id: ImageId) -> Option<Arc<Vec<Keypoint>>> {
194        self.r().keypoints.get(&image_id).cloned()
195    }
196    /// 기술자 조회(공유).
197    pub fn descriptors(&self, image_id: ImageId) -> Option<Arc<Descriptors>> {
198        self.r().descriptors.get(&image_id).cloned()
199    }
200    /// 키포인트가 있는지.
201    pub fn exists_keypoints(&self, image_id: ImageId) -> bool {
202        self.r().keypoints.contains_key(&image_id)
203    }
204    /// 기술자가 있는지.
205    pub fn exists_descriptors(&self, image_id: ImageId) -> bool {
206        self.r().descriptors.contains_key(&image_id)
207    }
208    /// 영상의 키포인트 수(없으면 0).
209    pub fn num_keypoints(&self, image_id: ImageId) -> usize {
210        self.r().keypoints.get(&image_id).map_or(0, |k| k.len())
211    }
212    /// 영상별 키포인트 수의 최댓값.
213    pub fn largest_keypoint_count(&self) -> usize {
214        self.r().keypoints.values().map(|k| k.len()).max().unwrap_or(0)
215    }
216
217    // ---------- 매칭 ----------
218
219    /// 원시 매칭 기록(덮어씀). (id1, id2) 순서의 인덱스 쌍을 받는다.
220    pub fn write_matches(&self, id1: ImageId, id2: ImageId, matches: &[FeatureMatch]) -> Result<()> {
221        let pid = pair_id_of(id1, id2)?;
222        let v = if swap_image_pair(id1, id2) { swap_matches(matches) } else { matches.to_vec() };
223        self.w().matches.insert(pid, Arc::new(v));
224        Ok(())
225    }
226    /// (id1, id2) 방향으로 원시 매칭 조회.
227    pub fn read_matches(&self, id1: ImageId, id2: ImageId) -> Option<Arc<Vec<FeatureMatch>>> {
228        let pid = pair_id_of(id1, id2).ok()?;
229        let m = self.r().matches.get(&pid).cloned()?;
230        if swap_image_pair(id1, id2) {
231            Some(Arc::new(swap_matches(&m)))
232        } else {
233            Some(m)
234        }
235    }
236    /// 원시 매칭이 있는지.
237    pub fn exists_matches(&self, id1: ImageId, id2: ImageId) -> bool {
238        pair_id_of(id1, id2).is_ok_and(|p| self.r().matches.contains_key(&p))
239    }
240    /// 원시 매칭 삭제.
241    pub fn delete_matches(&self, id1: ImageId, id2: ImageId) {
242        if let Ok(p) = pair_id_of(id1, id2) {
243            self.w().matches.remove(&p);
244        }
245    }
246
247    /// 두 뷰 기하 기록(덮어씀). (id1 → id2) 방향 기하를 받는다.
248    pub fn put_two_view(&self, id1: ImageId, id2: ImageId, tvg: &TwoViewGeometry) -> Result<()> {
249        let pid = pair_id_of(id1, id2)?;
250        let v = if swap_image_pair(id1, id2) { tvg.inverted() } else { tvg.clone() };
251        let mut g = self.w();
252        g.two_view.insert(pid, Arc::new(v));
253        g.tvg_log.push(pid);
254        Ok(())
255    }
256    /// (id1 → id2) 방향 두 뷰 기하.
257    pub fn get_two_view(&self, id1: ImageId, id2: ImageId) -> Option<Arc<TwoViewGeometry>> {
258        let pid = pair_id_of(id1, id2).ok()?;
259        let t = self.r().two_view.get(&pid).cloned()?;
260        if swap_image_pair(id1, id2) {
261            Some(Arc::new(t.inverted()))
262        } else {
263            Some(t)
264        }
265    }
266    /// 두 뷰 기하가 있는지.
267    pub fn contains_two_view(&self, id1: ImageId, id2: ImageId) -> bool {
268        pair_id_of(id1, id2).is_ok_and(|p| self.r().two_view.contains_key(&p))
269    }
270    /// 두 뷰 기하 삭제.
271    pub fn remove_two_view(&self, id1: ImageId, id2: ImageId) {
272        if let Ok(p) = pair_id_of(id1, id2) {
273            self.w().two_view.remove(&p);
274        }
275    }
276    /// 저장된 모든 두 뷰 기하(짝 id 오름차순, 저장 방향 = 작은 id → 큰 id).
277    pub fn two_view_geometries(&self) -> Vec<(PairId, Arc<TwoViewGeometry>)> {
278        let g = self.r();
279        let mut v: Vec<_> = g.two_view.iter().map(|(k, t)| (*k, t.clone())).collect();
280        v.sort_by_key(|x| x.0);
281        v
282    }
283    /// 로그 위치 `since` 이후 기록된 짝들과 새 로그 위치. 증분 대응 그래프 갱신용.
284    pub fn two_view_geometries_since(&self, since: usize) -> (Vec<(PairId, Arc<TwoViewGeometry>)>, usize) {
285        let g = self.r();
286        let end = g.tvg_log.len();
287        let mut out = Vec::new();
288        for &pid in &g.tvg_log[since.min(end)..] {
289            if let Some(t) = g.two_view.get(&pid) {
290                out.push((pid, t.clone()));
291            }
292        }
293        (out, end)
294    }
295    /// 저장된 두 뷰 기하 수.
296    pub fn num_two_view_geometries(&self) -> usize {
297        self.r().two_view.len()
298    }
299    /// 원시 매칭이 저장된 짝 수.
300    pub fn num_matched_pairs(&self) -> usize {
301        self.r().matches.len()
302    }
303
304    /// `keep` 이 참인 영상만 남긴 새 저장소. 카메라는 모두, 영상 id·짝 자료·기하 기록 순서는 그대로 유지한다.
305    /// 큰 자료(키포인트·기술자·매칭·기하)는 `Arc` 로 공유하므로 복사가 싸다. 점진 처리의 되감기용.
306    pub fn retain_copy(&self, keep: impl Fn(&StoreImage) -> bool) -> FeatureStore {
307        let g = self.r();
308        let images: BTreeMap<ImageId, StoreImage> = g.images.iter().filter(|(_, im)| keep(im)).map(|(k, v)| (*k, v.clone())).collect();
309        let pair_ok = |pid: &PairId| {
310            let (a, b) = images_of_pair(*pid);
311            images.contains_key(&a) && images.contains_key(&b)
312        };
313        let inner = Inner {
314            cameras: g.cameras.clone(),
315            name_to_id: images.values().map(|im| (im.name.clone(), im.image_id)).collect(),
316            keypoints: g.keypoints.iter().filter(|(k, _)| images.contains_key(k)).map(|(k, v)| (*k, v.clone())).collect(),
317            descriptors: g.descriptors.iter().filter(|(k, _)| images.contains_key(k)).map(|(k, v)| (*k, v.clone())).collect(),
318            pose_priors: g.pose_priors.iter().filter(|(k, _)| images.contains_key(k)).map(|(k, v)| (*k, *v)).collect(),
319            matches: g.matches.iter().filter(|(k, _)| pair_ok(k)).map(|(k, v)| (*k, v.clone())).collect(),
320            two_view: g.two_view.iter().filter(|(k, _)| pair_ok(k)).map(|(k, v)| (*k, v.clone())).collect(),
321            tvg_log: g.tvg_log.iter().copied().filter(|k| pair_ok(k)).collect(),
322            images,
323        };
324        FeatureStore { inner: RwLock::new(inner) }
325    }
326
327    // ---------- 저장/로드 ----------
328
329    const MAGIC: &'static [u8; 8] = b"C3DFS\0v1";
330    /// 0.3.0 까지 쓰던 표지. 읽을 때만 받아들인다(형식은 같음).
331    const LEGACY_MAGIC: &'static [u8; 8] = &[b'S', b'K', b'Y', b'F', b'S', 0, b'v', b'1'];
332
333    /// 자체 이진 형식으로 저장(리틀 엔디언).
334    pub fn save(&self, path: impl AsRef<Path>) -> Result<()> {
335        let g = self.r();
336        let mut w = BufWriter::new(std::fs::File::create(path)?);
337        w.write_all(Self::MAGIC)?;
338        w.put_u64(g.cameras.len() as u64)?;
339        for c in g.cameras.values() {
340            w.put_u32(c.camera_id)?;
341            w.put_i32(c.model.id())?;
342            w.put_u64(c.width)?;
343            w.put_u64(c.height)?;
344            w.put_u8(c.focal_from_prior as u8)?;
345            w.put_u64(c.params.len() as u64)?;
346            w.put_f64s(&c.params)?;
347        }
348        w.put_u64(g.images.len() as u64)?;
349        for im in g.images.values() {
350            w.put_u32(im.image_id)?;
351            w.put_u32(im.camera_id)?;
352            w.put_u64(im.name.len() as u64)?;
353            w.write_all(im.name.as_bytes())?;
354        }
355        let mut ids: Vec<_> = g.keypoints.keys().copied().collect();
356        ids.sort();
357        w.put_u64(ids.len() as u64)?;
358        for id in ids {
359            let k = &g.keypoints[&id];
360            w.put_u32(id)?;
361            w.put_u64(k.len() as u64)?;
362            for kp in k.iter() {
363                for x in kp.to_row() {
364                    w.put_f32(x)?;
365                }
366            }
367        }
368        let mut ids: Vec<_> = g.descriptors.keys().copied().collect();
369        ids.sort();
370        w.put_u64(ids.len() as u64)?;
371        for id in ids {
372            let d = &g.descriptors[&id];
373            w.put_u32(id)?;
374            w.put_u64(d.len() as u64)?;
375            w.write_all(d.as_slice())?;
376        }
377        let mut ids: Vec<_> = g.pose_priors.keys().copied().collect();
378        ids.sort();
379        w.put_u64(ids.len() as u64)?;
380        for id in ids {
381            let p = &g.pose_priors[&id];
382            w.put_u32(id)?;
383            w.put_f64s(p.position.as_slice())?;
384            w.put_i32(p.coordinate_system)?;
385            match p.gravity {
386                Some(gv) => {
387                    w.put_u8(1)?;
388                    w.put_f64s(gv.as_slice())?;
389                }
390                None => w.put_u8(0)?,
391            }
392        }
393        let mut pids: Vec<_> = g.matches.keys().copied().collect();
394        pids.sort();
395        w.put_u64(pids.len() as u64)?;
396        for pid in pids {
397            let m = &g.matches[&pid];
398            w.put_u64(pid)?;
399            write_match_list(&mut w, m)?;
400        }
401        let mut pids: Vec<_> = g.two_view.keys().copied().collect();
402        pids.sort();
403        w.put_u64(pids.len() as u64)?;
404        for pid in pids {
405            let t = &g.two_view[&pid];
406            w.put_u64(pid)?;
407            w.put_i32(t.config.as_i32())?;
408            for m in [&t.e, &t.f, &t.h] {
409                match m {
410                    Some(m) => {
411                        w.put_u8(1)?;
412                        for r in 0..3 {
413                            for c in 0..3 {
414                                w.put_f64(m[(r, c)])?;
415                            }
416                        }
417                    }
418                    None => w.put_u8(0)?,
419                }
420            }
421            match &t.cam1_to_cam2 {
422                Some(p) => {
423                    w.put_u8(1)?;
424                    w.put_f64s(&p.to_params())?;
425                }
426                None => w.put_u8(0)?,
427            }
428            write_match_list(&mut w, &t.inlier_matches)?;
429        }
430        w.flush()?;
431        Ok(())
432    }
433
434    /// `save` 형식 읽기.
435    pub fn load(path: impl AsRef<Path>) -> Result<Self> {
436        let mut r = BufReader::new(std::fs::File::open(path)?);
437        let mut magic = [0u8; 8];
438        r.read_exact(&mut magic)?;
439        if &magic != Self::MAGIC && &magic != Self::LEGACY_MAGIC {
440            return Err(Error::Format("특징 저장소 파일 표지가 다름".into()));
441        }
442        let mut g = Inner::default();
443        for _ in 0..check_count(r.get_u64()?, "카메라")? {
444            let id = r.get_u32()?;
445            let model = CameraModelKind::from_id(r.get_i32()?)?;
446            let width = r.get_u64()?;
447            let height = r.get_u64()?;
448            let prior = r.get_u8()? != 0;
449            let n = check_count(r.get_u64()?, "파라미터")?;
450            let mut params = Vec::with_capacity(n);
451            for _ in 0..n {
452                params.push(r.get_f64()?);
453            }
454            let mut c = Camera::new(id, model, width, height, params)?;
455            c.focal_from_prior = prior;
456            g.cameras.insert(id, c);
457        }
458        for _ in 0..check_count(r.get_u64()?, "영상")? {
459            let image_id = r.get_u32()?;
460            let camera_id = r.get_u32()?;
461            let n = check_count(r.get_u64()?, "이름")?;
462            let name = String::from_utf8(r.get_vec_u8(n)?).map_err(|_| Error::Format("이름 UTF-8 아님".into()))?;
463            g.name_to_id.insert(name.clone(), image_id);
464            g.images.insert(image_id, StoreImage { image_id, name, camera_id });
465        }
466        for _ in 0..check_count(r.get_u64()?, "키포인트 묶음")? {
467            let id = r.get_u32()?;
468            let n = check_count(r.get_u64()?, "키포인트")?;
469            let mut v = Vec::with_capacity(n);
470            for _ in 0..n {
471                let mut row = [0f32; 6];
472                for x in row.iter_mut() {
473                    *x = r.get_f32()?;
474                }
475                v.push(Keypoint::from_row(&row)?);
476            }
477            g.keypoints.insert(id, Arc::new(v));
478        }
479        for _ in 0..check_count(r.get_u64()?, "기술자 묶음")? {
480            let id = r.get_u32()?;
481            let n = check_count(r.get_u64()?, "기술자")?;
482            let d = Descriptors::from_vec(r.get_vec_u8(n * crate::features::DESCRIPTOR_DIM)?)?;
483            g.descriptors.insert(id, Arc::new(d));
484        }
485        for _ in 0..check_count(r.get_u64()?, "사전 위치")? {
486            let id = r.get_u32()?;
487            let p = r.get_f64_array::<3>()?;
488            let cs = r.get_i32()?;
489            let gravity = if r.get_u8()? != 0 { Some(Vec3::from(r.get_f64_array::<3>()?)) } else { None };
490            g.pose_priors.insert(id, PosePrior { position: Vec3::from(p), coordinate_system: cs, gravity });
491        }
492        for _ in 0..check_count(r.get_u64()?, "매칭 짝")? {
493            let pid = r.get_u64()?;
494            g.matches.insert(pid, Arc::new(read_match_list(&mut r)?));
495        }
496        for _ in 0..check_count(r.get_u64()?, "두 뷰 기하")? {
497            let pid = r.get_u64()?;
498            let config = TwoViewGeometryConfig::from_i32(r.get_i32()?)
499                .ok_or_else(|| Error::Format("두 뷰 기하 config 값이 잘못됨".into()))?;
500            let mut mats = [None, None, None];
501            for m in mats.iter_mut() {
502                if r.get_u8()? != 0 {
503                    let a = r.get_f64_array::<9>()?;
504                    *m = Some(Mat3::from_row_slice(&a));
505                }
506            }
507            let cam1_to_cam2 = if r.get_u8()? != 0 { Some(Rigid3::from_params(&r.get_f64_array::<7>()?)) } else { None };
508            let inlier_matches = read_match_list(&mut r)?;
509            let [e, f, h] = mats;
510            g.two_view.insert(
511                pid,
512                Arc::new(TwoViewGeometry { config, e, f, h, cam1_to_cam2, inlier_matches, tri_angle: None }),
513            );
514            g.tvg_log.push(pid);
515        }
516        Ok(Self { inner: RwLock::new(g) })
517    }
518}
519
520fn write_match_list(w: &mut impl Write, m: &[FeatureMatch]) -> Result<()> {
521    w.put_u64(m.len() as u64)?;
522    for x in m {
523        w.put_u32(x.idx1)?;
524        w.put_u32(x.idx2)?;
525    }
526    Ok(())
527}
528
529fn read_match_list(r: &mut impl Read) -> Result<Vec<FeatureMatch>> {
530    let n = check_count(r.get_u64()?, "매칭")?;
531    let mut v = Vec::with_capacity(n);
532    for _ in 0..n {
533        let a = r.get_u32()?;
534        let b = r.get_u32()?;
535        v.push(FeatureMatch::new(a, b));
536    }
537    Ok(v)
538}
539
540#[cfg(test)]
541mod tests {
542    use super::*;
543
544    #[test]
545    fn store_basic_and_roundtrip() {
546        let s = FeatureStore::new();
547        let mut cam = Camera::from_focal(CameraModelKind::OpenCv, 1000.0, 2000, 1000);
548        cam.focal_from_prior = true;
549        let c1 = s.add_camera(cam.clone()).unwrap();
550        assert_eq!(c1, 1);
551        let c2 = s.add_camera(cam).unwrap();
552        assert_eq!(c2, 2);
553        let i1 = s.add_image("camF/0.jpg", c1).unwrap();
554        let i2 = s.add_image("camR/0.jpg", c2).unwrap();
555        assert!(s.add_image("camF/0.jpg", c1).is_err());
556        assert_eq!(s.image_id_by_name("camR/0.jpg"), Some(i2));
557        s.set_keypoints(i1, vec![Keypoint::new(1.5, 2.5), Keypoint::from_scale_orientation(3.0, 4.0, 2.0, 0.5)]);
558        s.set_keypoints(i2, vec![Keypoint::new(0.5, 0.5)]);
559        let mut d = Descriptors::new();
560        d.push(&[7u8; 128]);
561        s.set_descriptors(i1, d);
562        s.set_pose_prior(i1, PosePrior { position: Vec3::new(37.5, 127.0, 50.0), coordinate_system: 0, gravity: None });
563        // reversed write
564        s.write_matches(i2, i1, &[FeatureMatch::new(0, 1)]).unwrap();
565        assert_eq!(s.read_matches(i1, i2).unwrap()[0], FeatureMatch::new(1, 0));
566        assert_eq!(s.read_matches(i2, i1).unwrap()[0], FeatureMatch::new(0, 1));
567        let h = Mat3::new(2.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0);
568        let tvg = TwoViewGeometry {
569            config: TwoViewGeometryConfig::PlanarOrRotation,
570            h: Some(h),
571            cam1_to_cam2: Some(Rigid3::new(crate::geometry::Quat::from_axis_angle(&Vec3::y(), 0.3), Vec3::new(1.0, 0.0, 0.0))),
572            inlier_matches: vec![FeatureMatch::new(0, 1)],
573            ..Default::default()
574        };
575        s.put_two_view(i2, i1, &tvg).unwrap();
576        let back = s.get_two_view(i2, i1).unwrap();
577        assert!((back.h.unwrap() - h).norm() < 1e-12);
578        let fwd = s.get_two_view(i1, i2).unwrap();
579        assert_eq!(fwd.inlier_matches[0], FeatureMatch::new(1, 0));
580        let p = fwd.cam1_to_cam2.unwrap() * tvg.cam1_to_cam2.unwrap();
581        assert!(p.rotation.angular_distance(&crate::geometry::Quat::IDENTITY) < 1e-7);
582        let (since, pos) = s.two_view_geometries_since(0);
583        assert_eq!(since.len(), 1);
584        assert_eq!(s.two_view_geometries_since(pos).0.len(), 0);
585
586        let dir = std::env::temp_dir().join(format!("cumulus3d_store_{}", std::process::id()));
587        std::fs::create_dir_all(&dir).unwrap();
588        let path = dir.join("store.bin");
589        s.save(&path).unwrap();
590        let l = FeatureStore::load(&path).unwrap();
591        assert_eq!(l.cameras(), s.cameras());
592        assert_eq!(l.images(), s.images());
593        assert_eq!(l.keypoints(i1), s.keypoints(i1));
594        assert_eq!(l.descriptors(i1), s.descriptors(i1));
595        assert_eq!(l.pose_prior(i1), s.pose_prior(i1));
596        assert_eq!(l.read_matches(i1, i2), s.read_matches(i1, i2));
597        assert_eq!(l.get_two_view(i1, i2), s.get_two_view(i1, i2));
598        // 옛 표지로 쓴 파일도 읽힌다.
599        let mut bytes = std::fs::read(&path).unwrap();
600        bytes[..8].copy_from_slice(FeatureStore::LEGACY_MAGIC);
601        let legacy = dir.join("legacy.bin");
602        std::fs::write(&legacy, &bytes).unwrap();
603        assert_eq!(FeatureStore::load(&legacy).unwrap().images(), s.images());
604        std::fs::remove_dir_all(&dir).ok();
605    }
606
607    #[test]
608    fn store_concurrent_adds() {
609        let s = Arc::new(FeatureStore::new());
610        s.add_camera(Camera::from_focal(CameraModelKind::OpenCv, 1.0, 10, 10)).unwrap();
611        std::thread::scope(|sc| {
612            for t in 0..8 {
613                let s = s.clone();
614                sc.spawn(move || {
615                    for k in 0..50 {
616                        let id = s.add_image(&format!("t{t}/{k}"), 1).unwrap();
617                        s.set_keypoints(id, vec![Keypoint::new(0.5, 0.5); k]);
618                    }
619                });
620            }
621        });
622        assert_eq!(s.num_images(), 400);
623        assert_eq!(s.largest_keypoint_count(), 49);
624    }
625
626    #[test]
627    fn retain_copy_drops_images_and_pairs() {
628        let s = FeatureStore::new();
629        let c = s.add_camera(Camera::from_focal(CameraModelKind::OpenCv, 100.0, 64, 48)).unwrap();
630        let ids: Vec<ImageId> = (0..3).map(|i| s.add_image(&format!("cam/{i}.jpg"), c).unwrap()).collect();
631        for &i in &ids {
632            s.set_keypoints(i, vec![Keypoint::new(1.0, 1.0)]);
633        }
634        let tvg = TwoViewGeometry { inlier_matches: vec![FeatureMatch::new(0, 0)], ..Default::default() };
635        s.put_two_view(ids[0], ids[1], &tvg).unwrap();
636        s.put_two_view(ids[1], ids[2], &tvg).unwrap();
637        s.write_matches(ids[0], ids[2], &[FeatureMatch::new(0, 0)]).unwrap();
638        let r = s.retain_copy(|im| im.image_id != ids[2]);
639        assert_eq!(r.num_images(), 2);
640        assert_eq!(r.num_cameras(), 1);
641        assert!(r.image_id_by_name("cam/2.jpg").is_none());
642        assert!(r.contains_two_view(ids[0], ids[1]));
643        assert!(!r.contains_two_view(ids[1], ids[2]));
644        assert!(!r.exists_matches(ids[0], ids[2]));
645        assert_eq!(r.two_view_geometries_since(0).1, 1);
646        // 새 영상 id 는 남은 최대 id + 1.
647        assert_eq!(r.add_image("cam/9.jpg", c).unwrap(), ids[2]);
648        // 원래 저장소는 그대로.
649        assert_eq!(s.num_images(), 3);
650    }
651}