<jupyter_start><jupyter_text>Binary Masks to COCO Annotation Formatby _<NAME>_The goal of this notebook is to use the binary masks created in the [previous notebook](03-png-to-binary-mask.ipynb) to create an annotated object detection dataset in the COCO format.Resources:- [Create your own COCO-style datasets](https://patrickwasp.com/create-your-own-coco-style-dataset/)---<jupyter_code># === Imports === #
import datetime
import json
import os
import re
import fnmatch
from PIL import Image
import numpy as np
# from pycococreatortools import pycococreatortools
# === pycococreatortools === #
import os
import re
import datetime
import numpy as np
from itertools import groupby
from skimage import measure
from PIL import Image
from pycocotools import mask

convert = lambda text: int(text) if text.isdigit() else text.lower()
natrual_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ]

def resize_binary_mask(array, new_size):
    image = Image.fromarray(array.astype(np.uint8)*255)
    image = image.resize(new_size)
    return np.asarray(image).astype(np.bool_)

def close_contour(contour):
    if not np.array_equal(contour[0], contour[-1]):
        contour = np.vstack((contour, contour[0]))
    return contour

def binary_mask_to_rle(binary_mask):
    rle = {'counts': [], 'size': list(binary_mask.shape)}
    counts = rle.get('counts')
    for i, (value, elements) in enumerate(groupby(binary_mask.ravel(order='F'))):
        if i == 0 and value == 1:
                counts.append(0)
        counts.append(len(list(elements)))

    return rle

def binary_mask_to_polygon(binary_mask, tolerance=0):
    """Converts a binary mask to COCO polygon representation
    Args:
        binary_mask: a 2D binary numpy array where '1's represent the object
        tolerance: Maximum distance from original points of polygon to approximated
            polygonal chain. If tolerance is 0, the original coordinate array is returned.
    """
    polygons = []
    # pad mask to close contours of shapes which start and end at an edge
    padded_binary_mask = np.pad(binary_mask, pad_width=1, mode='constant', constant_values=0)
    contours = measure.find_contours(padded_binary_mask, 0.5)
    contours = np.subtract(contours, 1)
    for contour in contours:
        contour = close_contour(contour)
        contour = measure.approximate_polygon(contour, tolerance)
        if len(contour) < 3:
            continue
        contour = np.flip(contour, axis=1)
        segmentation = contour.ravel().tolist()
        # after padding and subtracting 1 we may get -0.5 points in our segmentation 
        segmentation = [0 if i < 0 else i for i in segmentation]
        polygons.append(segmentation)

    return polygons

def create_image_info(image_id, file_name, image_size, 
                      date_captured=datetime.datetime.utcnow().isoformat(' '),
                      license_id=1, coco_url="", flickr_url=""):

    image_info = {
            "id": image_id,
            "file_name": file_name,
            "width": image_size[0],
            "height": image_size[1],
            "date_captured": date_captured,
            "license": license_id,
            "coco_url": coco_url,
            "flickr_url": flickr_url
    }

    return image_info

def create_annotation_info(annotation_id, image_id, category_info, binary_mask, 
                           image_size=None, tolerance=2, bounding_box=None):

    if image_size is not None:
        binary_mask = resize_binary_mask(binary_mask, image_size)

    binary_mask_encoded = mask.encode(np.asfortranarray(binary_mask.astype(np.uint8)))

    area = mask.area(binary_mask_encoded)
    if area < 1:
        return None

    if bounding_box is None:
        bounding_box = mask.toBbox(binary_mask_encoded)

    if category_info["is_crowd"]:
        is_crowd = 1
        segmentation = binary_mask_to_rle(binary_mask)
    else :
        is_crowd = 0
        segmentation = binary_mask_to_polygon(binary_mask, tolerance)
        if not segmentation:
            return None

    annotation_info = {
        "id": annotation_id,
        "image_id": image_id,
        "category_id": category_info["id"],
        "iscrowd": is_crowd,
        "area": area.tolist(),
        "bbox": bounding_box.tolist(),
        "segmentation": segmentation,
        "width": binary_mask.shape[1],
        "height": binary_mask.shape[0],
    } 

    return annotation_info
# === Directories === #
DATASET_NAME = "items_train2019"
ROOT_DIR = "/Users/Tobias/workshop/buildbox/neurecycle/pycoco/items/train"
IMAGE_DIR = os.path.join(ROOT_DIR, DATASET_NAME)
ANNOTATION_DIR = os.path.join(ROOT_DIR, "annotations")
# === MetaDataset === #

INFO = {
    "description": "Trash Panda Foreground Detection Sample Data",
    "url": "https://github.com/tobias-fyi/neurecycle/tree/master/pycoco/items/train",
    "version": "0.1.0",
    "year": 2019,
    "contributor": "tobias-fyi",
    "date_created": datetime.datetime.utcnow().isoformat(" ")
}

LICENSES = [
    {
        "id": 1,
        "name": "Attribution-NonCommercial-ShareAlike License",
        "url": "http://creativecommons.org/licenses/by-nc-sa/2.0/"
    }
]

CATEGORIES = [
    {"id": 1, "name": "cardboard", "supercategory": "item",},
    {"id": 2, "name": "glass_container", "supercategory": "item",},
    {"id": 3, "name": "metal_cans", "supercategory": "item",},
    {"id": 4, "name": "mixed_paper", "supercategory": "item",},
    {"id": 5, "name": "paper_treated", "supercategory": "item",},
    {"id": 6, "name": "plastic_bottle", "supercategory": "item",},
    {"id": 7, "name": "plastic_bubblewrap", "supercategory": "item",},
    {"id": 8, "name": "plastic_container_food", "supercategory": "item",},
    {"id": 9, "name": "plastic_film", "supercategory": "item",},
    {"id": 10, "name": "wrapper", "supercategory": "item",},
]
# === Helper functions === #
# === Helper functions === #
def filter_for_jpeg(root, files):
    """Filters out non-jpg or -jpeg files."""
    file_types = ["*.jpeg", "*.jpg"]
    file_types = r"|".join([fnmatch.translate(x) for x in file_types])
    files = [os.path.join(root, f) for f in files]
    files = [f for f in files if re.match(file_types, f)]

    return files


def filter_for_png(root, files):
    """Returns only .png files"""
    file_types = ["*.png"]
    file_types = r"|".join([fnmatch.translate(x) for x in file_types])
    files = [os.path.join(root, f) for f in files]
    files = [f for f in files if re.match(file_types, f)]

    return files


def filter_for_annotations(root, files, image_filename):
    file_types = ["*.png"]
    file_types = r"|".join([fnmatch.translate(x) for x in file_types])
    basename_no_extension = os.path.splitext(os.path.basename(image_filename))[0]
    file_name_prefix = basename_no_extension + ".*"
    files = [os.path.join(root, f) for f in files]
    files = [f for f in files if re.match(file_types, f)]
    files = [
        f
        for f in files
        if re.match(file_name_prefix, os.path.splitext(os.path.basename(f))[0])
    ]

    return files
def create_annotations():

    coco_output = {
        "info": INFO,
        "licenses": LICENSES,
        "categories": CATEGORIES,
        "images": [],
        "annotations": [],
    }

    image_id = 1
    segmentation_id = 1

    # Filter for png images
    for root, _, files in os.walk(IMAGE_DIR):
        image_files = filter_for_png(root, files)

        # Loop through each image
        for image_filename in image_files:
            image = Image.open(image_filename)
            image_info = pycococreatortools.create_image_info(
                image_id, os.path.basename(image_filename), image.size
            )
            coco_output["images"].append(image_info)

            # Filter for associated png annotations
            for root, _, files in os.walk(ANNOTATION_DIR):
                annotation_files = filter_for_annotations(root, files, image_filename)

                # Go through each associated annotation
                for annotation_filename in annotation_files:

                    print(annotation_filename)
                    class_id = [
                        x["id"] for x in CATEGORIES if x["name"] in annotation_filename
                    ][0]

                    category_info = {
                        "id": class_id,
                        "is_crowd": "crowd" in image_filename,
                    }
                    binary_mask = np.asarray(
                        Image.open(annotation_filename).convert("1")
                    ).astype(np.uint8)

                    annotation_info = pycococreatortools.create_annotation_info(
                        segmentation_id,
                        image_id,
                        category_info,
                        binary_mask,
                        image.size,
                        tolerance=2,
                    )

                    if annotation_info is not None:
                        coco_output["annotations"].append(annotation_info)

                    segmentation_id += 1

            image_id += 1

    with open(f"{ROOT_DIR}/instances_{DATASET_NAME}.json", "w") as output_json_file:
        json.dump(coco_output, output_json_file)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12149028077753779, 'fraction_numerical': 0.008099352051835854, 'mean_word_length': 2.6346153846153846, 'pattern_counts': {'":': 63, '<': 9, '<?xml version=': 0, '>': 6, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '570047', 'license': 'MIT', 'n_tokens_mistral': 2994, 'n_tokens_neox': 2835, 'n_words': 690, 'path': 'notebooks/04-pycoco-training-data.ipynb', 'repo_name': 'tobias-fyi/neurecycle', 'size': '9267'}