def merge_records_data(image_list: List[List[str]], image_paths: List[str], kapture_path: str, images_import_method: TransferAction): """ Merge several records data. keep only the first image. :param image_list: list of image_names :param image dir paths :param directory root path to the merged kapture :param images_import_method: choose how to import actual image files """ assert len(image_list) > 0 assert len(image_list) == len(image_paths) added_images = set() for images_filenames, record_dirpath in zip(image_list, image_paths): images_filenames_to_add = {images_filename for images_filename in images_filenames if images_filename not in added_images} import_record_data_from_dir_auto(record_dirpath, kapture_path, images_filenames_to_add, images_import_method) diff = set(images_filenames).difference(images_filenames_to_add) if len(diff) > 0: getLogger().warning(f'Cannot import some images because they were already added: {diff}') added_images.update(images_filenames_to_add)
def merge_matches(matches_list: List[Optional[kapture.Matches]], matches_paths: List[str], output_path: str) -> kapture.Matches: """ Merge several matches lists in one. :param matches_list: list of matches to merge :param matches_paths: matches files paths :param output_path: root path of the merged matches files :return: merged matches """ assert len(matches_list) > 0 assert len(matches_paths) == len(matches_list) merged_matches = kapture.Matches() for matches, matches_path in zip(matches_list, matches_paths): if matches is None: continue for pair in matches: if pair in merged_matches: getLogger().warning(f'{pair} was found multiple times.') else: merged_matches.add(pair[0], pair[1]) if output_path: in_path = kapture.io.features.get_matches_fullpath( pair, matches_path) out_path = kapture.io.features.get_matches_fullpath( pair, output_path) if in_path != out_path: # skip actual copy if file does not actually move. os.makedirs(os.path.dirname(out_path), exist_ok=True) shutil.copy(in_path, out_path) return merged_matches
def match_descriptors(self, descriptors_1, descriptors_2): if descriptors_1.shape[0] == 0 or descriptors_2.shape[0] == 0: return np.zeros((0, 3)) # send data to GPU descriptors1_torch = torch.from_numpy(descriptors_1).to(self._device) descriptors2_torch = torch.from_numpy(descriptors_2).to(self._device) # make sure its double (because CUDA tensors only supports floating-point) descriptors1_torch = descriptors1_torch.float() descriptors2_torch = descriptors2_torch.float() # sanity check if not descriptors1_torch.device == self._device: getLogger().debug('descriptor on device {} (requested {})'.format( descriptors1_torch.device, self._device)) if not descriptors2_torch.device == self._device: getLogger().debug('descriptor on device {} (requested {})'.format( descriptors2_torch.device, self._device)) simmilarity_matrix = descriptors1_torch @ descriptors2_torch.t() scores = torch.max(simmilarity_matrix, dim=1)[0] nearest_neighbor_idx_1vs2 = torch.max(simmilarity_matrix, dim=1)[1] nearest_neighbor_idx_2vs1 = torch.max(simmilarity_matrix, dim=0)[1] ids1 = torch.arange(0, simmilarity_matrix.shape[0], device=descriptors1_torch.device) # cross check mask = ids1 == nearest_neighbor_idx_2vs1[nearest_neighbor_idx_1vs2] matches_torch = torch.stack([ ids1[mask].type(torch.float), nearest_neighbor_idx_1vs2[mask].type(torch.float), scores[mask] ]).t() # retrieve data back from GPU matches = matches_torch.data.cpu().numpy() matches = matches.astype(np.float) return matches
def equal_points3d(points3d_a: Optional[kapture.Points3d], points3d_b: Optional[kapture.Points3d]) -> bool: """ Compare two instances of kapture.Points3d. :param points3d_a: first set of points3d :param points3d_b: second set of points3d :return: True if they are identical, False otherwise. """ if points3d_a is None and points3d_b is None: return True elif points3d_a is None and points3d_b is not None: return False elif points3d_a is not None and points3d_b is None: return False # ide guidance assert points3d_a is not None assert points3d_b is not None if len(points3d_a) != len(points3d_b): getLogger().debug('equal_points3d: a and b do not have the same number of elements') # internally converted to array of bool which cannot be a point3d bool_array = np.isclose(points3d_a.as_array(), points3d_b.as_array()) are_equal = bool_array.all() if not are_equal: diffs = [n for n, b in enumerate(bool_array) if not b] diffs = diffs[:15] diffs = ['element {} : {} != {}'.format(n, points3d_a[n], points3d_b[n]) for n in diffs] getLogger().debug('equal_points3d:\n{}'.format('\n'.join(diffs))) return are_equal
def delete_existing_kapture_files(dirpath: str, force_erase: bool, only: Optional[List[type]] = None, skip: Optional[List[type]] = None): """ Deletes all existing files / directories at dirpath that corresponds to kapture data. do not use only and skip at the same time. :param dirpath: :param force_erase: do not ask user confirmation. :param only: can be used to select files / directories to be removed. :param skip: can be used to select files / directories to be kept. :return: """ assert only is None or isinstance(only, list) assert skip is None or isinstance(skip, list) dirpath = path_secure(dirpath) csv_filepaths = [ path.join(dirpath, filename) for dtype, filename in CSV_FILENAMES.items() if (not only and not skip) or (only and dtype in only) or ( skip and dtype not in skip) ] features_dirpaths = [ path.join(dirpath, dirname) for dtype, dirname in FEATURES_DATA_DIRNAMES.items() if (not only and not skip) or (only and dtype in only) or ( skip and dtype not in skip) ] records_dirpaths = [RECORD_DATA_DIRNAME] # remove existing_files files (start with deepest/longest paths to avoid to delete files before dirs). existing_paths = list( reversed( sorted({ pathval for pathval in csv_filepaths + features_dirpaths + records_dirpaths if path.isfile(pathval) or path.isdir(pathval) }))) # if any if existing_paths: existing_paths_as_string = ', '.join(f'"{path.relpath(p, dirpath)}"' for p in existing_paths) # ask for permission to_delete = (force_erase or (input( f'{existing_paths_as_string} already in "{dirpath}".' ' Delete ? [y/N]').lower() == 'y')) # delete all or quit if to_delete: getLogger().info('deleting already' f' existing {existing_paths_as_string}') for pathval in existing_paths: if path.islink(pathval) or path.isfile(pathval): os.remove(pathval) else: rmtree(pathval) else: raise ValueError(f'{existing_paths_as_string} already exist')
def merge_image_features( feature_type: Type[Union[kapture.Keypoints, kapture.Descriptors, kapture.GlobalFeatures]], features_list: Union[List[Optional[kapture.Keypoints]], List[Optional[kapture.Descriptors]], List[Optional[kapture.GlobalFeatures]]], features_paths: List[str], output_path: str ) -> Union[kapture.Keypoints, kapture.Descriptors, kapture.GlobalFeatures]: """ Merge several features_list (keypoints, descriptors or global features_list) (of same type) in one. :param feature_type: the type of features_list :param features_list: the list of values :param features_paths: the paths :param output_path: root path of the features to construct :return: merged features object of the corresponding type """ assert len(features_list) > 0 assert len(features_paths) == len(features_list) # find no none value val = [d for d in features_list if d is not None] assert len(val) > 0 merged_features = feature_type(val[0].type_name, val[0].dtype, val[0].dsize) for features, features_path in zip(features_list, features_paths): if features is None: continue assert isinstance(features, feature_type) assert features.type_name == merged_features.type_name assert features.dtype == merged_features.dtype assert features.dsize == merged_features.dsize for name in features: if name in merged_features: getLogger().warning(f'{name} was found multiple times.') else: merged_features.add(name) if output_path: # TODO: uses kapture.io.features_list.get_image_features_dirpath() in_path = kapture.io.features.get_features_fullpath( feature_type, features_path, name) out_path = kapture.io.features.get_features_fullpath( feature_type, output_path, name) if in_path != out_path: # skip actual copy if file does not actually move. os.makedirs(os.path.dirname(out_path), exist_ok=True) shutil.copy(in_path, out_path) return merged_features
def log_difference(a: List[Tuple[Any, ...]], b: List[Tuple[Any, ...]], func_name: str, trim_count: int = 5) -> None: """ Records in the logger the difference between two values. :param a: first value :param b: second value :param func_name: comparison function to print :param trim_count: maximum number of values to record """ if len(a) != len(b): getLogger().debug(f'{func_name}: a and b do not have the same number of elements') else: diffs = [(va, vb) for va, vb in zip(a, b) if va != vb] diffs = diffs[:trim_count] diffs = ['({}) != ({})'.format(', '.join([str(f) for f in va]), ', '.join([str(f) for f in vb])) for va, vb in diffs] getLogger().debug('{}:\n{}'.format(func_name, '\n'.join(diffs)))
def equal_trajectories(trajectories_a: Optional[kapture.Trajectories], trajectories_b: Optional[kapture.Trajectories]) -> bool: """ Compare two instances of kapture.Trajectories. Poses are compared with is_distance_within_threshold(pose_transform_distance()) :param trajectories_a: first trajectory :param trajectories_b: second trajectory :return: True if they are identical, False otherwise. """ if trajectories_a is None and trajectories_b is None: return True elif trajectories_a is None and trajectories_b is not None: return False elif trajectories_a is not None and trajectories_b is None: return False flattened_a = list(flatten(trajectories_a, is_sorted=True)) flattened_b = list(flatten(trajectories_b, is_sorted=True)) if len(flattened_a) != len(flattened_b): getLogger().debug('equal_trajectories: a and b do not have the same number of elements') return False for (timestamp_a, sensor_id_a, pose_a), (timestamp_b, sensor_id_b, pose_b) in zip(flattened_a, flattened_b): if timestamp_a != timestamp_b or sensor_id_a != sensor_id_b: getLogger().debug( f'equal_trajectories: ({timestamp_a}, {sensor_id_a}, {pose_a.r_raw}, {pose_a.t_raw}) !=' f' ({timestamp_b}, {sensor_id_b}, {pose_b.r_raw}, {pose_b.t_raw})') return False if not equal_poses(pose_a, pose_b): getLogger().debug( f'equal_trajectories: ({timestamp_a}, {sensor_id_a}, {pose_a.r_raw}, {pose_a.t_raw}) ' f'is not close to ' f'({timestamp_b}, {sensor_id_b}, {pose_b.r_raw}, {pose_b.t_raw})') return False return True
def rigs_remove_inplace(trajectories: Trajectories, rigs: Rigs, max_depth: int = 10): """ Removes rigs poses and replaces them by the poses of every sensors in it. The operation is performed inplace, and modifies trajectories. This is useful for formats that does not have the rig notion. :param trajectories: input/output Trajectories where the rigs has to be replaced :param rigs: input Rigs that defines the rigs/sensors relationship. :param max_depth: maximum nested rig depth. """ assert isinstance(rigs, Rigs) assert isinstance(trajectories, Trajectories) # collect all poses of rigs in trajectories for iteration in range(max_depth): # repeat the operation while there is so rig remaining (nested rigs) jobs = [(timestamp, rig_id, pose_rig_from_world) for timestamp, poses_for_timestamp in trajectories.items() for rig_id, pose_rig_from_world in poses_for_timestamp.items() if rig_id in rigs] if len(jobs) == 0: # we are done break getLogger().debug(f'rigs_remove {len(jobs)} jobs at depth {iteration}') for timestamp, rig_id, pose_rig_from_world in tqdm( jobs, disable=getLogger().level >= logging.CRITICAL): for device_id, pose_device_from_rig in rigs[rig_id].items(): pose_cam_from_world = PoseTransform.compose( [pose_device_from_rig, pose_rig_from_world]) trajectories.setdefault(timestamp, {})[device_id] = pose_cam_from_world del trajectories[timestamp][rig_id] for timestamp in trajectories.keys(): if len(trajectories[timestamp]) == 0: del trajectories[timestamp]
def equal_sensors(sensors_a: Optional[kapture.Sensors], sensors_b: Optional[kapture.Sensors]) -> bool: """ Compare two instances of kapture.Sensors. model_params for cameras are considered equal if np.isclose says so. :param sensors_a: first sensor definition :param sensors_b: second sensor definition :return: True if they are identical, False otherwise. """ if sensors_a is None and sensors_b is None: return True elif sensors_a is None and sensors_b is not None: return False elif sensors_a is not None and sensors_b is None: return False flattened_a = list(flatten(sensors_a, is_sorted=True)) flattened_b = list(flatten(sensors_b, is_sorted=True)) if len(flattened_a) != len(flattened_b): getLogger().debug( 'equal_sensors: a and b do not have the same number of elements') return False for (sensor_id_a, sensor_a), (sensor_id_b, sensor_b) in zip(flattened_a, flattened_b): # handling special case: name_a='' and name_b=None equal_id = sensor_id_a == sensor_id_b equal_name = (not sensor_a.name and not sensor_b.name) or (sensor_a.name == sensor_b.name) equal_type = sensor_a.sensor_type == sensor_b.sensor_type if not equal_id or not equal_name or not equal_type: getLogger().debug( f'equal_sensors: ({sensor_id_a}, {sensor_a}) != ({sensor_id_b}, {sensor_b})' ) return False equal_params = False if sensor_a.sensor_type in kapture.ALL_CAMERA_SENSOR_TYPES: assert isinstance(sensor_a, Camera) assert isinstance(sensor_b, Camera) if sensor_a.sensor_type != sensor_b.sensor_type: return False if sensor_a.camera_type == sensor_b.camera_type: equal_params = equal_camera_params(sensor_a.camera_params, sensor_b.camera_params) else: equal_params = sensor_a.sensor_params == sensor_b.sensor_params if not equal_params: getLogger().debug( f'equal_sensors: ({sensor_id_a}, {sensor_a}) != ({sensor_id_b}, {sensor_b})' ) return False return True
# Copyright 2020-present NAVER Corp. Under BSD 3-clause license """ Paths manipulations and filesystem operations. """ import os import os.path as path import shutil import tempfile from typing import AnyStr, Iterable, Optional, Union, List from kapture.utils.logging import getLogger logger = getLogger() def path_secure(anypath: AnyStr) -> AnyStr: """ Make sure path representation is OS independent (same on linux and windows), because path can be used as an identifier (eg. images). :param anypath: path to normalize :return: normalized path """ return path.normpath(anypath).replace('\\', '/') def populate_files_in_dirpath(root_dirpath: str, filename_extensions: Optional[Union[ str, List[str]]] = None, do_relative_path: bool = True) -> Iterable[str]: