1use crate::camera::Camera;
39use crate::error::{Error, Result};
40use crate::geometry::{max_triangulation_angle, Rigid3, Sim3, Vec2, Vec3};
41use crate::ids::*;
42use rayon::prelude::*;
43use std::collections::{BTreeMap, HashMap};
44use std::sync::Arc;
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
48pub struct TrackEntry {
49 pub image_id: ImageId,
51 pub point2d_idx: Point2DIdx,
53}
54
55impl TrackEntry {
56 pub fn new(image_id: ImageId, point2d_idx: Point2DIdx) -> Self {
58 Self { image_id, point2d_idx }
59 }
60}
61
62#[derive(Clone, Copy, Debug, PartialEq)]
64pub struct Point2D {
65 pub xy: Vec2,
67 pub point3d_id: Point3DId,
69}
70
71impl Point2D {
72 pub fn new(xy: Vec2) -> Self {
74 Self { xy, point3d_id: INVALID_POINT3D_ID }
75 }
76 pub fn has_point3d(&self) -> bool {
78 self.point3d_id != INVALID_POINT3D_ID
79 }
80}
81
82#[derive(Clone, Debug, PartialEq)]
84pub struct Point3D {
85 pub xyz: Vec3,
87 pub color: [u8; 3],
89 pub error: f64,
91 pub track: Vec<TrackEntry>,
93}
94
95impl Point3D {
96 pub fn new(xyz: Vec3) -> Self {
98 Self { xyz, color: [0, 0, 0], error: -1.0, track: Vec::new() }
99 }
100 pub fn has_error(&self) -> bool {
102 self.error != -1.0
103 }
104 pub fn track_len(&self) -> usize {
106 self.track.len()
107 }
108}
109
110#[derive(Clone, Debug, PartialEq)]
112pub struct Rig {
113 pub rig_id: RigId,
115 pub ref_sensor_id: Option<SensorKey>,
117 pub sensors: BTreeMap<SensorKey, Option<Rigid3>>,
119}
120
121impl Rig {
122 pub fn new(rig_id: RigId) -> Self {
124 Self { rig_id, ref_sensor_id: None, sensors: BTreeMap::new() }
125 }
126 pub fn trivial(camera_id: CameraId) -> Self {
128 Self { rig_id: camera_id, ref_sensor_id: Some(SensorKey::camera(camera_id)), sensors: BTreeMap::new() }
129 }
130 pub fn num_sensors(&self) -> usize {
132 self.sensors.len() + self.ref_sensor_id.is_some() as usize
133 }
134 pub fn is_reference_sensor(&self, s: SensorKey) -> bool {
136 self.ref_sensor_id == Some(s)
137 }
138 pub fn has_sensor(&self, s: SensorKey) -> bool {
140 self.is_reference_sensor(s) || self.sensors.contains_key(&s)
141 }
142 pub fn rig_to_sensor(&self, s: SensorKey) -> Option<Rigid3> {
144 if self.is_reference_sensor(s) {
145 Some(Rigid3::identity())
146 } else {
147 self.sensors.get(&s).copied().flatten()
148 }
149 }
150}
151
152#[derive(Clone, Debug, PartialEq)]
154pub struct Frame {
155 pub frame_id: FrameId,
157 pub rig_id: RigId,
159 data_ids: Vec<SensorDataKey>,
160 pub world_to_rig: Option<Rigid3>,
162}
163
164impl Frame {
165 pub fn new(frame_id: FrameId, rig_id: RigId) -> Self {
167 Self { frame_id, rig_id, data_ids: Vec::new(), world_to_rig: None }
168 }
169 pub fn attach_data(&mut self, d: SensorDataKey) {
171 if let Err(pos) = self.data_ids.binary_search(&d) {
172 self.data_ids.insert(pos, d);
173 }
174 }
175 pub fn data_ids(&self) -> &[SensorDataKey] {
177 &self.data_ids
178 }
179 pub fn has_pose(&self) -> bool {
181 self.world_to_rig.is_some()
182 }
183 pub fn image_ids(&self) -> impl Iterator<Item = ImageId> + '_ {
185 self.data_ids.iter().filter(|d| d.sensor_id.sensor_type == SensorKind::Camera).map(|d| d.id as ImageId)
186 }
187}
188
189#[derive(Clone, Debug, PartialEq)]
191pub struct Image {
192 pub image_id: ImageId,
194 pub name: String,
196 pub camera_id: CameraId,
198 pub frame_id: FrameId,
200 points2d: Vec<Point2D>,
201 num_points3d: usize,
202}
203
204impl Image {
205 pub fn new(image_id: ImageId, name: impl Into<String>, camera_id: CameraId, points: impl IntoIterator<Item = Vec2>) -> Self {
207 Self {
208 image_id,
209 name: name.into(),
210 camera_id,
211 frame_id: INVALID_FRAME_ID,
212 points2d: points.into_iter().map(Point2D::new).collect(),
213 num_points3d: 0,
214 }
215 }
216 pub fn points2d(&self) -> &[Point2D] {
218 &self.points2d
219 }
220 pub fn point2d(&self, idx: Point2DIdx) -> &Point2D {
222 &self.points2d[idx as usize]
223 }
224 pub fn num_points2d(&self) -> usize {
226 self.points2d.len()
227 }
228 pub fn num_points3d(&self) -> usize {
230 self.num_points3d
231 }
232}
233
234#[derive(Clone, Copy, Debug, PartialEq, Eq)]
236pub enum FilterErrorUpdate {
237 None,
239 SumOverOriginalLength,
241 MeanOfRemaining,
243}
244
245#[derive(Clone, Copy, Debug, PartialEq)]
247pub struct NormalizeOptions {
248 pub fixed_scale: bool,
250 pub extent: f64,
252 pub p0: f64,
254 pub p1: f64,
256 pub use_images: bool,
258}
259
260impl Default for NormalizeOptions {
261 fn default() -> Self {
262 Self { fixed_scale: false, extent: 10.0, p0: 0.1, p1: 0.9, use_images: true }
263 }
264}
265
266#[derive(Clone, Debug, Default)]
268pub struct Reconstruction {
269 cameras: BTreeMap<CameraId, Camera>,
270 rigs: BTreeMap<RigId, Rig>,
271 frames: BTreeMap<FrameId, Frame>,
272 images: BTreeMap<ImageId, Arc<Image>>,
273 points3d: BTreeMap<Point3DId, Arc<Point3D>>,
274 registered_frames: Vec<FrameId>,
276 registered_image_count: usize,
277 max_point3d_id: Point3DId,
278}
279
280fn nf<T>(what: impl std::fmt::Display) -> Result<T> {
281 Err(Error::NotFound(what.to_string()))
282}
283
284impl Reconstruction {
285 pub fn new() -> Self {
287 Self::default()
288 }
289
290 pub fn cameras(&self) -> &BTreeMap<CameraId, Camera> {
294 &self.cameras
295 }
296 pub fn camera(&self, id: CameraId) -> Option<&Camera> {
298 self.cameras.get(&id)
299 }
300 pub fn camera_mut(&mut self, id: CameraId) -> Option<&mut Camera> {
302 self.cameras.get_mut(&id)
303 }
304 pub fn rigs(&self) -> &BTreeMap<RigId, Rig> {
306 &self.rigs
307 }
308 pub fn rig(&self, id: RigId) -> Option<&Rig> {
310 self.rigs.get(&id)
311 }
312 pub fn rig_mut(&mut self, id: RigId) -> Option<&mut Rig> {
314 self.rigs.get_mut(&id)
315 }
316 pub fn frames(&self) -> &BTreeMap<FrameId, Frame> {
318 &self.frames
319 }
320 pub fn frame(&self, id: FrameId) -> Option<&Frame> {
322 self.frames.get(&id)
323 }
324 pub fn images(&self) -> impl Iterator<Item = &Image> + '_ {
326 self.images.values().map(|a| &**a)
327 }
328 pub fn image(&self, id: ImageId) -> Option<&Image> {
330 self.images.get(&id).map(|a| &**a)
331 }
332 pub fn image_ids(&self) -> impl Iterator<Item = ImageId> + '_ {
334 self.images.keys().copied()
335 }
336 pub fn exists_image(&self, id: ImageId) -> bool {
338 self.images.contains_key(&id)
339 }
340 pub fn image_by_name(&self, name: &str) -> Option<&Image> {
342 self.images().find(|im| im.name == name)
343 }
344 pub fn points3d(&self) -> impl Iterator<Item = (Point3DId, &Point3D)> + '_ {
346 self.points3d.iter().map(|(k, v)| (*k, &**v))
347 }
348 pub fn point3d(&self, id: Point3DId) -> Option<&Point3D> {
350 self.points3d.get(&id).map(|a| &**a)
351 }
352 pub fn point3d_ids(&self) -> Vec<Point3DId> {
354 self.points3d.keys().copied().collect()
355 }
356 pub fn exists_point3d(&self, id: Point3DId) -> bool {
358 self.points3d.contains_key(&id)
359 }
360 pub fn num_cameras(&self) -> usize {
362 self.cameras.len()
363 }
364 pub fn num_rigs(&self) -> usize {
366 self.rigs.len()
367 }
368 pub fn num_frames(&self) -> usize {
370 self.frames.len()
371 }
372 pub fn num_images(&self) -> usize {
374 self.images.len()
375 }
376 pub fn num_points3d(&self) -> usize {
378 self.points3d.len()
379 }
380 pub fn registered_frame_count(&self) -> usize {
382 self.registered_frames.len()
383 }
384 pub fn registered_image_count(&self) -> usize {
386 self.registered_image_count
387 }
388 pub fn max_point3d_id(&self) -> Point3DId {
390 self.max_point3d_id
391 }
392 pub fn registered_frames(&self) -> &[FrameId] {
394 &self.registered_frames
395 }
396 pub fn registered_images(&self) -> Vec<ImageId> {
398 let mut out = Vec::with_capacity(self.registered_image_count);
399 for fid in &self.registered_frames {
400 if let Some(f) = self.frames.get(fid) {
401 out.extend(f.image_ids().filter(|i| self.images.contains_key(i)));
402 }
403 }
404 out
405 }
406 pub fn is_frame_registered(&self, frame_id: FrameId) -> bool {
408 self.registered_frames.contains(&frame_id)
409 }
410 pub fn is_image_registered(&self, image_id: ImageId) -> bool {
412 self.image(image_id).is_some_and(|im| self.is_frame_registered(im.frame_id))
413 }
414
415 pub fn world_to_cam(&self, image_id: ImageId) -> Option<Rigid3> {
417 let im = self.image(image_id)?;
418 let frame = self.frames.get(&im.frame_id)?;
419 let world_to_rig = frame.world_to_rig?;
420 let rig = self.rigs.get(&frame.rig_id)?;
421 let s = SensorKey::camera(im.camera_id);
422 if rig.is_reference_sensor(s) {
423 Some(world_to_rig)
424 } else {
425 Some(rig.rig_to_sensor(s)?.compose(&world_to_rig))
426 }
427 }
428 pub fn has_pose(&self, image_id: ImageId) -> bool {
430 self.image(image_id).and_then(|im| self.frames.get(&im.frame_id)).is_some_and(|f| f.has_pose())
431 }
432 pub fn projection_center(&self, image_id: ImageId) -> Option<Vec3> {
434 self.world_to_cam(image_id).map(|p| p.center())
435 }
436
437 pub fn add_camera(&mut self, camera: Camera) -> Result<()> {
441 if self.cameras.contains_key(&camera.camera_id) {
442 return Err(Error::AlreadyExists(format!("카메라 {}", camera.camera_id)));
443 }
444 if !camera.verify_params() {
445 return Err(Error::InvalidArgument(format!("카메라 {} 파라미터 개수", camera.camera_id)));
446 }
447 self.cameras.insert(camera.camera_id, camera);
448 Ok(())
449 }
450 pub fn add_camera_own_rig(&mut self, camera: Camera) -> Result<()> {
452 let id = camera.camera_id;
453 self.add_camera(camera)?;
454 self.rigs.entry(id).or_insert_with(|| Rig::trivial(id));
455 Ok(())
456 }
457 pub fn add_rig(&mut self, rig: Rig) -> Result<()> {
459 if self.rigs.contains_key(&rig.rig_id) {
460 return Err(Error::AlreadyExists(format!("rig {}", rig.rig_id)));
461 }
462 self.rigs.insert(rig.rig_id, rig);
463 Ok(())
464 }
465 pub fn add_frame(&mut self, frame: Frame) -> Result<()> {
467 if self.frames.contains_key(&frame.frame_id) {
468 return Err(Error::AlreadyExists(format!("프레임 {}", frame.frame_id)));
469 }
470 if !self.rigs.contains_key(&frame.rig_id) {
471 return nf(format!("rig {}", frame.rig_id));
472 }
473 self.frames.insert(frame.frame_id, frame);
474 Ok(())
475 }
476 pub fn add_image(&mut self, mut image: Image) -> Result<()> {
479 if self.images.contains_key(&image.image_id) {
480 return Err(Error::AlreadyExists(format!("영상 {}", image.image_id)));
481 }
482 if !self.cameras.contains_key(&image.camera_id) {
483 return nf(format!("카메라 {}", image.camera_id));
484 }
485 if image.frame_id != INVALID_FRAME_ID && !self.frames.contains_key(&image.frame_id) {
486 return nf(format!("프레임 {}", image.frame_id));
487 }
488 for p in image.points2d.iter_mut() {
489 p.point3d_id = INVALID_POINT3D_ID;
490 }
491 image.num_points3d = 0;
492 self.images.insert(image.image_id, Arc::new(image));
493 Ok(())
494 }
495 pub fn add_image_own_frame(&mut self, mut image: Image, world_to_cam: Option<Rigid3>) -> Result<()> {
497 if !self.cameras.contains_key(&image.camera_id) {
498 return nf(format!("카메라 {}", image.camera_id));
499 }
500 if self.images.contains_key(&image.image_id) {
501 return Err(Error::AlreadyExists(format!("영상 {}", image.image_id)));
502 }
503 let cam = image.camera_id;
504 self.rigs.entry(cam).or_insert_with(|| Rig::trivial(cam));
505 let mut frame = Frame::new(image.image_id, cam);
506 frame.attach_data(SensorDataKey::image(cam, image.image_id));
507 frame.world_to_rig = world_to_cam;
508 self.add_frame(frame)?;
509 image.frame_id = image.image_id;
510 self.add_image(image)
511 }
512
513 pub fn set_frame_pose(&mut self, frame_id: FrameId, world_to_rig: Option<Rigid3>) -> Result<()> {
517 if world_to_rig.is_none() && self.is_frame_registered(frame_id) {
518 return Err(Error::InvalidArgument(format!("등록된 프레임 {frame_id} 의 자세 제거")));
519 }
520 match self.frames.get_mut(&frame_id) {
521 Some(f) => {
522 f.world_to_rig = world_to_rig;
523 Ok(())
524 }
525 None => nf(format!("프레임 {frame_id}")),
526 }
527 }
528 pub fn set_world_to_cam(&mut self, image_id: ImageId, world_to_cam: Rigid3) -> Result<()> {
530 let im = self.image(image_id).ok_or_else(|| Error::NotFound(format!("영상 {image_id}")))?;
531 let (fid, s) = (im.frame_id, SensorKey::camera(im.camera_id));
532 let frame = self.frames.get(&fid).ok_or_else(|| Error::NotFound(format!("프레임 {fid}")))?;
533 let rig = self.rigs.get(&frame.rig_id).ok_or_else(|| Error::NotFound(format!("rig {}", frame.rig_id)))?;
534 let world_to_rig = if rig.is_reference_sensor(s) {
535 world_to_cam
536 } else {
537 let rig_to_cam =
538 rig.rig_to_sensor(s).ok_or_else(|| Error::InvalidArgument(format!("영상 {image_id} 센서가 rig 에서 미보정")))?;
539 rig_to_cam.inverse().compose(&world_to_cam)
540 };
541 self.frames.get_mut(&fid).expect("checked").world_to_rig = Some(world_to_rig);
542 Ok(())
543 }
544 pub fn register_frame(&mut self, frame_id: FrameId) -> Result<bool> {
546 let f = self.frames.get(&frame_id).ok_or_else(|| Error::NotFound(format!("프레임 {frame_id}")))?;
547 if !f.has_pose() {
548 return Err(Error::InvalidArgument(format!("자세 없는 프레임 {frame_id} 등록")));
549 }
550 if self.is_frame_registered(frame_id) {
551 return Ok(false);
552 }
553 let n = f.image_ids().filter(|i| self.images.contains_key(i)).count();
554 self.registered_frames.push(frame_id);
555 self.registered_image_count += n;
556 Ok(true)
557 }
558 pub fn register_image(&mut self, image_id: ImageId) -> Result<bool> {
560 let fid = self.image(image_id).ok_or_else(|| Error::NotFound(format!("영상 {image_id}")))?.frame_id;
561 self.register_frame(fid)
562 }
563 pub fn deregister_frame(&mut self, frame_id: FrameId) -> Result<bool> {
566 let Some(pos) = self.registered_frames.iter().position(|f| *f == frame_id) else {
567 return Ok(false);
568 };
569 let image_ids: Vec<ImageId> = self.frames[&frame_id].image_ids().filter(|i| self.images.contains_key(i)).collect();
570 for &iid in &image_ids {
571 let n = self.images[&iid].points2d.len();
572 for idx in 0..n {
573 if self.images[&iid].points2d[idx].has_point3d() {
574 self.delete_observation(iid, idx as Point2DIdx)?;
575 }
576 }
577 }
578 self.registered_image_count -= image_ids.len();
579 if let Some(f) = self.frames.get_mut(&frame_id) {
580 f.world_to_rig = None;
581 }
582 self.registered_frames.remove(pos);
583 Ok(true)
584 }
585 pub fn deregister_image(&mut self, image_id: ImageId) -> Result<bool> {
587 let fid = self.image(image_id).ok_or_else(|| Error::NotFound(format!("영상 {image_id}")))?.frame_id;
588 self.deregister_frame(fid)
589 }
590
591 fn link(&mut self, e: TrackEntry, pid: Point3DId) {
594 let im = Arc::make_mut(self.images.get_mut(&e.image_id).expect("검증됨"));
595 let p = &mut im.points2d[e.point2d_idx as usize];
596 if !p.has_point3d() {
597 im.num_points3d += 1;
598 }
599 p.point3d_id = pid;
600 }
601 fn unlink(&mut self, e: TrackEntry, pid: Point3DId) {
602 if let Some(a) = self.images.get_mut(&e.image_id) {
603 if a.points2d.get(e.point2d_idx as usize).is_some_and(|p| p.point3d_id == pid) {
604 let im = Arc::make_mut(a);
605 im.points2d[e.point2d_idx as usize].point3d_id = INVALID_POINT3D_ID;
606 im.num_points3d -= 1;
607 }
608 }
609 }
610 fn check_element(&self, e: &TrackEntry, pid: Point3DId) -> Result<()> {
611 let im = self.image(e.image_id).ok_or_else(|| Error::NotFound(format!("영상 {}", e.image_id)))?;
612 let p = im
613 .points2d
614 .get(e.point2d_idx as usize)
615 .ok_or_else(|| Error::InvalidArgument(format!("영상 {} 의 2D 점 {} 범위 밖", e.image_id, e.point2d_idx)))?;
616 if p.has_point3d() && p.point3d_id != pid {
617 return Err(Error::Invariant(format!("영상 {} 2D 점 {} 은 이미 3D 점 {} 에 연결됨", e.image_id, e.point2d_idx, p.point3d_id)));
618 }
619 Ok(())
620 }
621
622 pub fn add_point3d(&mut self, xyz: Vec3, track: Vec<TrackEntry>, color: [u8; 3]) -> Result<Point3DId> {
624 let id = self.max_point3d_id + 1;
627 self.add_point3d_with_id(id, Point3D { xyz, color, error: -1.0, track })?;
628 Ok(id)
629 }
630 pub fn add_point3d_with_id(&mut self, id: Point3DId, point: Point3D) -> Result<()> {
632 if id == INVALID_POINT3D_ID {
633 return Err(Error::InvalidArgument("무효 3D 점 id".into()));
634 }
635 if self.points3d.contains_key(&id) {
636 return Err(Error::AlreadyExists(format!("3D 점 {id}")));
637 }
638 for e in &point.track {
639 self.check_element(e, id)?;
640 }
641 for e in point.track.clone() {
642 self.link(e, id);
643 }
644 self.points3d.insert(id, Arc::new(point));
645 self.max_point3d_id = self.max_point3d_id.max(id);
646 Ok(())
647 }
648 pub fn add_observation(&mut self, point3d_id: Point3DId, e: TrackEntry) -> Result<()> {
650 if !self.points3d.contains_key(&point3d_id) {
651 return nf(format!("3D 점 {point3d_id}"));
652 }
653 self.check_element(&e, point3d_id)?;
654 if self.image(e.image_id).expect("검증됨").points2d[e.point2d_idx as usize].has_point3d() {
655 return Err(Error::Invariant(format!("관측 ({}, {}) 중복", e.image_id, e.point2d_idx)));
656 }
657 self.link(e, point3d_id);
658 Arc::make_mut(self.points3d.get_mut(&point3d_id).expect("검증됨")).track.push(e);
659 Ok(())
660 }
661 pub fn delete_observation(&mut self, image_id: ImageId, point2d_idx: Point2DIdx) -> Result<bool> {
664 let im = self.image(image_id).ok_or_else(|| Error::NotFound(format!("영상 {image_id}")))?;
665 let pid =
666 im.points2d.get(point2d_idx as usize).ok_or_else(|| Error::InvalidArgument(format!("2D 점 {point2d_idx} 범위 밖")))?.point3d_id;
667 if pid == INVALID_POINT3D_ID {
668 return Err(Error::InvalidArgument(format!("영상 {image_id} 2D 점 {point2d_idx} 에 3D 점 없음")));
669 }
670 let e = TrackEntry::new(image_id, point2d_idx);
671 if self.points3d[&pid].track.len() <= 2 {
672 self.delete_point3d(pid)?;
673 return Ok(true);
674 }
675 let p = Arc::make_mut(self.points3d.get_mut(&pid).expect("연결 불변식"));
676 if let Some(pos) = p.track.iter().position(|t| *t == e) {
677 p.track.remove(pos);
678 }
679 self.unlink(e, pid);
680 Ok(false)
681 }
682 pub fn delete_point3d(&mut self, id: Point3DId) -> Result<()> {
684 let p = self.points3d.remove(&id).ok_or_else(|| Error::NotFound(format!("3D 점 {id}")))?;
685 for e in &p.track {
686 self.unlink(*e, id);
687 }
688 Ok(())
689 }
690 pub fn merge_points3d(&mut self, a: Point3DId, b: Point3DId) -> Result<Point3DId> {
692 if a == b {
693 return Err(Error::InvalidArgument("같은 점 병합".into()));
694 }
695 let pa = self.points3d.get(&a).ok_or_else(|| Error::NotFound(format!("3D 점 {a}")))?.clone();
696 let pb = self.points3d.get(&b).ok_or_else(|| Error::NotFound(format!("3D 점 {b}")))?.clone();
697 let (wa, wb) = (pa.track.len() as f64, pb.track.len() as f64);
698 let w = (wa + wb).max(1.0);
699 let xyz = (pa.xyz * wa + pb.xyz * wb) / w;
700 let mut color = [0u8; 3];
701 for (k, c) in color.iter_mut().enumerate() {
702 *c = ((pa.color[k] as f64 * wa + pb.color[k] as f64 * wb) / w) as u8;
703 }
704 let mut track = pa.track.clone();
705 track.extend_from_slice(&pb.track);
706 self.delete_point3d(a)?;
707 self.delete_point3d(b)?;
708 self.add_point3d(xyz, track, color)
710 }
711 pub fn set_point3d_xyz(&mut self, id: Point3DId, xyz: Vec3) -> Result<()> {
713 let p = self.points3d.get_mut(&id).ok_or_else(|| Error::NotFound(format!("3D 점 {id}")))?;
714 Arc::make_mut(p).xyz = xyz;
715 Ok(())
716 }
717 pub fn set_point3d_error(&mut self, id: Point3DId, error: f64) -> Result<()> {
719 let p = self.points3d.get_mut(&id).ok_or_else(|| Error::NotFound(format!("3D 점 {id}")))?;
720 Arc::make_mut(p).error = error;
721 Ok(())
722 }
723 pub fn set_point3d_color(&mut self, id: Point3DId, color: [u8; 3]) -> Result<()> {
725 let p = self.points3d.get_mut(&id).ok_or_else(|| Error::NotFound(format!("3D 점 {id}")))?;
726 Arc::make_mut(p).color = color;
727 Ok(())
728 }
729 pub fn delete_all_points3d(&mut self) {
731 self.points3d.clear();
732 for a in self.images.values_mut() {
733 if a.num_points3d > 0 {
734 let im = Arc::make_mut(a);
735 for p in im.points2d.iter_mut() {
736 p.point3d_id = INVALID_POINT3D_ID;
737 }
738 im.num_points3d = 0;
739 }
740 }
741 }
742
743 pub fn total_observations(&self) -> usize {
747 self.registered_images().iter().map(|i| self.images[i].num_points3d).sum()
748 }
749 pub fn mean_track_len(&self) -> f64 {
751 if self.points3d.is_empty() {
752 0.0
753 } else {
754 self.total_observations() as f64 / self.points3d.len() as f64
755 }
756 }
757 pub fn mean_obs_per_registered_image(&self) -> f64 {
759 if self.registered_image_count == 0 {
760 0.0
761 } else {
762 self.total_observations() as f64 / self.registered_image_count as f64
763 }
764 }
765 pub fn mean_reproj_error(&self) -> f64 {
767 let (mut s, mut n) = (0.0, 0usize);
768 for p in self.points3d.values() {
769 if p.has_error() {
770 s += p.error;
771 n += 1;
772 }
773 }
774 if n == 0 {
775 0.0
776 } else {
777 s / n as f64
778 }
779 }
780
781 fn pose_cache(&self) -> HashMap<ImageId, (Rigid3, &Camera)> {
785 let mut m = HashMap::with_capacity(self.images.len());
786 for im in self.images() {
787 if let (Some(p), Some(c)) = (self.world_to_cam(im.image_id), self.cameras.get(&im.camera_id)) {
788 m.insert(im.image_id, (p, c));
789 }
790 }
791 m
792 }
793
794 pub fn squared_reprojection_error(pose: &Rigid3, camera: &Camera, xy: &Vec2, xyz: &Vec3) -> f64 {
796 match camera.cam_to_img(&pose.transform_point(xyz)) {
797 Some(p) => (p - xy).norm_squared(),
798 None => f64::MAX,
799 }
800 }
801
802 fn compute_point_error(&self, cache: &HashMap<ImageId, (Rigid3, &Camera)>, p: &Point3D) -> f64 {
803 if p.track.is_empty() {
804 return 0.0;
805 }
806 let mut s = 0.0;
807 for e in &p.track {
808 let sq = match cache.get(&e.image_id) {
809 Some((pose, cam)) => {
810 let xy = self.images[&e.image_id].points2d[e.point2d_idx as usize].xy;
811 Self::squared_reprojection_error(pose, cam, &xy, &p.xyz)
812 }
813 None => f64::MAX,
814 };
815 s += sq.sqrt();
816 }
817 s / p.track.len() as f64
818 }
819
820 pub fn update_point3d_errors(&mut self) {
822 let cache = self.pose_cache();
823 let errs: Vec<(Point3DId, f64)> = self.points3d.par_iter().map(|(id, p)| (*id, self.compute_point_error(&cache, p))).collect();
824 drop(cache);
825 for (id, e) in errs {
826 let p = self.points3d.get_mut(&id).expect("존재");
827 if p.error != e {
828 Arc::make_mut(p).error = e;
829 }
830 }
831 }
832 pub fn point3d_reprojection_error(&self, id: Point3DId) -> Option<f64> {
834 let p = self.point3d(id)?;
835 let mut cache = HashMap::new();
836 for e in &p.track {
837 if let (Some(pose), Some(im)) = (self.world_to_cam(e.image_id), self.image(e.image_id)) {
838 cache.insert(e.image_id, (pose, &self.cameras[&im.camera_id]));
839 }
840 }
841 Some(self.compute_point_error(&cache, p))
842 }
843
844 pub fn filter_observations<F>(
851 &mut self,
852 point_ids: Option<&[Point3DId]>,
853 max_error: f64,
854 error_fn: F,
855 update: FilterErrorUpdate,
856 ) -> usize
857 where
858 F: Fn(&Camera, &Rigid3, &Vec2, &Vec3) -> f64 + Sync,
859 {
860 let ids: Vec<Point3DId> = match point_ids {
861 Some(v) => v.iter().copied().filter(|i| self.points3d.contains_key(i)).collect(),
862 None => self.point3d_ids(),
863 };
864 let cache = self.pose_cache();
865 let evals: Vec<(Point3DId, Vec<f64>)> = ids
867 .par_iter()
868 .map(|id| {
869 let p = &self.points3d[id];
870 let errs = p
871 .track
872 .iter()
873 .map(|e| match cache.get(&e.image_id) {
874 Some((pose, cam)) => {
875 let xy = self.images[&e.image_id].points2d[e.point2d_idx as usize].xy;
876 error_fn(cam, pose, &xy, &p.xyz)
877 }
878 None => f64::INFINITY,
879 })
880 .collect();
881 (*id, errs)
882 })
883 .collect();
884 drop(cache);
885 self.apply_observation_filter(evals, max_error, update)
886 }
887
888 pub fn apply_observation_filter(&mut self, evals: Vec<(Point3DId, Vec<f64>)>, max_error: f64, update: FilterErrorUpdate) -> usize {
892 let mut removed = 0usize;
893 for (id, errs) in evals {
894 let Some(p) = self.points3d.get(&id) else { continue };
895 let track = p.track.clone();
896 let len = track.len();
897 if errs.len() != len {
898 continue;
899 }
900 if len < 2 {
901 removed += len;
902 self.delete_point3d(id).expect("존재");
903 continue;
904 }
905 let bad: Vec<usize> = (0..len).filter(|&k| errs[k].is_nan() || errs[k] > max_error).collect();
906 if bad.len() >= len - 1 {
907 removed += len;
908 self.delete_point3d(id).expect("존재");
909 continue;
910 }
911 let mut point_deleted = false;
912 for &k in &bad {
913 removed += 1;
914 if self.delete_observation(track[k].image_id, track[k].point2d_idx).expect("연결 불변식") {
915 point_deleted = true;
916 break;
917 }
918 }
919 if point_deleted {
920 continue;
921 }
922 let inl: Vec<f64> = (0..len).filter(|k| !bad.contains(k)).map(|k| errs[k]).collect();
923 let new_err = match update {
924 FilterErrorUpdate::None => None,
925 FilterErrorUpdate::SumOverOriginalLength => Some(inl.iter().sum::<f64>() / len as f64),
926 FilterErrorUpdate::MeanOfRemaining => Some(inl.iter().sum::<f64>() / inl.len() as f64),
927 };
928 if let Some(e) = new_err {
929 self.set_point3d_error(id, e).expect("존재");
930 }
931 }
932 removed
933 }
934
935 pub fn filter_observations_with_large_reprojection_error(
937 &mut self,
938 max_reproj_error_px: f64,
939 point_ids: Option<&[Point3DId]>,
940 ) -> usize {
941 self.filter_observations(
942 point_ids,
943 max_reproj_error_px,
944 |cam, pose, xy, xyz| Self::squared_reprojection_error(pose, cam, xy, xyz).sqrt(),
945 FilterErrorUpdate::SumOverOriginalLength,
946 )
947 }
948
949 pub fn filter_points3d_with_small_triangulation_angle(&mut self, min_tri_angle_deg: f64, point_ids: Option<&[Point3DId]>) -> usize {
952 let min_rad = min_tri_angle_deg.to_radians();
953 let ids: Vec<Point3DId> = match point_ids {
954 Some(v) => v.iter().copied().filter(|i| self.points3d.contains_key(i)).collect(),
955 None => self.point3d_ids(),
956 };
957 let centers: HashMap<ImageId, Vec3> = self.pose_cache().into_iter().map(|(k, (p, _))| (k, p.center())).collect();
958 let to_delete: Vec<(Point3DId, usize)> = ids
959 .par_iter()
960 .filter_map(|id| {
961 let p = &self.points3d[id];
962 let cs: Vec<Vec3> = p.track.iter().filter_map(|e| centers.get(&e.image_id).copied()).collect();
963 if max_triangulation_angle(&cs, &p.xyz) >= min_rad {
964 None
965 } else {
966 Some((*id, p.track.len()))
967 }
968 })
969 .collect();
970 let mut removed = 0;
971 for (id, n) in to_delete {
972 removed += n;
973 self.delete_point3d(id).expect("존재");
974 }
975 removed
976 }
977
978 pub fn transform(&mut self, t: &Sim3) {
982 for p in self.points3d.values_mut() {
983 let m = Arc::make_mut(p);
984 m.xyz = t.transform_point(&m.xyz);
985 }
986 for f in self.frames.values_mut() {
987 if let Some(p) = f.world_to_rig {
988 f.world_to_rig = Some(t.transform_pose(&p));
989 }
990 }
991 for r in self.rigs.values_mut() {
992 for v in r.sensors.values_mut().flatten() {
993 v.translation *= t.scale;
994 }
995 }
996 }
997
998 pub fn normalize(&mut self, opts: &NormalizeOptions) -> Option<Sim3> {
1000 if self.registered_frames.len() < 2 {
1001 return None;
1002 }
1003 let coords: Vec<Vec3> = if opts.use_images {
1004 self.registered_images().iter().filter_map(|i| self.projection_center(*i)).collect()
1005 } else {
1006 self.points3d.values().map(|p| p.xyz).collect()
1007 };
1008 if coords.is_empty() {
1009 return None;
1010 }
1011 let n = coords.len();
1012 let e = n - 1;
1013 let lo = e.min((opts.p0 * e as f64).floor() as usize);
1014 let hi = e.min((opts.p1 * e as f64).ceil() as usize);
1015 let mut bmin = Vec3::zeros();
1016 let mut bmax = Vec3::zeros();
1017 let mut center = Vec3::zeros();
1018 for ax in 0..3 {
1019 let mut v: Vec<f64> = coords.iter().map(|c| c[ax]).collect();
1020 v.sort_by(|a, b| a.total_cmp(b));
1021 bmin[ax] = v[lo];
1022 bmax[ax] = v[hi];
1023 let slice = &v[lo..=hi];
1024 center[ax] = slice.iter().sum::<f64>() / slice.len() as f64;
1025 }
1026 let diag = (bmax - bmin).norm();
1027 let s = if opts.fixed_scale || diag < f64::EPSILON { 1.0 } else { opts.extent / diag };
1028 let t = Sim3::new(s, crate::geometry::Quat::IDENTITY, -s * center);
1029 self.transform(&t);
1030 Some(t)
1031 }
1032
1033 pub fn deregister_images_by_id(&mut self, ids: &[ImageId]) -> Vec<String> {
1037 let mut warnings = Vec::new();
1038 for &id in ids {
1039 match self.image(id).map(|im| im.frame_id) {
1040 None => warnings.push(format!("Image with ID {id} does not exist")),
1041 Some(fid) => match self.deregister_frame(fid) {
1042 Ok(true) => {}
1043 _ => warnings.push(format!("Image with ID {id} is not registered")),
1044 },
1045 }
1046 }
1047 warnings
1048 }
1049 pub fn deregister_images_by_name(&mut self, names: &[&str]) -> Vec<String> {
1051 let mut warnings = Vec::new();
1052 for &name in names {
1053 match self.image_by_name(name).map(|im| im.image_id) {
1054 None => warnings.push(format!("Image with name {name} does not exist")),
1055 Some(id) => warnings.extend(self.deregister_images_by_id(&[id])),
1056 }
1057 }
1058 warnings
1059 }
1060
1061 pub fn remove_unregistered(&mut self) {
1063 let unreg: Vec<ImageId> = self.images.keys().copied().filter(|i| !self.is_image_registered(*i)).collect();
1064 for &iid in &unreg {
1065 let n = self.images[&iid].points2d.len();
1066 for idx in 0..n {
1067 if self.images[&iid].points2d[idx].has_point3d() {
1068 let _ = self.delete_observation(iid, idx as Point2DIdx);
1069 }
1070 }
1071 self.images.remove(&iid);
1072 }
1073 let reg: std::collections::HashSet<FrameId> = self.registered_frames.iter().copied().collect();
1074 self.frames.retain(|k, _| reg.contains(k));
1075 let used_cams: std::collections::HashSet<CameraId> = self.images.values().map(|im| im.camera_id).collect();
1076 let used_rigs: std::collections::HashSet<RigId> = self.frames.values().map(|f| f.rig_id).collect();
1077 self.cameras.retain(|k, _| used_cams.contains(k));
1078 self.rigs.retain(|k, _| used_rigs.contains(k));
1079 }
1080
1081 pub fn extract_colors<S, F>(&mut self, mut load: F)
1086 where
1087 F: FnMut(&Image) -> Option<S>,
1088 S: Fn(f64, f64) -> Option<[f64; 3]>,
1089 {
1090 let mut acc: HashMap<Point3DId, ([f64; 3], usize)> = HashMap::new();
1091 for iid in self.registered_images() {
1092 let im = &self.images[&iid];
1093 let Some(sampler) = load(im) else { continue };
1094 for p in im.points2d.iter().filter(|p| p.has_point3d()) {
1095 if let Some(c) = sampler(p.xy.x - 0.5, p.xy.y - 0.5) {
1096 let a = acc.entry(p.point3d_id).or_insert(([0.0; 3], 0));
1097 for (dst, v) in a.0.iter_mut().zip(c) {
1098 *dst += v;
1099 }
1100 a.1 += 1;
1101 }
1102 }
1103 }
1104 for (id, p) in self.points3d.iter_mut() {
1105 let color = match acc.get(id) {
1106 Some((s, n)) => s.map(|v| (v / *n as f64).round().clamp(0.0, 255.0) as u8),
1107 None => [0, 0, 0],
1108 };
1109 if p.color != color {
1110 Arc::make_mut(p).color = color;
1111 }
1112 }
1113 }
1114
1115 pub fn check_invariants(&self) -> Result<()> {
1119 let bad = |m: String| Err(Error::Invariant(m));
1120 for (pid, p) in &self.points3d {
1121 for e in &p.track {
1122 let Some(im) = self.image(e.image_id) else { return bad(format!("점 {pid}: 영상 {} 없음", e.image_id)) };
1123 match im.points2d.get(e.point2d_idx as usize) {
1124 Some(q) if q.point3d_id == *pid => {}
1125 _ => return bad(format!("점 {pid}: ({}, {}) 역연결 불일치", e.image_id, e.point2d_idx)),
1126 }
1127 }
1128 }
1129 for im in self.images() {
1130 let mut n = 0;
1131 for (idx, q) in im.points2d.iter().enumerate() {
1132 if q.has_point3d() {
1133 n += 1;
1134 let Some(p) = self.point3d(q.point3d_id) else {
1135 return bad(format!("영상 {} 2D {idx}: 3D 점 {} 없음", im.image_id, q.point3d_id));
1136 };
1137 if !p.track.contains(&TrackEntry::new(im.image_id, idx as u32)) {
1138 return bad(format!("영상 {} 2D {idx}: 트랙에 없음", im.image_id));
1139 }
1140 }
1141 }
1142 if n != im.num_points3d {
1143 return bad(format!("영상 {} 연결 수 캐시 {} != {}", im.image_id, im.num_points3d, n));
1144 }
1145 }
1146 if self.registered_images().len() != self.registered_image_count {
1147 return bad("등록 영상 수 불일치".into());
1148 }
1149 if let Some(m) = self.points3d.keys().next_back() {
1150 if *m > self.max_point3d_id {
1151 return bad("최대 3D 점 id 불일치".into());
1152 }
1153 }
1154 Ok(())
1155 }
1156}
1157
1158pub fn bilinear_rgb(data: &[u8], width: usize, height: usize, x: f64, y: f64) -> Option<[f64; 3]> {
1161 if width == 0 || height == 0 || !(x >= 0.0 && y >= 0.0 && x <= (width - 1) as f64 && y <= (height - 1) as f64) {
1162 return None;
1163 }
1164 let x0 = x.floor() as usize;
1165 let y0 = y.floor() as usize;
1166 let x1 = (x0 + 1).min(width - 1);
1167 let y1 = (y0 + 1).min(height - 1);
1168 let dx = x - x0 as f64;
1169 let dy = y - y0 as f64;
1170 let px = |xx: usize, yy: usize, c: usize| data[(yy * width + xx) * 3 + c] as f64;
1171 let mut out = [0.0; 3];
1172 for (c, o) in out.iter_mut().enumerate() {
1173 *o = (1.0 - dy) * ((1.0 - dx) * px(x0, y0, c) + dx * px(x1, y0, c)) + dy * ((1.0 - dx) * px(x0, y1, c) + dx * px(x1, y1, c));
1174 }
1175 Some(out)
1176}
1177
1178#[cfg(test)]
1179pub(crate) mod tests {
1180 use super::*;
1181 use crate::camera::CameraModelKind;
1182 use crate::geometry::Quat;
1183
1184 pub(crate) fn line_scene(n: u32, m: usize) -> Reconstruction {
1186 let mut r = Reconstruction::new();
1187 let mut cam = Camera::from_focal(CameraModelKind::Pinhole, 100.0, 200, 200);
1188 cam.camera_id = 1;
1189 r.add_camera_own_rig(cam).unwrap();
1190 for i in 1..=n {
1191 let pts = (0..m).map(|k| Vec2::new(10.0 + k as f64, 20.0));
1192 let pose = Rigid3::new(Quat::IDENTITY, Vec3::new(-(i as f64), 0.0, 0.0));
1193 r.add_image_own_frame(Image::new(i, format!("im{i}.jpg"), 1, pts), Some(pose)).unwrap();
1194 r.register_image(i).unwrap();
1195 }
1196 r
1197 }
1198
1199 #[test]
1200 fn add_delete_observations_and_invariants() {
1201 let mut r = line_scene(4, 5);
1202 let t = |i, k| TrackEntry::new(i, k);
1203 let p2 = r.add_point3d(Vec3::new(0.0, 0.0, 5.0), vec![t(1, 0), t(2, 0)], [1, 2, 3]).unwrap();
1204 let p3 = r.add_point3d(Vec3::new(0.0, 0.0, 5.0), vec![t(1, 1), t(2, 1), t(3, 1)], [0; 3]).unwrap();
1205 let p4 = r.add_point3d(Vec3::new(0.0, 0.0, 5.0), vec![t(1, 2), t(2, 2), t(3, 2), t(4, 2)], [0; 3]).unwrap();
1206 assert_eq!((p2, p3, p4), (1, 2, 3));
1207 r.check_invariants().unwrap();
1208 assert!(r.add_point3d(Vec3::zeros(), vec![t(1, 0), t(3, 0)], [0; 3]).is_err());
1210 r.check_invariants().unwrap();
1211 assert_eq!(r.image(1).unwrap().num_points3d(), 3);
1212 r.add_observation(p2, t(3, 0)).unwrap();
1213 assert!(r.add_observation(p2, t(3, 0)).is_err());
1214 assert_eq!(r.point3d(p2).unwrap().track.len(), 3);
1215 assert!(!r.delete_observation(3, 0).unwrap());
1216 assert!(r.delete_observation(1, 0).unwrap()); assert!(!r.exists_point3d(p2));
1218 assert_eq!(r.image(2).unwrap().num_points3d(), 2);
1219 r.check_invariants().unwrap();
1220 let m = r.merge_points3d(p3, p4).unwrap();
1222 assert_eq!(m, 4);
1223 assert_eq!(r.point3d(m).unwrap().track.len(), 7);
1224 r.check_invariants().unwrap();
1225 let snapshot = r.clone();
1227 r.delete_point3d(m).unwrap();
1228 assert!(snapshot.exists_point3d(m));
1229 assert_eq!(snapshot.image(1).unwrap().num_points3d(), 2);
1230 assert_eq!(r.image(1).unwrap().num_points3d(), 0);
1231 snapshot.check_invariants().unwrap();
1232 r.check_invariants().unwrap();
1233 }
1234
1235 #[test]
1236 fn image_deleter_rule() {
1237 let mut r = line_scene(4, 5);
1238 let t = |i, k| TrackEntry::new(i, k);
1239 let a = r.add_point3d(Vec3::new(1.0, 0.0, 5.0), vec![t(1, 0), t(2, 0)], [0; 3]).unwrap();
1240 let b = r.add_point3d(Vec3::new(1.0, 0.0, 5.0), vec![t(1, 1), t(2, 1), t(3, 1)], [0; 3]).unwrap();
1241 let c = r.add_point3d(Vec3::new(1.0, 0.0, 5.0), vec![t(1, 2), t(2, 2), t(3, 2), t(4, 2)], [0; 3]).unwrap();
1242 let d = r.add_point3d(Vec3::new(1.0, 0.0, 5.0), vec![t(2, 3), t(3, 3)], [0; 3]).unwrap();
1243 for (id, e) in [(a, 1.0), (b, 2.0), (c, 3.0), (d, 4.0)] {
1244 r.set_point3d_error(id, e).unwrap();
1245 }
1246 let w = r.deregister_images_by_id(&[1, 99]);
1247 assert_eq!(w.len(), 1);
1248 assert!(!r.exists_point3d(a));
1249 assert_eq!(r.point3d(b).unwrap().track.len(), 2);
1250 assert_eq!(r.point3d(c).unwrap().track.len(), 3);
1251 assert_eq!(r.point3d(d).unwrap().track.len(), 2);
1252 assert_eq!(r.point3d(b).unwrap().error, 2.0);
1253 assert_eq!(r.point3d(c).unwrap().error, 3.0);
1254 assert!(!r.is_image_registered(1));
1255 assert!(!r.registered_images().contains(&1));
1256 assert_eq!(r.registered_image_count(), 3);
1257 assert_eq!(r.num_images(), 4);
1258 assert_eq!(r.deregister_images_by_name(&["im1.jpg"]).len(), 1);
1260 r.check_invariants().unwrap();
1261 r.remove_unregistered();
1262 assert_eq!(r.num_images(), 3);
1263 r.check_invariants().unwrap();
1264 }
1265
1266 #[test]
1267 fn filters() {
1268 let r = line_scene(3, 4);
1269 let t = |i, k| TrackEntry::new(i, k);
1270 let x = Vec3::new(2.0, 0.0, 10.0);
1272 let mut obs = Vec::new();
1273 for i in 1..=3u32 {
1274 let pose = r.world_to_cam(i).unwrap();
1275 let px = r.camera(1).unwrap().cam_to_img(&(pose * x)).unwrap();
1276 obs.push(px);
1277 }
1278 let mut r2 = Reconstruction::new();
1280 r2.add_camera_own_rig(r.camera(1).unwrap().clone()).unwrap();
1281 for i in 1..=3u32 {
1282 let mut pts = vec![obs[(i - 1) as usize]; 4];
1283 pts[1].x += 4.01; pts[2].x += 3.99;
1285 r2.add_image_own_frame(Image::new(i, format!("{i}"), 1, pts), r.world_to_cam(i)).unwrap();
1286 r2.register_image(i).unwrap();
1287 }
1288 let r = &mut r2;
1289 let good = r.add_point3d(x, vec![t(1, 0), t(2, 0), t(3, 0)], [0; 3]).unwrap();
1290 let one_bad = r.add_point3d(x, vec![t(1, 1), t(2, 2), t(3, 3)], [0; 3]);
1291 let one_bad = one_bad.unwrap();
1293 let two_bad = r.add_point3d(x, vec![t(2, 1), t(3, 1), t(1, 3)], [0; 3]).unwrap();
1294 let removed = r.filter_observations_with_large_reprojection_error(4.0, None);
1295 assert_eq!(removed, 1 + 3);
1296 assert!(r.exists_point3d(good));
1297 assert_eq!(r.point3d(one_bad).unwrap().track.len(), 2);
1298 assert!((r.point3d(one_bad).unwrap().error - 3.99 / 3.0).abs() < 1e-9);
1299 assert!(!r.exists_point3d(two_bad));
1300 r.check_invariants().unwrap();
1301 let ang = crate::geometry::triangulation_angle(&Vec3::new(1.0, 0.0, 0.0), &Vec3::new(3.0, 0.0, 0.0), &x).to_degrees();
1303 assert_eq!(r.filter_points3d_with_small_triangulation_angle(ang - 1e-9, None), 2);
1305 assert!(r.exists_point3d(good));
1306 assert_eq!(r.filter_points3d_with_small_triangulation_angle(ang + 1e-9, None), 3);
1307 assert_eq!(r.num_points3d(), 0);
1308 r.check_invariants().unwrap();
1309 }
1310
1311 #[test]
1312 fn errors_and_normalize() {
1313 let mut r = line_scene(11, 1);
1314 r.update_point3d_errors();
1315 let opts = NormalizeOptions::default();
1316 let t = r.normalize(&opts).unwrap();
1317 let centers: Vec<Vec3> = r.registered_images().iter().map(|i| r.projection_center(*i).unwrap()).collect();
1318 assert!((t.scale - 10.0 / 8.0).abs() < 1e-12);
1320 let mut xs: Vec<f64> = centers.iter().map(|c| c.x).collect();
1321 xs.sort_by(|a, b| a.total_cmp(b));
1322 assert!(((xs[9] - xs[1]) - 10.0).abs() < 1e-9);
1323 let mean: f64 = xs[1..=9].iter().sum::<f64>() / 9.0;
1324 assert!(mean.abs() < 1e-9);
1325 }
1326
1327 #[test]
1328 fn rig_pose_derivation() {
1329 let mut r = Reconstruction::new();
1330 for c in [1u32, 2] {
1331 let mut cam = Camera::from_focal(CameraModelKind::Pinhole, 100.0, 200, 200);
1332 cam.camera_id = c;
1333 r.add_camera(cam).unwrap();
1334 }
1335 let mut rig = Rig::new(7);
1336 rig.ref_sensor_id = Some(SensorKey::camera(1));
1337 let rig_to_c2 = Rigid3::new(Quat::from_axis_angle(&Vec3::y(), 0.2), Vec3::new(0.5, 0.0, 0.0));
1338 rig.sensors.insert(SensorKey::camera(2), Some(rig_to_c2));
1339 r.add_rig(rig).unwrap();
1340 let mut f = Frame::new(3, 7);
1341 f.attach_data(SensorDataKey::image(2, 11));
1342 f.attach_data(SensorDataKey::image(1, 10));
1343 r.add_frame(f).unwrap();
1344 let mut im = Image::new(10, "a", 1, []);
1345 im.frame_id = 3;
1346 r.add_image(im).unwrap();
1347 let mut im = Image::new(11, "b", 2, []);
1348 im.frame_id = 3;
1349 r.add_image(im).unwrap();
1350 let want = Rigid3::new(Quat::from_axis_angle(&Vec3::x(), 0.1), Vec3::new(1.0, 2.0, 3.0));
1351 r.set_world_to_cam(11, want).unwrap();
1352 let got = r.world_to_cam(11).unwrap();
1353 assert!((got.translation - want.translation).norm() < 1e-12);
1354 r.register_frame(3).unwrap();
1355 assert_eq!(r.registered_image_count(), 2);
1356 assert_eq!(r.registered_images(), vec![10, 11]);
1357 let s = Sim3::new(2.0, Quat::from_axis_angle(&Vec3::z(), 0.4), Vec3::new(1.0, 1.0, 1.0));
1358 let c_before = r.projection_center(11).unwrap();
1359 r.transform(&s);
1360 assert!((r.projection_center(11).unwrap() - s.transform_point(&c_before)).norm() < 1e-10);
1361 }
1362
1363 #[test]
1364 fn bilinear() {
1365 let data = vec![0u8, 0, 0, 100, 100, 100, 200, 200, 200, 255, 255, 255];
1366 let c = bilinear_rgb(&data, 2, 2, 0.5, 0.5).unwrap();
1367 assert!((c[0] - 138.75).abs() < 1e-9);
1368 assert!(bilinear_rgb(&data, 2, 2, 1.5, 0.0).is_none());
1369 }
1370}