Optimize mask conversions (#1097)

main
zhiltsov-max 6 years ago committed by Nikita Manovich
parent 93b3c091f5
commit 04c7669cf5

@ -20,7 +20,7 @@ from datumaro.components.formats.voc import (VocTask, VocPath,
parse_label_map, make_voc_label_map, make_voc_categories, write_label_map parse_label_map, make_voc_label_map, make_voc_categories, write_label_map
) )
from datumaro.util.image import save_image from datumaro.util.image import save_image
from datumaro.util.mask_tools import apply_colormap, remap_mask from datumaro.util.mask_tools import paint_mask, remap_mask
def _convert_attr(name, attributes, type_conv, default=None, warn=True): def _convert_attr(name, attributes, type_conv, default=None, warn=True):
@ -367,7 +367,7 @@ class _Converter:
if colormap is None: if colormap is None:
colormap = self._categories[AnnotationType.mask].colormap colormap = self._categories[AnnotationType.mask].colormap
data = self._remap_mask(data) data = self._remap_mask(data)
data = apply_colormap(data, colormap) data = paint_mask(data, colormap)
save_image(path, data) save_image(path, data)
def save_label_map(self): def save_label_map(self):

@ -30,65 +30,79 @@ def invert_colormap(colormap):
tuple(a): index for index, a in colormap.items() tuple(a): index for index, a in colormap.items()
} }
def check_is_mask(mask):
assert len(mask.shape) in {2, 3}
if len(mask.shape) == 3:
assert mask.shape[2] == 1
_default_colormap = generate_colormap() _default_colormap = generate_colormap()
_default_unpaint_colormap = invert_colormap(_default_colormap) _default_unpaint_colormap = invert_colormap(_default_colormap)
def _default_unpaint_colormap_fn(r, g, b): def unpaint_mask(painted_mask, inverse_colormap=None):
return _default_unpaint_colormap[(r, g, b)] # Covert color mask to index mask
def unpaint_mask(painted_mask, colormap=None): # mask: HWC BGR [0; 255]
# expect HWC BGR [0; 255] image # colormap: (R, G, B) -> index
# expect RGB->index colormap
assert len(painted_mask.shape) == 3 assert len(painted_mask.shape) == 3
if colormap is None: if inverse_colormap is None:
colormap = _default_unpaint_colormap_fn inverse_colormap = _default_unpaint_colormap
if callable(colormap):
map_fn = lambda a: colormap(int(a[2]), int(a[1]), int(a[0])) if callable(inverse_colormap):
map_fn = lambda a: inverse_colormap(
(a >> 16) & 255, (a >> 8) & 255, a & 255
)
else: else:
map_fn = lambda a: colormap[(int(a[2]), int(a[1]), int(a[0]))] map_fn = lambda a: inverse_colormap[(
(a >> 16) & 255, (a >> 8) & 255, a & 255
)]
unpainted_mask = np.apply_along_axis(map_fn, painted_mask = painted_mask.astype(int)
1, np.reshape(painted_mask, (-1, 3))) painted_mask = painted_mask[:, :, 0] + \
unpainted_mask = np.reshape(unpainted_mask, (painted_mask.shape[:2])) (painted_mask[:, :, 1] << 8) + \
return unpainted_mask.astype(int) (painted_mask[:, :, 2] << 16)
uvals, unpainted_mask = np.unique(painted_mask, return_inverse=True)
palette = np.array([map_fn(v) for v in uvals], dtype=np.float32)
unpainted_mask = palette[unpainted_mask].reshape(painted_mask.shape[:2])
return unpainted_mask
def apply_colormap(mask, colormap=None): def paint_mask(mask, colormap=None):
# expect HW [0; max_index] mask # Applies colormap to index mask
# expect index->RGB colormap
assert len(mask.shape) == 2 # mask: HW(C) [0; max_index] mask
# colormap: index -> (R, G, B)
check_is_mask(mask)
if colormap is None: if colormap is None:
colormap = _default_colormap colormap = _default_colormap
if callable(colormap): if callable(colormap):
map_fn = lambda p: colormap(int(p[0]))[::-1] map_fn = colormap
else: else:
map_fn = lambda p: colormap[int(p[0])][::-1] map_fn = lambda c: colormap.get(c, (-1, -1, -1))
painted_mask = np.apply_along_axis(map_fn, 1, np.reshape(mask, (-1, 1))) palette = np.array([map_fn(c)[::-1] for c in range(256)], dtype=np.float32)
painted_mask = np.reshape(painted_mask, (*mask.shape, 3)) mask = mask.astype(np.uint8)
return painted_mask.astype(np.float32) painted_mask = palette[mask].reshape((*mask.shape[:2], 3))
return painted_mask
def remap_mask(mask, map_fn): def remap_mask(mask, map_fn):
# Changes mask elements from one colormap to another # Changes mask elements from one colormap to another
assert len(mask.shape) == 2
shape = mask.shape # mask: HW(C) [0; max_index] mask
mask = np.reshape(mask, (-1, 1)) check_is_mask(mask)
mask = np.apply_along_axis(map_fn, 1, mask)
mask = np.reshape(mask, shape) return np.array([map_fn(c) for c in range(256)], dtype=np.uint8)[mask]
return mask
def load_mask(path, colormap=None): def load_mask(path, inverse_colormap=None):
mask = load_image(path) mask = load_image(path)
if colormap is not None: if inverse_colormap is not None:
if len(mask.shape) == 3 and mask.shape[2] != 1: if len(mask.shape) == 3 and mask.shape[2] != 1:
mask = unpaint_mask(mask, colormap=colormap) mask = unpaint_mask(mask, inverse_colormap)
return mask return mask
def lazy_mask(path, colormap=None): def lazy_mask(path, inverse_colormap=None):
return lazy_image(path, lambda path: load_mask(path, colormap)) return lazy_image(path, lambda path: load_mask(path, inverse_colormap))
def mask_to_rle(binary_mask): def mask_to_rle(binary_mask):

@ -67,3 +67,58 @@ class PolygonConversionsTest(TestCase):
for i, (e_mask, c_mask) in enumerate(zip(expected, computed)): for i, (e_mask, c_mask) in enumerate(zip(expected, computed)):
self.assertTrue(np.array_equal(e_mask, c_mask), self.assertTrue(np.array_equal(e_mask, c_mask),
'#%s: %s\n%s\n' % (i, e_mask, c_mask)) '#%s: %s\n%s\n' % (i, e_mask, c_mask))
class ColormapOperationsTest(TestCase):
def test_can_paint_mask(self):
mask = np.zeros((1, 3), dtype=np.uint8)
mask[:, 0] = 0
mask[:, 1] = 1
mask[:, 2] = 2
colormap = mask_tools.generate_colormap(3)
expected = np.zeros((*mask.shape, 3), dtype=np.uint8)
expected[:, 0] = colormap[0][::-1]
expected[:, 1] = colormap[1][::-1]
expected[:, 2] = colormap[2][::-1]
actual = mask_tools.paint_mask(mask, colormap)
self.assertTrue(np.array_equal(expected, actual),
'%s\nvs.\n%s' % (expected, actual))
def test_can_unpaint_mask(self):
colormap = mask_tools.generate_colormap(3)
inverse_colormap = mask_tools.invert_colormap(colormap)
mask = np.zeros((1, 3, 3), dtype=np.uint8)
mask[:, 0] = colormap[0][::-1]
mask[:, 1] = colormap[1][::-1]
mask[:, 2] = colormap[2][::-1]
expected = np.zeros((1, 3), dtype=np.uint8)
expected[:, 0] = 0
expected[:, 1] = 1
expected[:, 2] = 2
actual = mask_tools.unpaint_mask(mask, inverse_colormap)
self.assertTrue(np.array_equal(expected, actual),
'%s\nvs.\n%s' % (expected, actual))
def test_can_remap_mask(self):
class_count = 10
remap_fn = lambda c: class_count - c
src = np.empty((class_count, class_count), dtype=np.uint8)
for c in range(class_count):
src[c:, c:] = c
expected = np.empty_like(src)
for c in range(class_count):
expected[c:, c:] = remap_fn(c)
actual = mask_tools.remap_mask(src, remap_fn)
self.assertTrue(np.array_equal(expected, actual),
'%s\nvs.\n%s' % (expected, actual))
Loading…
Cancel
Save