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

Add mask_path to fo.Detection labels #4693

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion fiftyone/core/labels.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,18 +401,75 @@ class Detection(_HasAttributesDict, _HasID, Label):
mask (None): an instance segmentation mask for the detection within
its bounding box, which should be a 2D binary or 0/1 integer numpy
array
mask_path (None): the absolute path to the instance segmentation image
on disk
confidence (None): a confidence in ``[0, 1]`` for the detection
index (None): an index for the object
attributes ({}): a dict mapping attribute names to :class:`Attribute`
instances
"""

_MEDIA_FIELD = "mask_path"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to Voxel51, this would be the first instance of a media field that could be embedded in a list (Detections), which means these methods will need to be updated:

There could be more such places too.


label = fof.StringField()
bounding_box = fof.ListField(fof.FloatField())
mask = fof.ArrayField()
mask_path = fof.StringField()
confidence = fof.FloatField()
index = fof.IntField()

@property
def has_mask(self):
"""Whether this instance has a mask."""
return self.mask is not None or self.mask_path is not None

def get_mask(self):
"""Returns the segmentation mask for this instance.

Returns:
a numpy array, or ``None``
"""
if self.mask is not None:
return self.mask

if self.mask_path is not None:
return _read_mask(self.mask_path)

return None

def import_mask(self, update=False):
"""Imports this instance's mask from disk to its :attr:`mask`
attribute.

Args:
outpath: the path to write the map
update (False): whether to clear this instance's :attr:`mask_path`
attribute after importing
"""
if self.mask_path is not None:
self.mask = _read_mask(self.mask_path)

if update:
self.mask_path = None

def export_mask(self, outpath, update=False):
"""Exports this instance's mask to the given path.

Args:
outpath: the path to write the mask
update (False): whether to clear this instance's :attr:`mask`
attribute and set its :attr:`mask_path` attribute when
exporting in-database segmentations
"""
if self.mask_path is not None:
etau.copy_file(self.mask_path, outpath)
else:
_write_mask(self.mask, outpath)

if update:
self.mask = None
self.mask_path = outpath

def to_polyline(self, tolerance=2, filled=True):
"""Returns a :class:`Polyline` representation of this instance.

Expand Down Expand Up @@ -467,7 +524,8 @@ def to_segmentation(self, mask=None, frame_size=None, target=255):
Returns:
a :class:`Segmentation`
"""
if self.mask is None:
mask = self.get_mask()
if mask is None:
raise ValueError(
"Only detections with their `mask` attributes populated can "
"be converted to segmentations"
Expand Down
4 changes: 2 additions & 2 deletions fiftyone/utils/coco.py
Original file line number Diff line number Diff line change
Expand Up @@ -1299,7 +1299,7 @@ def from_label(
x, y, w, h = label.bounding_box
bbox = [x * width, y * height, w * width, h * height]

if label.mask is not None:
if label.has_mask() is not None:
segmentation = _instance_to_coco_segmentation(
label, frame_size, iscrowd=iscrowd, tolerance=tolerance
)
Expand Down Expand Up @@ -2110,7 +2110,7 @@ def _coco_objects_to_detections(
)

if detection is not None and (
not load_segmentations or detection.mask is not None
not load_segmentations or detection.has_mask() is not None
):
detections.append(detection)

Expand Down
2 changes: 1 addition & 1 deletion fiftyone/utils/cvat.py
Original file line number Diff line number Diff line change
Expand Up @@ -6400,7 +6400,7 @@ def _create_detection_shapes(
}
)
elif label_type in ("instance", "instances"):
if det.mask is None:
if det.has_mask() is None:
continue

polygon = det.to_polyline()
Expand Down
2 changes: 1 addition & 1 deletion fiftyone/utils/eta.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ def to_detected_object(detection, name=None, extra_attrs=True):
bry = tly + h
bounding_box = etag.BoundingBox.from_coords(tlx, tly, brx, bry)

mask = detection.mask
mask = detection.get_mask()
confidence = detection.confidence

attrs = _to_eta_attributes(detection, extra_attrs=extra_attrs)
Expand Down