Skip to content

Commit 1a275e7

Browse files
authored
Merge pull request #235 from Labelbox/ms/consistent-drawing-logic
update drawing logic
2 parents e1d29db + 5bf1691 commit 1a275e7

File tree

11 files changed

+64
-49
lines changed

11 files changed

+64
-49
lines changed

examples/label_export/images.ipynb

Lines changed: 5 additions & 14 deletions
Large diffs are not rendered by default.

labelbox/data/annotation_types/data/raster.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ def bytes_to_np(self, image_bytes: bytes) -> np.ndarray:
3838
Returns:
3939
numpy array representing the image
4040
"""
41-
return np.array(Image.open(BytesIO(image_bytes)))[:, :, :3]
41+
arr = np.array(Image.open(BytesIO(image_bytes)))
42+
if len(arr.shape) == 2:
43+
arr = np.stack((arr,) * 3, axis=-1)
44+
return arr[:, :, :3]
4245

4346
def np_to_bytes(self, arr: np.ndarray) -> bytes:
4447
"""

labelbox/data/annotation_types/geometry/geometry.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Dict, Any, Optional, Union
1+
from typing import Dict, Any, Optional, Union, Tuple
22

33
import geojson
44
import numpy as np
@@ -23,7 +23,12 @@ def shapely(
2323
geom.MultiLineString, geom.MultiPolygon]:
2424
return geom.shape(self.geometry)
2525

26-
def raster(self, *args, **kwargs) -> np.ndarray:
26+
def raster(self,
27+
height: Optional[int] = None,
28+
width: Optional[int] = None,
29+
canvas: Optional[np.ndarray] = None,
30+
color: Optional[Union[int, Tuple[int, int, int]]] = None,
31+
thickness: Optional[int] = 1) -> np.ndarray:
2732
raise NotImplementedError("Subclass must override this")
2833

2934
def get_or_create_canvas(self, height: Optional[int], width: Optional[int],

labelbox/data/annotation_types/geometry/line.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ def geometry(self) -> geojson.MultiLineString:
1919
def raster(self,
2020
height: Optional[int] = None,
2121
width: Optional[int] = None,
22-
thickness: int = 1,
23-
color: Union[Tuple, int] = (255, 255, 255),
24-
canvas: Optional[np.ndarray] = None) -> np.ndarray:
22+
canvas: Optional[np.ndarray] = None,
23+
color: Union[int, Tuple[int, int, int]] = (255, 255, 255),
24+
thickness: int = 1) -> np.ndarray:
2525
"""
2626
Draw the line onto a 3d mask
2727
Args:

labelbox/data/annotation_types/geometry/mask.py

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class Mask(Geometry):
1818

1919
@property
2020
def geometry(self):
21-
mask = self.raster(binary=True)
21+
mask = self.raster(color=1)
2222
polygons = (
2323
shape(shp)
2424
for shp, val in shapes(mask, mask=None)
@@ -29,28 +29,45 @@ def geometry(self):
2929
def raster(self,
3030
height: Optional[int] = None,
3131
width: Optional[int] = None,
32-
binary=False) -> np.ndarray:
32+
canvas: Optional[np.ndarray] = None,
33+
color: Optional[Union[int, Tuple[int, int, int]]] = None,
34+
thickness=None) -> np.ndarray:
3335
"""
36+
# TODO: Optionally use the color. a color of 1 will result in a binary canvas
37+
38+
3439
Removes all pixels from the segmentation mask that do not equal self.color
3540
3641
Args:
37-
height:
38-
42+
height (int): Optionally resize mask height before drawing.
43+
width (int): Optionally resize mask width before drawing.
44+
canvas (np.ndarray): Optionall provide a canvas to draw on
45+
color (Union[int, Tuple[int,int,int]]): Color to draw the canvas.
46+
Defaults to using the encoded color in the mask.
47+
int will return the mask as a 1d array
48+
tuple[int,int,int] will return the mask as a 3d array
49+
thickness (None): Unused, exists for a consistent interface.
3950
Returns:
4051
np.ndarray representing only this object
52+
as opposed to the mask that this object references which might have multiple objects determined by colors
4153
"""
54+
4255
mask = self.mask.value
4356
mask = np.alltrue(mask == self.color, axis=2).astype(np.uint8)
57+
4458
if height is not None or width is not None:
4559
mask = cv2.resize(mask,
4660
(width or mask.shape[1], height or mask.shape[0]))
47-
if binary:
48-
return mask
49-
else:
50-
color_image = np.zeros((mask.shape[0], mask.shape[1], 3),
51-
dtype=np.uint8)
52-
color_image[mask.astype(np.bool)] = self.color
53-
return color_image
61+
62+
dims = [mask.shape[0], mask.shape[1]]
63+
color = color or self.color
64+
if isinstance(color, (tuple, list)):
65+
dims = dims + [len(color)]
66+
67+
canvas = canvas if canvas is not None else np.zeros(tuple(dims),
68+
dtype=np.uint8)
69+
canvas[mask.astype(np.bool)] = color
70+
return canvas
5471

5572
def create_url(self, signer: Callable[[bytes], str]) -> str:
5673
"""

labelbox/data/annotation_types/geometry/point.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ def geometry(self) -> geojson.Point:
1818
def raster(self,
1919
height: Optional[int] = None,
2020
width: Optional[int] = None,
21-
thickness: int = 1,
22-
color: Union[Tuple, int] = (255, 255, 255),
23-
canvas: Optional[np.ndarray] = None) -> np.ndarray:
21+
canvas: Optional[np.ndarray] = None,
22+
color: Union[int, Tuple[int, int, int]] = (255, 255, 255),
23+
thickness: int = 10) -> np.ndarray:
2424
"""
2525
Draw the point onto a 3d mask
2626
Args:
@@ -37,4 +37,4 @@ def raster(self,
3737
return cv2.circle(canvas, (int(self.x), int(self.y)),
3838
radius=thickness,
3939
color=color,
40-
thickness=-1)
40+
thickness=1)

labelbox/data/annotation_types/geometry/polygon.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ def geometry(self) -> geojson.MultiPolygon:
2121
def raster(self,
2222
height: Optional[int] = None,
2323
width: Optional[int] = None,
24-
color: Union[int, Tuple] = (255, 255, 255),
25-
thickness: int = -1,
26-
canvas: Optional[np.ndarray] = None) -> np.ndarray:
24+
canvas: Optional[np.ndarray] = None,
25+
color: Union[int, Tuple[int, int, int]] = (255, 255, 255),
26+
thickness: int = -1) -> np.ndarray:
2727
"""
2828
Draw the polygon onto a 3d mask
2929
Args:

labelbox/data/annotation_types/geometry/rectangle.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ def geometry(self) -> geojson.geometry.Geometry:
3131
def raster(self,
3232
height: Optional[int] = None,
3333
width: Optional[int] = None,
34-
color: Union[int, Tuple] = (255, 255, 255),
35-
thickness: int = -1,
36-
canvas: Optional[np.ndarray] = None) -> np.ndarray:
34+
canvas: Optional[np.ndarray] = None,
35+
color: Union[int, Tuple[int, int, int]] = (255, 255, 255),
36+
thickness: int = -1) -> np.ndarray:
3737
"""
3838
Draw the rectangle onto a 3d mask
3939
Args:

labelbox/data/metrics/iou.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,11 @@ def mask_miou(ground_truths: List[ObjectAnnotation],
128128
Returns:
129129
float representing the iou score for the masks
130130
"""
131-
prediction_np = np.max(
132-
[pred.value.raster(binary=True) for pred in predictions], axis=0)
133-
ground_truth_np = np.max([
134-
ground_truth.value.raster(binary=True) for ground_truth in ground_truths
135-
],
136-
axis=0)
131+
prediction_np = np.max([pred.value.raster(color=1) for pred in predictions],
132+
axis=0)
133+
ground_truth_np = np.max(
134+
[ground_truth.value.raster(color=1) for ground_truth in ground_truths],
135+
axis=0)
137136
if prediction_np.shape != ground_truth_np.shape:
138137
raise ValueError(
139138
"Prediction and mask must have the same shape."

tests/data/annotation_types/geometry/test_line.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@ def test_line():
1919
expected['coordinates'] = tuple([tuple([tuple(x) for x in points])])
2020
assert line.shapely.__geo_interface__ == expected
2121

22-
raster = line.raster(height=32, width=32)
22+
raster = line.raster(height=32, width=32, thickness = 1)
2323
assert (cv2.imread("tests/data/assets/line.png") == raster).all()

0 commit comments

Comments
 (0)