Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1114 rle support for coco #1163

Merged
merged 60 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
e30d4a9
add test for generating annotation with mask
emSko May 2, 2024
cc5e72d
test for RLE format
emSko May 2, 2024
7f114cb
RLE decoding
emSko May 2, 2024
6913ecf
2 annotations with segmentation, one polygon one RLE
emSko May 2, 2024
4a068ff
binary mask to RL encoding
emSko May 3, 2024
dc1ce02
move rle encode decode functions to dataset/utils.py
emSko May 3, 2024
869204d
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 5, 2024
18a790e
typing chanches and doc strings for rle_to_mask and mask_to_rle funct…
emSko May 6, 2024
3ade7f0
fix order caused error with mask generation in coco_annotations_to_de…
emSko May 6, 2024
2727152
merge with upstream
emSko May 6, 2024
a941583
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 6, 2024
88a43cc
unit tests for rle_to_mask and mask_to_rle functions
emSko May 6, 2024
6b5dacd
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 6, 2024
a9f821c
Assertion error when number of pixel in RLE does not match the nuber …
emSko May 7, 2024
3618ac3
merge
emSko May 7, 2024
d59ec0f
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 7, 2024
aac1d88
assertion for valid mask in mask_to_rle function
emSko May 7, 2024
36a301e
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 7, 2024
649e273
fix the line lengths
emSko May 8, 2024
008711a
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 8, 2024
e4e66f8
faster mask to rle
emSko May 10, 2024
39477fe
Merge branch '1114_RLE_support_for_COCO' of github.com:emSko/supervis…
emSko May 10, 2024
7bd92b0
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 10, 2024
8c857dd
speed up rle to mask
emSko May 12, 2024
d292cbf
Merge branch '1114_RLE_support_for_COCO' of github.com:emSko/supervis…
emSko May 12, 2024
14b5734
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 12, 2024
d21c98a
docs updated; `rle_to_mask` and `mask_to_rle` added to `__init__.py`
SkalskiP May 13, 2024
9a1c112
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 13, 2024
59b273b
small refactor
SkalskiP May 13, 2024
cccacab
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 13, 2024
aab861c
test_coco_annotations_to_detections result matrices defined in place
emSko May 15, 2024
f08404e
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 15, 2024
d8679d9
automatic RLE for masks with holes or in multiple pieces
emSko May 16, 2024
eefa05c
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 16, 2024
1e8ee47
craete copies of mask in _has_holes and _mask_has_multiple_segments
emSko May 16, 2024
ce05a0c
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 16, 2024
3ee72e3
check for empty mask
emSko May 16, 2024
511f0ab
check for empty mask
emSko May 16, 2024
b3b7455
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 16, 2024
879ae94
test polygon mask when mask in single component and no holes
emSko May 17, 2024
ab775d9
Attempt to fix SegFault
LinasKo May 17, 2024
aad5e2a
merge seg fault fix
emSko May 17, 2024
0cd2c9d
documentation change for as_coco
emSko May 17, 2024
8d432c7
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 17, 2024
a40bf78
coco mock method refactoring
emSko May 20, 2024
2626263
move has_holes and mask_has_multiple_segments to detections utils
emSko May 20, 2024
179d00b
merge
emSko May 20, 2024
7ee84c4
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 20, 2024
68f07e1
tests for mask_has_holes
emSko May 20, 2024
f0fc76c
Merge branch '1114_RLE_support_for_COCO' of github.com:emSko/supervis…
emSko May 20, 2024
c26eb1a
unit tests for test_mask_has_multiple_segments
emSko May 20, 2024
dfac30c
change docu for mask_has_multiple_segments and mask_has_holes and add…
emSko May 20, 2024
7b42d83
Merge branch 'develop' into 1114_RLE_support_for_COCO
emSko May 20, 2024
ab55e81
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 20, 2024
6dbf3f6
fix for unit tests for test_mask_has_holes
emSko May 20, 2024
f271625
small refactor + docs improvements
SkalskiP May 21, 2024
996b0a3
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 21, 2024
1d43042
small refactor + docs improvements
SkalskiP May 21, 2024
c3855c9
small refactor + docs improvements
SkalskiP May 21, 2024
5840889
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
50 changes: 44 additions & 6 deletions supervision/dataset/formats/coco.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from supervision.dataset.utils import (
approximate_mask_with_polygons,
map_detections_class_id,
rle_to_mask,
)
from supervision.detection.core import Detections
from supervision.detection.utils import polygon_to_mask
Expand Down Expand Up @@ -69,6 +70,26 @@ def _polygons_to_masks(
)


def _rles_to_masks(
rles: List[np.ndarray], resolution_wh: Tuple[int, int]
) -> np.ndarray:
return np.array(
[rle_to_mask(rle=rle, resolution_wh=resolution_wh) for rle in rles],
dtype=bool,
)


def _concatenate_annotation_masks(mask_polygon, mask_rle):
if mask_polygon.ndim == 3 and mask_rle.ndim == 3:
return np.concatenate((mask_polygon, mask_rle))
elif mask_polygon.ndim == 3:
return mask_polygon
elif mask_rle.ndim == 3:
return mask_rle
else:
None


def coco_annotations_to_detections(
image_annotations: List[dict], resolution_wh: Tuple[int, int], with_masks: bool
) -> Detections:
Expand All @@ -88,10 +109,25 @@ def coco_annotations_to_detections(
np.asarray(image_annotation["segmentation"], dtype=np.int32), (-1, 2)
)
for image_annotation in image_annotations
if not image_annotation["iscrowd"]
]
mask_polygon = _polygons_to_masks(
polygons=polygons, resolution_wh=resolution_wh
)

rles = [
np.array(image_annotation["segmentation"]["counts"])
for image_annotation in image_annotations
if image_annotation["iscrowd"]
]
mask = _polygons_to_masks(polygons=polygons, resolution_wh=resolution_wh)
mask_rle = _rles_to_masks(rles=rles, resolution_wh=resolution_wh)

return Detections(
class_id=np.asarray(class_ids, dtype=int), xyxy=xyxy, mask=mask
class_id=np.asarray(class_ids, dtype=int),
xyxy=xyxy,
mask=_concatenate_annotation_masks(
emSko marked this conversation as resolved.
Show resolved Hide resolved
mask_polygon=mask_polygon, mask_rle=mask_rle
),
)

return Detections(xyxy=xyxy, class_id=np.asarray(class_ids, dtype=int))
Expand All @@ -108,24 +144,26 @@ def detections_to_coco_annotations(
coco_annotations = []
for xyxy, mask, _, class_id, _, _ in detections:
box_width, box_height = xyxy[2] - xyxy[0], xyxy[3] - xyxy[1]
polygon = []
segmentation = []
if mask is not None:
polygon = list(
segmentation = list(
approximate_mask_with_polygons(
mask=mask,
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
)[0].flatten()
)
# todo: flag for when to use RLE?
# segmentation = {"counts": mask_to_rle(binary_mask=mask), "size": list(mask.shape[:2])}
coco_annotation = {
"id": annotation_id,
"image_id": image_id,
"category_id": int(class_id),
"bbox": [xyxy[0], xyxy[1], box_width, box_height],
"area": box_width * box_height,
"segmentation": [polygon] if polygon else [],
"iscrowd": 0,
"segmentation": [segmentation] if segmentation else [],
"iscrowd": 0, ## todo: iscrowd depends on flag 1 if RLE 0 if polygon
}
coco_annotations.append(coco_annotation)
annotation_id += 1
Expand Down
21 changes: 21 additions & 0 deletions supervision/dataset/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import copy
import os
import random
from itertools import groupby
from pathlib import Path
from typing import Dict, List, Optional, Tuple, TypeVar

Expand Down Expand Up @@ -129,3 +130,23 @@ def train_test_split(

split_index = int(len(data) * train_ratio)
return data[:split_index], data[split_index:]


def rle_to_mask(rle: np.ndarray, resolution_wh: Tuple[int, int]) -> np.ndarray:
emSko marked this conversation as resolved.
Show resolved Hide resolved
width, height = resolution_wh

zero_one_values = np.zeros_like(rle)
zero_one_values[1::2] = 1

decoded_rle = np.repeat(zero_one_values, rle)
return decoded_rle.reshape((height, width), order="F")


def mask_to_rle(binary_mask: np.ndarray) -> list:
emSko marked this conversation as resolved.
Show resolved Hide resolved
rle = []
for _, group in groupby(binary_mask.ravel(order="F")):
rle.append(len(list(group)))

if binary_mask[0][0] == 1:
rle = [0] + rle
return rle
136 changes: 135 additions & 1 deletion test/dataset/formats/test_coco.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,17 @@ def mock_cock_coco_annotation(
category_id: int = 0,
bbox: Tuple[float, float, float, float] = (0.0, 0.0, 0.0, 0.0),
area: float = 0.0,
segmentation: List[list] = None,
iscrowd: bool = False,
) -> dict:
return {
"id": annotation_id,
"image_id": image_id,
"category_id": category_id,
"bbox": list(bbox),
"area": area,
"iscrowd": 0,
"segmentation": segmentation,
"iscrowd": int(iscrowd),
}


Expand Down Expand Up @@ -226,6 +229,137 @@ def test_group_coco_annotations_by_image_id(
),
DoesNotRaise(),
), # two image annotations
(
[
mock_cock_coco_annotation(
emSko marked this conversation as resolved.
Show resolved Hide resolved
category_id=0,
bbox=(0, 0, 10, 10),
area=10 * 10,
segmentation=[[0, 0, 4, 0, 4, 5, 9, 5, 9, 9, 0, 9]],
)
],
(20, 20),
True,
Detections(
xyxy=np.array([[0, 0, 10, 10]], dtype=np.float32),
class_id=np.array([0], dtype=int),
mask=np.array(
[
0 if i >= 10 or j >= 10 or (i < 5 and j >= 5) else 1
for i in range(0, 20)
for j in range(0, 20)
]
).reshape((1, 20, 20)),
),
DoesNotRaise(),
), # single image annotations with mask, segmentation mask in L-like shape, like below:
# 1 0 0 0
# 1 1 0 0
# 0 0 0 0
# 0 0 0 0
(
[
mock_cock_coco_annotation(
category_id=0,
bbox=(0, 0, 10, 10),
area=10 * 10,
segmentation={
"size": [20, 20],
"counts": [
0,
10,
10,
10,
10,
10,
10,
10,
10,
10,
15,
5,
15,
5,
15,
5,
15,
5,
15,
5,
210,
],
},
iscrowd=True,
)
],
(20, 20),
True,
Detections(
xyxy=np.array([[0, 0, 10, 10]], dtype=np.float32),
class_id=np.array([0], dtype=int),
mask=np.array(
[
0 if i >= 10 or j >= 10 or (i < 5 and j >= 5) else 1
for i in range(0, 20)
for j in range(0, 20)
]
).reshape((1, 20, 20)),
),
DoesNotRaise(),
), # single image annotations with mask, RLE segmentation mask in L-like shape, like below:
# 1 0 0 0
# 1 1 0 0
# 0 0 0 0
# 0 0 0 0
(
[
mock_cock_coco_annotation(
category_id=0,
bbox=(0, 0, 10, 10),
area=10 * 10,
segmentation=[[0, 0, 4, 0, 4, 5, 9, 5, 9, 9, 0, 9]],
),
mock_cock_coco_annotation(
category_id=0,
bbox=(5, 0, 5, 5),
area=5 * 5,
segmentation={
"size": [20, 20],
"counts": [100, 5, 15, 5, 15, 5, 15, 5, 15, 5, 215],
},
iscrowd=True,
),
],
(20, 20),
True,
Detections(
xyxy=np.array([[0, 0, 10, 10], [5, 0, 10, 5]], dtype=np.float32),
class_id=np.array([0, 0], dtype=int),
mask=np.array(
[
np.array(
[
0 if i >= 10 or j >= 10 or (i < 5 and j >= 5) else 1
for i in range(0, 20)
for j in range(0, 20)
]
).reshape((20, 20)),
np.array(
[
1 if j > 4 and j < 10 and i < 5 else 0
for i in range(0, 20)
for j in range(0, 20)
]
).reshape((20, 20)),
]
),
),
DoesNotRaise(),
), # two image annotations with mask, one mask as polygon in in L-like shape, second as RLE in shape of square, like below (P = polygon, R = RLE):
# P R 0 0
# P P 0 0
# 0 0 0 0
# 0 0 0 0
],
)
def test_coco_annotations_to_detections(
Expand Down