11

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.

ali_m
  • 71,714
  • 23
  • 223
  • 298
ph0t0n
  • 735
  • 1
  • 10
  • 24
  • Could you post a sample file somewhere? – nneonneo Mar 08 '13 at 00:36
  • 1
    PIL struggles with a lot of things, even as simple as interlaced PNGs. I'd be inclined to suspect it simply doesn't support 16-bit images correctly. –  Mar 08 '13 at 00:53
  • @nneonneo Unfortunately, the data in the files I'm working with is proprietary, and I'm not sure how to give an example TIFF that fails as this one does. I know that makes it very hard to troubleshoot, but I was hoping someone would just know what to do here... I tried to make the script as comprehensive as I could – ph0t0n Mar 08 '13 at 00:59
  • @duskwuff I was afraid of that. Is there a better way to work with TIFF in Python (preferably something very lightweight and with good docs?) – ph0t0n Mar 08 '13 at 01:00

2 Answers2

8

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.

ph0t0n
  • 735
  • 1
  • 10
  • 24
  • I am also working with 16-bit uncompressed TIFF images. I must have a slightly different build of Matplotlib, or slightly different TIFF images, because this code didn't work for me. Matplotlib returned an array with dimensions (1024, 1024, 4) instead of (1024, 1024) and the max value of the image, as indicated by a call to max(img.ravel()), had been incorrectly downscaled to 255. For what it's worth, I resorted to loading my images in Matlab and saving them back out as mat files, which can be reliably loaded with scipy. – Mike Roberts Jul 06 '13 at 05:15
  • @Mike Roberts, the last dimension of the array (4) might contain every component of the pixel. Every component of an RGB(A) pixel being generally 8bit long, that's why the max is 255, which means it's even better than getting a simple 1024x1024 array. You can still convert the 4 components to one 32bit-long by ORing every component binary-shifted, eg. red | green << 8 | blue << 16 | alpha << 24 – Steve K Aug 12 '13 at 06:33
  • 1
    @SteveK Hi SteveK, thanks for the suggestion. I disagree with your claim that having to perform bit-twiddling operations on the returned array is "even better than getting a simple 1024x1024 array". The OP asks about 16-bit greyscale TIFF images, so the only appropriate return type is a 1024x1024 of uint16, at least in my opinion. Moreover, as a client of this bit-twiddling interface, how am I supposed to know that alpha contains the high-order byte and red contains the low-order byte? This convention is not always adhered to (e.g., color image data is sometimes stored as BGRA). – Mike Roberts Aug 22 '13 at 06:40
  • oh, I missed this crucial piece of information (greyscale). Obviously, in this case, it's far better to have only one (16bit word) integer per pixel. Also, for the pixel ordering, you're right. But in any case with an RGBA image, you will have to mess with bytes and with finding their order (BGRA, RGBA, ARGB...). Not so obvious indeed. – Steve K Aug 22 '13 at 06:54
5

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)
Jeremy Muhlich
  • 162
  • 2
  • 4