ph0t0n ph0t0n - 6 months ago 53
Python Question

Python PIL struggles with uncompressed 16-bit TIFF images

My system is Mac OS X v10.8.2. I have several 2560x500 uncompressed 16-bit TIFF images (grayscale, unsigned 16-bit integers). I first attempt to load them using PIL (installed via Homebrew, version 1.7.8):

from PIL import Image
import numpy as np

filename = 'Rocks_2ptCal_750KHz_20ms_1ma_120KV_2013-03-06_20-02-12.tif'
img = Image.open(filename)

# >>> img
# <PIL.TiffImagePlugin.TiffImageFile image mode=I;16B size=2560x500 at 0x10A383C68>

img.show()

# almost all pixels displayed as white. Not correct.
# MatLab, EZ-draw, even Mac Preview show correct images in grayscale.

imgdata = list(img.getdata())

# most values negative:
# >>> imgdata[0:10]
# [-26588, -24079, -27822, -26045, -27245, -25368, -26139, -28454, -30675, -28455]

imgarray = np.asarray(imgdata, dtype=np.uint16)

# values now correct
# >>> imgarray
# array([38948, 41457, 37714, ..., 61922, 59565, 60035], dtype=uint16)


The negative values are off by 65,536... probably not a coincidence.

If I pretend to alter pixels and revert back to TIFF image via PIL (by just putting the array back as an image):

newimg = Image.fromarray(imgarray)


I get errors:

File "/usr/local/lib/python2.7/site-packages/PIL/Image.py", line 1884, in fromarray
raise TypeError("Cannot handle this data type")
TypeError: Cannot handle this data type


I can't find
Image.fromarray()
in the PIL documentation. I've tried loading via
Image.fromstring()
, but I don't understand the PIL documentation and there is little in the way of example.

As shown in the code above, PIL seems to "detect" the data as
I;16B
. From what I can tell from the PIL docs, mode
I
is:

*I* (32-bit signed integer pixels)


Obviously, that is not correct.

I find many posts on SX suggesting that PIL doesn't support 16-bit images. I've found suggestions to use pylibtiff, but I believe that is Windows only?

I am looking for a "lightweight" way to work with these TIFF images in Python. I'm surprised it is this difficult and that leads me to believe the problem will be obvious to others.

Answer

It turns out that Matplotlib handles 16-bit uncompressed TIFF images in two lines of code:

import matplotlib.pyplot as plt
img = plt.imread(filename)

# >>> img
# array([[38948, 41457, 37714, ..., 61511, 61785, 61824],
#       [39704, 38083, 36690, ..., 61419, 60086, 61910],
#       [41449, 39169, 38178, ..., 60192, 60969, 63538],
#       ...,
#       [37963, 39531, 40339, ..., 62351, 62646, 61793],
#       [37462, 37409, 38370, ..., 61125, 62497, 59770],
#       [39753, 36905, 38778, ..., 61922, 59565, 60035]], dtype=uint16)

Et voila. I suppose this doesn't meet my requirements as "lightweight" since Matplotlib is (to me) a heavy module, but it is spectacularly simple to get the image into a Numpy array. I hope this helps someone else find a solution quickly as this wasn't obvious to me.