Skip to content

Commit e1cbec6

Browse files
committed
Working decoder
1 parent 794d143 commit e1cbec6

File tree

3 files changed

+87
-6
lines changed

3 files changed

+87
-6
lines changed

labelbox/lbx.py

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,35 @@
1-
"Module for interacting with the LBX file format."
1+
"""Module for interacting with the LBX file format.
2+
3+
LBX File Format - lossless compression for segmented images
4+
5+
HEADERS
6+
bytes 0-3 -> LBX version
7+
bytes 4-7 -> pixel width
8+
bytes 8-11 -> pixel height
9+
bytes 12-15 -> decompressed bytes
10+
bytes 16-19 -> # colors
11+
bytes 20-23 -> # blocks
12+
13+
BODY
14+
- COLORS -
15+
- BLOCKS -
16+
17+
COLOR
18+
byte 0 -> red
19+
byte 1 -> blue
20+
byte 2 -> green
21+
byte 3 -> alpha (always 255)
22+
23+
BLOCK
24+
bytes 0-1 -> R | G value of pixel (superpixel layer #)
25+
bytes 2-3 -> # consecutive occurences
26+
"""
27+
import itertools
28+
import struct
29+
30+
import numpy as np
31+
from PIL import Image
32+
233

334
def encode(image):
435
"""Converts a `PIL.Image` to a `io.BytesIO` with LBX encoded data.
@@ -11,6 +42,7 @@ def encode(image):
1142
"""
1243
return image
1344

45+
1446
def decode(lbx):
1547
"""Decodes a `io.BytesIO` with LBX encoded data into a `PIL.Image`.
1648
@@ -20,4 +52,33 @@ def decode(lbx):
2052
Returns:
2153
A `PIL.Image` of the decoded data.
2254
"""
23-
return lbx
55+
version, width, height, byteLength, numColors, numBlocks = \
56+
map(lambda x: x[0], struct.iter_unpack('<i', lbx.read(24)))
57+
colormap = np.array(
58+
list(_grouper(
59+
map(lambda x: x[0],
60+
struct.iter_unpack('<B', lbx.read(4 * numColors))),
61+
4)) +
62+
[[0, 0, 0, 255]]
63+
)
64+
65+
image_data = np.zeros((width * height, 4), dtype='uint8')
66+
offset = 0
67+
for _ in range(numBlocks):
68+
layer = struct.unpack('<H', lbx.read(2))[0]
69+
run_length = struct.unpack('<H', lbx.read(2))[0]
70+
image_data[offset:offset + run_length, :] = colormap[layer]
71+
offset += run_length
72+
assert offset == width * height, 'number of bytes read does not equal numBytes in header'
73+
74+
reshaped_image = np.reshape(image_data, (height, width, 4))
75+
im = Image.fromarray(reshaped_image, mode='RGBA')
76+
return im
77+
78+
79+
def _grouper(iterable, n, fillvalue=None):
80+
"Collect data into fixed-length chunks or blocks"
81+
# grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
82+
args = [iter(iterable)] * n
83+
return itertools.zip_longest(*args, fillvalue=fillvalue)
84+

tests/test_lbx.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,31 @@
11
from io import BytesIO
22

33
import labelbox.lbx as lbx
4-
import numpy as np
54
from PIL import Image
5+
import pytest
66

7-
def test_identity(datadir):
7+
@pytest.fixture
8+
def im_png(datadir):
89
with open(datadir.join('PNG_transparency_demonstration_2.png'), 'rb') as f:
9-
im = Image.open(BytesIO(f.read()))
10-
assert np.all(np.array(lbx.decode(lbx.encode(im))) == np.array(im))
10+
yield Image.open(BytesIO(f.read()))
11+
12+
@pytest.fixture
13+
def lbx_sample(datadir):
14+
with open(datadir.join('sample.lbx'), 'rb') as f:
15+
yield f
16+
17+
def test_lbx_decode(lbx_sample):
18+
im = lbx.decode(lbx_sample)
19+
assert im.size == (500, 375)
20+
21+
def test_lbx_encode(im_png):
22+
lbx_encoded = lbx.encode(im_png)
23+
version, width, height = map(lambda x: x[0], struct.iter_unpack('<i', lbx.read(12)))
24+
assert version == 1
25+
assert width == 800
26+
assert height == 600
27+
28+
29+
# def test_identity(im_png):
30+
# assert np.all(np.array(lbx.decode(lbx.encode(im_png))) == np.array(im_png))
1131

tests/test_lbx/sample.lbx

5.47 KB
Binary file not shown.

0 commit comments

Comments
 (0)