1use 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#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct StoreImage {
20 pub image_id: ImageId,
22 pub name: String,
24 pub camera_id: CameraId,
26}
27
28#[derive(Clone, Copy, Debug, PartialEq)]
30pub struct PosePrior {
31 pub position: Vec3,
33 pub coordinate_system: i32,
35 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 tvg_log: Vec<PairId>,
51}
52
53#[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 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 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 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 pub fn camera(&self, id: CameraId) -> Option<Camera> {
103 self.r().cameras.get(&id).cloned()
104 }
105 pub fn cameras(&self) -> Vec<Camera> {
107 self.r().cameras.values().cloned().collect()
108 }
109 pub fn num_cameras(&self) -> usize {
111 self.r().cameras.len()
112 }
113
114 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 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 pub fn image(&self, id: ImageId) -> Option<StoreImage> {
145 self.r().images.get(&id).cloned()
146 }
147 pub fn image_id_by_name(&self, name: &str) -> Option<ImageId> {
149 self.r().name_to_id.get(name).copied()
150 }
151 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 pub fn images(&self) -> Vec<StoreImage> {
158 self.r().images.values().cloned().collect()
159 }
160 pub fn image_ids(&self) -> Vec<ImageId> {
162 self.r().images.keys().copied().collect()
163 }
164 pub fn num_images(&self) -> usize {
166 self.r().images.len()
167 }
168 pub fn exists_image(&self, id: ImageId) -> bool {
170 self.r().images.contains_key(&id)
171 }
172
173 pub fn set_pose_prior(&self, image_id: ImageId, prior: PosePrior) {
175 self.w().pose_priors.insert(image_id, prior);
176 }
177 pub fn pose_prior(&self, image_id: ImageId) -> Option<PosePrior> {
179 self.r().pose_priors.get(&image_id).copied()
180 }
181
182 pub fn set_keypoints(&self, image_id: ImageId, kps: Vec<Keypoint>) {
186 self.w().keypoints.insert(image_id, Arc::new(kps));
187 }
188 pub fn set_descriptors(&self, image_id: ImageId, d: Descriptors) {
190 self.w().descriptors.insert(image_id, Arc::new(d));
191 }
192 pub fn keypoints(&self, image_id: ImageId) -> Option<Arc<Vec<Keypoint>>> {
194 self.r().keypoints.get(&image_id).cloned()
195 }
196 pub fn descriptors(&self, image_id: ImageId) -> Option<Arc<Descriptors>> {
198 self.r().descriptors.get(&image_id).cloned()
199 }
200 pub fn exists_keypoints(&self, image_id: ImageId) -> bool {
202 self.r().keypoints.contains_key(&image_id)
203 }
204 pub fn exists_descriptors(&self, image_id: ImageId) -> bool {
206 self.r().descriptors.contains_key(&image_id)
207 }
208 pub fn num_keypoints(&self, image_id: ImageId) -> usize {
210 self.r().keypoints.get(&image_id).map_or(0, |k| k.len())
211 }
212 pub fn largest_keypoint_count(&self) -> usize {
214 self.r().keypoints.values().map(|k| k.len()).max().unwrap_or(0)
215 }
216
217 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 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 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 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 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 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 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 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 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 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 pub fn num_two_view_geometries(&self) -> usize {
297 self.r().two_view.len()
298 }
299 pub fn num_matched_pairs(&self) -> usize {
301 self.r().matches.len()
302 }
303
304 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 const MAGIC: &'static [u8; 8] = b"C3DFS\0v1";
330 const LEGACY_MAGIC: &'static [u8; 8] = &[b'S', b'K', b'Y', b'F', b'S', 0, b'v', b'1'];
332
333 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 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 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 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 assert_eq!(r.add_image("cam/9.jpg", c).unwrap(), ids[2]);
648 assert_eq!(s.num_images(), 3);
650 }
651}