opensfm/types.py (240 lines of code) (raw):

"""Basic types for building a reconstruction.""" from typing import Dict, Optional import numpy as np from opensfm import pygeometry from opensfm import pymap from opensfm.geo import TopocentricConverter PANOSHOT_RIG_PREFIX = "panoshot_" class ShotMesh(object): """Triangular mesh of points visible in a shot Attributes: vertices: (list of vectors) mesh vertices faces: (list of triplets) triangles' topology """ def __init__(self): self.vertices = None self.faces = None class Reconstruction(object): """Defines the reconstructed scene. Attributes: cameras (Dict(Camera)): List of cameras. shots (Dict(Shot)): List of reconstructed shots. points (Dict(Point)): List of reconstructed points. reference (TopocentricConverter): Topocentric reference converter. """ def __init__(self) -> None: """Defaut constructor""" self.map = pymap.Map() self.camera_view = pymap.CameraView(self.map) self.bias_view = pymap.BiasView(self.map) self.rig_cameras_view = pymap.RigCameraView(self.map) self.rig_instances_view = pymap.RigInstanceView(self.map) self.shot_view = pymap.ShotView(self.map) self.pano_shot_view = pymap.PanoShotView(self.map) self.landmark_view = pymap.LandmarkView(self.map) def __repr__(self): return ( "<Reconstruction" f" cameras={len(self.cameras)}" f" shots={len(self.shots)}" f" points={len(self.points)}" f" rig_cameras={len(self.rig_cameras)}" f" rig_instances={len(self.rig_instances)}" ">" ) def get_cameras(self) -> pymap.CameraView: return self.camera_view def set_cameras(self, value: Dict[str, pygeometry.Camera]) -> None: for cam in value.values(): self.map.create_camera(cam) cameras = property(get_cameras, set_cameras) def get_biases(self) -> pymap.BiasView: return self.bias_view def set_biases(self, value: Dict[str, pygeometry.Similarity]) -> None: for cam_id, bias in value.items(): self.map.set_bias(cam_id, bias) def set_bias(self, cam_id: str, bias: pygeometry.Similarity) -> None: self.map.set_bias(cam_id, bias) biases = property(get_biases, set_biases) def get_rig_cameras(self) -> pymap.RigCameraView: return self.rig_cameras_view def set_rig_cameras(self, values: Dict[str, pymap.RigCamera]) -> None: for rig_camera in values.values(): self.map.create_rig_camera(rig_camera) rig_cameras = property(get_rig_cameras, set_rig_cameras) def get_rig_instances(self) -> pymap.RigInstanceView: return self.rig_instances_view def set_rig_instances(self, values: Dict[str, pymap.RigInstance]) -> None: for rig_instance in values.values(): self.add_rig_instance(rig_instance) def remove_rig_instance(self, rig_instance_id: str) -> None: self.map.remove_rig_instance(rig_instance_id) rig_instances = property(get_rig_instances, set_rig_instances) def get_shots(self) -> pymap.ShotView: return self.shot_view def set_shots(self, value: Dict[str, pymap.Shot]) -> None: for shot in value.values(): self.add_shot(shot) shots = property(get_shots, set_shots) def get_pano_shots(self) -> pymap.PanoShotView: return self.pano_shot_view def set_pano_shots(self, value: Dict[str, pymap.Shot]) -> None: for shot in value.values(): self.add_pano_shot(shot) pano_shots = property(get_pano_shots, set_pano_shots) def get_points(self) -> pymap.LandmarkView: return self.landmark_view def set_points(self, value: Dict[str, pymap.Landmark]) -> None: self.map.clear_observations_and_landmarks() for point in value.values(): self.add_point(point) def remove_point(self, point_id: str) -> None: self.map.remove_landmark(point_id) points = property(get_points, set_points) def get_reference(self) -> TopocentricConverter: ref = self.map.get_reference() return TopocentricConverter(ref.lat, ref.lon, ref.alt) def set_reference(self, value: TopocentricConverter) -> None: self.map.set_reference(value.lat, value.lon, value.alt) reference = property(get_reference, set_reference) # Cameras def add_camera(self, camera: pygeometry.Camera) -> pygeometry.Camera: """Add a camera in the list :param camera: The camera. """ if camera.id not in self.cameras: return self.map.create_camera(camera) else: return self.get_camera(camera.id) def get_camera(self, id: str) -> pygeometry.Camera: """Return a camera by id. :return: If exists returns the camera, otherwise None. """ return self.cameras.get(id) # Rigs def add_rig_camera(self, rig_camera: pymap.RigCamera) -> pymap.RigCamera: """Add a rig camera in the list :param rig_camera: The rig camera. """ if rig_camera.id not in self.rig_cameras: return self.map.create_rig_camera(rig_camera) else: return self.rig_cameras.get(rig_camera.id) def add_rig_instance(self, rig_instance: pymap.RigInstance) -> pymap.RigInstance: """Creates a copy of the passed rig instance in the current reconstruction""" for camera in rig_instance.rig_cameras.values(): if camera.id not in self.rig_cameras: self.map.create_rig_camera(camera) in_any_instance = any( (set(rig_instance.shots) & set(ri.shots)) for ri in self.rig_instances.values() ) if in_any_instance: raise RuntimeError("Shots already exist in another instance") if rig_instance.id not in self.rig_instances: self.map.create_rig_instance(rig_instance.id) return self.map.update_rig_instance(rig_instance) # Shot def create_shot( self, shot_id: str, camera_id: str, pose: Optional[pygeometry.Pose] = None, rig_camera_id: Optional[str] = None, rig_instance_id: Optional[str] = None, ) -> pymap.Shot: passed_rig_camera_id = rig_camera_id if rig_camera_id else camera_id passed_rig_instance_id = rig_instance_id if rig_instance_id else shot_id if (not rig_camera_id) and (camera_id not in self.rig_cameras): self.add_rig_camera(pymap.RigCamera(pygeometry.Pose(), camera_id)) if passed_rig_camera_id not in self.rig_cameras: raise RuntimeError( f"Rig Camera {passed_rig_camera_id} doesn't exist in reconstruction" ) if (not rig_instance_id) and (shot_id not in self.rig_instances): self.add_rig_instance(pymap.RigInstance(shot_id)) if passed_rig_instance_id not in self.rig_instances: raise RuntimeError( f"Rig Instance {passed_rig_instance_id} doesn't exist in reconstruction" ) if pose is None: created_shot = self.map.create_shot( shot_id, camera_id, passed_rig_camera_id, passed_rig_instance_id ) else: created_shot = self.map.create_shot( shot_id, camera_id, passed_rig_camera_id, passed_rig_instance_id, pose ) return created_shot def add_shot(self, shot: pymap.Shot) -> pymap.Shot: """Creates a copy of the passed shot in the current reconstruction If the shot belong to a Rig, we recursively copy the entire rig instance, so rigs stay consistents. """ if shot.camera.id not in self.cameras: self.add_camera(shot.camera) if shot.rig_instance_id not in self.rig_instances: self.map.create_rig_instance(shot.rig_instance_id) if shot.rig_camera_id not in self.rig_cameras: self.map.create_rig_camera(shot.rig_camera) if shot.id not in self.shots: self.map.create_shot( shot.id, shot.camera.id, shot.rig_camera_id, shot.rig_instance_id, shot.pose, ) ret = self.map.update_shot(shot) return ret def get_shot(self, id: str) -> pymap.Shot: """Return a shot by id. :return: If exists returns the shot, otherwise None. """ return self.shots.get(id) def remove_shot(self, shot_id: str) -> None: self.map.remove_shot(shot_id) # PanoShot def create_pano_shot( self, shot_id: str, camera_id: str, pose: Optional[pygeometry.Pose] = None ) -> pymap.Shot: if pose is None: pose = pygeometry.Pose() rig_camera_id = f"{PANOSHOT_RIG_PREFIX}{camera_id}" if rig_camera_id not in self.rig_cameras: self.add_rig_camera(pymap.RigCamera(pygeometry.Pose(), rig_camera_id)) rig_instance_id = f"{PANOSHOT_RIG_PREFIX}{shot_id}" if rig_instance_id not in self.rig_instances: self.add_rig_instance(pymap.RigInstance(rig_instance_id)) created_shot = self.map.create_pano_shot( shot_id, camera_id, rig_camera_id, rig_instance_id, pose ) return created_shot def add_pano_shot(self, pshot: pymap.Shot) -> pymap.Shot: if pshot.camera.id not in self.cameras: self.add_camera(pshot.camera) if pshot.id not in self.pano_shots: self.create_pano_shot(pshot.id, pshot.camera.id, pshot.pose) return self.map.update_pano_shot(pshot) def get_pano_shot(self, id: str) -> pymap.Shot: """Return a shot by id. :return: If exists returns the shot, otherwise None. """ return self.pano_shots.get(id) def remove_pano_shot(self, shot_id: str) -> None: self.map.remove_pano_shot(shot_id) def create_point( self, point_id: str, coord: Optional[np.ndarray] = None ) -> pymap.Landmark: if coord is None: return self.map.create_landmark(point_id, np.array([0, 0, 0])) return self.map.create_landmark(point_id, coord) def add_point(self, point: pymap.Landmark) -> pymap.Landmark: """Add a point in the list :param point: The point. """ if point.coordinates is None: new_pt = self.map.create_landmark(point.id, np.array([0, 0, 0])) else: new_pt = self.map.create_landmark(point.id, point.coordinates) if point.color is not None: new_pt.color = point.color return new_pt def get_point(self, id: str) -> pymap.Landmark: """Return a point by id. :return: If exists returns the point, otherwise None. """ return self.points.get(id) def add_observation( self, shot_id: str, lm_id: str, observation: pymap.Observation ) -> None: """Adds an observation between a shot and a landmark :param shot_id: The id of the shot :param lm_id: The id of the landmark :param observation: The observation """ self.map.add_observation(shot_id, lm_id, observation) def remove_observation(self, shot_id: str, lm_id: str) -> None: self.map.remove_observation(shot_id, lm_id) def __deepcopy__(self, d): # create new reconstruction rec_cpy = Reconstruction() rec_cpy.reference = self.reference copy_observations = False # Check if we also need the observations if "copy_observations" in d: copy_observations = d["copy_observations"] # Copy the cameras rec_cpy.cameras = self.cameras # Copy the shots for shot in self.shots.values(): rec_cpy.add_shot(shot) # Copy the pano shots for shot in self.pano_shots.values(): rec_cpy.add_pano_shot(shot) # Copy the points for point in self.points.values(): rec_cpy.add_point(point) if copy_observations: for shot, obs_id in point.get_observations().items(): obs = shot.get_observation(obs_id) rec_cpy.add_observation(shot.id, point.id, obs) # Copy the biases for bias_id, bias in self.biases.items(): rec_cpy.set_bias(bias_id, bias) return rec_cpy def add_correspondences_from_tracks_manager( self, tracks_manager: pymap.TracksManager ) -> None: for track_id in tracks_manager.get_track_ids(): if track_id not in self.points: continue track_obs = tracks_manager.get_track_observations(track_id) for shot_id in track_obs.keys(): if shot_id in self.shots: observation = tracks_manager.get_observation(shot_id, track_id) self.add_observation(shot_id, track_id, observation)