Skip to content

Commit e0bcc46

Browse files
zhiltsov-maxnmanovic
authored andcommitted
[Datumaro] Instance polygon-mask conversions in COCO format (#1008)
* Microoptimizations * Mask conversion functions * Add mask-polygon conversions * Add mask-polygon conversions in coco * Add mask-polygon conversions in coco * Update requirements * Option to disable crop * Fix cli parameter passing * Fix test * Fixes in COCO
1 parent 8da20b3 commit e0bcc46

File tree

9 files changed

+915
-357
lines changed

9 files changed

+915
-357
lines changed

datumaro/datumaro/components/converters/ms_coco.py

Lines changed: 294 additions & 175 deletions
Large diffs are not rendered by default.

datumaro/datumaro/components/extractor.py

Lines changed: 87 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,16 @@ def __eq__(self, other):
159159

160160
class MaskObject(Annotation):
161161
# pylint: disable=redefined-builtin
162-
def __init__(self, image=None, label=None,
162+
def __init__(self, image=None, label=None, z_order=None,
163163
id=None, attributes=None, group=None):
164164
super().__init__(id=id, type=AnnotationType.mask,
165165
attributes=attributes, group=group)
166166
self._image = image
167167
self._label = label
168+
169+
if z_order is None:
170+
z_order = 0
171+
self._z_order = z_order
168172
# pylint: enable=redefined-builtin
169173

170174
@property
@@ -181,22 +185,69 @@ def painted_data(self, colormap):
181185
raise NotImplementedError()
182186

183187
def area(self):
184-
raise NotImplementedError()
188+
if self._label is None:
189+
raise NotImplementedError()
190+
return np.count_nonzero(self.image)
185191

186192
def extract(self, class_id):
187193
raise NotImplementedError()
188194

189-
def bbox(self):
190-
raise NotImplementedError()
195+
def get_bbox(self):
196+
if self._label is None:
197+
raise NotImplementedError()
198+
image = self.image
199+
cols = np.any(image, axis=0)
200+
rows = np.any(image, axis=1)
201+
x0, x1 = np.where(cols)[0][[0, -1]]
202+
y0, y1 = np.where(rows)[0][[0, -1]]
203+
return [x0, y0, x1 - x0, y1 - y0]
204+
205+
@property
206+
def z_order(self):
207+
return self._z_order
191208

192209
def __eq__(self, other):
193210
if not super().__eq__(other):
194211
return False
195212
return \
196213
(self.label == other.label) and \
214+
(self.z_order == other.z_order) and \
197215
(self.image is not None and other.image is not None and \
198216
np.all(self.image == other.image))
199217

218+
class RleMask(MaskObject):
219+
# pylint: disable=redefined-builtin
220+
def __init__(self, rle=None, label=None, z_order=None,
221+
id=None, attributes=None, group=None):
222+
lazy_decode = self._lazy_decode(rle)
223+
super().__init__(image=lazy_decode, label=label, z_order=z_order,
224+
id=id, attributes=attributes, group=group)
225+
226+
self._rle = rle
227+
# pylint: enable=redefined-builtin
228+
229+
@staticmethod
230+
def _lazy_decode(rle):
231+
from pycocotools import mask as mask_utils
232+
return lambda: mask_utils.decode(rle).astype(np.bool)
233+
234+
def area(self):
235+
from pycocotools import mask as mask_utils
236+
return mask_utils.area(self._rle)
237+
238+
def bbox(self):
239+
from pycocotools import mask as mask_utils
240+
return mask_utils.toBbox(self._rle)
241+
242+
@property
243+
def rle(self):
244+
return self._rle
245+
246+
def __eq__(self, other):
247+
if not isinstance(other, __class__):
248+
return super().__eq__(other)
249+
return self._rle == other._rle
250+
200251
def compute_iou(bbox_a, bbox_b):
201252
aX, aY, aW, aH = bbox_a
202253
bX, bY, bW, bH = bbox_b
@@ -217,12 +268,16 @@ def compute_iou(bbox_a, bbox_b):
217268

218269
class ShapeObject(Annotation):
219270
# pylint: disable=redefined-builtin
220-
def __init__(self, type, points=None, label=None,
271+
def __init__(self, type, points=None, label=None, z_order=None,
221272
id=None, attributes=None, group=None):
222273
super().__init__(id=id, type=type,
223274
attributes=attributes, group=group)
224275
self.points = points
225276
self.label = label
277+
278+
if z_order is None:
279+
z_order = 0
280+
self._z_order = z_order
226281
# pylint: enable=redefined-builtin
227282

228283
def area(self):
@@ -247,22 +302,24 @@ def get_bbox(self):
247302
def get_points(self):
248303
return self.points
249304

250-
def get_mask(self):
251-
raise NotImplementedError()
305+
@property
306+
def z_order(self):
307+
return self._z_order
252308

253309
def __eq__(self, other):
254310
if not super().__eq__(other):
255311
return False
256312
return \
257313
(self.points == other.points) and \
314+
(self.z_order == other.z_order) and \
258315
(self.label == other.label)
259316

260317
class PolyLineObject(ShapeObject):
261318
# pylint: disable=redefined-builtin
262-
def __init__(self, points=None,
263-
label=None, id=None, attributes=None, group=None):
319+
def __init__(self, points=None, label=None, z_order=None,
320+
id=None, attributes=None, group=None):
264321
super().__init__(type=AnnotationType.polyline,
265-
points=points, label=label,
322+
points=points, label=label, z_order=z_order,
266323
id=id, attributes=attributes, group=group)
267324
# pylint: enable=redefined-builtin
268325

@@ -274,12 +331,12 @@ def area(self):
274331

275332
class PolygonObject(ShapeObject):
276333
# pylint: disable=redefined-builtin
277-
def __init__(self, points=None,
334+
def __init__(self, points=None, z_order=None,
278335
label=None, id=None, attributes=None, group=None):
279336
if points is not None:
280337
assert len(points) % 2 == 0 and 3 <= len(points) // 2, "Wrong polygon points: %s" % points
281338
super().__init__(type=AnnotationType.polygon,
282-
points=points, label=label,
339+
points=points, label=label, z_order=z_order,
283340
id=id, attributes=attributes, group=group)
284341
# pylint: enable=redefined-builtin
285342

@@ -291,15 +348,15 @@ def area(self):
291348

292349
_, _, w, h = self.get_bbox()
293350
rle = mask_utils.frPyObjects([self.get_points()], h, w)
294-
area = mask_utils.area(rle)
351+
area = mask_utils.area(rle)[0]
295352
return area
296353

297354
class BboxObject(ShapeObject):
298355
# pylint: disable=redefined-builtin
299-
def __init__(self, x=0, y=0, w=0, h=0,
300-
label=None, id=None, attributes=None, group=None):
356+
def __init__(self, x=0, y=0, w=0, h=0, label=None, z_order=None,
357+
id=None, attributes=None, group=None):
301358
super().__init__(type=AnnotationType.bbox,
302-
points=[x, y, x + w, y + h], label=label,
359+
points=[x, y, x + w, y + h], label=label, z_order=z_order,
303360
id=id, attributes=attributes, group=group)
304361
# pylint: enable=redefined-builtin
305362

@@ -368,7 +425,7 @@ class PointsObject(ShapeObject):
368425
])
369426

370427
# pylint: disable=redefined-builtin
371-
def __init__(self, points=None, visibility=None, label=None,
428+
def __init__(self, points=None, visibility=None, label=None, z_order=None,
372429
id=None, attributes=None, group=None):
373430
if points is not None:
374431
assert len(points) % 2 == 0
@@ -381,10 +438,10 @@ def __init__(self, points=None, visibility=None, label=None,
381438
else:
382439
visibility = []
383440
for _ in range(len(points) // 2):
384-
visibility.append(self.Visibility.absent)
441+
visibility.append(self.Visibility.visible)
385442

386443
super().__init__(type=AnnotationType.points,
387-
points=points, label=label,
444+
points=points, label=label, z_order=z_order,
388445
id=id, attributes=attributes, group=group)
389446

390447
self.visibility = visibility
@@ -393,6 +450,17 @@ def __init__(self, points=None, visibility=None, label=None,
393450
def area(self):
394451
return 0
395452

453+
def get_bbox(self):
454+
xs = [p for p, v in zip(self.points[0::2], self.visibility)
455+
if v != __class__.Visibility.absent]
456+
ys = [p for p, v in zip(self.points[1::2], self.visibility)
457+
if v != __class__.Visibility.absent]
458+
x0 = min(xs, default=0)
459+
x1 = max(xs, default=0)
460+
y0 = min(ys, default=0)
461+
y1 = max(ys, default=0)
462+
return [x0, y0, x1 - x0, y1 - y0]
463+
396464
def __eq__(self, other):
397465
if not super().__eq__(other):
398466
return False

datumaro/datumaro/components/extractors/ms_coco.py

Lines changed: 18 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,21 @@
44
# SPDX-License-Identifier: MIT
55

66
from collections import OrderedDict
7-
from itertools import chain
8-
import numpy as np
97
import os.path as osp
108

119
from pycocotools.coco import COCO
1210
import pycocotools.mask as mask_utils
1311

1412
from datumaro.components.extractor import (Extractor, DatasetItem,
1513
DEFAULT_SUBSET_NAME, AnnotationType,
16-
LabelObject, MaskObject, PointsObject, PolygonObject,
14+
LabelObject, RleMask, PointsObject, PolygonObject,
1715
BboxObject, CaptionObject,
1816
LabelCategories, PointsCategories
1917
)
2018
from datumaro.components.formats.ms_coco import CocoTask, CocoPath
2119
from datumaro.util.image import lazy_image
2220

2321

24-
class RleMask(MaskObject):
25-
# pylint: disable=redefined-builtin
26-
def __init__(self, rle=None, label=None,
27-
id=None, attributes=None, group=None):
28-
lazy_decode = lambda: mask_utils.decode(rle).astype(np.bool)
29-
super().__init__(image=lazy_decode, label=label,
30-
id=id, attributes=attributes, group=group)
31-
32-
self._rle = rle
33-
# pylint: enable=redefined-builtin
34-
35-
def area(self):
36-
return mask_utils.area(self._rle)
37-
38-
def bbox(self):
39-
return mask_utils.toBbox(self._rle)
40-
41-
def __eq__(self, other):
42-
if not isinstance(other, __class__):
43-
return super().__eq__(other)
44-
return self._rle == other._rle
45-
4622
class CocoExtractor(Extractor):
4723
def __init__(self, path, task, merge_instance_polygons=False):
4824
super().__init__()
@@ -144,8 +120,7 @@ def _load_items(self, loader):
144120

145121
anns = loader.getAnnIds(imgIds=img_id)
146122
anns = loader.loadAnns(anns)
147-
anns = list(chain(*(
148-
self._load_annotations(ann, image_info) for ann in anns)))
123+
anns = sum((self._load_annotations(a, image_info) for a in anns), [])
149124

150125
items[img_id] = DatasetItem(id=img_id, subset=self._subset,
151126
image=image, annotations=anns)
@@ -167,25 +142,34 @@ def _load_annotations(self, ann, image_info=None):
167142
if 'score' in ann:
168143
attributes['score'] = ann['score']
169144

170-
if self._task is CocoTask.instances:
145+
group = ann_id # make sure all tasks' annotations are merged
146+
147+
if self._task in [CocoTask.instances, CocoTask.person_keypoints]:
171148
x, y, w, h = ann['bbox']
172149
label_id = self._get_label_id(ann)
173-
group = None
174150

175151
is_crowd = bool(ann['iscrowd'])
176152
attributes['is_crowd'] = is_crowd
177153

154+
if self._task is CocoTask.person_keypoints:
155+
keypoints = ann['keypoints']
156+
points = [p for i, p in enumerate(keypoints) if i % 3 != 2]
157+
visibility = keypoints[2::3]
158+
parsed_annotations.append(
159+
PointsObject(points, visibility, label=label_id,
160+
id=ann_id, attributes=attributes, group=group)
161+
)
162+
178163
segmentation = ann.get('segmentation')
179164
if segmentation is not None:
180-
group = ann_id
181165
rle = None
182166

183167
if isinstance(segmentation, list):
184168
# polygon - a single object can consist of multiple parts
185169
for polygon_points in segmentation:
186170
parsed_annotations.append(PolygonObject(
187171
points=polygon_points, label=label_id,
188-
id=ann_id, group=group, attributes=attributes
172+
id=ann_id, attributes=attributes, group=group
189173
))
190174

191175
if self._merge_instance_polygons:
@@ -204,7 +188,7 @@ def _load_annotations(self, ann, image_info=None):
204188

205189
if rle is not None:
206190
parsed_annotations.append(RleMask(rle=rle, label=label_id,
207-
id=ann_id, group=group, attributes=attributes
191+
id=ann_id, attributes=attributes, group=group
208192
))
209193

210194
parsed_annotations.append(
@@ -214,30 +198,14 @@ def _load_annotations(self, ann, image_info=None):
214198
elif self._task is CocoTask.labels:
215199
label_id = self._get_label_id(ann)
216200
parsed_annotations.append(
217-
LabelObject(label=label_id, id=ann_id, attributes=attributes)
218-
)
219-
elif self._task is CocoTask.person_keypoints:
220-
keypoints = ann['keypoints']
221-
points = [p for i, p in enumerate(keypoints) if i % 3 != 2]
222-
visibility = keypoints[2::3]
223-
bbox = ann.get('bbox')
224-
label_id = self._get_label_id(ann)
225-
group = None
226-
if bbox is not None:
227-
group = ann_id
228-
parsed_annotations.append(
229-
PointsObject(points, visibility, label=label_id,
201+
LabelObject(label=label_id,
230202
id=ann_id, attributes=attributes, group=group)
231203
)
232-
if bbox is not None:
233-
parsed_annotations.append(
234-
BboxObject(*bbox, label=label_id, group=group)
235-
)
236204
elif self._task is CocoTask.captions:
237205
caption = ann['caption']
238206
parsed_annotations.append(
239207
CaptionObject(caption,
240-
id=ann_id, attributes=attributes)
208+
id=ann_id, attributes=attributes, group=group)
241209
)
242210
else:
243211
raise NotImplementedError()

0 commit comments

Comments
 (0)