Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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')
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
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)))
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
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]
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
# 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]: