Example #1
0
    def test_simple_trial_run_generated(self):
        # TODO: The state of things:
        # - With the configured sequence, the ORB-SLAM subprocess silently dies about frame 346-347
        # - With my bastardised removal of the subprocess, it seems to work? and not crash?
        # - I have no idea what is different? the pipe?
        sequence_folder, left_path, right_path = ndds_loader.find_files(NDDS_SEQUENCE)
        camera_intrinsics = ndds_loader.read_camera_intrinsics(left_path / '_camera_settings.json')
        max_img_id = ndds_loader.find_max_img_id(lambda idx: left_path / ndds_loader.IMG_TEMPLATE.format(idx))
        with (NDDS_SEQUENCE / 'timestamps.json').open('r') as fp:
            timestamps = json.load(fp)
        self.temp_folder.mkdir(parents=True, exist_ok=True)
        path_manager = PathManager([VOCAB_PATH.parent], self.temp_folder)

        subject = OrbSlam2(
            vocabulary_file=str(VOCAB_PATH.name),
            mode=SensorMode.STEREO,
            vocabulary_branching_factor=5,
            vocabulary_depth=6,
            vocabulary_seed=0,
            depth_threshold=387.0381720715473,
            orb_num_features=598,
            orb_scale_factor=np.power(480 / 104, 1 / 11),  # = (480 / min_height)^(1/num_levels)
            orb_num_levels=11,
            orb_ini_threshold_fast=86,
            orb_min_threshold_fast=48
        )
        subject.resolve_paths(path_manager)
        subject.set_camera_intrinsics(camera_intrinsics, 0.1)

        # Read the first frame data to get the baseline
        left_frame_data = ndds_loader.read_json(left_path / ndds_loader.DATA_TEMPLATE.format(0))
        right_frame_data = ndds_loader.read_json(right_path / ndds_loader.DATA_TEMPLATE.format(0))
        left_camera_pose = ndds_loader.read_camera_pose(left_frame_data)
        right_camera_pose = ndds_loader.read_camera_pose(right_frame_data)
        subject.set_stereo_offset(left_camera_pose.find_relative(right_camera_pose))

        subject.start_trial(ImageSequenceType.SEQUENTIAL, seed=0)
        image_group = 'test'
        with image_manager.get().get_group(image_group, allow_write=True):
            for img_idx in range(max_img_id + 1):
                left_pixels = image_utils.read_colour(left_path / ndds_loader.IMG_TEMPLATE.format(img_idx))
                right_pixels = image_utils.read_colour(right_path / ndds_loader.IMG_TEMPLATE.format(img_idx))

                image = StereoImage(
                    pixels=left_pixels,
                    right_pixels=right_pixels,
                    image_group=image_group,
                    metadata=imeta.ImageMetadata(camera_pose=left_camera_pose),
                    right_metadata=imeta.ImageMetadata(camera_pose=right_camera_pose)
                )
                subject.process_image(image, timestamps[img_idx])
        result = subject.finish_trial()

        self.assertIsInstance(result, SLAMTrialResult)
        self.assertEqual(subject, result.system)
        self.assertTrue(result.success)
        self.assertFalse(result.has_scale)
        self.assertIsNotNone(result.run_time)
        self.assertEqual(max_img_id + 1, len(result.results))
Example #2
0
    def test_simple_trial_run_generated(self):
        sequence_folder, left_path, right_path = ndds_loader.find_files(
            NDDS_SEQUENCE)
        camera_intrinsics = ndds_loader.read_camera_intrinsics(
            left_path / '_camera_settings.json')
        max_img_id = ndds_loader.find_max_img_id(
            lambda idx: left_path / ndds_loader.IMG_TEMPLATE.format(idx))
        with (NDDS_SEQUENCE / 'timestamps.json').open('r') as fp:
            timestamps = json.load(fp)

        subject = LibVisOMonoSystem(
            matcher_nms_n=10,
            matcher_nms_tau=66,
            matcher_match_binsize=50,
            matcher_match_radius=245,
            matcher_match_disp_tolerance=2,
            matcher_outlier_disp_tolerance=5,
            matcher_outlier_flow_tolerance=2,
            matcher_multi_stage=False,
            matcher_half_resolution=False,
            matcher_refinement=MatcherRefinement.SUBPIXEL,
            bucketing_max_features=6,
            bucketing_bucket_width=136,
            bucketing_bucket_height=102,
            height=1.0,
            pitch=0.0,
            ransac_iters=439,
            inlier_threshold=4.921875,
            motion_threshold=609.375)
        subject.set_camera_intrinsics(camera_intrinsics, 0.1)

        subject.start_trial(ImageSequenceType.SEQUENTIAL, seed=0)
        image_group = 'test'
        with image_manager.get().get_group(image_group, allow_write=True):
            for img_idx in range(max_img_id + 1):
                pixels = image_utils.read_colour(
                    left_path / ndds_loader.IMG_TEMPLATE.format(img_idx))
                image = Image(
                    _id=bson.ObjectId(),
                    pixels=pixels,
                    image_group=image_group,
                    metadata=imeta.ImageMetadata(camera_pose=Transform()))
                subject.process_image(image, timestamps[img_idx])
        result = subject.finish_trial()

        self.assertIsInstance(result, SLAMTrialResult)
        self.assertEqual(subject, result.system)
        self.assertTrue(result.success)
        self.assertFalse(result.has_scale)
        self.assertIsNotNone(result.run_time)
        self.assertEqual(max_img_id + 1, len(result.results))
Example #3
0
def import_sequence(root_folder: Path, left_path: Path, right_path: Path,
                    depth_quality: DepthNoiseQuality = DepthNoiseQuality.KINECT_NOISE) -> ImageCollection:
    """
    Import the sequence, as a bunch of stereo images, and then organised into an ImageCollection.
    ImageCollection and StereoImage objects are saved.

    :param root_folder: The root folder to import from, containing the timestamps and settings files
    :param left_path: The path to the left image sequences
    :param right_path: The path to the right image sequences
    :param depth_quality: Noisy depth is generated as we go, the quality to use when doing so
    :return: The imported image collection
    """
    # Read the timestamps and the generator settings
    # These are saved from python, so need for special loading
    with (root_folder / 'settings.json').open('r') as fp:
        settings = json_load(fp)
    with (root_folder / 'timestamps.json').open('r') as fp:
        timestamps = json_load(fp)

    # Read the camera settings from file
    left_camera_intrinsics = read_camera_intrinsics(left_path / '_camera_settings.json')
    right_camera_intrinsics = read_camera_intrinsics(right_path / '_camera_settings.json')
    # left_object_labels = read_object_classes(left_path / '_object_settings.json')
    # right_object_labels = read_object_classes(right_path / '_object_settings.json')

    max_img_id = min(
        find_max_img_id(lambda idx: left_path / IMG_TEMPLATE.format(idx)),
        find_max_img_id(lambda idx: right_path / IMG_TEMPLATE.format(idx)),
    )
    if len(timestamps) != max_img_id + 1:
        raise RuntimeError(f"Maximum image id {max_img_id} didn't match the number "
                           f"of available timestamps ({len(timestamps)}), cannot associate.")

    # Read meta-information from the generator, including the timestamps
    sequence_name = root_folder.name
    (
        trajectory_id, environment_type, light_level, time_of_day, simulation_world,
        lighting_model, texture_mipmap_bias, normal_maps_enabled, roughness_enabled, min_object_size,
        geometry_decimation
    ) = parse_settings(settings)

    # Import all the images
    images = []
    image_group = sequence_name
    origin = None
    # Open the image manager for writing once, so that we're not constantly opening and closing it with each image
    with arvet.database.image_manager.get().get_group(image_group, allow_write=True):
        for img_idx in range(max_img_id + 1):
            logging.getLogger(__name__).info(f"Loading image {img_idx}...")
            # Read the raw data for the left image
            left_frame_data = read_json(left_path / DATA_TEMPLATE.format(img_idx))
            left_pixels = image_utils.read_colour(left_path / IMG_TEMPLATE.format(img_idx))
            # left_label_image = image_utils.read_colour(left_path / INSTANCE_TEMPLATE.format(img_idx))
            left_true_depth = load_depth_image(left_path / DEPTH_TEMPLATE.format(img_idx))

            # Read the raw data for the right image
            right_frame_data = read_json(right_path / DATA_TEMPLATE.format(img_idx))
            right_pixels = image_utils.read_colour(right_path / IMG_TEMPLATE.format(img_idx))
            # right_label_image = image_utils.read_colour(right_path / INSTANCE_TEMPLATE.format(img_idx))
            right_true_depth = load_depth_image(right_path / DEPTH_TEMPLATE.format(img_idx))

            # Ensure all images are c_contiguous
            if not left_pixels.flags.c_contiguous:
                left_pixels = np.ascontiguousarray(left_pixels)
            # if not left_label_image.flags.c_contiguous:
            #     left_label_image = np.ascontiguousarray(left_label_image)
            if not left_true_depth.flags.c_contiguous:
                left_true_depth = np.ascontiguousarray(left_true_depth)
            if not right_pixels.flags.c_contiguous:
                right_pixels = np.ascontiguousarray(right_pixels)
            # if not right_label_image.flags.c_contiguous:
            #     right_label_image = np.ascontiguousarray(right_label_image)
            if not right_true_depth.flags.c_contiguous:
                right_true_depth = np.ascontiguousarray(right_true_depth)

            # Extract the poses
            left_camera_pose = read_camera_pose(left_frame_data)
            right_camera_pose = read_camera_pose(right_frame_data)

            # If we have object data, extract labels for them as well
            # Not working? removed
            # if len(left_object_labels) > 0:
            #     left_labelled_objects = find_labelled_objects(left_label_image, left_frame_data, left_object_labels)
            # else:
            #     left_labelled_objects = []
            # if len(right_object_labels) > 0:
            #     right_labelled_objects = find_labelled_objects(right_label_image, right_frame_data, right_object_labels)
            # else:
            #     right_labelled_objects = []
            left_labelled_objects = []
            right_labelled_objects = []

            # Compute a noisy depth image
            noisy_depth = create_noisy_depth_image(
                left_true_depth=left_true_depth,
                right_true_depth=right_true_depth,
                camera_intrinsics=left_camera_intrinsics,
                right_camera_relative_pose=left_camera_pose.find_relative(right_camera_pose),
                quality_level=depth_quality
            )

            # Re-centre the camera poses relative to the first frame
            if origin is None:
                origin = left_camera_pose
            left_camera_pose = origin.find_relative(left_camera_pose)
            right_camera_pose = origin.find_relative(right_camera_pose)

            left_metadata = imeta.make_metadata(
                pixels=left_pixels,
                depth=left_true_depth,
                camera_pose=left_camera_pose,
                intrinsics=left_camera_intrinsics,
                source_type=imeta.ImageSourceType.SYNTHETIC,
                environment_type=environment_type,
                light_level=light_level,
                time_of_day=time_of_day,
                simulation_world=simulation_world,
                lighting_model=lighting_model,
                texture_mipmap_bias=texture_mipmap_bias,
                normal_maps_enabled=normal_maps_enabled,
                roughness_enabled=roughness_enabled,
                geometry_decimation=geometry_decimation,
                minimum_object_volume=min_object_size,
                labelled_objects=left_labelled_objects
            )
            right_metadata = imeta.make_right_metadata(
                pixels=right_pixels,
                depth=right_true_depth,
                camera_pose=right_camera_pose,
                intrinsics=right_camera_intrinsics,
                labelled_objects=right_labelled_objects,
                left_metadata=left_metadata
            )
            image = StereoImage(
                pixels=left_pixels,
                right_pixels=right_pixels,
                depth=noisy_depth,
                true_depth=left_true_depth,
                right_true_depth=right_true_depth,
                image_group=image_group,
                metadata=left_metadata,
                right_metadata=right_metadata,
            )
            try:
                image.save()
            except KeyError as exp:
                logging.getLogger(__name__).info(f"Key error while saving image {img_idx} in sequence {sequence_name}, "
                                                 f"read from {left_path / IMG_TEMPLATE.format(img_idx)}")
                raise exp
            images.append(image)

    # Create and save the image collection
    collection = ImageCollection(
        images=images,
        image_group=sequence_name,
        timestamps=timestamps,
        sequence_type=ImageSequenceType.SEQUENTIAL,
        dataset="generated-SLAM-data",
        sequence_name=sequence_name,
        trajectory_id=trajectory_id
    )
    collection.save()
    return collection
Example #4
0
def verify_sequence(image_collection: ImageCollection,
                    root_folder: Path) -> bool:
    """
    Load a dataset produced by the Nvidia dataset generator
    :return:
    """
    root_folder = Path(root_folder)
    # depth_quality = DepthNoiseQuality(depth_quality)
    valid = True
    sequence_name = image_collection.sequence_name

    # Step 0: Check the root folder to see if it needs to be extracted from a tarfile
    sequence_folder = root_folder / sequence_name
    delete_when_done = None
    if not sequence_folder.is_dir():
        sequence_tarfile = root_folder / (sequence_name + '.tar.gz')
        if sequence_tarfile.is_file() and tarfile.is_tarfile(sequence_tarfile):
            logging.getLogger(__name__).info(
                f"Extracting sequence from {sequence_tarfile}")
            delete_when_done = sequence_folder
            with tarfile.open(sequence_tarfile) as tar_fp:
                tar_fp.extractall(sequence_folder)
        else:
            # Could find neither a dir nor a tarfile to extract from
            raise NotADirectoryError(
                f"Neither {sequence_folder} nor {sequence_tarfile} exists for us to extract"
            )
    sequence_folder, left_path, right_path = ndds_loader.find_files(
        sequence_folder)
    logging.getLogger(__name__).info(
        f"{sequence_name}: Selected {sequence_folder}, {left_path}, {right_path} as source folders"
    )

    # Read the maximum image id, and make sure it matches the collection
    max_img_id = min(
        ndds_loader.find_max_img_id(
            lambda idx: left_path / ndds_loader.IMG_TEMPLATE.format(idx)),
        ndds_loader.find_max_img_id(
            lambda idx: right_path / ndds_loader.IMG_TEMPLATE.format(idx)),
    )
    if len(image_collection) != max_img_id + 1:
        logging.getLogger(__name__).warning(
            f"Maximum image id {max_img_id} did not match the length of the "
            f"image collection ({len(image_collection)})")
        # make sure we don't try and get images from the collection it doesn't have
        max_img_id = min(max_img_id, len(image_collection) - 1)
        valid = False

    # Verify the images
    # Open the image manager for writing once, so that we're not constantly opening and closing it with each image
    total_invalid_images = 0
    with arvet.database.image_manager.get().get_group(
            image_collection.get_image_group()):
        for img_idx in range(max_img_id + 1):
            img_valid = True
            # Expand the file paths for this image
            left_img_path = left_path / ndds_loader.IMG_TEMPLATE.format(
                img_idx)
            left_depth_path = left_path / ndds_loader.DEPTH_TEMPLATE.format(
                img_idx)
            right_img_path = right_path / ndds_loader.IMG_TEMPLATE.format(
                img_idx)
            right_depth_path = right_path / ndds_loader.DEPTH_TEMPLATE.format(
                img_idx)

            # Read the raw data for the left image
            left_pixels = image_utils.read_colour(left_img_path)
            left_true_depth = ndds_loader.load_depth_image(left_depth_path)

            # Read the raw data for the right image
            right_pixels = image_utils.read_colour(right_img_path)
            right_true_depth = ndds_loader.load_depth_image(right_depth_path)

            # Ensure all images are c_contiguous
            if not left_pixels.flags.c_contiguous:
                left_pixels = np.ascontiguousarray(left_pixels)
            if not left_true_depth.flags.c_contiguous:
                left_true_depth = np.ascontiguousarray(left_true_depth)
            if not right_pixels.flags.c_contiguous:
                right_pixels = np.ascontiguousarray(right_pixels)
            if not right_true_depth.flags.c_contiguous:
                right_true_depth = np.ascontiguousarray(right_true_depth)

            # Compute image hashes
            left_hash = bytes(xxhash.xxh64(left_pixels).digest())
            right_hash = bytes(xxhash.xxh64(right_pixels).digest())

            # Compute a noisy depth image
            # noisy_depth = create_noisy_depth_image(
            #     left_ground_truth_depth=left_ground_truth_depth,
            #     right_ground_truth_depth=right_ground_truth_depth,
            #     camera_intrinsics=left_camera_intrinsics,
            #     right_camera_relative_pose=left_camera_pose.find_relative(right_camera_pose),
            #     quality_level=depth_quality
            # )

            # Load the image from the database
            try:
                _, image = image_collection[img_idx]
                left_actual_pixels = image.left_pixels
                left_actual_ground_truth_depth = image.left_true_depth
                right_actual_pixels = image.right_pixels
                right_actual_ground_truth_depth = image.right_true_depth
            except (KeyError, IOError, RuntimeError):
                logging.getLogger(__name__).exception(
                    f"Error loading image {img_idx}")
                valid = False
                continue

            # Compare the loaded image data to the data read from disk
            if not np.array_equal(left_pixels, left_actual_pixels):
                logging.getLogger(__name__).error(
                    f"Image {img_idx}: Left pixels do not match data read from {left_img_path}"
                )
                total_invalid_images += 1
                valid = False
            if left_hash != bytes(image.metadata.img_hash):
                logging.getLogger(__name__).error(
                    f"Image {img_idx}: Left hash does not match metadata {image.metadata.img_hash}"
                )
                valid = False
                img_valid = False
            if not np.array_equal(left_true_depth,
                                  left_actual_ground_truth_depth):
                logging.getLogger(__name__).error(
                    f"Image {img_idx}: Left depth does not match data read from {left_depth_path}"
                )
                valid = False
                img_valid = False
            if not np.array_equal(right_pixels, right_actual_pixels):
                logging.getLogger(__name__).error(
                    f"Image {img_idx}: 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):
                logging.getLogger(__name__).error(
                    f"Image {img_idx}: Right hash does not match metadata {image.right_metadata.img_hash}"
                )
                valid = False
                img_valid = False
            if not np.array_equal(right_true_depth,
                                  right_actual_ground_truth_depth):
                logging.getLogger(__name__).error(
                    f"Image {img_idx}: Right depth does not match data read from {right_depth_path}"
                )
                valid = False
                img_valid = False
            if not img_valid:
                total_invalid_images += 1

    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)

    if valid:
        logging.getLogger(__name__).info(
            f"Verification of {sequence_name} successful.")
    else:
        logging.getLogger(__name__).info(
            f"Verification of {sequence_name} ({image_collection.pk}) "
            f"FAILED, ({total_invalid_images} images failed)")
    return valid
Example #5
0
def import_dataset(root_folder, dataset_name, **_):
    """
    Load a TUM RGB-D sequence into the database.


    :return:
    """
    root_folder = Path(root_folder)

    # Step 0: 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))

    # Step 1: Find the relevant metadata files
    root_folder, rgb_path, depth_path, trajectory_path = find_files(
        root_folder)

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

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

    # Step 3: Load the images from the metadata
    first_timestamp = None
    image_group = dataset_name
    images = []
    timestamps = []
    with arvet.database.image_manager.get().get_group(image_group,
                                                      allow_write=True):
        for timestamp, image_file, camera_pose, depth_file in all_metadata:
            # Re-zero the timestamps
            if first_timestamp is None:
                first_timestamp = timestamp
            timestamp = (timestamp - first_timestamp)

            rgb_data = image_utils.read_colour(
                os.path.join(root_folder, image_file))
            depth_data = image_utils.read_depth(
                os.path.join(root_folder, depth_file))
            depth_data = depth_data / 5000  # Re-scale depth to meters
            camera_intrinsics = get_camera_intrinsics(root_folder)

            metadata = imeta.make_metadata(
                pixels=rgb_data,
                depth=depth_data,
                camera_pose=camera_pose,
                intrinsics=camera_intrinsics,
                source_type=imeta.ImageSourceType.REAL_WORLD,
                environment_type=environment_types.get(
                    dataset_name, imeta.EnvironmentType.INDOOR_CLOSE),
                light_level=imeta.LightingLevel.WELL_LIT,
                time_of_day=imeta.TimeOfDay.DAY,
            )
            image = Image(pixels=rgb_data,
                          depth=depth_data,
                          image_group=image_group,
                          metadata=metadata)
            image.save()
            images.append(image)
            timestamps.append(timestamp)

    # Create and save the image collection
    collection = ImageCollection(images=images,
                                 timestamps=timestamps,
                                 sequence_type=ImageSequenceType.SEQUENTIAL,
                                 dataset='TUM RGB-D',
                                 sequence_name=dataset_name,
                                 trajectory_id=dataset_name)
    collection.save()

    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 collection
Example #6
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
Example #7
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
Example #8
0
def import_dataset(root_folder, dataset_name, **_):
    """
    Load an Autonomous Systems Lab dataset into the database.
    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 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.
    :return:
    """
    if not os.path.isdir(root_folder):
        raise NotADirectoryError(
            "'{0}' is not a directory".format(root_folder))

    # Step 1: 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) = find_files(root_folder)

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

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

    # Change the coordinates correctly on the extrinsics. Has to happen after rectification
    left_extrinsics = fix_coordinates(left_extrinsics)
    right_extrinsics = fix_coordinates(right_extrinsics)

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

    # Step 5: Load the images from the metadata
    first_timestamp = None
    image_group = dataset_name
    images = []
    timestamps = []
    with arvet.database.image_manager.get().get_group(image_group,
                                                      allow_write=True):
        for timestamp, left_image_file, right_image_file, robot_pose in all_metadata:
            # Timestamps are in POSIX nanoseconds, re-zero them to the start of the dataset, and scale to seconds
            if first_timestamp is None:
                first_timestamp = timestamp
            timestamp = (timestamp - first_timestamp) / 1e9

            left_data = image_utils.read_colour(
                os.path.join(root_folder, 'cam0', 'data', left_image_file))
            right_data = image_utils.read_colour(
                os.path.join(root_folder, 'cam1', 'data', right_image_file))

            # Error check the loaded image data
            if left_data is None or left_data.size is 0:
                logging.getLogger(__name__).warning(
                    "Could not read left image \"{0}\", result is empty. Skipping."
                    .format(
                        os.path.join(root_folder, 'cam0', 'data',
                                     left_image_file)))
                continue
            if right_data is None or right_data.size is 0:
                logging.getLogger(__name__).warning(
                    "Could not read right image \"{0}\", result is empty. Skipping."
                    .format(
                        os.path.join(root_folder, 'cam1', 'data',
                                     right_image_file)))
                continue

            left_data = cv2.remap(left_data, left_x, left_y, cv2.INTER_LINEAR)
            right_data = cv2.remap(right_data, right_x, right_y,
                                   cv2.INTER_LINEAR)

            left_pose = robot_pose.find_independent(left_extrinsics)
            right_pose = robot_pose.find_independent(right_extrinsics)

            left_metadata = imeta.make_metadata(
                pixels=left_data,
                camera_pose=left_pose,
                intrinsics=left_intrinsics,
                source_type=imeta.ImageSourceType.REAL_WORLD,
                environment_type=environment_types.get(
                    dataset_name, imeta.EnvironmentType.INDOOR_CLOSE),
                light_level=imeta.LightingLevel.WELL_LIT,
                time_of_day=imeta.TimeOfDay.DAY,
            )
            right_metadata = imeta.make_right_metadata(
                pixels=right_data,
                left_metadata=left_metadata,
                camera_pose=right_pose,
                intrinsics=right_intrinsics)
            image = StereoImage(pixels=left_data,
                                right_pixels=right_data,
                                image_group=image_group,
                                metadata=left_metadata,
                                right_metadata=right_metadata)
            image.save()
            images.append(image)
            timestamps.append(timestamp)

    # Create and save the image collection
    collection = ImageCollection(images=images,
                                 timestamps=timestamps,
                                 sequence_type=ImageSequenceType.SEQUENTIAL,
                                 dataset='EuRoC MAV',
                                 sequence_name=dataset_name,
                                 trajectory_id=dataset_name)
    collection.save()
    return collection