Skip to content
Snippets Groups Projects
Unverified Commit 5db9b2e3 authored by Cao Yuhang's avatar Cao Yuhang Committed by GitHub
Browse files

Rename BitMap to Bitmap, perfect unit test. (#2391)

* rename BitMap to Bitmap, add input check for polygon

* fix test mask

* fix test config

* complete test

* add mask contest test for bitmap resize

* update with np.diag

* perfect test polygon resize

* perfect test polygon crop
parent da09e7e5
No related branches found
No related tags found
No related merge requests found
from .mask_target import mask_target from .mask_target import mask_target
from .structures import BitMapMasks, PolygonMasks from .structures import BitmapMasks, PolygonMasks
from .utils import split_combined_polys from .utils import split_combined_polys
__all__ = [ __all__ = [
'split_combined_polys', 'mask_target', 'BitMapMasks', 'PolygonMasks' 'split_combined_polys', 'mask_target', 'BitmapMasks', 'PolygonMasks'
] ]
...@@ -40,6 +40,11 @@ class BaseInstanceMasks(metaclass=ABCMeta): ...@@ -40,6 +40,11 @@ class BaseInstanceMasks(metaclass=ABCMeta):
def expand(self, expanded_h, expanded_w, top, left): def expand(self, expanded_h, expanded_w, top, left):
pass pass
@property
@abstractmethod
def areas(self):
pass
@abstractmethod @abstractmethod
def to_ndarray(self): def to_ndarray(self):
pass pass
...@@ -49,7 +54,7 @@ class BaseInstanceMasks(metaclass=ABCMeta): ...@@ -49,7 +54,7 @@ class BaseInstanceMasks(metaclass=ABCMeta):
pass pass
class BitMapMasks(BaseInstanceMasks): class BitmapMasks(BaseInstanceMasks):
"""This class represents masks in the form of bitmaps. """This class represents masks in the form of bitmaps.
Args: Args:
...@@ -78,7 +83,7 @@ class BitMapMasks(BaseInstanceMasks): ...@@ -78,7 +83,7 @@ class BitMapMasks(BaseInstanceMasks):
def __getitem__(self, index): def __getitem__(self, index):
masks = self.masks[index].reshape(-1, self.height, self.width) masks = self.masks[index].reshape(-1, self.height, self.width)
return BitMapMasks(masks, self.height, self.width) return BitmapMasks(masks, self.height, self.width)
def __iter__(self): def __iter__(self):
return iter(self.masks) return iter(self.masks)
...@@ -95,7 +100,7 @@ class BitMapMasks(BaseInstanceMasks): ...@@ -95,7 +100,7 @@ class BitMapMasks(BaseInstanceMasks):
interpolation (str): same as :func:`mmcv.imrescale` interpolation (str): same as :func:`mmcv.imrescale`
Returns: Returns:
BitMapMasks: the rescaled masks BitmapMasks: the rescaled masks
""" """
if len(self.masks) == 0: if len(self.masks) == 0:
new_w, new_h = mmcv.rescale_size((self.width, self.height), scale) new_w, new_h = mmcv.rescale_size((self.width, self.height), scale)
...@@ -106,7 +111,7 @@ class BitMapMasks(BaseInstanceMasks): ...@@ -106,7 +111,7 @@ class BitMapMasks(BaseInstanceMasks):
for mask in self.masks for mask in self.masks
]) ])
height, width = rescaled_masks.shape[1:] height, width = rescaled_masks.shape[1:]
return BitMapMasks(rescaled_masks, height, width) return BitmapMasks(rescaled_masks, height, width)
def resize(self, out_shape, interpolation='nearest'): def resize(self, out_shape, interpolation='nearest'):
"""Resize masks to the given out_shape. """Resize masks to the given out_shape.
...@@ -116,7 +121,7 @@ class BitMapMasks(BaseInstanceMasks): ...@@ -116,7 +121,7 @@ class BitMapMasks(BaseInstanceMasks):
interpolation (str): see `mmcv.imresize` interpolation (str): see `mmcv.imresize`
Returns: Returns:
BitMapMasks: the resized masks BitmapMasks: the resized masks
""" """
if len(self.masks) == 0: if len(self.masks) == 0:
resized_masks = np.empty((0, *out_shape), dtype=np.uint8) resized_masks = np.empty((0, *out_shape), dtype=np.uint8)
...@@ -125,7 +130,7 @@ class BitMapMasks(BaseInstanceMasks): ...@@ -125,7 +130,7 @@ class BitMapMasks(BaseInstanceMasks):
mmcv.imresize(mask, out_shape, interpolation=interpolation) mmcv.imresize(mask, out_shape, interpolation=interpolation)
for mask in self.masks for mask in self.masks
]) ])
return BitMapMasks(resized_masks, *out_shape) return BitmapMasks(resized_masks, *out_shape)
def flip(self, flip_direction='horizontal'): def flip(self, flip_direction='horizontal'):
"""flip masks alone the given direction. """flip masks alone the given direction.
...@@ -134,7 +139,7 @@ class BitMapMasks(BaseInstanceMasks): ...@@ -134,7 +139,7 @@ class BitMapMasks(BaseInstanceMasks):
flip_direction (str): either 'horizontal' or 'vertical' flip_direction (str): either 'horizontal' or 'vertical'
Returns: Returns:
BitMapMasks: the flipped masks BitmapMasks: the flipped masks
""" """
assert flip_direction in ('horizontal', 'vertical') assert flip_direction in ('horizontal', 'vertical')
...@@ -145,7 +150,7 @@ class BitMapMasks(BaseInstanceMasks): ...@@ -145,7 +150,7 @@ class BitMapMasks(BaseInstanceMasks):
mmcv.imflip(mask, direction=flip_direction) mmcv.imflip(mask, direction=flip_direction)
for mask in self.masks for mask in self.masks
]) ])
return BitMapMasks(flipped_masks, self.height, self.width) return BitmapMasks(flipped_masks, self.height, self.width)
def pad(self, out_shape, pad_val=0): def pad(self, out_shape, pad_val=0):
"""Pad masks to the given size of (h, w). """Pad masks to the given size of (h, w).
...@@ -155,7 +160,7 @@ class BitMapMasks(BaseInstanceMasks): ...@@ -155,7 +160,7 @@ class BitMapMasks(BaseInstanceMasks):
pad_val (int): the padded value pad_val (int): the padded value
Returns: Returns:
BitMapMasks: the padded masks BitmapMasks: the padded masks
""" """
if len(self.masks) == 0: if len(self.masks) == 0:
padded_masks = np.empty((0, *out_shape), dtype=np.uint8) padded_masks = np.empty((0, *out_shape), dtype=np.uint8)
...@@ -164,7 +169,7 @@ class BitMapMasks(BaseInstanceMasks): ...@@ -164,7 +169,7 @@ class BitMapMasks(BaseInstanceMasks):
mmcv.impad(mask, out_shape, pad_val=pad_val) mmcv.impad(mask, out_shape, pad_val=pad_val)
for mask in self.masks for mask in self.masks
]) ])
return BitMapMasks(padded_masks, *out_shape) return BitmapMasks(padded_masks, *out_shape)
def crop(self, bbox): def crop(self, bbox):
"""Crop each mask by the given bbox. """Crop each mask by the given bbox.
...@@ -173,7 +178,7 @@ class BitMapMasks(BaseInstanceMasks): ...@@ -173,7 +178,7 @@ class BitMapMasks(BaseInstanceMasks):
bbox (ndarray): bbox in format [x1, y1, x2, y2], shape (4, ) bbox (ndarray): bbox in format [x1, y1, x2, y2], shape (4, )
Return: Return:
BitMapMasks: the cropped masks. BitmapMasks: the cropped masks.
""" """
assert isinstance(bbox, np.ndarray) assert isinstance(bbox, np.ndarray)
assert bbox.ndim == 1 assert bbox.ndim == 1
...@@ -190,7 +195,7 @@ class BitMapMasks(BaseInstanceMasks): ...@@ -190,7 +195,7 @@ class BitMapMasks(BaseInstanceMasks):
cropped_masks = np.empty((0, h, w), dtype=np.uint8) cropped_masks = np.empty((0, h, w), dtype=np.uint8)
else: else:
cropped_masks = self.masks[:, y1:y1 + h, x1:x1 + w] cropped_masks = self.masks[:, y1:y1 + h, x1:x1 + w]
return BitMapMasks(cropped_masks, h, w) return BitmapMasks(cropped_masks, h, w)
def crop_and_resize(self, def crop_and_resize(self,
bboxes, bboxes,
...@@ -214,7 +219,7 @@ class BitMapMasks(BaseInstanceMasks): ...@@ -214,7 +219,7 @@ class BitMapMasks(BaseInstanceMasks):
""" """
if len(self.masks) == 0: if len(self.masks) == 0:
empty_masks = np.empty((0, *out_shape), dtype=np.uint8) empty_masks = np.empty((0, *out_shape), dtype=np.uint8)
return BitMapMasks(empty_masks, *out_shape) return BitmapMasks(empty_masks, *out_shape)
resized_masks = [] resized_masks = []
for i in range(len(bboxes)): for i in range(len(bboxes)):
...@@ -228,7 +233,7 @@ class BitMapMasks(BaseInstanceMasks): ...@@ -228,7 +233,7 @@ class BitMapMasks(BaseInstanceMasks):
mask[y1:y1 + h, x1:x1 + w], mask[y1:y1 + h, x1:x1 + w],
out_shape, out_shape,
interpolation=interpolation)) interpolation=interpolation))
return BitMapMasks(np.stack(resized_masks), *out_shape) return BitmapMasks(np.stack(resized_masks), *out_shape)
def expand(self, expanded_h, expanded_w, top, left): def expand(self, expanded_h, expanded_w, top, left):
"""see `transforms.Expand`.""" """see `transforms.Expand`."""
...@@ -240,7 +245,16 @@ class BitMapMasks(BaseInstanceMasks): ...@@ -240,7 +245,16 @@ class BitMapMasks(BaseInstanceMasks):
dtype=np.uint8) dtype=np.uint8)
expanded_mask[:, top:top + self.height, expanded_mask[:, top:top + self.height,
left:left + self.width] = self.masks left:left + self.width] = self.masks
return BitMapMasks(expanded_mask, expanded_h, expanded_w) return BitmapMasks(expanded_mask, expanded_h, expanded_w)
@property
def areas(self):
"""Compute area of each instance
Return:
ndarray: areas of each instance
"""
return self.masks.sum((1, 2))
def to_ndarray(self): def to_ndarray(self):
return self.masks return self.masks
...@@ -297,7 +311,7 @@ class PolygonMasks(BaseInstanceMasks): ...@@ -297,7 +311,7 @@ class PolygonMasks(BaseInstanceMasks):
return len(self.masks) return len(self.masks)
def rescale(self, scale, interpolation=None): def rescale(self, scale, interpolation=None):
"""see BitMapMasks.rescale""" """see BitmapMasks.rescale"""
new_w, new_h = mmcv.rescale_size((self.width, self.height), scale) new_w, new_h = mmcv.rescale_size((self.width, self.height), scale)
if len(self.masks) == 0: if len(self.masks) == 0:
rescaled_masks = PolygonMasks([], new_h, new_w) rescaled_masks = PolygonMasks([], new_h, new_w)
...@@ -306,7 +320,7 @@ class PolygonMasks(BaseInstanceMasks): ...@@ -306,7 +320,7 @@ class PolygonMasks(BaseInstanceMasks):
return rescaled_masks return rescaled_masks
def resize(self, out_shape, interpolation=None): def resize(self, out_shape, interpolation=None):
"""see BitMapMasks.resize""" """see BitmapMasks.resize"""
if len(self.masks) == 0: if len(self.masks) == 0:
resized_masks = PolygonMasks([], *out_shape) resized_masks = PolygonMasks([], *out_shape)
else: else:
...@@ -325,7 +339,7 @@ class PolygonMasks(BaseInstanceMasks): ...@@ -325,7 +339,7 @@ class PolygonMasks(BaseInstanceMasks):
return resized_masks return resized_masks
def flip(self, flip_direction='horizontal'): def flip(self, flip_direction='horizontal'):
"""see BitMapMasks.flip""" """see BitmapMasks.flip"""
assert flip_direction in ('horizontal', 'vertical') assert flip_direction in ('horizontal', 'vertical')
if len(self.masks) == 0: if len(self.masks) == 0:
flipped_masks = PolygonMasks([], self.height, self.width) flipped_masks = PolygonMasks([], self.height, self.width)
...@@ -349,7 +363,7 @@ class PolygonMasks(BaseInstanceMasks): ...@@ -349,7 +363,7 @@ class PolygonMasks(BaseInstanceMasks):
return flipped_masks return flipped_masks
def crop(self, bbox): def crop(self, bbox):
"""see BitMapMasks.crop""" """see BitmapMasks.crop"""
assert isinstance(bbox, np.ndarray) assert isinstance(bbox, np.ndarray)
assert bbox.ndim == 1 assert bbox.ndim == 1
...@@ -368,6 +382,7 @@ class PolygonMasks(BaseInstanceMasks): ...@@ -368,6 +382,7 @@ class PolygonMasks(BaseInstanceMasks):
for poly_per_obj in self.masks: for poly_per_obj in self.masks:
cropped_poly_per_obj = [] cropped_poly_per_obj = []
for p in poly_per_obj: for p in poly_per_obj:
# pycocotools will clip the boundary
p = p.copy() p = p.copy()
p[0::2] -= bbox[0] p[0::2] -= bbox[0]
p[1::2] -= bbox[1] p[1::2] -= bbox[1]
...@@ -388,7 +403,7 @@ class PolygonMasks(BaseInstanceMasks): ...@@ -388,7 +403,7 @@ class PolygonMasks(BaseInstanceMasks):
out_shape, out_shape,
inds, inds,
interpolation='bilinear'): interpolation='bilinear'):
"""see BitMapMasks.crop_and_resize""" """see BitmapMasks.crop_and_resize"""
out_h, out_w = out_shape out_h, out_w = out_shape
if len(self.masks) == 0: if len(self.masks) == 0:
return PolygonMasks([], out_h, out_w) return PolygonMasks([], out_h, out_w)
...@@ -407,6 +422,7 @@ class PolygonMasks(BaseInstanceMasks): ...@@ -407,6 +422,7 @@ class PolygonMasks(BaseInstanceMasks):
for p in mask: for p in mask:
p = p.copy() p = p.copy()
# crop # crop
# pycocotools will clip the boundary
p[0::2] -= bbox[0] p[0::2] -= bbox[0]
p[1::2] -= bbox[1] p[1::2] -= bbox[1]
...@@ -420,7 +436,42 @@ class PolygonMasks(BaseInstanceMasks): ...@@ -420,7 +436,42 @@ class PolygonMasks(BaseInstanceMasks):
def to_bitmap(self): def to_bitmap(self):
"""convert polygon masks to bitmap masks""" """convert polygon masks to bitmap masks"""
bitmap_masks = self.to_ndarray() bitmap_masks = self.to_ndarray()
return BitMapMasks(bitmap_masks, self.height, self.width) return BitmapMasks(bitmap_masks, self.height, self.width)
@property
def areas(self):
"""Compute areas of masks.
This func is modified from
https://github.com/facebookresearch/detectron2/blob/ffff8acc35ea88ad1cb1806ab0f00b4c1c5dbfd9/detectron2/structures/masks.py#L387
Only works with Polygons, using the shoelace formula
Return:
ndarray: areas of each instance
""" # noqa: W501
area = []
for polygons_per_obj in self.masks:
area_per_obj = 0
for p in polygons_per_obj:
area_per_obj += self._polygon_area(p[0::2], p[1::2])
area.append(area_per_obj)
return np.asarray(area)
def _polygon_area(self, x, y):
"""Compute the area of a component of a polygon.
Using the shoelace formula:
https://stackoverflow.com/questions/24467972/calculate-area-of-polygon-given-x-y-coordinates
Args:
x (ndarray): x coordinates of the component
y (ndarray): y coordinates of the component
Return:
float: the are of the component
""" # noqa: 501
return 0.5 * np.abs(
np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1)))
def to_ndarray(self): def to_ndarray(self):
if len(self.masks) == 0: if len(self.masks) == 0:
......
...@@ -4,7 +4,7 @@ import mmcv ...@@ -4,7 +4,7 @@ import mmcv
import numpy as np import numpy as np
import pycocotools.mask as maskUtils import pycocotools.mask as maskUtils
from mmdet.core import BitMapMasks, PolygonMasks from mmdet.core import BitmapMasks, PolygonMasks
from ..registry import PIPELINES from ..registry import PIPELINES
...@@ -149,7 +149,7 @@ class LoadAnnotations(object): ...@@ -149,7 +149,7 @@ class LoadAnnotations(object):
h, w = results['img_info']['height'], results['img_info']['width'] h, w = results['img_info']['height'], results['img_info']['width']
gt_masks = results['ann_info']['masks'] gt_masks = results['ann_info']['masks']
if self.poly2mask: if self.poly2mask:
gt_masks = BitMapMasks( gt_masks = BitmapMasks(
[self._poly2mask(mask, h, w) for mask in gt_masks], h, w) [self._poly2mask(mask, h, w) for mask in gt_masks], h, w)
else: else:
gt_masks = PolygonMasks( gt_masks = PolygonMasks(
......
from os.path import dirname, exists, join, relpath from os.path import dirname, exists, join, relpath
from mmdet.core import BitMapMasks, PolygonMasks from mmdet.core import BitmapMasks, PolygonMasks
def _get_config_directory(): def _get_config_directory():
...@@ -98,7 +98,7 @@ def test_config_data_pipeline(): ...@@ -98,7 +98,7 @@ def test_config_data_pipeline():
assert mode in ('polygon', 'bitmap') assert mode in ('polygon', 'bitmap')
if mode == 'bitmap': if mode == 'bitmap':
masks = np.random.randint(0, 2, (num_obj, h, w), dtype=np.uint8) masks = np.random.randint(0, 2, (num_obj, h, w), dtype=np.uint8)
masks = BitMapMasks(masks, h, w) masks = BitmapMasks(masks, h, w)
else: else:
masks = [] masks = []
for i in range(num_obj): for i in range(num_obj):
......
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment