Пример #1
0
def verify_dataset(image_collection: ImageCollection,
                   root_folder: typing.Union[str, PurePath],
                   dataset_name: str,
                   repair: bool = False):
    """
    Examine an existing Autonomous Systems Lab dataset in the database, and check if for errors
    See http://projects.asl.ethz.ch/datasets/doku.php?id=kmavvisualinertialdatasets#downloads

    Some information drawn from the ethz_asl dataset tools, see: https://github.com/ethz-asl/dataset_tools
    :param image_collection: The existing image collection from the loader
    :param root_folder: The body folder, containing body.yaml (i.e. the extracted mav0 folder)
    :param dataset_name: The name of the dataset, see the manager for the list of valid values.
    :param repair: If possible, fix missing images in the dataset
    :return:
    """
    root_folder = Path(root_folder)
    dataset_name = str(dataset_name)
    repair = bool(repair)
    if not root_folder.is_dir():
        raise NotADirectoryError(
            "'{0}' is not a directory".format(root_folder))
    image_group = dataset_name
    valid = True
    irreparable = False

    # Check the image group on the image collection
    if image_collection.image_group != image_group:
        if repair:
            image_collection.image_group = image_group
            image_collection.save()
            logging.getLogger(__name__).info(
                f"Fixed incorrect image group for {image_collection.sequence_name}"
            )
        else:
            logging.getLogger(__name__).warning(
                f"{image_collection.sequence_name} has incorrect image group {image_group}"
            )
            valid = False

    # Find the various files containing the data (that's a 6-element tuple unpack for the return value)
    (root_folder, left_rgb_path, left_camera_intrinsics_path, right_rgb_path,
     right_camera_intrinsics_path,
     trajectory_path) = euroc_loader.find_files(root_folder)

    # Read the meta-information from the files (that's a 6-element tuple unpack for the return value)
    left_image_files = euroc_loader.read_image_filenames(left_rgb_path)
    left_extrinsics, left_intrinsics = euroc_loader.get_camera_calibration(
        left_camera_intrinsics_path)
    right_image_files = euroc_loader.read_image_filenames(left_rgb_path)
    right_extrinsics, right_intrinsics = euroc_loader.get_camera_calibration(
        right_camera_intrinsics_path)

    # Create stereo rectification matrices from the intrinsics
    left_x, left_y, left_intrinsics, right_x, right_y, right_intrinsics = euroc_loader.rectify(
        left_extrinsics, left_intrinsics, right_extrinsics, right_intrinsics)

    # Associate the different data types by timestamp. Trajectory last because it's bigger than the stereo.
    all_metadata = euroc_loader.associate_data(left_image_files,
                                               right_image_files)

    # Load the images from the metadata
    total_invalid_images = 0
    total_fixed_images = 0
    image_index = 0
    with arvet.database.image_manager.get().get_group(image_group,
                                                      allow_write=repair):
        for timestamp, left_image_file, right_image_file in all_metadata:
            changed = False
            img_valid = True
            # Skip if we've hit the end of the data
            if image_index >= len(image_collection):
                logging.getLogger(__name__).error(
                    f"Image {image_index} is missing from the dataset")
                irreparable = True
                valid = False
                total_invalid_images += 1
                continue

            left_img_path = root_folder / 'cam0' / 'data' / left_image_file
            right_img_path = root_folder / 'cam1' / 'data' / right_image_file
            left_pixels = image_utils.read_colour(left_img_path)
            right_pixels = image_utils.read_colour(right_img_path)

            # Error check the loaded image data
            # The EuRoC Sequences MH_04_difficult and V2_03_difficult are missing the first right frame
            # So we actually start loading from
            # In general, frames that are missing are skipped, and do not increment image index
            if left_pixels is None or left_pixels.size is 0:
                logging.getLogger(__name__).warning(
                    f"Could not read left image \"{left_img_path}\", result is empty. Image is skipped."
                )
                continue
            if right_pixels is None or right_pixels.size is 0:
                logging.getLogger(__name__).warning(
                    f"Could not read right image \"{right_img_path}\", result is empty. Image is skipped."
                )
                continue

            left_pixels = cv2.remap(left_pixels, left_x, left_y,
                                    cv2.INTER_LINEAR)
            right_pixels = cv2.remap(right_pixels, right_x, right_y,
                                     cv2.INTER_LINEAR)
            left_hash = bytes(xxhash.xxh64(left_pixels).digest())
            right_hash = bytes(xxhash.xxh64(right_pixels).digest())

            # Load the image from the database
            try:
                _, image = image_collection[image_index]
            except (KeyError, IOError, RuntimeError):
                logging.getLogger(__name__).exception(
                    f"Error loading image object {image_index}")
                valid = False
                image_index += 1  # Index is valid, increment when done
                continue

            # First, check the image group
            if image.image_group != image_group:
                if repair:
                    image.image_group = image_group
                    changed = True
                logging.getLogger(__name__).warning(
                    f"Image {image_index} has incorrect group {image.image_group}"
                )
                valid = False
                img_valid = False

            # Load the pixels from the image
            try:
                left_actual_pixels = image.left_pixels
            except (KeyError, IOError, RuntimeError):
                left_actual_pixels = None
            try:
                right_actual_pixels = image.right_pixels
            except (KeyError, IOError, RuntimeError):
                right_actual_pixels = None

            # Compare the loaded image data to the data read from disk
            if left_actual_pixels is None or not np.array_equal(
                    left_pixels, left_actual_pixels):
                if repair:
                    image.store_pixels(left_pixels)
                    changed = True
                else:
                    logging.getLogger(__name__).error(
                        f"Image {image_index}: Left pixels do not match data read from {left_img_path}"
                    )
                img_valid = False
                valid = False
            if left_hash != bytes(image.metadata.img_hash):
                if repair:
                    image.metadata.img_hash = left_hash
                    changed = True
                else:
                    logging.getLogger(__name__).error(
                        f"Image {image_index}: Left hash does not match metadata"
                    )
                valid = False
                img_valid = False
            if right_actual_pixels is None or not np.array_equal(
                    right_pixels, right_actual_pixels):
                if repair:
                    image.store_right_pixels(right_pixels)
                    changed = True
                else:
                    logging.getLogger(__name__).error(
                        f"Image {image_index}: Right pixels do not match data read from {right_img_path}"
                    )
                valid = False
                img_valid = False
            if right_hash != bytes(image.right_metadata.img_hash):
                if repair:
                    image.right_metadata.img_hash = right_hash
                    changed = True
                else:
                    logging.getLogger(__name__).error(
                        f"Image {image_index}: Right hash does not match metadata"
                    )
                valid = False
                img_valid = False
            if changed and repair:
                logging.getLogger(__name__).warning(
                    f"Image {image_index}: repaired")
                image.save()
                total_fixed_images += 1
            if not img_valid:
                total_invalid_images += 1
            image_index += 1

    if irreparable:
        # Images are missing entirely, needs re-import
        logging.getLogger(__name__).error(
            f"Image Collection {image_collection.pk} for sequence {dataset_name} "
            "is IRREPARABLE, invalidate and re-import")
    elif repair:
        # Re-save the modified image collection
        logging.getLogger(__name__).info(
            f"{image_collection.sequence_name} repaired successfully "
            f"({total_fixed_images} image files fixed).")
    elif valid:
        logging.getLogger(__name__).info(
            f"Verification of {image_collection.sequence_name} successful.")
    else:
        logging.getLogger(__name__).error(
            f"Verification of {image_collection.sequence_name} ({image_collection.pk}) "
            f"FAILED, ({total_invalid_images} images failed)")
    return valid
Пример #2
0
def verify_dataset(image_collection: ImageCollection,
                   root_folder: typing.Union[str, Path],
                   dataset_name: str,
                   repair: bool = False):
    """
    Load a TUM RGB-D sequence into the database.


    :return:
    """
    root_folder = Path(root_folder)
    dataset_name = str(dataset_name)
    repair = bool(repair)
    valid = True
    irreparable = False
    image_group = dataset_name

    # Check the root folder to see if it needs to be extracted from a tarfile
    delete_when_done = None
    if not root_folder.is_dir():
        if (root_folder.parent / dataset_name).is_dir():
            # The root was a tarball, but the extracted data already exists, just use that as the root
            root_folder = root_folder.parent / dataset_name
        else:
            candidate_tar_file = root_folder.parent / (dataset_name + '.tgz')
            if candidate_tar_file.is_file() and tarfile.is_tarfile(
                    candidate_tar_file):
                # Root is actually a tarfile, extract it. find_roots with handle folder structures
                with tarfile.open(candidate_tar_file) as tar_fp:
                    tar_fp.extractall(root_folder.parent / dataset_name)
                root_folder = root_folder.parent / dataset_name
                delete_when_done = root_folder
            else:
                # Could find neither a dir nor a tarfile to extract from
                raise NotADirectoryError(
                    "'{0}' is not a directory".format(root_folder))

    # Check the image group on the image collection
    if image_collection.image_group != image_group:
        if repair:
            image_collection.image_group = image_group
            image_collection.save()
            logging.getLogger(__name__).info(
                f"Fixed incorrect image group for {image_collection.sequence_name}"
            )
        else:
            logging.getLogger(__name__).warning(
                f"{image_collection.sequence_name} has incorrect image group {image_collection.image_group}"
            )
            valid = False

    # Find the relevant metadata files
    root_folder, rgb_path, depth_path, trajectory_path = tum_loader.find_files(
        root_folder)

    # Step 2: Read the metadata from them
    image_files = tum_loader.read_image_filenames(rgb_path)
    trajectory = tum_loader.read_trajectory(trajectory_path,
                                            image_files.keys())
    depth_files = tum_loader.read_image_filenames(depth_path)

    # Step 3: Associate the different data types by timestamp
    all_metadata = tum_loader.associate_data(image_files, trajectory,
                                             depth_files)

    # Step 3: Load the images from the metadata
    total_invalid_images = 0
    total_fixed_images = 0
    with arvet.database.image_manager.get().get_group(image_group,
                                                      allow_write=repair):
        for img_idx, (timestamp, image_file, camera_pose,
                      depth_file) in enumerate(all_metadata):
            changed = False
            img_valid = True
            img_path = root_folder / image_file
            depth_path = root_folder / depth_file
            rgb_data = image_utils.read_colour(img_path)
            depth_data = image_utils.read_depth(depth_path)
            depth_data = depth_data / 5000  # Re-scale depth to meters
            img_hash = bytes(xxhash.xxh64(rgb_data).digest())

            # Load the image from the database
            try:
                _, image = image_collection[img_idx]
            except (KeyError, IOError, RuntimeError):
                logging.getLogger(__name__).exception(
                    f"Error loading image object {img_idx}")
                valid = False
                total_invalid_images += 1
                continue

            # First, check the image group
            if image.image_group != image_group:
                if repair:
                    image.image_group = image_group
                    changed = True
                logging.getLogger(__name__).warning(
                    f"Image {img_idx} has incorrect group {image.image_group}")
                valid = False
                img_valid = False

            # Load the pixels from the image
            try:
                actual_pixels = image.pixels
            except (KeyError, IOError, RuntimeError):
                actual_pixels = None
            try:
                actual_depth = image.depth
            except (KeyError, IOError, RuntimeError):
                actual_depth = None

            # Compare the loaded image data to the data read from disk
            if actual_pixels is None or not np.array_equal(
                    rgb_data, actual_pixels):
                if repair:
                    image.store_pixels(rgb_data)
                    changed = True
                else:
                    logging.getLogger(__name__).error(
                        f"Image {img_idx}: Pixels do not match data read from {img_path}"
                    )
                valid = False
                img_valid = False
            if img_hash != bytes(image.metadata.img_hash):
                if repair:
                    image.metadata.img_hash = img_hash
                    changed = True
                else:
                    logging.getLogger(__name__).error(
                        f"Image {img_idx}: Image hash does not match metadata")
                valid = False
                img_valid = False
            if actual_depth is None or not np.array_equal(
                    depth_data, actual_depth):
                if repair:
                    image.store_depth(depth_data)
                    changed = True
                else:
                    logging.getLogger(__name__).error(
                        f"Image {img_idx}: Depth does not match data read from {depth_path}"
                    )
                valid = False
                img_valid = False
            if changed and repair:
                logging.getLogger(__name__).warning(
                    f"Image {img_idx}: repaired")
                image.save()
                total_fixed_images += 1
            if not img_valid:
                total_invalid_images += 1

    if irreparable:
        # Images are missing entirely, needs re-import
        logging.getLogger(__name__).error(
            f"Image Collection {image_collection.pk} for sequence {dataset_name} "
            "is IRREPARABLE, invalidate and re-import")
    elif repair:
        # Re-save the modified image collection
        logging.getLogger(__name__).info(
            f"{image_collection.sequence_name} repaired successfully "
            f"({total_fixed_images} image files fixed).")
    elif valid:
        logging.getLogger(__name__).info(
            f"Verification of {image_collection.sequence_name} successful.")
    else:
        logging.getLogger(__name__).error(
            f"Verification of {image_collection.sequence_name} ({image_collection.pk}) "
            f"FAILED, ({total_invalid_images} images failed)")

    if delete_when_done is not None and delete_when_done.exists():
        # We're done and need to clean up after ourselves
        shutil.rmtree(delete_when_done)

    return valid
Пример #3
0
def verify_dataset(image_collection: ImageCollection, root_folder: typing.Union[str, Path],
                   sequence_number: int, repair: bool = False):
    """
    Load a KITTI image sequences into the database.
    :return:
    """
    root_folder = Path(root_folder)
    sequence_number = int(sequence_number)
    repair = bool(repair)
    if not 0 <= sequence_number < 11:
        raise ValueError("Cannot import sequence {0}, it is invalid".format(sequence_number))
    root_folder = kitti_loader.find_root(root_folder, sequence_number)
    data = pykitti.odometry(root_folder, sequence="{0:02}".format(sequence_number))
    image_group = f"KITTI_{sequence_number:06}"
    valid = True
    irreparable = False

    # Check the Image Collection
    if image_collection.image_group != image_group:
        if repair:
            image_collection.image_group = image_group
            image_collection.save()
            logging.getLogger(__name__).info(
                f"Fixed incorrect image group for {image_collection.sequence_name}")
        else:
            logging.getLogger(__name__).warning(
                f"{image_collection.sequence_name} has incorrect image group {image_collection.image_group}")
            valid = False

    # dataset.calib:      Calibration data are accessible as a named tuple
    # dataset.timestamps: Timestamps are parsed into a list of timedelta objects
    # dataset.poses:      Generator to load ground truth poses T_w_cam0
    # dataset.camN:       Generator to load individual images from camera N
    # dataset.gray:       Generator to load monochrome stereo pairs (cam0, cam1)
    # dataset.rgb:        Generator to load RGB stereo pairs (cam2, cam3)
    # dataset.velo:       Generator to load velodyne scans as [x,y,z,reflectance]
    total_invalid_images = 0
    total_fixed_images = 0
    with arvet.database.image_manager.get().get_group(image_group, allow_write=repair):
        for img_idx, (left_image, right_image, timestamp, pose) in enumerate(
                zip(data.cam2, data.cam3, data.timestamps, data.poses)):
            changed = False
            img_valid = True
            if img_idx >= len(image_collection):
                logging.getLogger(__name__).error(f"Image {img_idx} is missing from the dataset")
                irreparable = True
                valid = False
                continue

            left_image = np.array(left_image)
            right_image = np.array(right_image)
            left_hash = bytes(xxhash.xxh64(left_image).digest())
            right_hash = bytes(xxhash.xxh64(right_image).digest())

            # Load the image object from the database
            try:
                _, image = image_collection[img_idx]
            except (KeyError, IOError, RuntimeError):
                logging.getLogger(__name__).exception(f"Error loading image object {img_idx}")
                valid = False
                total_invalid_images += 1
                continue

            # First, check the image group
            if image.image_group != image_group:
                if repair:
                    image.image_group = image_group
                    changed = True
                logging.getLogger(__name__).warning(f"Image {img_idx} has incorrect group {image.image_group}")
                valid = False

            # Load the pixels from the image
            try:
                left_actual_pixels = image.left_pixels
            except (KeyError, IOError, RuntimeError):
                left_actual_pixels = None
            try:
                right_actual_pixels = image.right_pixels
            except (KeyError, IOError, RuntimeError):
                right_actual_pixels = None

            # Compare the loaded image data to the data read from disk
            if left_actual_pixels is None or not np.array_equal(left_image, left_actual_pixels):
                if repair:
                    image.store_pixels(left_image)
                    changed = True
                else:
                    logging.getLogger(__name__).error(f"Image {img_idx}: Left pixels do not match data read from disk")
                valid = False
                img_valid = False
            if left_hash != bytes(image.metadata.img_hash):
                if repair:
                    image.metadata.img_hash = left_hash
                    changed = True
                else:
                    logging.getLogger(__name__).error(f"Image {img_idx}: Left hash does not match metadata")
                valid = False
                img_valid = False
            if right_actual_pixels is None or not np.array_equal(right_image, right_actual_pixels):
                if repair:
                    image.store_right_pixels(right_image)
                    changed = True
                else:
                    logging.getLogger(__name__).error(f"Image {img_idx}: Right pixels do not match data read from disk")
                valid = False
                img_valid = False
            if right_hash != bytes(image.right_metadata.img_hash):
                if repair:
                    image.right_metadata.img_hash = right_hash
                    changed = True
                else:
                    logging.getLogger(__name__).error(f"Image {img_idx}: Right hash does not match metadata")
                valid = False
                img_valid = False
            if changed and repair:
                logging.getLogger(__name__).warning(f"Image {img_idx}: repaired")
                image.save()
                total_fixed_images += 1
            if not img_valid:
                total_invalid_images += 1

    if irreparable:
        # Images are missing entirely, needs re-import
        logging.getLogger(__name__).error(f"Image Collection {image_collection.pk} for sequence "
                                          f"{image_collection.sequence_name} is IRREPARABLE, invalidate and re-import")
    elif repair:
        # Re-save the modified image collection
        logging.getLogger(__name__).info(f"{image_collection.sequence_name} repaired successfully "
                                         f"({total_fixed_images} image files fixed).")
    elif valid:
        logging.getLogger(__name__).info(f"Verification of {image_collection.sequence_name} successful.")
    else:
        logging.getLogger(__name__).error(
            f"Verification of {image_collection.sequence_name} ({image_collection.pk}) "
            f"FAILED, ({total_invalid_images} images failed)")
    return valid