Skip to content

Commit 3109422

Browse files
committed
Fix background handling
1 parent 63deabd commit 3109422

File tree

3 files changed

+40
-30
lines changed

3 files changed

+40
-30
lines changed

labelbox/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"The Labelbox python package."
22

3-
__version__ = '0.0.5.dev1'
3+
__version__ = '0.0.5'

labelbox/lbx.py

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,36 @@
3333
import numpy as np
3434
from PIL import Image
3535

36+
# NOTE: image-segmentation front-end requires background pixels to be white to
37+
# render them transparent
3638
BACKGROUND_RGBA = np.array([255, 255, 255, 255], dtype=np.uint8)
3739
BACKGROUND_RGBA.flags.writeable = False
40+
3841
_HEADER_LENGTH = 6 * 4
3942

4043

41-
def encode(image_in: Image, colormap: List[np.array]):
42-
"""Converts a RGB `Image` to a `io.BytesIO` with LBX encoded data.
44+
def encode(image_in: Image, colormap: List[np.array]) -> BytesIO:
45+
"""Converts a RGB `Image` representing a segmentation map into the LBX data format.
46+
47+
Given a segmentation map representing an image, convert it to the LBX format.
48+
Background pixels should be represented using the `BACKGROUND_RGBA` RGBA value.
4349
4450
Args:
4551
image_in: The image to encode.
46-
colormap: Ordered list of `np.array`s each of length 3 representing
47-
a RGB color. The ordering of this list determines which colors
48-
map to which class labels in the project ontology.
52+
colormap: Ordered list of `np.array`s each of length 3 representing a
53+
RGB color. Do not include `BACKGROUND_RGBA`; it will be automatically
54+
accounted for. Every pixel in `image_in` must match some entry in this
55+
list. The ordering of this list determines which colors map to which
56+
class labels in the project ontology.
4957
5058
Returns:
51-
A `io.BytesIO` containing the LBX encoded image.
59+
The LBX encoded bytes.
5260
"""
5361
image = image_in.convert('RGBA')
5462
pixel_words = np.array(image).reshape(-1, 4)
5563
pixel_words.flags.writeable = False
5664

57-
colormap = [BACKGROUND_RGBA] + \
58-
list(map(lambda color: np.append(color, 255).astype(np.uint8), colormap))
65+
colormap = list(map(lambda color: np.append(color, 255).astype(np.uint8), colormap))
5966

6067
input_byte_len = len(np.array(image).flat)
6168
buff = BytesIO(bytes([0] * (len(colormap) * 4 + input_byte_len)))
@@ -66,12 +73,12 @@ def _color_to_key(color):
6673
return hash(color.tostring())
6774

6875
color_dict = dict()
76+
color_dict[_color_to_key(BACKGROUND_RGBA)] = 0 # manually add bg
6977
for i, color in enumerate(colormap):
7078
color.flags.writeable = False
7179
struct.pack_into('<BBBB', buff.getbuffer(), offset, *color)
72-
color_dict[_color_to_key(color)] = i
80+
color_dict[_color_to_key(color)] = i+1
7381
offset += 4
74-
# color_dict[_color_to_key(BACKGROUND_RGBA)] = len(colormap)
7582

7683
count = 0
7784
pixel_ints = np.apply_along_axis(_color_to_key, 1, pixel_words)
@@ -84,9 +91,11 @@ def _color_to_key(color):
8491
struct.pack_into('<HH', buff.getbuffer(), offset, color_dict[pixel_int], count)
8592
offset += 4
8693
count = 0
87-
except KeyError as e:
88-
logging.error('Could not find color {} in colormap'.format(pixel_words[i]))
89-
raise e
94+
except KeyError as exc:
95+
logging.error('Could not find color %s in colormap', pixel_words[i])
96+
if np.all(pixel_words[i] == np.array([0, 0, 0, 0])):
97+
logging.error('Did you remember to set background pixels to `BACKGROUND_RGBA`?')
98+
raise exc
9099

91100
# write header
92101
struct.pack_into(
@@ -97,35 +106,31 @@ def _color_to_key(color):
97106
return buff
98107

99108

100-
def decode(lbx: BytesIO):
101-
"""Decodes a `BytesIO` with LBX encoded data into a `PIL.Image`.
109+
def decode(lbx: BytesIO) -> Image:
110+
"""Decodes LBX encoded byte data into an image.
102111
103112
Args:
104113
lbx: A byte buffer containing the LBX encoded image data.
105114
106115
Returns:
107-
A `PIL.Image` of the decoded data.
116+
A RGBA image of the decoded data.
108117
"""
109-
version, width, height, byte_length, num_colors, num_blocks = \
110-
map(lambda x: x[0], struct.iter_unpack('<i', lbx.read(_HEADER_LENGTH)))
111-
assert version == 1, 'this method only supports LBX v1 format'
118+
version, width, height, byte_length, num_colors, num_blocks =\
119+
struct.unpack('<' + 'i'*int(_HEADER_LENGTH/4), lbx.read(_HEADER_LENGTH))
120+
assert version == 1, 'only LBX v1 format is supported'
112121

113122
colormap = np.array(
114-
list(_grouper(
115-
map(lambda x: x[0],
116-
struct.iter_unpack('<B', lbx.read(4 * num_colors))),
117-
4)) +
118-
[BACKGROUND_RGBA])
123+
[BACKGROUND_RGBA] +
124+
list(_grouper(struct.unpack('<' + 'B'*4*num_colors, lbx.read(4 * num_colors)), 4)))
119125

120126
image_data = np.zeros((width * height, 4), dtype='uint8')
121127
offset = 0
122128
for _ in range(num_blocks):
123-
layer = struct.unpack('<H', lbx.read(2))[0]
124-
run_length = struct.unpack('<H', lbx.read(2))[0]
129+
layer, run_length = struct.unpack('<HH', lbx.read(4))
125130
image_data[offset:offset + run_length, :] = colormap[layer]
126131
offset += run_length
127132
assert 4*offset == byte_length, \
128-
'number of bytes read does not equal numBytes in header'
133+
'number of bytes read does not equal num bytes specified in header'
129134

130135
reshaped_image = np.reshape(image_data, (height, width, 4))
131136
return Image.fromarray(reshaped_image, mode='RGBA')

tests/test_lbx.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@
1010
@pytest.fixture
1111
def im_png(datadir):
1212
with open(datadir.join('sample.png'), 'rb') as f:
13-
yield Image.open(BytesIO(f.read()))
13+
image = np.array(Image.open(BytesIO(f.read())))
14+
15+
# convert black to BG
16+
image[np.apply_along_axis(np.all, 2, image[:, :, :3] == [0, 0, 0])] = [255, 255, 255, 255]
17+
image = Image.fromarray(image)
18+
yield image
1419

1520

1621
@pytest.fixture
@@ -30,7 +35,7 @@ def test_lbx_encode(im_png):
3035
np.array([0, 128, 0]),
3136
]
3237
lbx_encoded = lbx.encode(im_png, colormap)
33-
version, width, height = map(lambda x: x[0], struct.iter_unpack('<i', lbx_encoded.read(12)))
38+
version, width, height = struct.unpack('<iii', lbx_encoded.read(12))
3439
assert version == 1
3540
assert width == 500
3641
assert height == 375

0 commit comments

Comments
 (0)