diff --git a/local.env b/local.env index 7f00a9c..7998f37 100644 --- a/local.env +++ b/local.env @@ -1,6 +1,6 @@ -TEAM_ID = 448 -WORKSPACE_ID = 690 -PROJECT_ID = 36688 +TEAM_ID = 431 +WORKSPACE_ID = 1019 +PROJECT_ID = 42759 # options: "images" "annotations" modal.state.selectedOutput="images" diff --git a/src/convert_geometry.py b/src/convert_geometry.py index 2a62ada..ccd5f79 100644 --- a/src/convert_geometry.py +++ b/src/convert_geometry.py @@ -1,8 +1,9 @@ import supervisely as sly from supervisely.annotation.json_geometries_map import GET_GEOMETRY_FROM_STR -from supervisely.geometry import polyline, rectangle +from supervisely import Bitmap, Polyline, Rectangle from supervisely.sly_logger import logger - +from typing import List +import uuid def prepare_meta(meta: sly.ProjectMeta): new_classes = [] @@ -21,6 +22,19 @@ def prepare_meta(meta: sly.ProjectMeta): return meta +def convert_w_binding_key( + label: sly.Label, new_obj_class: sly.ObjClass, binding_key: str +) -> List[sly.Label]: + """ + Convert geometries with a binding key (used only for bitmap labels for further grouping) + """ + + if binding_key is None and label.geometry.name() == sly.Bitmap.name(): + binding_key = uuid.uuid4().hex + + return [label.clone(binding_key=binding_key) for label in label.convert(new_obj_class)] + + def convert_annotation(ann_info, img_info, src_meta, dst_meta, rectangle_mark): try: ann = sly.Annotation.from_json(ann_info.annotation, src_meta) @@ -28,27 +42,30 @@ def convert_annotation(ann_info, img_info, src_meta, dst_meta, rectangle_mark): sly.logger.debug(f"Exception while creating sly.Annotation from JSON: {e}") return sly.Annotation((img_info.height, img_info.width)) new_labels = [] - for lbl in ann.labels: - try: - new_cls = dst_meta.obj_classes.get(lbl.obj_class.name) - if lbl.obj_class.geometry_type == new_cls.geometry_type: - new_labels.append(lbl) - else: - converted_label = lbl.convert(new_cls) - if lbl.obj_class.geometry_type == polyline.Polyline: - raise NotImplementedError("Shape Polyline is not supported") - if lbl.obj_class.geometry_type == rectangle.Rectangle: - new_descr = converted_label[0].description + " " + rectangle_mark - new_label = converted_label[0].clone(description=new_descr) - converted_label.pop() - converted_label.append(new_label) - new_labels.extend(converted_label) - except NotImplementedError: - logger.warning( - f"Unsupported conversion of annotation '{lbl.obj_class.geometry_type.name()}' type to '{new_cls.geometry_type.name()}'. Skipping annotation with [ID: {lbl.to_json()['id']}]", - exc_info=False, - ) - continue + + groups = ann.get_bindings() + for binding_key, labels in groups.items(): + for label in labels: + try: + new_cls = dst_meta.obj_classes.get(label.obj_class.name) + if label.obj_class.geometry_type == new_cls.geometry_type: + new_labels.append(label) + else: + converted_label = convert_w_binding_key(label, new_cls, binding_key) + if label.obj_class.geometry_type == Polyline: + raise NotImplementedError("Shape Polyline is not supported") + if label.obj_class.geometry_type == Rectangle: + new_descr = converted_label[0].description + " " + rectangle_mark + new_label = converted_label[0].clone(description=new_descr) + converted_label.pop() + converted_label.append(new_label) + new_labels.extend(converted_label) + except NotImplementedError: + logger.warning( + f"Unsupported conversion of annotation '{label.obj_class.geometry_type.name()}' type to '{new_cls.geometry_type.name()}'. Skipping annotation with [ID: {label.to_json()['id']}]", + exc_info=False, + ) + continue new_tags = [] for tag in ann.img_tags: tag_meta = dst_meta.get_tag_meta(tag.meta.name) diff --git a/src/functions.py b/src/functions.py index 5ee024b..f60b8d1 100644 --- a/src/functions.py +++ b/src/functions.py @@ -4,8 +4,9 @@ import numpy as np import supervisely as sly -from supervisely.geometry import bitmap +# from supervisely.geometry import bitmap from supervisely.io.fs import mkdir +from typing import List incremental_id = 0 @@ -76,6 +77,18 @@ def create_coco_dataset(coco_dataset_dir): return img_dir, ann_dir +def get_bbox_labels(bbox_points: List[List[int]]) -> List[List[int]]: + """ + A helper function to convert list of bbox points into a bbox which contains them all + """ + min_x = min(bbox[0][0] for bbox in bbox_points) + min_y = min(bbox[0][1] for bbox in bbox_points) + max_x = max(bbox[1][0] for bbox in bbox_points) + max_y = max(bbox[1][1] for bbox in bbox_points) + + return [[min_x, min_y], [max_x, max_y]] + + def create_coco_annotation( categories_mapping, image_infos, @@ -90,6 +103,8 @@ def create_coco_annotation( ): global incremental_id for image_info, ann in zip(image_infos, anns): + incremental_id += 1 + image_coco_ann = dict( license="None", file_name=image_info.name, @@ -104,37 +119,64 @@ def create_coco_annotation( if coco_captions is not None and include_captions: coco_captions["images"].append(image_coco_ann) - for label in ann.labels: - if rectangle_mark in label.description: - segmentation = [] - elif label.geometry.name() == bitmap.Bitmap.name(): - segmentation = extend_mask_up_to_image( - label.geometry.data, - (image_info.height, image_info.width), - label.geometry.origin, - ) - segmentation = coco_segmentation_rle(segmentation) - else: - segmentation = label.geometry.to_json()["points"]["exterior"] - segmentation = [coco_segmentation(segmentation)] - - bbox = label.geometry.to_bbox().to_json()["points"]["exterior"] - bbox = coco_bbox(bbox) - - label_id += 1 - coco_ann["annotations"].append( - dict( - segmentation=segmentation, # a list of polygon vertices around the object, but can also be a run-length-encoded (RLE) bit mask - area=label.geometry.area, # Area is measured in pixels (e. a 10px by 20px box would have an area of 200) - iscrowd=0, # Is Crowd specifies whether the segmentation is for a single object or for a group/cluster of objects - image_id=incremental_id, # The image id corresponds to a specific image in the dataset - bbox=bbox, # he COCO bounding box format is [top left x position, top left y position, width, height] - category_id=categories_mapping[ - label.obj_class.name - ], # The category id corresponds to a single category specified in the categories section - id=label_id, # Each annotation also has an id (unique to all other annotations in the dataset) + groups = ann.get_bindings() + for binding_key, labels in groups.items(): + labels: List[sly.Label] + if binding_key is not None: # -> converted bitmap labels + to_segm = lambda x: x.geometry.to_json()["points"]["exterior"] + segmentation = [coco_segmentation(to_segm(label)) for label in labels] + bbox_points = [l.geometry.to_bbox().to_json()["points"]["exterior"] for l in labels] + bbox = coco_bbox(get_bbox_labels(bbox_points)) + label_id += 1 + coco_ann["annotations"].append( + dict( + segmentation=segmentation, # a list of polygon vertices around the object, but can also be a run-length-encoded (RLE) bit mask + area=sum( + [label.area for label in labels] + ), # Area is measured in pixels (e. a 10px by 20px box would have an area of 200) + iscrowd=0, # Is Crowd specifies whether the segmentation is for a single object or for a group/cluster of objects + image_id=incremental_id, # The image id corresponds to a specific image in the dataset + bbox=bbox, # he COCO bounding box format is [top left x position, top left y position, width, height] + category_id=categories_mapping[ + labels[0].obj_class.name + ], # The category id corresponds to a single category specified in the categories section + id=label_id, # Each annotation also has an id (unique to all other annotations in the dataset) + ) ) - ) + else: # -> other labels such as rectangles and polygons + for label in labels: + if rectangle_mark in label.description: + segmentation = [] + # elif ( + # label.geometry.name() == bitmap.Bitmap.name() + # ): # There are no bitmap labels, as they are converted into polygons in advance? Most likely redundant code + # segmentation = extend_mask_up_to_image( + # label.geometry.data, + # (image_info.height, image_info.width), + # label.geometry.origin, + # ) + # segmentation = coco_segmentation_rle(segmentation) + else: + segmentation = label.geometry.to_json()["points"]["exterior"] + segmentation = [coco_segmentation(segmentation)] + + bbox = label.geometry.to_bbox().to_json()["points"]["exterior"] + bbox = coco_bbox(bbox) + + label_id += 1 + coco_ann["annotations"].append( + dict( + segmentation=segmentation, # a list of polygon vertices around the object, but can also be a run-length-encoded (RLE) bit mask + area=label.geometry.area, # Area is measured in pixels (e. a 10px by 20px box would have an area of 200) + iscrowd=0, # Is Crowd specifies whether the segmentation is for a single object or for a group/cluster of objects + image_id=incremental_id, # The image id corresponds to a specific image in the dataset + bbox=bbox, # he COCO bounding box format is [top left x position, top left y position, width, height] + category_id=categories_mapping[ + label.obj_class.name + ], # The category id corresponds to a single category specified in the categories section + id=label_id, # Each annotation also has an id (unique to all other annotations in the dataset) + ) + ) if coco_captions is not None and include_captions: for tag in ann.img_tags: if ( @@ -150,7 +192,7 @@ def create_coco_annotation( ) ) progress.iter_done_report() - incremental_id += 1 + return coco_ann, label_id, coco_captions, caption_id