Example #1
0
def test_png_to_jpg(
        data_dir,
):
    """
    Test for the cvdata.convert.png_to_jpg() function

    :param data_dir: temporary directory into which test files will be loaded
    """
    png_file_path = os.path.join(str(data_dir), "james.png")
    jpg_file_path = convert.png_to_jpg(png_file_path)
    converted_jpg_file_path = os.path.join(str(data_dir), "james.jpg")
    assert jpg_file_path == converted_jpg_file_path
    expected_jpg_file_path = os.path.join(str(data_dir), "expected_james.jpg")
    assert images_equal(converted_jpg_file_path, expected_jpg_file_path)
Example #2
0
def clean_darknet(
    darknet_dir: str,
    images_dir: str,
    label_replacements: Dict,
    label_removals: List[str] = None,
    label_keep: List[str] = None,
    problems_dir: str = None,
):
    """
    TODO

    :param darknet_dir:
    :param images_dir:
    :param label_replacements:
    :param label_removals:
    :param problems_dir:
    :return:
    """

    _logger.info("Cleaning dataset with Darknet annotations")

    # convert all PNG images to JPG, and remove the original PNG file
    for file_id in matching_ids(darknet_dir, images_dir, ".txt", ".png"):
        png_file_path = os.path.join(images_dir, file_id + ".png")
        png_to_jpg(png_file_path, remove_png=True)

    # get the set of file IDs of the Darknet-format annotations and corresponding images
    file_ids = purge_non_matching(images_dir, darknet_dir, "darknet",
                                  problems_dir)

    # loop over all the matching files and clean the Darknet annotations
    for file_id in tqdm(file_ids):

        # update the Darknet annotation file
        src_annotation_file_path = os.path.join(darknet_dir, file_id + ".txt")
        for line in fileinput.input(src_annotation_file_path, inplace=True):

            # get the bounding box label
            parts = line.split()
            label = parts[0]

            # skip rewriting this line if it's a label we want removed
            if (label_removals is not None) and (label in label_removals):
                continue

            # skip rewriting this line if it's a label we do not want to keep
            if (label_keep is not None) and (label not in label_keep):
                continue

            # get the bounding box coordinates
            center_x = float(parts[1])
            center_y = float(parts[2])
            bbox_width = float(parts[3])
            bbox_height = float(parts[4])

            if (label_replacements is not None) and (label
                                                     in label_replacements):
                # update the label
                label = label_replacements[label]

            # make sure we don't have wonky bounding box values
            # and if so we'll skip them
            if (center_x > 1.0) or (center_x < 0.0):
                # report the issue via log message
                _logger.warning(
                    "Bounding box center X is out of valid range -- skipping "
                    f"in Darknet annotation file {src_annotation_file_path}", )
                continue

            if (center_y > 1.0) or (center_y < 0.0):
                # report the issue via log message
                _logger.warning(
                    "Bounding box center Y is out of valid range -- skipping "
                    f"in Darknet annotation file {src_annotation_file_path}", )
                continue

            if (bbox_width > 1.0) or (bbox_width < 0.0):
                # report the issue via log message
                _logger.warning(
                    "Bounding box width is out of valid range -- skipping "
                    f"in Darknet annotation file {src_annotation_file_path}", )
                continue

            if (bbox_height > 1.0) or (bbox_height < 0.0):
                # report the issue via log message
                _logger.warning(
                    "Bounding box height is out of valid range -- skipping "
                    f"in Darknet annotation file {src_annotation_file_path}", )
                continue

            # write the line back into the file in-place
            darknet_parts = [
                label,
                f'{center_x:.4f}',
                f'{center_y:.4f}',
                f'{bbox_width:.4f}',
                f'{bbox_height:.4f}',
            ]
            print(" ".join(darknet_parts))
Example #3
0
def clean_pascal(
    pascal_dir: str,
    images_dir: str,
    label_replacements: Dict = None,
    label_removals: List[str] = None,
    label_keep: List[str] = None,
    problems_dir: str = None,
):
    """
    TODO

    :param pascal_dir:
    :param images_dir:
    :param label_replacements:
    :param label_removals:
    :param problems_dir:
    :return:
    """

    _logger.info("Cleaning dataset with PASCAL annotations")

    # convert all PNG images to JPG, and remove the original PNG file
    for file_id in matching_ids(pascal_dir, images_dir, ".xml", ".png"):
        png_file_path = os.path.join(images_dir, file_id + ".png")
        png_to_jpg(png_file_path, remove_png=True)

    # get the set of file IDs of the Darknet-format annotations and corresponding images
    file_ids = purge_non_matching(images_dir, pascal_dir, "pascal",
                                  problems_dir)

    # loop over all the matching files and clean the PASCAL annotations
    for i, file_id in tqdm(enumerate(file_ids)):

        # get the image width and height
        jpg_file_name = file_id + ".jpg"
        image_file_path = os.path.join(images_dir, jpg_file_name)
        image = Image.open(image_file_path)
        img_width, img_height = image.size

        # update the image file name in the PASCAL file
        src_annotation_file_path = os.path.join(pascal_dir, file_id + ".xml")
        if os.path.exists(src_annotation_file_path):
            tree = etree.parse(src_annotation_file_path)
            root = tree.getroot()

            size = tree.find("size")
            width = int(size.find("width").text)
            height = int(size.find("height").text)

            if (width != img_width) or (height != img_height):
                # something's amiss that we can't reasonably fix, remove files
                if problems_dir is not None:
                    for file_path in [
                            src_annotation_file_path, image_file_path
                    ]:
                        dest_file_path = os.path.join(
                            problems_dir,
                            os.path.split(file_path)[1])
                        shutil.move(file_path, dest_file_path)
                else:
                    os.remove(src_annotation_file_path)
                    os.remove(image_file_path)
                continue

            # update the image file name
            file_name = root.find("filename")
            if (file_name is not None) and (file_name.text != jpg_file_name):
                file_name.text = jpg_file_name

            # loop over all bounding boxes
            for obj in root.iter("object"):

                # replace all bounding box labels if specified in the replacement dictionary
                name = obj.find("name")
                if (name is None) or ((label_removals is not None) and
                                      (name.text in label_removals)):
                    # drop the bounding box
                    parent = obj.getparent()
                    parent.remove(obj)
                    # move on, nothing more to do for this box
                    continue
                # skip rewriting this line if it's a label we do not want to keep
                elif (name is None) or ((label_keep is not None) and
                                        (name.text not in label_keep)):
                    # drop the bounding box
                    parent = obj.getparent()
                    parent.remove(obj)
                    # move on, nothing more to do for this box
                    continue
                elif (label_replacements
                      is not None) and (name.text in label_replacements):
                    # update the label
                    name.text = label_replacements[name.text]

                # for each bounding box make sure we have max
                # values that are one less than the width/height
                bbox = obj.find("bndbox")
                bbox_min_x = int(float(bbox.find("xmin").text))
                bbox_min_y = int(float(bbox.find("ymin").text))
                bbox_max_x = int(float(bbox.find("xmax").text))
                bbox_max_y = int(float(bbox.find("ymax").text))

                # make sure we don't have wonky values with mins > maxs
                if (bbox_min_x >= bbox_max_x) or (bbox_min_y >= bbox_max_y):
                    # drop the bounding box
                    _logger.warning(
                        "Dropping bounding box for object in file "
                        f"{src_annotation_file_path} due to invalid "
                        "min/max values", )
                    parent = obj.getparent()
                    parent.remove(obj)

                else:
                    # make sure the max values don't go past the edge
                    if bbox_max_x >= img_width:
                        bbox.find("xmax").text = str(img_width - 1)
                    if bbox_max_y >= img_height:
                        bbox.find("ymax").text = str(img_height - 1)

            # drop the image path, it's not reliable
            path = root.find("path")
            if path is not None:
                parent = path.getparent()
                parent.remove(path)

            # drop the image folder, it's not reliable
            folder = root.find("folder")
            if folder is not None:
                parent = folder.getparent()
                parent.remove(folder)

            # write the tree back to file
            tree.write(src_annotation_file_path)
Example #4
0
def clean_kitti(
    kitti_dir: str,
    images_dir: str,
    label_replacements: Dict = None,
    label_removals: List[str] = None,
    label_keep: List[str] = None,
    problems_dir: str = None,
):
    """
    TODO

    :param kitti_dir:
    :param images_dir:
    :param label_replacements:
    :param label_removals:
    :param problems_dir:
    :return:
    """

    _logger.info("Cleaning dataset with KITTI annotations")

    # convert all PNG images to JPG, and remove the original PNG file
    for file_id in matching_ids(kitti_dir, images_dir, ".txt", ".png"):
        png_file_path = os.path.join(images_dir, file_id + ".png")
        png_to_jpg(png_file_path, remove_png=True)

    # get the set of file IDs of the Darknet-format annotations and corresponding images
    file_ids = purge_non_matching(images_dir, kitti_dir, "kitti", problems_dir)

    # loop over all the matching files and clean the KITTI annotations
    for file_id in tqdm(file_ids):

        # get the image width and height
        jpg_file_name = file_id + ".jpg"
        image_file_path = os.path.join(images_dir, jpg_file_name)
        image = Image.open(image_file_path)
        img_width, img_height = image.size

        # update the image file name in the KITTI annotation file
        src_annotation_file_path = os.path.join(kitti_dir, file_id + ".txt")
        for line in fileinput.input(src_annotation_file_path, inplace=True):

            parts = line.split()
            label = parts[0]

            # skip rewriting this line if it's a label we want removed
            if (label_removals is not None) and (label in label_removals):
                continue

            # skip rewriting this line if it's a label we do not want to keep
            if (label_keep is not None) and (label not in label_keep):
                continue

            truncated = parts[1]
            occluded = parts[2]
            alpha = parts[3]
            bbox_min_x = int(float(parts[4]))
            bbox_min_y = int(float(parts[5]))
            bbox_max_x = int(float(parts[6]))
            bbox_max_y = int(float(parts[7]))
            dim_x = parts[8]
            dim_y = parts[9]
            dim_z = parts[10]
            loc_x = parts[11]
            loc_y = parts[12]
            loc_z = parts[13]
            rotation_y = parts[14]
            # not all KITTI-formatted files have a score field
            if len(parts) == 16:
                score = parts[15]
            else:
                score = " "

            if (label_replacements is not None) and (label
                                                     in label_replacements):
                # update the label
                label = label_replacements[label]

            # make sure we don't have wonky bounding box values
            # with mins > maxs, and if so we'll reverse them
            if bbox_min_x > bbox_max_x:
                # report the issue via log message
                _logger.warning(
                    "Bounding box minimum X is greater than the maximum X "
                    f"in KITTI annotation file {src_annotation_file_path}", )
                tmp_holder = bbox_min_x
                bbox_min_x = bbox_max_x
                bbox_max_x = tmp_holder

            if bbox_min_y > bbox_max_y:
                # report the issue via log message
                _logger.warning(
                    "Bounding box minimum Y is greater than the maximum Y "
                    f"in KITTI annotation file {src_annotation_file_path}", )
                tmp_holder = bbox_min_y
                bbox_min_y = bbox_max_y
                bbox_max_y = tmp_holder

            # perform sanity checks on max values
            if bbox_max_x >= img_width:
                # report the issue via log message
                _logger.warning(
                    "Bounding box maximum X is greater than width in KITTI "
                    f"annotation file {src_annotation_file_path}", )

                # fix the issue
                bbox_max_x = img_width - 1

            if bbox_max_y >= img_height:
                # report the issue via log message
                _logger.warning(
                    "Bounding box maximum Y is greater than height in KITTI "
                    f"annotation file {src_annotation_file_path}", )

                # fix the issue
                bbox_max_y = img_height - 1

            # write the line back into the file in-place
            kitti_parts = [
                label,
                truncated,
                occluded,
                alpha,
                f'{bbox_min_x:.1f}',
                f'{bbox_min_y:.1f}',
                f'{bbox_max_x:.1f}',
                f'{bbox_max_y:.1f}',
                dim_x,
                dim_y,
                dim_z,
                loc_x,
                loc_y,
                loc_z,
                rotation_y,
            ]
            if len(parts) == 16:
                kitti_parts.append(score)
            print(" ".join(kitti_parts))