1use crate::camera::{Camera, CameraModelKind};
39use crate::error::{Error, Result};
40use crate::features::{Descriptors, FeatureMatch, Keypoint, TwoViewGeometry, TwoViewGeometryConfig};
41use crate::geometry::{Mat3, Rigid3, Vec3};
42use crate::ids::*;
43use crate::io::binary::{check_count, ReadLe, WriteLe};
44use std::collections::{BTreeMap, HashMap};
45use std::io::{BufReader, BufWriter, Read, Write};
46use std::path::Path;
47use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
48
49#[derive(Clone, Debug, PartialEq, Eq)]
51pub struct StoreImage {
52 pub image_id: ImageId,
54 pub name: String,
56 pub camera_id: CameraId,
58}
59
60#[derive(Clone, Copy, Debug, PartialEq)]
62pub struct PosePrior {
63 pub position: Vec3,
65 pub coordinate_system: i32,
67 pub gravity: Option<Vec3>,
69}
70
71#[derive(Default)]
72struct Inner {
73 cameras: BTreeMap<CameraId, Camera>,
74 images: BTreeMap<ImageId, StoreImage>,
75 name_to_id: HashMap<String, ImageId>,
76 keypoints: HashMap<ImageId, Arc<Vec<Keypoint>>>,
77 descriptors: HashMap<ImageId, Arc<Descriptors>>,
78 pose_priors: HashMap<ImageId, PosePrior>,
79 matches: HashMap<PairId, Arc<Vec<FeatureMatch>>>,
80 two_view: HashMap<PairId, Arc<TwoViewGeometry>>,
81 tvg_log: Vec<PairId>,
83}
84
85#[derive(Default)]
87pub struct FeatureStore {
88 inner: RwLock<Inner>,
89}
90
91fn swap_matches(m: &[FeatureMatch]) -> Vec<FeatureMatch> {
92 m.iter().map(|x| x.swapped()).collect()
93}
94
95impl FeatureStore {
96 pub fn new() -> Self {
98 Self::default()
99 }
100
101 fn r(&self) -> RwLockReadGuard<'_, Inner> {
102 self.inner.read().unwrap_or_else(|e| e.into_inner())
103 }
104 fn w(&self) -> RwLockWriteGuard<'_, Inner> {
105 self.inner.write().unwrap_or_else(|e| e.into_inner())
106 }
107
108 pub fn add_camera(&self, mut camera: Camera) -> Result<CameraId> {
112 let mut g = self.w();
113 if camera.camera_id == INVALID_CAMERA_ID {
114 camera.camera_id = g.cameras.keys().next_back().map_or(1, |k| k + 1);
115 } else if g.cameras.contains_key(&camera.camera_id) {
116 return Err(Error::AlreadyExists(format!("카메라 {}", camera.camera_id)));
117 }
118 let id = camera.camera_id;
119 g.cameras.insert(id, camera);
120 Ok(id)
121 }
122 pub fn update_camera(&self, camera: Camera) -> Result<()> {
124 let mut g = self.w();
125 match g.cameras.get_mut(&camera.camera_id) {
126 Some(c) => {
127 *c = camera;
128 Ok(())
129 }
130 None => Err(Error::NotFound(format!("카메라 {}", camera.camera_id))),
131 }
132 }
133 pub fn camera(&self, id: CameraId) -> Option<Camera> {
135 self.r().cameras.get(&id).cloned()
136 }
137 pub fn cameras(&self) -> Vec<Camera> {
139 self.r().cameras.values().cloned().collect()
140 }
141 pub fn num_cameras(&self) -> usize {
143 self.r().cameras.len()
144 }
145
146 pub fn add_image(&self, name: &str, camera_id: CameraId) -> Result<ImageId> {
150 let mut g = self.w();
151 let id = g.images.keys().next_back().map_or(1, |k| k + 1);
152 Self::add_image_locked(&mut g, id, name, camera_id)
153 }
154 pub fn add_image_with_id(&self, image_id: ImageId, name: &str, camera_id: CameraId) -> Result<ImageId> {
156 Self::add_image_locked(&mut self.w(), image_id, name, camera_id)
157 }
158 fn add_image_locked(g: &mut Inner, image_id: ImageId, name: &str, camera_id: CameraId) -> Result<ImageId> {
159 if image_id as u64 >= MAX_NUM_IMAGES {
160 return Err(Error::InvalidArgument(format!("영상 id {image_id} 가 상한 이상")));
161 }
162 if !g.cameras.contains_key(&camera_id) {
163 return Err(Error::NotFound(format!("카메라 {camera_id}")));
164 }
165 if g.name_to_id.contains_key(name) {
166 return Err(Error::AlreadyExists(format!("영상 이름 {name}")));
167 }
168 if g.images.contains_key(&image_id) {
169 return Err(Error::AlreadyExists(format!("영상 {image_id}")));
170 }
171 g.name_to_id.insert(name.to_string(), image_id);
172 g.images.insert(image_id, StoreImage { image_id, name: name.to_string(), camera_id });
173 Ok(image_id)
174 }
175 pub fn image(&self, id: ImageId) -> Option<StoreImage> {
177 self.r().images.get(&id).cloned()
178 }
179 pub fn image_id_by_name(&self, name: &str) -> Option<ImageId> {
181 self.r().name_to_id.get(name).copied()
182 }
183 pub fn image_by_name(&self, name: &str) -> Option<StoreImage> {
185 let g = self.r();
186 g.name_to_id.get(name).and_then(|id| g.images.get(id)).cloned()
187 }
188 pub fn images(&self) -> Vec<StoreImage> {
190 self.r().images.values().cloned().collect()
191 }
192 pub fn image_ids(&self) -> Vec<ImageId> {
194 self.r().images.keys().copied().collect()
195 }
196 pub fn num_images(&self) -> usize {
198 self.r().images.len()
199 }
200 pub fn exists_image(&self, id: ImageId) -> bool {
202 self.r().images.contains_key(&id)
203 }
204
205 pub fn set_pose_prior(&self, image_id: ImageId, prior: PosePrior) {
207 self.w().pose_priors.insert(image_id, prior);
208 }
209 pub fn pose_prior(&self, image_id: ImageId) -> Option<PosePrior> {
211 self.r().pose_priors.get(&image_id).copied()
212 }
213
214 pub fn set_keypoints(&self, image_id: ImageId, kps: Vec<Keypoint>) {
218 self.w().keypoints.insert(image_id, Arc::new(kps));
219 }
220 pub fn set_descriptors(&self, image_id: ImageId, d: Descriptors) {
222 self.w().descriptors.insert(image_id, Arc::new(d));
223 }
224 pub fn keypoints(&self, image_id: ImageId) -> Option<Arc<Vec<Keypoint>>> {
226 self.r().keypoints.get(&image_id).cloned()
227 }
228 pub fn descriptors(&self, image_id: ImageId) -> Option<Arc<Descriptors>> {
230 self.r().descriptors.get(&image_id).cloned()
231 }
232 pub fn exists_keypoints(&self, image_id: ImageId) -> bool {
234 self.r().keypoints.contains_key(&image_id)
235 }
236 pub fn exists_descriptors(&self, image_id: ImageId) -> bool {
238 self.r().descriptors.contains_key(&image_id)
239 }
240 pub fn num_keypoints(&self, image_id: ImageId) -> usize {
242 self.r().keypoints.get(&image_id).map_or(0, |k| k.len())
243 }
244 pub fn largest_keypoint_count(&self) -> usize {
246 self.r().keypoints.values().map(|k| k.len()).max().unwrap_or(0)
247 }
248
249 pub fn write_matches(&self, id1: ImageId, id2: ImageId, matches: &[FeatureMatch]) -> Result<()> {
253 let pid = pair_id_of(id1, id2)?;
254 let v = if swap_image_pair(id1, id2) { swap_matches(matches) } else { matches.to_vec() };
255 self.w().matches.insert(pid, Arc::new(v));
256 Ok(())
257 }
258 pub fn read_matches(&self, id1: ImageId, id2: ImageId) -> Option<Arc<Vec<FeatureMatch>>> {
260 let pid = pair_id_of(id1, id2).ok()?;
261 let m = self.r().matches.get(&pid).cloned()?;
262 if swap_image_pair(id1, id2) {
263 Some(Arc::new(swap_matches(&m)))
264 } else {
265 Some(m)
266 }
267 }
268 pub fn exists_matches(&self, id1: ImageId, id2: ImageId) -> bool {
270 pair_id_of(id1, id2).is_ok_and(|p| self.r().matches.contains_key(&p))
271 }
272 pub fn delete_matches(&self, id1: ImageId, id2: ImageId) {
274 if let Ok(p) = pair_id_of(id1, id2) {
275 self.w().matches.remove(&p);
276 }
277 }
278
279 pub fn put_two_view(&self, id1: ImageId, id2: ImageId, tvg: &TwoViewGeometry) -> Result<()> {
281 let pid = pair_id_of(id1, id2)?;
282 let v = if swap_image_pair(id1, id2) { tvg.inverted() } else { tvg.clone() };
283 let mut g = self.w();
284 g.two_view.insert(pid, Arc::new(v));
285 g.tvg_log.push(pid);
286 Ok(())
287 }
288 pub fn get_two_view(&self, id1: ImageId, id2: ImageId) -> Option<Arc<TwoViewGeometry>> {
290 let pid = pair_id_of(id1, id2).ok()?;
291 let t = self.r().two_view.get(&pid).cloned()?;
292 if swap_image_pair(id1, id2) {
293 Some(Arc::new(t.inverted()))
294 } else {
295 Some(t)
296 }
297 }
298 pub fn contains_two_view(&self, id1: ImageId, id2: ImageId) -> bool {
300 pair_id_of(id1, id2).is_ok_and(|p| self.r().two_view.contains_key(&p))
301 }
302 pub fn remove_two_view(&self, id1: ImageId, id2: ImageId) {
304 if let Ok(p) = pair_id_of(id1, id2) {
305 self.w().two_view.remove(&p);
306 }
307 }
308 pub fn two_view_geometries(&self) -> Vec<(PairId, Arc<TwoViewGeometry>)> {
310 let g = self.r();
311 let mut v: Vec<_> = g.two_view.iter().map(|(k, t)| (*k, t.clone())).collect();
312 v.sort_by_key(|x| x.0);
313 v
314 }
315 pub fn two_view_geometries_since(&self, since: usize) -> (Vec<(PairId, Arc<TwoViewGeometry>)>, usize) {
317 let g = self.r();
318 let end = g.tvg_log.len();
319 let mut out = Vec::new();
320 for &pid in &g.tvg_log[since.min(end)..] {
321 if let Some(t) = g.two_view.get(&pid) {
322 out.push((pid, t.clone()));
323 }
324 }
325 (out, end)
326 }
327 pub fn num_two_view_geometries(&self) -> usize {
329 self.r().two_view.len()
330 }
331 pub fn num_matched_pairs(&self) -> usize {
333 self.r().matches.len()
334 }
335
336 pub fn retain_copy(&self, keep: impl Fn(&StoreImage) -> bool) -> FeatureStore {
340 let g = self.r();
341 let images: BTreeMap<ImageId, StoreImage> = g.images.iter().filter(|(_, im)| keep(im)).map(|(k, v)| (*k, v.clone())).collect();
342 let pair_ok = |pid: &PairId| {
343 let (a, b) = images_of_pair(*pid);
344 images.contains_key(&a) && images.contains_key(&b)
345 };
346 let inner = Inner {
347 cameras: g.cameras.clone(),
348 name_to_id: images.values().map(|im| (im.name.clone(), im.image_id)).collect(),
349 keypoints: g.keypoints.iter().filter(|(k, _)| images.contains_key(k)).map(|(k, v)| (*k, v.clone())).collect(),
350 descriptors: g.descriptors.iter().filter(|(k, _)| images.contains_key(k)).map(|(k, v)| (*k, v.clone())).collect(),
351 pose_priors: g.pose_priors.iter().filter(|(k, _)| images.contains_key(k)).map(|(k, v)| (*k, *v)).collect(),
352 matches: g.matches.iter().filter(|(k, _)| pair_ok(k)).map(|(k, v)| (*k, v.clone())).collect(),
353 two_view: g.two_view.iter().filter(|(k, _)| pair_ok(k)).map(|(k, v)| (*k, v.clone())).collect(),
354 tvg_log: g.tvg_log.iter().copied().filter(|k| pair_ok(k)).collect(),
355 images,
356 };
357 FeatureStore { inner: RwLock::new(inner) }
358 }
359
360 const MAGIC: &'static [u8; 8] = b"C3DFS\0v1";
363 const LEGACY_MAGIC: &'static [u8; 8] = &[b'S', b'K', b'Y', b'F', b'S', 0, b'v', b'1'];
365
366 pub fn save(&self, path: impl AsRef<Path>) -> Result<()> {
368 let g = self.r();
369 let mut w = BufWriter::new(std::fs::File::create(path)?);
370 w.write_all(Self::MAGIC)?;
371 w.put_u64(g.cameras.len() as u64)?;
372 for c in g.cameras.values() {
373 w.put_u32(c.camera_id)?;
374 w.put_i32(c.model.id())?;
375 w.put_u64(c.width)?;
376 w.put_u64(c.height)?;
377 w.put_u8(c.focal_from_prior as u8)?;
378 w.put_u64(c.params.len() as u64)?;
379 w.put_f64s(&c.params)?;
380 }
381 w.put_u64(g.images.len() as u64)?;
382 for im in g.images.values() {
383 w.put_u32(im.image_id)?;
384 w.put_u32(im.camera_id)?;
385 w.put_u64(im.name.len() as u64)?;
386 w.write_all(im.name.as_bytes())?;
387 }
388 let mut ids: Vec<_> = g.keypoints.keys().copied().collect();
389 ids.sort();
390 w.put_u64(ids.len() as u64)?;
391 for id in ids {
392 let k = &g.keypoints[&id];
393 w.put_u32(id)?;
394 w.put_u64(k.len() as u64)?;
395 for kp in k.iter() {
396 for x in kp.to_row() {
397 w.put_f32(x)?;
398 }
399 }
400 }
401 let mut ids: Vec<_> = g.descriptors.keys().copied().collect();
402 ids.sort();
403 w.put_u64(ids.len() as u64)?;
404 for id in ids {
405 let d = &g.descriptors[&id];
406 w.put_u32(id)?;
407 w.put_u64(d.len() as u64)?;
408 w.write_all(d.as_slice())?;
409 }
410 let mut ids: Vec<_> = g.pose_priors.keys().copied().collect();
411 ids.sort();
412 w.put_u64(ids.len() as u64)?;
413 for id in ids {
414 let p = &g.pose_priors[&id];
415 w.put_u32(id)?;
416 w.put_f64s(p.position.as_slice())?;
417 w.put_i32(p.coordinate_system)?;
418 match p.gravity {
419 Some(gv) => {
420 w.put_u8(1)?;
421 w.put_f64s(gv.as_slice())?;
422 }
423 None => w.put_u8(0)?,
424 }
425 }
426 let mut pids: Vec<_> = g.matches.keys().copied().collect();
427 pids.sort();
428 w.put_u64(pids.len() as u64)?;
429 for pid in pids {
430 let m = &g.matches[&pid];
431 w.put_u64(pid)?;
432 write_match_list(&mut w, m)?;
433 }
434 let mut pids: Vec<_> = g.two_view.keys().copied().collect();
435 pids.sort();
436 w.put_u64(pids.len() as u64)?;
437 for pid in pids {
438 let t = &g.two_view[&pid];
439 w.put_u64(pid)?;
440 w.put_i32(t.config.as_i32())?;
441 for m in [&t.e, &t.f, &t.h] {
442 match m {
443 Some(m) => {
444 w.put_u8(1)?;
445 for r in 0..3 {
446 for c in 0..3 {
447 w.put_f64(m[(r, c)])?;
448 }
449 }
450 }
451 None => w.put_u8(0)?,
452 }
453 }
454 match &t.cam1_to_cam2 {
455 Some(p) => {
456 w.put_u8(1)?;
457 w.put_f64s(&p.to_params())?;
458 }
459 None => w.put_u8(0)?,
460 }
461 write_match_list(&mut w, &t.inlier_matches)?;
462 }
463 w.flush()?;
464 Ok(())
465 }
466
467 pub fn load(path: impl AsRef<Path>) -> Result<Self> {
469 let mut r = BufReader::new(std::fs::File::open(path)?);
470 let mut magic = [0u8; 8];
471 r.read_exact(&mut magic)?;
472 if &magic != Self::MAGIC && &magic != Self::LEGACY_MAGIC {
473 return Err(Error::Format("특징 저장소 파일 표지가 다름".into()));
474 }
475 let mut g = Inner::default();
476 for _ in 0..check_count(r.get_u64()?, "카메라")? {
477 let id = r.get_u32()?;
478 let model = CameraModelKind::from_id(r.get_i32()?)?;
479 let width = r.get_u64()?;
480 let height = r.get_u64()?;
481 let prior = r.get_u8()? != 0;
482 let n = check_count(r.get_u64()?, "파라미터")?;
483 let mut params = Vec::with_capacity(n);
484 for _ in 0..n {
485 params.push(r.get_f64()?);
486 }
487 let mut c = Camera::new(id, model, width, height, params)?;
488 c.focal_from_prior = prior;
489 g.cameras.insert(id, c);
490 }
491 for _ in 0..check_count(r.get_u64()?, "영상")? {
492 let image_id = r.get_u32()?;
493 let camera_id = r.get_u32()?;
494 let n = check_count(r.get_u64()?, "이름")?;
495 let name = String::from_utf8(r.get_vec_u8(n)?).map_err(|_| Error::Format("이름 UTF-8 아님".into()))?;
496 g.name_to_id.insert(name.clone(), image_id);
497 g.images.insert(image_id, StoreImage { image_id, name, camera_id });
498 }
499 for _ in 0..check_count(r.get_u64()?, "키포인트 묶음")? {
500 let id = r.get_u32()?;
501 let n = check_count(r.get_u64()?, "키포인트")?;
502 let mut v = Vec::with_capacity(n);
503 for _ in 0..n {
504 let mut row = [0f32; 6];
505 for x in row.iter_mut() {
506 *x = r.get_f32()?;
507 }
508 v.push(Keypoint::from_row(&row)?);
509 }
510 g.keypoints.insert(id, Arc::new(v));
511 }
512 for _ in 0..check_count(r.get_u64()?, "기술자 묶음")? {
513 let id = r.get_u32()?;
514 let n = check_count(r.get_u64()?, "기술자")?;
515 let d = Descriptors::from_vec(r.get_vec_u8(n * crate::features::DESCRIPTOR_DIM)?)?;
516 g.descriptors.insert(id, Arc::new(d));
517 }
518 for _ in 0..check_count(r.get_u64()?, "사전 위치")? {
519 let id = r.get_u32()?;
520 let p = r.get_f64_array::<3>()?;
521 let cs = r.get_i32()?;
522 let gravity = if r.get_u8()? != 0 { Some(Vec3::from(r.get_f64_array::<3>()?)) } else { None };
523 g.pose_priors.insert(id, PosePrior { position: Vec3::from(p), coordinate_system: cs, gravity });
524 }
525 for _ in 0..check_count(r.get_u64()?, "매칭 짝")? {
526 let pid = r.get_u64()?;
527 g.matches.insert(pid, Arc::new(read_match_list(&mut r)?));
528 }
529 for _ in 0..check_count(r.get_u64()?, "두 뷰 기하")? {
530 let pid = r.get_u64()?;
531 let config =
532 TwoViewGeometryConfig::from_i32(r.get_i32()?).ok_or_else(|| Error::Format("두 뷰 기하 config 값이 잘못됨".into()))?;
533 let mut mats = [None, None, None];
534 for m in mats.iter_mut() {
535 if r.get_u8()? != 0 {
536 let a = r.get_f64_array::<9>()?;
537 *m = Some(Mat3::from_row_slice(&a));
538 }
539 }
540 let cam1_to_cam2 = if r.get_u8()? != 0 { Some(Rigid3::from_params(&r.get_f64_array::<7>()?)) } else { None };
541 let inlier_matches = read_match_list(&mut r)?;
542 let [e, f, h] = mats;
543 g.two_view.insert(pid, Arc::new(TwoViewGeometry { config, e, f, h, cam1_to_cam2, inlier_matches, tri_angle: None }));
544 g.tvg_log.push(pid);
545 }
546 Ok(Self { inner: RwLock::new(g) })
547 }
548}
549
550fn write_match_list(w: &mut impl Write, m: &[FeatureMatch]) -> Result<()> {
551 w.put_u64(m.len() as u64)?;
552 for x in m {
553 w.put_u32(x.idx1)?;
554 w.put_u32(x.idx2)?;
555 }
556 Ok(())
557}
558
559fn read_match_list(r: &mut impl Read) -> Result<Vec<FeatureMatch>> {
560 let n = check_count(r.get_u64()?, "매칭")?;
561 let mut v = Vec::with_capacity(n);
562 for _ in 0..n {
563 let a = r.get_u32()?;
564 let b = r.get_u32()?;
565 v.push(FeatureMatch::new(a, b));
566 }
567 Ok(v)
568}
569
570#[cfg(test)]
571mod tests {
572 use super::*;
573
574 #[test]
575 fn store_basic_and_roundtrip() {
576 let s = FeatureStore::new();
577 let mut cam = Camera::from_focal(CameraModelKind::OpenCv, 1000.0, 2000, 1000);
578 cam.focal_from_prior = true;
579 let c1 = s.add_camera(cam.clone()).unwrap();
580 assert_eq!(c1, 1);
581 let c2 = s.add_camera(cam).unwrap();
582 assert_eq!(c2, 2);
583 let i1 = s.add_image("camF/0.jpg", c1).unwrap();
584 let i2 = s.add_image("camR/0.jpg", c2).unwrap();
585 assert!(s.add_image("camF/0.jpg", c1).is_err());
586 assert_eq!(s.image_id_by_name("camR/0.jpg"), Some(i2));
587 s.set_keypoints(i1, vec![Keypoint::new(1.5, 2.5), Keypoint::from_scale_orientation(3.0, 4.0, 2.0, 0.5)]);
588 s.set_keypoints(i2, vec![Keypoint::new(0.5, 0.5)]);
589 let mut d = Descriptors::new();
590 d.push(&[7u8; 128]);
591 s.set_descriptors(i1, d);
592 s.set_pose_prior(i1, PosePrior { position: Vec3::new(37.5, 127.0, 50.0), coordinate_system: 0, gravity: None });
593 s.write_matches(i2, i1, &[FeatureMatch::new(0, 1)]).unwrap();
595 assert_eq!(s.read_matches(i1, i2).unwrap()[0], FeatureMatch::new(1, 0));
596 assert_eq!(s.read_matches(i2, i1).unwrap()[0], FeatureMatch::new(0, 1));
597 let h = Mat3::new(2.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0);
598 let tvg = TwoViewGeometry {
599 config: TwoViewGeometryConfig::PlanarOrRotation,
600 h: Some(h),
601 cam1_to_cam2: Some(Rigid3::new(crate::geometry::Quat::from_axis_angle(&Vec3::y(), 0.3), Vec3::new(1.0, 0.0, 0.0))),
602 inlier_matches: vec![FeatureMatch::new(0, 1)],
603 ..Default::default()
604 };
605 s.put_two_view(i2, i1, &tvg).unwrap();
606 let back = s.get_two_view(i2, i1).unwrap();
607 assert!((back.h.unwrap() - h).norm() < 1e-12);
608 let fwd = s.get_two_view(i1, i2).unwrap();
609 assert_eq!(fwd.inlier_matches[0], FeatureMatch::new(1, 0));
610 let p = fwd.cam1_to_cam2.unwrap() * tvg.cam1_to_cam2.unwrap();
611 assert!(p.rotation.angular_distance(&crate::geometry::Quat::IDENTITY) < 1e-7);
612 let (since, pos) = s.two_view_geometries_since(0);
613 assert_eq!(since.len(), 1);
614 assert_eq!(s.two_view_geometries_since(pos).0.len(), 0);
615
616 let dir = std::env::temp_dir().join(format!("cumulus3d_store_{}", std::process::id()));
617 std::fs::create_dir_all(&dir).unwrap();
618 let path = dir.join("store.bin");
619 s.save(&path).unwrap();
620 let l = FeatureStore::load(&path).unwrap();
621 assert_eq!(l.cameras(), s.cameras());
622 assert_eq!(l.images(), s.images());
623 assert_eq!(l.keypoints(i1), s.keypoints(i1));
624 assert_eq!(l.descriptors(i1), s.descriptors(i1));
625 assert_eq!(l.pose_prior(i1), s.pose_prior(i1));
626 assert_eq!(l.read_matches(i1, i2), s.read_matches(i1, i2));
627 assert_eq!(l.get_two_view(i1, i2), s.get_two_view(i1, i2));
628 let mut bytes = std::fs::read(&path).unwrap();
630 bytes[..8].copy_from_slice(FeatureStore::LEGACY_MAGIC);
631 let legacy = dir.join("legacy.bin");
632 std::fs::write(&legacy, &bytes).unwrap();
633 assert_eq!(FeatureStore::load(&legacy).unwrap().images(), s.images());
634 std::fs::remove_dir_all(&dir).ok();
635 }
636
637 #[test]
638 fn store_concurrent_adds() {
639 let s = Arc::new(FeatureStore::new());
640 s.add_camera(Camera::from_focal(CameraModelKind::OpenCv, 1.0, 10, 10)).unwrap();
641 std::thread::scope(|sc| {
642 for t in 0..8 {
643 let s = s.clone();
644 sc.spawn(move || {
645 for k in 0..50 {
646 let id = s.add_image(&format!("t{t}/{k}"), 1).unwrap();
647 s.set_keypoints(id, vec![Keypoint::new(0.5, 0.5); k]);
648 }
649 });
650 }
651 });
652 assert_eq!(s.num_images(), 400);
653 assert_eq!(s.largest_keypoint_count(), 49);
654 }
655
656 #[test]
657 fn retain_copy_drops_images_and_pairs() {
658 let s = FeatureStore::new();
659 let c = s.add_camera(Camera::from_focal(CameraModelKind::OpenCv, 100.0, 64, 48)).unwrap();
660 let ids: Vec<ImageId> = (0..3).map(|i| s.add_image(&format!("cam/{i}.jpg"), c).unwrap()).collect();
661 for &i in &ids {
662 s.set_keypoints(i, vec![Keypoint::new(1.0, 1.0)]);
663 }
664 let tvg = TwoViewGeometry { inlier_matches: vec![FeatureMatch::new(0, 0)], ..Default::default() };
665 s.put_two_view(ids[0], ids[1], &tvg).unwrap();
666 s.put_two_view(ids[1], ids[2], &tvg).unwrap();
667 s.write_matches(ids[0], ids[2], &[FeatureMatch::new(0, 0)]).unwrap();
668 let r = s.retain_copy(|im| im.image_id != ids[2]);
669 assert_eq!(r.num_images(), 2);
670 assert_eq!(r.num_cameras(), 1);
671 assert!(r.image_id_by_name("cam/2.jpg").is_none());
672 assert!(r.contains_two_view(ids[0], ids[1]));
673 assert!(!r.contains_two_view(ids[1], ids[2]));
674 assert!(!r.exists_matches(ids[0], ids[2]));
675 assert_eq!(r.two_view_geometries_since(0).1, 1);
676 assert_eq!(r.add_image("cam/9.jpg", c).unwrap(), ids[2]);
678 assert_eq!(s.num_images(), 3);
680 }
681}