Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 428
ph0t0n Avatar asked Mar 08 '13 00:03

ph0t0n


2 Answers

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.

like image 153
ph0t0n Avatar answered Nov 01 '22 06:11

ph0t0n


Try Pillow, the “friendly” PIL fork. They've somewhat recently added better support for 16- and 32-bit images including in the numpy array interface. This code will work with the latest Pillow:

from PIL import Image
import numpy as np

img = Image.open('data.tif')
data = np.array(img)
like image 5
Jeremy Muhlich Avatar answered Nov 01 '22 06:11

Jeremy Muhlich