#!/usr/bin/env python
#-*- coding: utf8 -*-
"""
FabIO class for dealing with TIFF images.
In facts wraps TiffIO from V. Armando Solé (available in PyMca) or falls back to PIL
Authors:
........
* Henning O. Sorensen & Erik Knudsen:
Center for Fundamental Research: Metal Structures in Four Dimensions;
Risoe National Laboratory;
Frederiksborgvej 399;
DK-4000 Roskilde;
email:erik.knudsen@risoe.dk
* Jérôme Kieffer:
European Synchrotron Radiation Facility;
Grenoble (France)
License: GPLv3+
"""
# Get ready for python3:
from __future__ import with_statement, print_function, division
__authors__ = ["Jérôme Kieffer", "Henning O. Sorensen", "Erik Knudsen"]
__date__ = "11/07/2011"
__license__ = "GPLv3+"
__copyright__ = "ESRF, Grenoble & Risoe National Laboratory"
__status__ = "stable"
import time, logging, struct
logger = logging.getLogger("tifimage")
try:
from PIL import Image
except ImportError:
logger.warning("PIL is not installed ... trying to do without")
Image = None
import numpy
from .fabioimage import fabioimage
from .TiffIO import TiffIO
PIL_TO_NUMPY = { "I;16": numpy.uint16,
"F": numpy.float32,
"1": numpy.bool,
"I": numpy.int32,
"L": numpy.uint8,
}
LITTLE_ENDIAN = 1234
BIG_ENDIAN = 3412
TYPES = {0:'invalid', 1:'byte', 2:'ascii', 3:'short', 4:'long', 5:'rational', 6:'sbyte', 7:'undefined', 8:'sshort', 9:'slong', 10:'srational', 11:'float', 12:'double'}
TYPESIZES = {0:0, 1:1, 2:1, 3:2, 4:4, 5:8, 6:1, 7:1, 8:2, 9:4, 10:8, 11:4, 12:8}
baseline_tiff_tags = {
256:'ImageWidth',
257:'ImageLength',
306:'DateTime',
315:'Artist',
258:'BitsPerSample',
265:'CellLength',
264:'CellWidth',
259:'Compression',
262:'PhotometricInterpretation',
296:'ResolutionUnit',
282:'XResolution',
283:'YResolution',
278:'RowsPerStrip',
273:'StripOffset',
279:'StripByteCounts',
270:'ImageDescription',
271:'Make',
272:'Model',
320:'ColorMap',
305:'Software',
339:'SampleFormat',
33432:'Copyright'
}
[docs]class tifimage(fabioimage):
"""
Images in TIF format
Wraps TiffIO
"""
_need_a_seek_to_read = True
def __init__(self, *args, **kwds):
""" Tifimage constructor adds an nbits member attribute """
self.nbits = None
fabioimage.__init__(self, *args, **kwds)
self.lib = None
def _readheader(self, infile):
"""
Try to read Tiff images header...
"""
# try:
# self.header = { "filename" : infile.name }
# except AttributeError:
# self.header = {}
#
# t = Tiff_header(infile.read())
# self.header = t.header
# try:
# self.dim1 = int(self.header['ImageWidth'])
# self.dim2 = int(self.header['ImageLength'])
# except (KeyError):
# logger.warning("image dimensions could not be determined from header tags, trying to go on anyway")
# read the first 32 bytes to determine size
header = numpy.fromstring(infile.read(64), numpy.uint16)
self.dim1 = int(header[9])
self.dim2 = int(header[15])
# nbits is not a fabioimage attribute...
self.nbits = int(header[21]) # number of bits
[docs] def read(self, fname, frame=None):
"""
Wrapper for TiffIO.
"""
infile = self._open(fname, "rb")
self._readheader(infile)
infile.seek(0)
self.lib = None
try:
tiffIO = TiffIO(infile)
if tiffIO.getNumberOfImages() > 0:
#No support for now of multi-frame tiff images
self.data = tiffIO.getImage(0)
self.header = tiffIO.getInfo(0)
except Exception, error:
logger.warning("Unable to read %s with TiffIO due to %s, trying PIL" % (fname, error))
else:
if self.data.ndim == 2:
self.dim2, self.dim1 = self.data.shape
elif self.data.ndim == 3:
self.dim2, self.dim1, ncol = self.data.shape
logger.warning("Third dimension is the color")
else:
logger.warning("dataset has %s dimensions (%s), check for errors !!!!", self.data.ndim, self.data.shape)
self.lib = "TiffIO"
if (self.lib is None):
if Image:
try:
infile.seek(0)
self.pilimage = Image.open(infile)
except Exception:
logger.error("Error in opening %s with PIL" % fname)
self.lib = None
infile.seek(0)
else:
self.lib = "PIL"
self.dim1, self.dim2 = self.pilimage.size
if self.pilimage.mode in PIL_TO_NUMPY:
self.data = numpy.fromstring(self.pilimage.tostring(), PIL_TO_NUMPY[self.pilimage.mode])
else: #probably RGB or RGBA images: rely on PIL to convert it to greyscale float.
self.data = numpy.fromstring(self.pilimage.convert("F").tostring(), numpy.float32)
self.data.shape = (self.dim2, self.dim1)
else:
logger.error("Error in opening %s: no tiff reader managed to read the file.", fname)
self.lib = None
infile.seek(0)
self.bpp = len(numpy.zeros(1, dtype=self.data.dtype).tostring())
self.bytecode = self.data.dtype.name
self.resetvals()
return self
[docs] def write(self, fname):
"""
Overrides the fabioimage.write method and provides a simple TIFF image writer.
@param fname: name of the file to save the image to
@tag_type fname: string or unicode (file?)...
"""
tiffIO = TiffIO(fname, mode="w")
tiffIO.writeImage(self.data, info=self.header, software="fabio.tifimage", date=time.ctime())
#define a couple of helper classes here:
[docs]class Image_File_Directory(object):
def __init__(self, instring=None, offset= -1):
self.entries = []
self.offset = offset
self.count = None
[docs] def unpack(self, instring, offset= -1):
if (offset == -1): offset = self.offset
strInput = instring[offset:]
self.count = struct.unpack_from("H", strInput[:2])[0]
#0th IFD contains count-1 entries (count includes the adress of the next IFD)
for i in range(self.count - 1):
e = Image_File_Directory_entry().unpack(strInput[2 + 12 * (i + 1):])
if (e != None):
self.entries.append(e)
#extract data associated with tags
for e in self.entries:
if (e.val == None):
e.extract_data(instring)
#do we have some more ifds in this file
offset_next = struct.unpack_from("L", instring[offset + 2 + self.count * 12:])[0]
return offset_next
[docs]class Image_File_Directory_entry(object):
def __init__(self, tag=0, tag_type=0, count=0, offset=0):
self.tag = tag
self.tag_type = tag_type
self.count = count
self.val_offset = offset
self.val = None
[docs] def unpack(self, strInput):
idfentry = strInput[:12]
################################################################################
# # TOFIX: How is it possible that HHL (2+2+4 bytes has a size of )
################################################################################
(tag, tag_type, count) = struct.unpack_from("HHL", idfentry)
self.tag = tag
self.count = count
self.tag_type = tag_type
self.val = None
if (count <= 0):
logger.warning("Tag # %s has an invalid count: %s. Tag is ignored" % (tag, count))
return
if(count * TYPESIZES[tag_type] <= 4):
self.val_offset = 8
self.extract_data(idfentry)
self.val_offset = None
else:
self.val_offset = struct.unpack_from("L", idfentry[8:])[0]
return self