Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Commit

Permalink
[feat] export BrushLabels to COCO
Browse files Browse the repository at this point in the history
  • Loading branch information
cdpath committed Apr 12, 2023
1 parent fc5eb78 commit 82f778c
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 4 deletions.
38 changes: 38 additions & 0 deletions label_studio_converter/brush.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@
from collections import defaultdict
from itertools import groupby

try:
import pycocotools.mask
except ImportError:
pycocotools_imported = False
else:
pycocotools_imported = True


logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -188,6 +196,36 @@ def convert_task_dir(items, out_dir, out_format='numpy'):
convert_task(item, out_dir, out_format)


def binary_mask_to_rle(binary_mask: np.ndarray):
"""from binary image mask to uncompressed coco rle"""
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 {'counts': counts, 'size': list(binary_mask.shape)}


def ls_rle_to_coco_rle(ls_rle, height, width):
"""from LS rle to compressed coco rle"""
ls_mask = decode_rle(ls_rle)
ls_mask = np.reshape(ls_mask, [height, width, 4])[:, :, 3]
ls_mask = np.where(ls_mask > 0, 1, 0)
binary_mask = np.asfortranarray(ls_mask)
coco_rle = binary_mask_to_rle(binary_mask)
result = pycocotools.mask.frPyObjects(coco_rle, *coco_rle.get('size'))
result["counts"] = result["counts"].decode()
return result


def get_cocomask_area(segmentation):
return int(pycocotools.mask.area(segmentation))


def get_cocomask_bounding_box(segmentation):
return pycocotools.mask.toBbox(segmentation).tolist()


# convert_task_dir('/ls/test/completions', '/ls/test/completions/output', 'numpy')


Expand Down
39 changes: 37 additions & 2 deletions label_studio_converter/converter.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import io
import itertools
import math
import logging
import ujson as json
Expand Down Expand Up @@ -30,6 +31,8 @@
get_annotator,
get_json_root_type,
prettify_result,
get_cocomask_area,
get_cocomask_bounding_box,
)
from label_studio_converter import brush
from label_studio_converter.audio import convert_to_asr_json_manifest
Expand Down Expand Up @@ -314,8 +317,23 @@ def _get_supported_formats(self):
or 'PolygonLabels' in output_tag_types
and 'Labels' in output_tag_types
):
all_formats.remove(Format.COCO.name)
all_formats.remove(Format.YOLO.name)
if not (
'Image' in input_tag_types
and (
'RectangleLabels' in output_tag_types
or 'PolygonLabels' in output_tag_types
or 'BrushLabels' in output_tag_types
or 'brushlabels' in output_tag_types
)
or 'Rectangle' in output_tag_types
and 'Labels' in output_tag_types
or 'PolygonLabels' in output_tag_types
and 'Labels' in output_tag_types
or 'Brush' in output_tag_types
and 'Labels' in output_tag_types
):
all_formats.remove(Format.COCO.name)
if not (
'Image' in input_tag_types
and (
Expand Down Expand Up @@ -596,7 +614,12 @@ def add_image(images, width, height, image_id, image_path):

for label in labels:
category_name = None
for key in ['rectanglelabels', 'polygonlabels', 'labels']:
for key in [
'rectanglelabels',
'polygonlabels',
'brushlabels',
'labels',
]:
if key in label and len(label[key]) > 0:
category_name = label[key][0]
break
Expand Down Expand Up @@ -659,6 +682,18 @@ def add_image(images, width, height, image_id, image_path):
'area': get_polygon_area(x, y),
}
)
elif 'brushlabels' in label and brush.pycocotools_imported:
segmentation = brush.ls_rle_to_coco_rle(label["rle"], height, width)
annotations.append(
{
"image_id": image_id,
"segmentation": segmentation,
"area": brush.get_cocomask_area(segmentation),
"bbox": brush.get_cocomask_bounding_box(segmentation),
"iscrowd": 1,
"category_id": category_id,
}
)
else:
raise ValueError("Unknown label type")

Expand Down
8 changes: 8 additions & 0 deletions label_studio_converter/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,14 @@ def get_polygon_bounding_box(x, y):
return [x1, y1, x2 - x1, y2 - y1]


def get_cocomask_area(segmentation):
return int(pycocotools.mask.area(segmentation))


def get_cocomask_bounding_box(segmentation):
return pycocotools.mask.toBbox(segmentation).tolist()


def get_annotator(item, default=None, int_id=False):
"""Get annotator id or email from annotation"""
annotator = item['completed_by']
Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,3 @@ nltk==3.6.7
label-studio-tools>=0.0.2
ujson
ijson~=3.2.0.post0

5 changes: 5 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
'Operating System :: OS Independent',
],
install_requires=requirements,
extras_require={
"coco": [
"pycocotools==2.0.6",
],
},
python_requires='>=3.6',
entry_points={
'console_scripts': [
Expand Down
37 changes: 36 additions & 1 deletion tests/test_brush.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
"""
Test for the brush.py module
"""
import pytest
import unittest
import urllib3
import json
import numpy as np

from label_studio_converter.brush import encode_rle, image2annotation
from label_studio_converter.brush import (
encode_rle,
image2annotation,
binary_mask_to_rle,
ls_rle_to_coco_rle,
)


def test_image2annotation():
Expand Down Expand Up @@ -71,3 +78,31 @@ def test_rle_encoding():
56,
32,
]


def test_binary_mask_to_rle():
# Test with a simple binary mask
binary_mask = np.array([[0, 1, 1, 0], [1, 1, 1, 0], [0, 0, 0, 1]])
rle = binary_mask_to_rle(binary_mask)
assert rle == {'counts': [1, 1, 1, 2, 1, 2, 3, 1], 'size': [3, 4]}

# Test with a binary mask that is all zeros
binary_mask = np.zeros((3, 4))
rle = binary_mask_to_rle(binary_mask)
assert rle == {'counts': [12], 'size': [3, 4]}

# Test with a binary mask that is all ones
binary_mask = np.ones((4, 5))
rle = binary_mask_to_rle(binary_mask)
assert rle == {'counts': [0, 20], 'size': [4, 5]}


def test_ls_rle_to_coco_rle():
# Test with a simple LS RLE
pytest.importorskip("pycocotools")
ls_rle = encode_rle([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24])
height = 2
width = 3
coco_rle = ls_rle_to_coco_rle(ls_rle, height, width)
assert coco_rle == {'counts': '06', 'size': [2, 3]}

0 comments on commit 82f778c

Please sign in to comment.