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

Rename BitMap to Bitmap, perfect unit test. #2391

Merged
merged 8 commits into from
Apr 6, 2020
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 2 additions & 2 deletions mmdet/core/mask/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from .mask_target import mask_target
from .structures import BitMapMasks, PolygonMasks
from .structures import BitmapMasks, PolygonMasks
from .utils import split_combined_polys

__all__ = [
'split_combined_polys', 'mask_target', 'BitMapMasks', 'PolygonMasks'
'split_combined_polys', 'mask_target', 'BitmapMasks', 'PolygonMasks'
]
80 changes: 59 additions & 21 deletions mmdet/core/mask/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ def crop_and_resize(self,
def expand(self, expanded_h, expanded_w, top, left):
pass

@abstractmethod
def area(self):
Copy link
Member

Choose a reason for hiding this comment

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

  • area -> areas
  • @abstractproperty

pass

@abstractmethod
def to_ndarray(self):
pass
Expand All @@ -49,7 +53,7 @@ def to_tensor(self, dtype, device):
pass


class BitMapMasks(BaseInstanceMasks):
class BitmapMasks(BaseInstanceMasks):
"""This class represents masks in the form of bitmaps.

Args:
Expand Down Expand Up @@ -78,7 +82,7 @@ def __init__(self, masks, height, width):

def __getitem__(self, index):
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):
return iter(self.masks)
Expand All @@ -95,7 +99,7 @@ def rescale(self, scale, interpolation='nearest'):
interpolation (str): same as :func:`mmcv.imrescale`

Returns:
BitMapMasks: the rescaled masks
BitmapMasks: the rescaled masks
"""
if len(self.masks) == 0:
new_w, new_h = mmcv.rescale_size((self.width, self.height), scale)
Expand All @@ -106,7 +110,7 @@ def rescale(self, scale, interpolation='nearest'):
for mask in self.masks
])
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'):
"""Resize masks to the given out_shape.
Expand All @@ -116,7 +120,7 @@ def resize(self, out_shape, interpolation='nearest'):
interpolation (str): see `mmcv.imresize`

Returns:
BitMapMasks: the resized masks
BitmapMasks: the resized masks
"""
if len(self.masks) == 0:
resized_masks = np.empty((0, *out_shape), dtype=np.uint8)
Expand All @@ -125,7 +129,7 @@ def resize(self, out_shape, interpolation='nearest'):
mmcv.imresize(mask, out_shape, interpolation=interpolation)
for mask in self.masks
])
return BitMapMasks(resized_masks, *out_shape)
return BitmapMasks(resized_masks, *out_shape)

def flip(self, flip_direction='horizontal'):
"""flip masks alone the given direction.
Expand All @@ -134,7 +138,7 @@ def flip(self, flip_direction='horizontal'):
flip_direction (str): either 'horizontal' or 'vertical'

Returns:
BitMapMasks: the flipped masks
BitmapMasks: the flipped masks
"""
assert flip_direction in ('horizontal', 'vertical')

Expand All @@ -145,7 +149,7 @@ def flip(self, flip_direction='horizontal'):
mmcv.imflip(mask, direction=flip_direction)
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):
"""Pad masks to the given size of (h, w).
Expand All @@ -155,7 +159,7 @@ def pad(self, out_shape, pad_val=0):
pad_val (int): the padded value

Returns:
BitMapMasks: the padded masks
BitmapMasks: the padded masks
"""
if len(self.masks) == 0:
padded_masks = np.empty((0, *out_shape), dtype=np.uint8)
Expand All @@ -164,7 +168,7 @@ def pad(self, out_shape, pad_val=0):
mmcv.impad(mask, out_shape, pad_val=pad_val)
for mask in self.masks
])
return BitMapMasks(padded_masks, *out_shape)
return BitmapMasks(padded_masks, *out_shape)

def crop(self, bbox):
"""Crop each mask by the given bbox.
Expand All @@ -173,7 +177,7 @@ def crop(self, bbox):
bbox (ndarray): bbox in format [x1, y1, x2, y2], shape (4, )

Return:
BitMapMasks: the cropped masks.
BitmapMasks: the cropped masks.
"""
assert isinstance(bbox, np.ndarray)
assert bbox.ndim == 1
Expand All @@ -190,7 +194,7 @@ def crop(self, bbox):
cropped_masks = np.empty((0, h, w), dtype=np.uint8)
else:
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,
bboxes,
Expand All @@ -214,7 +218,7 @@ def crop_and_resize(self,
"""
if len(self.masks) == 0:
empty_masks = np.empty((0, *out_shape), dtype=np.uint8)
return BitMapMasks(empty_masks, *out_shape)
return BitmapMasks(empty_masks, *out_shape)

resized_masks = []
for i in range(len(bboxes)):
Expand All @@ -228,7 +232,7 @@ def crop_and_resize(self,
mask[y1:y1 + h, x1:x1 + w],
out_shape,
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):
"""see `transforms.Expand`."""
Expand All @@ -240,7 +244,15 @@ def expand(self, expanded_h, expanded_w, top, left):
dtype=np.uint8)
expanded_mask[:, top:top + self.height,
left:left + self.width] = self.masks
return BitMapMasks(expanded_mask, expanded_h, expanded_w)
return BitmapMasks(expanded_mask, expanded_h, expanded_w)

def area(self):
"""Compute area of each instance

Return:
ndarray: areas of each instance
"""
return self.masks.sum((1, 2))

def to_ndarray(self):
return self.masks
Expand Down Expand Up @@ -297,7 +309,7 @@ def __len__(self):
return len(self.masks)

def rescale(self, scale, interpolation=None):
"""see BitMapMasks.rescale"""
"""see BitmapMasks.rescale"""
new_w, new_h = mmcv.rescale_size((self.width, self.height), scale)
if len(self.masks) == 0:
rescaled_masks = PolygonMasks([], new_h, new_w)
Expand All @@ -306,7 +318,7 @@ def rescale(self, scale, interpolation=None):
return rescaled_masks

def resize(self, out_shape, interpolation=None):
"""see BitMapMasks.resize"""
"""see BitmapMasks.resize"""
if len(self.masks) == 0:
resized_masks = PolygonMasks([], *out_shape)
else:
Expand All @@ -325,7 +337,7 @@ def resize(self, out_shape, interpolation=None):
return resized_masks

def flip(self, flip_direction='horizontal'):
"""see BitMapMasks.flip"""
"""see BitmapMasks.flip"""
assert flip_direction in ('horizontal', 'vertical')
if len(self.masks) == 0:
flipped_masks = PolygonMasks([], self.height, self.width)
Expand All @@ -349,7 +361,7 @@ def flip(self, flip_direction='horizontal'):
return flipped_masks

def crop(self, bbox):
"""see BitMapMasks.crop"""
"""see BitmapMasks.crop"""
assert isinstance(bbox, np.ndarray)
assert bbox.ndim == 1

Expand All @@ -368,6 +380,7 @@ def crop(self, bbox):
for poly_per_obj in self.masks:
cropped_poly_per_obj = []
for p in poly_per_obj:
# pycocotools will clip the boundary
p = p.copy()
p[0::2] -= bbox[0]
p[1::2] -= bbox[1]
Expand All @@ -388,7 +401,7 @@ def crop_and_resize(self,
out_shape,
inds,
interpolation='bilinear'):
"""see BitMapMasks.crop_and_resize"""
"""see BitmapMasks.crop_and_resize"""
out_h, out_w = out_shape
if len(self.masks) == 0:
return PolygonMasks([], out_h, out_w)
Expand All @@ -407,6 +420,7 @@ def crop_and_resize(self,
for p in mask:
p = p.copy()
# crop
# pycocotools will clip the boundary
p[0::2] -= bbox[0]
p[1::2] -= bbox[1]

Expand All @@ -420,7 +434,24 @@ def crop_and_resize(self,
def to_bitmap(self):
"""convert polygon masks to bitmap masks"""
bitmap_masks = self.to_ndarray()
return BitMapMasks(bitmap_masks, self.height, self.width)
return BitmapMasks(bitmap_masks, self.height, self.width)

def area(self):
""" Compute area of masks using the shoelace formula
Copy link
Member

Choose a reason for hiding this comment

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

Add a blank line between the summary and description.

https://stackoverflow.com/questions/24467972/calculate-area-of-polygon-given-x-y-coordinates
This func is modified from
https://github.com/facebookresearch/detectron2/blob/ffff8acc35ea88ad1cb1806ab0f00b4c1c5dbfd9/detectron2/structures/masks.py#L387

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 += polygon_area(p[0::2], p[1::2])
area.append(area_per_obj)
return np.asarray(area)

def to_ndarray(self):
if len(self.masks) == 0:
Expand Down Expand Up @@ -455,3 +486,10 @@ def polygon_to_bitmap(polygons, height, width):
rle = maskUtils.merge(rles)
bitmap_mask = maskUtils.decode(rle).astype(np.bool)
return bitmap_mask


def polygon_area(x, y):
Copy link
Member

Choose a reason for hiding this comment

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

Make it a private method of PolygonMasks.

"""Using the shoelace formula
https://stackoverflow.com/questions/24467972/calculate-area-of-polygon-given-x-y-coordinates
""" # noqa: 501
return 0.5 * np.abs(np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1)))
4 changes: 2 additions & 2 deletions mmdet/datasets/pipelines/loading.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import numpy as np
import pycocotools.mask as maskUtils

from mmdet.core import BitMapMasks, PolygonMasks
from mmdet.core import BitmapMasks, PolygonMasks
from ..registry import PIPELINES


Expand Down Expand Up @@ -149,7 +149,7 @@ def _load_masks(self, results):
h, w = results['img_info']['height'], results['img_info']['width']
gt_masks = results['ann_info']['masks']
if self.poly2mask:
gt_masks = BitMapMasks(
gt_masks = BitmapMasks(
[self._poly2mask(mask, h, w) for mask in gt_masks], h, w)
else:
gt_masks = PolygonMasks(
Expand Down
4 changes: 2 additions & 2 deletions tests/test_config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from os.path import dirname, exists, join, relpath

from mmdet.core import BitMapMasks, PolygonMasks
from mmdet.core import BitmapMasks, PolygonMasks


def _get_config_directory():
Expand Down Expand Up @@ -98,7 +98,7 @@ def dummy_masks(h, w, num_obj=3, mode='bitmap'):
assert mode in ('polygon', 'bitmap')
if mode == 'bitmap':
masks = np.random.randint(0, 2, (num_obj, h, w), dtype=np.uint8)
masks = BitMapMasks(masks, h, w)
masks = BitmapMasks(masks, h, w)
else:
masks = []
for i in range(num_obj):
Expand Down
Loading