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 Detection label #16

Open
wants to merge 2 commits into
base: cloned_develop_991c1
Choose a base branch
from
Open
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
103 changes: 102 additions & 1 deletion fiftyone/core/labels.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,18 +401,118 @@ 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"

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 transform_mask(self, targets_map, outpath=None, update=False):
"""Transforms this instance's mask according to the provided targets
map.

This method can be used to transform between grayscale and RGB masks,
or it can be used to edit the pixel values or colors of a mask without
changing the number of channels.

Note that any pixel values not in ``targets_map`` will be zero in the
transformed mask.

Args:
targets_map: a dict mapping existing pixel values (2D masks) or RGB
hex strings (3D masks) to new pixel values or RGB hex strings.
You may convert between grayscale and RGB using this argument
outpath (None): an optional path to write the transformed mask on
disk
update (False): whether to save the transformed mask on this
instance

Returns:
the transformed mask
"""
mask = self.get_mask()
if mask is None:
return

mask = _transform_mask(mask, targets_map)

if outpath is not None:
_write_mask(mask, outpath)

if update:
self.mask = None
self.mask_path = outpath
elif update:
if self.mask_path is not None:
_write_mask(mask, self.mask_path)
else:
self.mask = mask

return mask

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

Expand Down Expand Up @@ -467,7 +567,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.get_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.get_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.get_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()
Copy link
Author

Choose a reason for hiding this comment

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

category Error Handling

The code is accessing the get_mask() method of the detection object without handling the potential AttributeError exception that could be raised if the method is not defined. To make this code more robust, consider wrapping the detection.get_mask() call in a try/except block that catches AttributeError and handles it appropriately, such as setting mask to None or providing a default mask value if the method is not available.

Chat with Korbit by mentioning @korbit-ai, and give a 👍 or 👎 to help Korbit improve your reviews.

confidence = detection.confidence

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