BF: fix conversion of bit images to numpy arrays

Numpy cannot form arrays from bits.  To convert bit images to numpy,
convert bits to bytes.

From suggestion by Alexander Karpinsky, with thanks.

Fixes gh-350.
This commit is contained in:
Matthew Brett 2016-08-07 15:25:43 -07:00
parent f12febf3a9
commit 824a0c232c
3 changed files with 40 additions and 14 deletions

View File

@ -246,7 +246,7 @@ else:
_MODE_CONV = { _MODE_CONV = {
# official modes # official modes
"1": ('|b1', None), # broken "1": ('|b1', None), # Bits need to be extended to bytes
"L": ('|u1', None), "L": ('|u1', None),
"LA": ('|u1', 2), "LA": ('|u1', 2),
"I": (_ENDIAN + 'i4', None), "I": (_ENDIAN + 'i4', None),
@ -615,17 +615,21 @@ class Image(object):
self.save(b, 'PNG') self.save(b, 'PNG')
return b.getvalue() return b.getvalue()
def __getattr__(self, name): @property
if name == "__array_interface__": def __array_interface__(self):
# numpy array interface support # numpy array interface support
new = {} new = {}
shape, typestr = _conv_type_shape(self) shape, typestr = _conv_type_shape(self)
new['shape'] = shape new['shape'] = shape
new['typestr'] = typestr new['typestr'] = typestr
new['data'] = self.tobytes()
new['version'] = 3 new['version'] = 3
if self.mode == '1':
# Binary images need to be extended from bits to bytes
# See: https://github.com/python-pillow/Pillow/issues/350
new['data'] = self.tobytes('raw', 'L')
else:
new['data'] = self.tobytes()
return new return new
raise AttributeError(name)
def __getstate__(self): def __getstate__(self):
return [ return [

View File

@ -25,13 +25,26 @@ class TestImageArray(PillowTestCase):
self.assertEqual(test("RGBX"), (3, (100, 128, 4), '|u1', 51200)) self.assertEqual(test("RGBX"), (3, (100, 128, 4), '|u1', 51200))
def test_fromarray(self): def test_fromarray(self):
class Wrapper(object):
""" Class with API matching Image.fromarray """
def __init__(self, img, arr_params):
self.img = img
self.__array_interface__ = arr_params
def tobytes(self):
return self.img.tobytes()
def test(mode): def test(mode):
i = im.convert(mode) i = im.convert(mode)
a = i.__array_interface__ a = i.__array_interface__
a["strides"] = 1 # pretend it's non-contiguous a["strides"] = 1 # pretend it's non-contigous
i.__array_interface__ = a # patch in new version of attribute # Make wrapper instance for image, new array interface
out = Image.fromarray(i) wrapped = Wrapper(i, a)
out = Image.fromarray(wrapped)
return out.mode, out.size, list(i.getdata()) == list(out.getdata()) return out.mode, out.size, list(i.getdata()) == list(out.getdata())
# self.assertEqual(test("1"), ("1", (128, 100), True)) # self.assertEqual(test("1"), ("1", (128, 100), True))
self.assertEqual(test("L"), ("L", (128, 100), True)) self.assertEqual(test("L"), ("L", (128, 100), True))
self.assertEqual(test("I"), ("I", (128, 100), True)) self.assertEqual(test("I"), ("I", (128, 100), True))

View File

@ -117,6 +117,15 @@ class TestNumpy(PillowTestCase):
self._test_img_equals_nparray(img, np_img) self._test_img_equals_nparray(img, np_img)
self.assertEqual(np_img.dtype, numpy.dtype('<u2')) self.assertEqual(np_img.dtype, numpy.dtype('<u2'))
def test_1bit(self):
# Test that 1-bit arrays convert to numpy and back
# See: https://github.com/python-pillow/Pillow/issues/350
arr = numpy.array([[1, 0, 0, 1, 0], [0, 1, 0, 0, 0]], 'u1')
img = Image.fromarray(arr * 255).convert('1')
self.assertEqual(img.mode, '1')
arr_back = numpy.array(img)
numpy.testing.assert_array_equal(arr, arr_back)
def test_save_tiff_uint16(self): def test_save_tiff_uint16(self):
''' '''
Open a single-channel uint16 greyscale image and verify that it can be saved without Open a single-channel uint16 greyscale image and verify that it can be saved without