15

How can I convert and save a 16 bit single-channel TIF in Python?

I can load a 16 and 32 bit image without an issue, and see that the 32 bit image is mode F and the 16 bit image is mode I;16S:

import Image
i32 = Image.open('32.tif')
i16 = Image.open('16.tif')
i32
# <TiffImagePlugin.TiffImageFile image mode=F size=2000x1600 at 0x1098E5518>
i16
# <TiffImagePlugin.TiffImageFile image mode=I;16S size=2000x1600 at 0x1098B6DD0>

But I am having trouble working with the 16 bit image. If I want to save either as PNG, I cannot do so directly:

i32.save('foo.png')
# IOError: cannot write mode F as PNG
i16.save('foo.png')
# ValueError: unrecognized mode

If I convert the 32 bit image, I can save it:

i32.convert('L').save('foo.png')

But the same command will not work with the 16 bit image:

i16.convert('L').save('foo.png')
# ValueError: unrecognized mode
mankoff
  • 2,225
  • 6
  • 25
  • 42
  • Pil should have support: http://effbot.org/zone/pil-changes-114.htm "(1.1.4a2 released) Improved support for 16-bit unsigned integer images (mode “I;16”). This includes TIFF reader support, and support for “getextrema” and “point” (from Klamer Shutte)." Can you provide a sample file? – Mark Ransom Aug 30 '11 at 17:43
  • OK. I'm using PIL 1.1.7 so the support is there. Edited the question. The issue isn't PIL but I still don't know what I'm doing wrong... – mankoff Aug 30 '11 at 17:46
  • Still would love to see a sample file. – Mark Ransom Aug 30 '11 at 18:10
  • here is a sample file: ftp://sidads.colorado.edu/pub/DATASETS/ICESHELVES/pine/pinei_2002300_1525_modis_ch02.tif – mankoff Aug 30 '11 at 18:29
  • And 32 bit: ftp://sidads.colorado.edu/pub/DATASETS/ICESHELVES/modis_iceshelf_archive/pinei/images/pinei_2011229_1525_modis_ch32.tif – mankoff Aug 30 '11 at 18:36

4 Answers4

9

For lossless conversion from 16 bit grayscale TIFF to PNG use PythonMagick:

from PythonMagick import Image
Image('pinei_2002300_1525_modis_ch02.tif').write("foo.png")
cgohlke
  • 9,142
  • 2
  • 33
  • 36
  • 1
    How can that possibly be lossless? You're going from 16 bits per pixel to 8. – Mark Ransom Aug 30 '11 at 20:04
  • 11
    The PNG format supports 16 bit per channel for grayscale (1 channel). http://en.wikipedia.org/wiki/Portable_Network_Graphics#Color_depth – cgohlke Aug 30 '11 at 21:22
  • 2
    I have been using PNG for many years and never knew it supported 16 bits per channel. Going back to RFC 2083 I can see that it was that way from the beginning. Thanks for teaching me something today! – Mark Ransom Aug 30 '11 at 21:42
  • Good solution **but** PythonMagick isn't included in many default installations (EPD, sage) making the code less portable. – mankoff Sep 10 '11 at 16:26
8

You appear to have stumbled into a PIL bug, or a corner case that was unimplemented.

Here's a workaround:

i16.mode = 'I'
i16.point(lambda i:i*(1./256)).convert('L').save('foo.png')
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • I get: `ValueError: unknown raw mode` – mankoff Aug 30 '11 at 19:22
  • @mankoff: sorry, I must have tried it two different ways and copied the wrong one. I've edited the answer from `mode='I;16'` to `mode='I'`, try it now. – Mark Ransom Aug 30 '11 at 19:37
  • This seems to work. Is data being lost? I think (?) the `I` mode is 8 bit. – mankoff Aug 30 '11 at 20:06
  • @mankoff, if nothing else the conversion to 'L' mode will truncate to 8 bits. If you want to see the raw data try `getpixel` or `load` after the mode is set to `'I'`. – Mark Ransom Aug 30 '11 at 20:09
8

Stumbled on this thread trying to save 16 bit TIFF images with PIL / numpy.

Versions: python 2.7.1 - numpy 1.6.1 - PIL 1.1.7

Here's a quick test I wrote. uint16 numpy array -> converted to string -> converted to a PIL image of type 'I;16' -> saved as a 16 bit TIFF.

Opening the image in ImageJ shows the right horizontal gradient pattern and the image type is 'Bits per pixel: 16 (unsigned)'

import Image
import numpy

data = numpy.zeros((1024,1024),numpy.uint16)

h,w = data.shape

for i in range(h):
    data[i,:] = numpy.arange(w)

im = Image.fromstring('I;16',(w,h),data.tostring())
im.save('test_16bit.tif')

edit: As of 1.1.7, PIL does not support writing compressed files, but pylibtiff does (lzw compression). The test code thus becomes (tested with pylibtiff 0.3):

import Image
import numpy
from libtiff import TIFFimage

data = numpy.zeros((1024,1024),numpy.uint16)

h,w = data.shape

for i in range(w):
    data[:,i] = numpy.arange(h)

tiff = TIFFimage(data, description='')
tiff.write_file('test_16bit.tif', compression='lzw')
#flush the file to disk:
del tiff

Please note: test code changed to generate a vertical gradient otherwise, no compression achieved (refer to warning: pylibtiff currently supports reading and writing images that are stored using TIFF strips).

EgorZ
  • 81
  • 1
  • 4
0

Convert an ImageJ TIFF to a JPEG with PIL 4.1+

im = numpy.array(Image.open('my.tiff'))
image = Image.fromarray(im / numpy.amax(im) * 255)
image.save('my.jpg')
Adam Gradzki
  • 139
  • 2
  • 9