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..94051a0 100644 --- a/src/convert_geometry.py +++ b/src/convert_geometry.py @@ -1,8 +1,8 @@ import supervisely as sly from supervisely.annotation.json_geometries_map import GET_GEOMETRY_FROM_STR -from supervisely.geometry import polyline, rectangle +from supervisely.geometry import polyline, rectangle, bitmap from supervisely.sly_logger import logger - +from typing import List def prepare_meta(meta: sly.ProjectMeta): new_classes = [] @@ -21,6 +21,14 @@ def prepare_meta(meta: sly.ProjectMeta): return meta +def convert_w_binding_key(label, new_obj_class: sly.ObjClass, binding_key=None) -> List: + labels = [] + geometries = label.geometry.convert(new_obj_class.geometry_type) + for g in geometries: + labels.append(label.clone(geometry=g, obj_class=new_obj_class, binding_key=binding_key)) + return labels + + 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,16 +36,20 @@ 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: + + for idx, label in enumerate(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) + 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 = lbl.convert(new_cls) - if lbl.obj_class.geometry_type == polyline.Polyline: + binding_key = None + if label.obj_class.geometry_type == bitmap.Bitmap: + binding_key = f"{label.obj_class.name}_label_{idx}" + converted_label = convert_w_binding_key(label, new_cls, binding_key) + if label.obj_class.geometry_type == polyline.Polyline: raise NotImplementedError("Shape Polyline is not supported") - if lbl.obj_class.geometry_type == rectangle.Rectangle: + if label.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() @@ -45,7 +57,7 @@ def convert_annotation(ann_info, img_info, src_meta, dst_meta, rectangle_mark): 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']}]", + 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 diff --git a/src/functions.py b/src/functions.py index 5ee024b..106d7de 100644 --- a/src/functions.py +++ b/src/functions.py @@ -6,6 +6,7 @@ import supervisely as sly from supervisely.geometry import bitmap from supervisely.io.fs import mkdir +from typing import List, Union 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, int]]) -> List[List[int, 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, @@ -89,6 +102,7 @@ def create_coco_annotation( rectangle_mark, ): global incremental_id + incremental_id += 1 for image_info, ann in zip(image_infos, anns): image_coco_ann = dict( license="None", @@ -104,53 +118,77 @@ 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) + ) ) - ) - if coco_captions is not None and include_captions: - for tag in ann.img_tags: - if ( - tag.meta.name == "caption" - and tag.meta.value_type == sly.TagValueType.ANY_STRING - ): - caption_id += 1 - coco_captions["annotations"].append( + 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) + + bbox = label.geometry.to_bbox().to_json()["points"]["exterior"] + bbox = coco_bbox(bbox) + + label_id += 1 + coco_ann["annotations"].append( dict( - image_id=incremental_id, - id=caption_id, - caption=tag.value, + 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) ) ) - progress.iter_done_report() - incremental_id += 1 + if coco_captions is not None and include_captions: + for tag in ann.img_tags: + if ( + tag.meta.name == "caption" + and tag.meta.value_type == sly.TagValueType.ANY_STRING + ): + caption_id += 1 + coco_captions["annotations"].append( + dict( + image_id=incremental_id, + id=caption_id, + caption=tag.value, + ) + ) + progress.iter_done_report() return coco_ann, label_id, coco_captions, caption_id