#/*##########################################################################
# Copyright (C) 2012 European Synchrotron Radiation Facility
#
# This file is part of the PyMca X-ray Fluorescence Toolkit developed at
# the ESRF by the Software group.
#
# This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# PyMca is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
#############################################################################*/
__author__ = "V.A. Sole - ESRF Data Analysis"
__revision__ = 1501
import sys
import os
import struct
import numpy
DEBUG = 0
ALLOW_MULTIPLE_STRIPS = False
TAG_ID = { 256:"NumberOfColumns", # S or L ImageWidth
257:"NumberOfRows", # S or L ImageHeight
258:"BitsPerSample", # S Number of bits per component
259:"Compression", # SHORT (1 - NoCompression, ...
262:"PhotometricInterpretation", # SHORT (0 - WhiteIsZero, 1 -BlackIsZero, 2 - RGB, 3 - Palette color
270:"ImageDescription", # ASCII
273:"StripOffsets", # S or L, for each strip, the byte offset of the strip
278:"RowsPerStrip", # S or L, number of rows in each back may be not for the last
279:"StripByteCounts", # S or L, The number of bytes in the strip AFTER any compression
305:"Software", # ASCII
306:"Date", # ASCII
320:"Colormap", # Colormap of Palette-color Images
339:"SampleFormat", # SHORT Interpretation of data in each pixel
}
#TILES ARE TO BE SUPPORTED TOO ...
TAG_NUMBER_OF_COLUMNS = 256
TAG_NUMBER_OF_ROWS = 257
TAG_BITS_PER_SAMPLE = 258
TAG_PHOTOMETRIC_INTERPRETATION = 262
TAG_COMPRESSION = 259
TAG_IMAGE_DESCRIPTION = 270
TAG_STRIP_OFFSETS = 273
TAG_ROWS_PER_STRIP = 278
TAG_STRIP_BYTE_COUNTS = 279
TAG_SOFTWARE = 305
TAG_DATE = 306
TAG_COLORMAP = 320
TAG_SAMPLE_FORMAT = 339
FIELD_TYPE = {1:('BYTE', "B"),
2:('ASCII', "s"), #string ending with binary zero
3:('SHORT', "H"),
4:('LONG', "I"),
5:('RATIONAL', "II"),
6:('SBYTE', "b"),
7:('UNDEFINED', "B"),
8:('SSHORT', "h"),
9:('SLONG', "i"),
10:('SRATIONAL', "ii"),
11:('FLOAT', "f"),
12:('DOUBLE', "d")}
FIELD_TYPE_OUT = { 'B': 1,
's': 2,
'H': 3,
'I': 4,
'II': 5,
'b': 6,
'h': 8,
'i': 9,
'ii': 10,
'f': 11,
'd': 12}
#sample formats (http://www.awaresystems.be/imaging/tiff/tiffflags/sampleformat.html)
SAMPLE_FORMAT_UINT = 1
SAMPLE_FORMAT_INT = 2
SAMPLE_FORMAT_FLOAT = 3 #floating point
SAMPLE_FORMAT_VOID = 4 #undefined data, usually assumed UINT
SAMPLE_FORMAT_COMPLEXINT = 5
SAMPLE_FORMAT_COMPLEXIEEEFP = 6
[docs]class TiffIO(object):
def __init__(self, filename, mode=None, cache_length=20, mono_output=False):
if mode is None:
mode = 'rb'
if 'b' not in mode:
mode = mode + 'b'
if 'a' in mode.lower():
raise IOError("Mode %s makes no sense on TIFF files. Consider 'rb+'" % mode)
if ('w' in mode):
if '+' not in mode:
mode += '+'
#if isinstance(filename, file): #does not work in python 3
if hasattr(filename, "seek"):
fd = filename
self._access = None
else:
#the b is needed for windows and python 3
fd = open(filename, mode)
self._access = mode
self._initInternalVariables(fd)
self._maxImageCacheLength = cache_length
self._forceMonoOutput = mono_output
def _initInternalVariables(self, fd=None):
if fd is None:
fd = self.fd
else:
self.fd = fd
# read the order
fd.seek(0)
order = fd.read(2).decode()
if len(order):
if order == "II":
#intel, little endian
fileOrder = "little"
self._structChar = '<'
elif order == "MM":
#motorola, high endian
fileOrder = "big"
self._structChar = '>'
else:
raise IOError("File is not a Mar CCD file, nor a TIFF file")
a = fd.read(2)
fortyTwo = struct.unpack(self._structChar + "H", a)[0]
if fortyTwo != 42:
raise IOError("Invalid TIFF version %d" % fortyTwo)
else:
if DEBUG:
print("VALID TIFF VERSION")
if sys.byteorder != fileOrder:
swap = True
else:
swap = False
else:
if sys.byteorder == "little":
self._structChar = '<'
else:
self._structChar = '>'
swap = False
self._swap = swap
self._IFD = []
self._imageDataCacheIndex = []
self._imageDataCache = []
self._imageInfoCacheIndex = []
self._imageInfoCache = []
self.getImageFileDirectories(fd)
def __makeSureFileIsOpen(self):
if not self.fd.closed:
return
if DEBUG:
print("Reopening closed file")
fileName = self.fd.name
if self._access is None:
#we do not own the file
#open in read mode
newFile = open(fileName, 'rb')
else:
newFile = open(fileName, self._access)
self.fd = newFile
def __makeSureFileIsClosed(self):
if self._access is None:
#we do not own the file
if DEBUG:
print("Not closing not owned file")
return
if not self.fd.closed:
self.fd.close()
[docs] def getNumberOfImages(self):
#update for the case someone has done anything?
self._updateIFD()
return len(self._IFD)
def _updateIFD(self):
self.__makeSureFileIsOpen()
self.getImageFileDirectories()
self.__makeSureFileIsClosed()
[docs] def getImageFileDirectories(self, fd=None):
if fd is None:
fd = self.fd
else:
self.fd = fd
st = self._structChar
fd.seek(4)
self._IFD = []
nImages = 0
fmt = st + 'I'
inStr = fd.read(struct.calcsize(fmt))
if not len(inStr):
offsetToIFD = 0
else:
offsetToIFD = struct.unpack(fmt, inStr)[0]
if DEBUG:
print("Offset to first IFD = %d" % offsetToIFD)
while offsetToIFD != 0:
self._IFD.append(offsetToIFD)
nImages += 1
fd.seek(offsetToIFD)
fmt = st + 'H'
numberOfDirectoryEntries = struct.unpack(fmt, fd.read(struct.calcsize(fmt)))[0]
if DEBUG:
print("Number of directory entries = %d" % numberOfDirectoryEntries)
fmt = st + 'I'
fd.seek(offsetToIFD + 2 + 12 * numberOfDirectoryEntries)
offsetToIFD = struct.unpack(fmt, fd.read(struct.calcsize(fmt)))[0]
if DEBUG:
print("Next Offset to IFD = %d" % offsetToIFD)
#offsetToIFD = 0
if DEBUG:
print("Number of images found = %d" % nImages)
return nImages
def _parseImageFileDirectory(self, nImage):
offsetToIFD = self._IFD[nImage]
st = self._structChar
fd = self.fd
fd.seek(offsetToIFD)
fmt = st + 'H'
numberOfDirectoryEntries = struct.unpack(fmt, fd.read(struct.calcsize(fmt)))[0]
if DEBUG:
print("Number of directory entries = %d" % numberOfDirectoryEntries)
fmt = st + 'HHI4s'
tagIDList = []
fieldTypeList = []
nValuesList = []
valueOffsetList = []
for i in range(numberOfDirectoryEntries):
tagID, fieldType, nValues, valueOffset = struct.unpack(fmt, fd.read(12))
tagIDList.append(tagID)
fieldTypeList.append(fieldType)
nValuesList.append(nValues)
if nValues == 1:
ftype, vfmt = FIELD_TYPE[fieldType]
if ftype not in ['ASCII', 'RATIONAL', 'SRATIONAL']:
vfmt = st + vfmt
actualValue = struct.unpack(vfmt, valueOffset[0: struct.calcsize(vfmt)])[0]
valueOffsetList.append(actualValue)
else:
valueOffsetList.append(valueOffset)
elif (nValues < 5) and (fieldType == 2):
ftype, vfmt = FIELD_TYPE[fieldType]
vfmt = st + "%d%s" % (nValues, vfmt)
actualValue = struct.unpack(vfmt, valueOffset[0: struct.calcsize(vfmt)])[0]
valueOffsetList.append(actualValue)
else:
valueOffsetList.append(valueOffset)
if DEBUG:
if tagID in TAG_ID:
print("tagID = %s" % TAG_ID[tagID])
else:
print("tagID = %d" % tagID)
print("fieldType = %s" % FIELD_TYPE[fieldType][0])
print("nValues = %d" % nValues)
#if nValues == 1:
# print("valueOffset = %s" % valueOffset)
return tagIDList, fieldTypeList, nValuesList, valueOffsetList
def _readIFDEntry(self, tag, tagIDList, fieldTypeList, nValuesList, valueOffsetList):
fd = self.fd
st = self._structChar
idx = tagIDList.index(tag)
nValues = nValuesList[idx]
output = []
ftype, vfmt = FIELD_TYPE[fieldTypeList[idx]]
vfmt = st + "%d%s" % (nValues, vfmt)
requestedBytes = struct.calcsize(vfmt)
if nValues == 1:
output.append(valueOffsetList[idx])
elif requestedBytes < 5:
output.append(valueOffsetList[idx])
else:
offset = fd.seek(struct.unpack(st + "I", valueOffsetList[idx])[0])
output = struct.unpack(vfmt, fd.read(requestedBytes))
return output
[docs] def getData(self, nImage, **kw):
if nImage >= len(self._IFD):
#update prior to raise an index error error
self._updateIFD()
return self._readImage(nImage, **kw)
[docs] def getImage(self, nImage):
return self.getData(nImage)
[docs] def getInfo(self, nImage, **kw):
if nImage >= len(self._IFD):
#update prior to raise an index error error
self._updateIFD()
current = self._IFD[nImage]
return self._readInfo(nImage)
def _readInfo(self, nImage, close=True):
if nImage in self._imageInfoCacheIndex:
if DEBUG:
print("Reading info from cache")
return self._imageInfoCache[self._imageInfoCacheIndex.index(nImage)]
#read the header
self.__makeSureFileIsOpen()
tagIDList, fieldTypeList, nValuesList, valueOffsetList = self._parseImageFileDirectory(nImage)
#rows and columns
nColumns = valueOffsetList[tagIDList.index(TAG_NUMBER_OF_COLUMNS)]
nRows = valueOffsetList[tagIDList.index(TAG_NUMBER_OF_ROWS)]
#bits per sample
idx = tagIDList.index(TAG_BITS_PER_SAMPLE)
nBits = valueOffsetList[idx]
if nValuesList[idx] != 1:
#this happens with RGB and friends, nBits is not a single value
nBits = self._readIFDEntry(TAG_BITS_PER_SAMPLE,
tagIDList, fieldTypeList, nValuesList, valueOffsetList)
if TAG_COLORMAP in tagIDList:
idx = tagIDList.index(TAG_COLORMAP)
tmpColormap = self._readIFDEntry(TAG_COLORMAP,
tagIDList, fieldTypeList, nValuesList, valueOffsetList)
if max(tmpColormap) > 255:
tmpColormap = numpy.array(tmpColormap, dtype=numpy.uint16)
tmpColormap = (tmpColormap / 256.).astype(numpy.uint8)
else:
tmpColormap = numpy.array(tmpColormap, dtype=numpy.uint8)
tmpColormap.shape = 3, -1
colormap = numpy.zeros((tmpColormap.shape[-1], 3), tmpColormap.dtype)
colormap[:, :] = tmpColormap.T
tmpColormap = None
else:
colormap = None
#sample format
if TAG_SAMPLE_FORMAT in tagIDList:
sampleFormat = valueOffsetList[tagIDList.index(TAG_SAMPLE_FORMAT)]
else:
#set to unknown
sampleFormat = SAMPLE_FORMAT_VOID
# compression
compression = False
compression_type = 1
if TAG_COMPRESSION in tagIDList:
compression_type = valueOffsetList[tagIDList.index(TAG_COMPRESSION)]
if compression_type == 1:
compression = False
else:
compression = True
#photometric interpretation
interpretation = 1
if TAG_PHOTOMETRIC_INTERPRETATION in tagIDList:
interpretation = valueOffsetList[tagIDList.index(TAG_PHOTOMETRIC_INTERPRETATION)]
else:
print("WARNING: Non standard TIFF. Photometric interpretation TAG missing")
helpString = ""
if sys.version > '2.6':
helpString = eval('b""')
if TAG_IMAGE_DESCRIPTION in tagIDList:
imageDescription = self._readIFDEntry(TAG_IMAGE_DESCRIPTION,
tagIDList, fieldTypeList, nValuesList, valueOffsetList)
if type(imageDescription) in [type([1]), type((1,))]:
imageDescription = helpString.join(imageDescription)
else:
imageDescription = "%d/%d" % (nImage + 1, len(self._IFD))
if sys.version < '3.0':
defaultSoftware = "Unknown Software"
else:
defaultSoftware = bytes("Unknown Software",
encoding='utf-8')
if TAG_SOFTWARE in tagIDList:
software = self._readIFDEntry(TAG_SOFTWARE,
tagIDList, fieldTypeList, nValuesList, valueOffsetList)
if type(software) in [type([1]), type((1,))]:
software = helpString.join(software)
else:
software = defaultSoftware
if software == defaultSoftware:
try:
if sys.version < '3.0':
if imageDescription.upper().startswith("IMAGEJ"):
software = imageDescription.split("=")[0]
else:
tmpString = imageDescription.decode()
if tmpString.upper().startswith("IMAGEJ"):
software = bytes(tmpString.split("=")[0],
encoding='utf-8')
except:
pass
if TAG_DATE in tagIDList:
date = self._readIFDEntry(TAG_DATE,
tagIDList, fieldTypeList, nValuesList, valueOffsetList)
if type(date) in [type([1]), type((1,))]:
date = helpString.join(date)
else:
date = "Unknown Date"
stripOffsets = self._readIFDEntry(TAG_STRIP_OFFSETS,
tagIDList, fieldTypeList, nValuesList, valueOffsetList)
if TAG_ROWS_PER_STRIP in tagIDList:
rowsPerStrip = self._readIFDEntry(TAG_ROWS_PER_STRIP,
tagIDList, fieldTypeList, nValuesList, valueOffsetList)[0]
else:
rowsPerStrip = nRows
print("WARNING: Non standard TIFF. Rows per strip TAG missing")
if TAG_STRIP_BYTE_COUNTS in tagIDList:
stripByteCounts = self._readIFDEntry(TAG_STRIP_BYTE_COUNTS,
tagIDList, fieldTypeList, nValuesList, valueOffsetList)
else:
print("WARNING: Non standard TIFF. Strip byte counts TAG missing")
if hasattr(nBits, 'index'):
expectedSum = 0
for n in nBits:
expectedSum += int(nRows * nColumns * n / 8)
else:
expectedSum = int(nRows * nColumns * nBits / 8)
stripByteCounts = [expectedSum]
if close:
self.__makeSureFileIsClosed()
if self._forceMonoOutput and (interpretation > 1):
#color image but asked monochrome output
nBits = 32
colormap = None
sampleFormat = SAMPLE_FORMAT_FLOAT
interpretation = 1
#we cannot rely on any cache in this case
useInfoCache = False
if DEBUG:
print("FORCED MONO")
else:
useInfoCache = True
info = {}
info["nRows"] = nRows
info["nColumns"] = nColumns
info["nBits"] = nBits
info["compression"] = compression
info["compression_type"] = compression_type
info["imageDescription"] = imageDescription
info["stripOffsets"] = stripOffsets #This contains the file offsets to the data positions
info["rowsPerStrip"] = rowsPerStrip
info["stripByteCounts"] = stripByteCounts #bytes in strip since I do not support compression
info["software"] = software
info["date"] = date
info["colormap"] = colormap
info["sampleFormat"] = sampleFormat
info["photometricInterpretation"] = interpretation
infoDict = {}
if sys.version < '3.0':
testString = 'PyMca'
else:
testString = eval('b"PyMca"')
if software.startswith(testString):
#str to make sure python 2.x sees it as string and not unicode
if sys.version < '3.0':
descriptionString = imageDescription
else:
descriptionString = str(imageDescription.decode())
#interpret the image description in terms of supplied
#information at writing time
items = descriptionString.split('=')
for i in range(int(len(items) / 2)):
key = "%s" % items[i * 2]
#get rid of the \n at the end of the value
value = "%s" % items[i * 2 + 1][:-1]
infoDict[key] = value
info['info'] = infoDict
if (self._maxImageCacheLength > 0) and useInfoCache:
self._imageInfoCacheIndex.insert(0, nImage)
self._imageInfoCache.insert(0, info)
if len(self._imageInfoCacheIndex) > self._maxImageCacheLength:
self._imageInfoCacheIndex = self._imageInfoCacheIndex[:self._maxImageCacheLength]
self._imageInfoCache = self._imageInfoCache[:self._maxImageCacheLength]
return info
def _readImage(self, nImage, **kw):
if DEBUG:
print("Reading image %d" % nImage)
if 'close' in kw:
close = kw['close']
else:
close = True
rowMin = kw.get('rowMin', None)
rowMax = kw.get('rowMax', None)
if nImage in self._imageDataCacheIndex:
if DEBUG:
print("Reading image data from cache")
return self._imageDataCache[self._imageDataCacheIndex.index(nImage)]
self.__makeSureFileIsOpen()
if self._forceMonoOutput:
oldMono = True
else:
oldMono = False
try:
self._forceMonoOutput = False
info = self._readInfo(nImage, close=False)
self._forceMonoOutput = oldMono
except:
self._forceMonoOutput = oldMono
raise
compression = info['compression']
compression_type = info['compression_type']
if compression:
if compression_type != 32773:
raise IOError("Compressed TIFF images not supported except packbits")
else:
#PackBits compression
if DEBUG:
print("Using PackBits compression")
interpretation = info["photometricInterpretation"]
if interpretation == 2:
#RGB
pass
#raise IOError("RGB Image. Only grayscale images supported")
elif interpretation == 3:
#Palette Color Image
pass
#raise IOError("Palette-color Image. Only grayscale images supported")
elif interpretation > 2:
#Palette Color Image
raise IOError("Only grayscale images supported")
nRows = info["nRows"]
nColumns = info["nColumns"]
nBits = info["nBits"]
colormap = info["colormap"]
sampleFormat = info["sampleFormat"]
if rowMin is None:
rowMin = 0
if rowMax is None:
rowMax = nRows - 1
if rowMin < 0:
rowMin = nRows - rowMin
if rowMax < 0:
rowMax = nRows - rowMax
if rowMax < rowMin:
txt = "Max Row smaller than Min Row. Reverse selection not supported"
raise NotImplemented(txt)
if rowMin >= nRows:
raise IndexError("Image only has %d rows" % nRows)
if rowMax >= nRows:
raise IndexError("Image only has %d rows" % nRows)
if sampleFormat == SAMPLE_FORMAT_FLOAT:
if nBits == 32:
dtype = numpy.float32
elif nBits == 64:
dtype = numpy.float64
else:
raise ValueError("Unsupported number of bits for a float: %d" % nBits)
elif sampleFormat in [SAMPLE_FORMAT_UINT, SAMPLE_FORMAT_VOID]:
if nBits in [8, (8, 8, 8), [8, 8, 8]]:
dtype = numpy.uint8
elif nBits in [16, (16, 16, 16), [16, 16, 16]]:
dtype = numpy.uint16
elif nBits in [32, (32, 32, 32), [32, 32, 32]]:
dtype = numpy.uint32
elif nBits in [64, (64, 64, 64), [64, 64, 64]]:
dtype = numpy.uint64
else:
raise ValueError("Unsupported number of bits for unsigned int: %s" % (nBits,))
elif sampleFormat == SAMPLE_FORMAT_INT:
if nBits in [8, (8, 8, 8), [8, 8, 8]]:
dtype = numpy.int8
elif nBits in [16, (16, 16, 16), [16, 16, 16]]:
dtype = numpy.int16
elif nBits in [32, (32, 32, 32), [32, 32, 32]]:
dtype = numpy.int32
elif nBits in [64, (64, 64, 64), [64, 64, 64]]:
dtype = numpy.int64
else:
raise ValueError("Unsupported number of bits for signed int: %s" % (nBits,))
else:
raise ValueError("Unsupported combination. Bits = %s Format = %d" % (nBits, sampleFormat))
if hasattr(nBits, 'index'):
image = numpy.zeros((nRows, nColumns, len(nBits)), dtype=dtype)
elif colormap is not None:
#should I use colormap dtype?
image = numpy.zeros((nRows, nColumns, 3), dtype=dtype)
else:
image = numpy.zeros((nRows, nColumns), dtype=dtype)
fd = self.fd
st = self._structChar
stripOffsets = info["stripOffsets"] #This contains the file offsets to the data positions
rowsPerStrip = info["rowsPerStrip"]
stripByteCounts = info["stripByteCounts"] #bytes in strip since I do not support compression
rowStart = 0
if len(stripOffsets) == 1:
bytesPerRow = int(stripByteCounts[0] / rowsPerStrip)
fd.seek(stripOffsets[0] + rowMin * bytesPerRow)
nBytes = (rowMax - rowMin + 1) * bytesPerRow
if self._swap:
readout = numpy.fromstring(fd.read(nBytes), dtype).byteswap()
else:
readout = numpy.fromstring(fd.read(nBytes), dtype)
if hasattr(nBits, 'index'):
readout.shape = -1, nColumns, len(nBits)
elif info['colormap'] is not None:
readout = colormap[readout]
else:
readout.shape = -1, nColumns
image[rowMin:rowMax + 1, :] = readout
else:
for i in range(len(stripOffsets)):
#the amount of rows
nRowsToRead = rowsPerStrip
rowEnd = int(min(rowStart + nRowsToRead, nRows))
if rowEnd < rowMin:
rowStart += nRowsToRead
continue
if (rowStart > rowMax):
break
#we are in position
fd.seek(stripOffsets[i])
#the amount of bytes to read
nBytes = stripByteCounts[i]
if compression_type == 32773:
try:
bufferBytes = bytes()
except:
#python 2.5 ...
bufferBytes = ""
#packBits
readBytes = 0
#intermediate buffer
tmpBuffer = fd.read(nBytes)
while readBytes < nBytes:
n = struct.unpack('b', tmpBuffer[readBytes:(readBytes + 1)])[0]
readBytes += 1
if n >= 0:
#should I prevent reading more than the
#length of the chain? Let's python raise
#the exception...
bufferBytes += tmpBuffer[readBytes:\
readBytes + (n + 1)]
readBytes += (n + 1)
elif n > -128:
bufferBytes += (-n + 1) * tmpBuffer[readBytes:(readBytes + 1)]
readBytes += 1
else:
#if read -128 ignore the byte
continue
if self._swap:
readout = numpy.fromstring(bufferBytes, dtype).byteswap()
else:
readout = numpy.fromstring(bufferBytes, dtype)
if hasattr(nBits, 'index'):
readout.shape = -1, nColumns, len(nBits)
elif info['colormap'] is not None:
readout = colormap[readout]
readout.shape = -1, nColumns, 3
else:
readout.shape = -1, nColumns
image[rowStart:rowEnd, :] = readout
else:
if 1:
#use numpy
if self._swap:
readout = numpy.fromstring(fd.read(nBytes), dtype).byteswap()
else:
readout = numpy.fromstring(fd.read(nBytes), dtype)
if hasattr(nBits, 'index'):
readout.shape = -1, nColumns, len(nBits)
elif colormap is not None:
readout = colormap[readout]
readout.shape = -1, nColumns, 3
else:
readout.shape = -1, nColumns
image[rowStart:rowEnd, :] = readout
else:
#using struct
readout = numpy.array(struct.unpack(st + "%df" % int(nBytes / 4), fd.read(nBytes)),
dtype=dtype)
if hasattr(nBits, 'index'):
readout.shape = -1, nColumns, len(nBits)
elif colormap is not None:
readout = colormap[readout]
readout.shape = -1, nColumns, 3
else:
readout.shape = -1, nColumns
image[rowStart:rowEnd, :] = readout
rowStart += nRowsToRead
if close:
self.__makeSureFileIsClosed()
if len(image.shape) == 3:
#color image
if self._forceMonoOutput:
#color image, convert to monochrome
image = (image[:, :, 0] * 0.114 + \
image[:, :, 1] * 0.587 + \
image[:, :, 2] * 0.299).astype(numpy.float32)
if (rowMin == 0) and (rowMax == (nRows - 1)):
self._imageDataCacheIndex.insert(0, nImage)
self._imageDataCache.insert(0, image)
if len(self._imageDataCacheIndex) > self._maxImageCacheLength:
self._imageDataCacheIndex = self._imageDataCacheIndex[:self._maxImageCacheLength]
self._imageDataCache = self._imageDataCache[:self._maxImageCacheLength]
return image
[docs] def writeImage(self, image0, info=None, software=None, date=None):
if software is None:
software = 'PyMca.TiffIO'
#if date is None:
# date = time.ctime()
self.__makeSureFileIsOpen()
fd = self.fd
#prior to do anything, perform some tests
if not len(image0.shape):
raise ValueError("Empty image")
if len(image0.shape) == 1:
#get a different view
image = image0[:]
image.shape = 1, -1
else:
image = image0
if image.dtype == numpy.float64:
image = image.astype(numpy.float32)
fd.seek(0)
mode = fd.mode
name = fd.name
if 'w' in mode:
#we have to overwrite the file
self.__makeSureFileIsClosed()
fd = None
if os.path.exists(name):
os.remove(name)
fd = open(name, mode='wb+')
self._initEmptyFile(fd)
self.fd = fd
#read the file size
self.__makeSureFileIsOpen()
fd = self.fd
fd.seek(0, os.SEEK_END)
endOfFile = fd.tell()
if fd.tell() == 0:
self._initEmptyFile(fd)
fd.seek(0, os.SEEK_END)
endOfFile = fd.tell()
#init internal variables
self._initInternalVariables(fd)
st = self._structChar
#get the image file directories
nImages = self.getImageFileDirectories()
if DEBUG:
print("File contains %d images" % nImages)
if nImages == 0:
fd.seek(4)
fmt = st + 'I'
fd.write(struct.pack(fmt, endOfFile))
else:
fd.seek(self._IFD[-1])
fmt = st + 'H'
numberOfDirectoryEntries = struct.unpack(fmt, fd.read(struct.calcsize(fmt)))[0]
fmt = st + 'I'
pos = self._IFD[-1] + 2 + 12 * numberOfDirectoryEntries
fd.seek(pos)
fmt = st + 'I'
fd.write(struct.pack(fmt, endOfFile))
fd.flush()
#and we can write at the end of the file, find out the file length
fd.seek(0, os.SEEK_END)
#get the description information from the input information
if info is None:
description = info
else:
description = "%s" % ""
for key in info.keys():
description += "%s=%s\n" % (key, info[key])
#get the image file directory
outputIFD = self._getOutputIFD(image, description=description,
software=software,
date=date)
#write the new IFD
fd.write(outputIFD)
#write the image
if self._swap:
fd.write(image.byteswap().tostring())
else:
fd.write(image.tostring())
fd.flush()
self.fd = fd
self.__makeSureFileIsClosed()
def _initEmptyFile(self, fd=None):
if fd is None:
fd = self.fd
if sys.byteorder == "little":
order = "II"
#intel, little endian
fileOrder = "little"
self._structChar = '<'
else:
order = "MM"
#motorola, high endian
fileOrder = "big"
self._structChar = '>'
st = self._structChar
if fileOrder == sys.byteorder:
self._swap = False
else:
self._swap = True
fd.seek(0)
if sys.version < '3.0':
fd.write(struct.pack(st + '2s', order))
fd.write(struct.pack(st + 'H', 42))
fd.write(struct.pack(st + 'I', 0))
else:
fd.write(struct.pack(st + '2s', bytes(order, 'utf-8')))
fd.write(struct.pack(st + 'H', 42))
fd.write(struct.pack(st + 'I', 0))
fd.flush()
def _getOutputIFD(self, image, description=None, software=None, date=None):
#the tags have to be in order
#the very minimum is
#256:"NumberOfColumns", # S or L ImageWidth
#257:"NumberOfRows", # S or L ImageHeight
#258:"BitsPerSample", # S Number of bits per component
#259:"Compression", # SHORT (1 - NoCompression, ...
#262:"PhotometricInterpretation", # SHORT (0 - WhiteIsZero, 1 -BlackIsZero, 2 - RGB, 3 - Palette color
#270:"ImageDescription", # ASCII
#273:"StripOffsets", # S or L, for each strip, the byte offset of the strip
#278:"RowsPerStrip", # S or L, number of rows in each back may be not for the last
#279:"StripByteCounts", # S or L, The number of bytes in the strip AFTER any compression
#305:"Software", # ASCII
#306:"Date", # ASCII
#339:"SampleFormat", # SHORT Interpretation of data in each pixel
nDirectoryEntries = 9
imageDescription = None
if description is not None:
descriptionLength = len(description)
while descriptionLength < 4:
description = description + " "
descriptionLength = len(description)
if sys.version >= '3.0':
description = bytes(description, 'utf-8')
elif type(description) != type(""):
try:
description = description.decode('utf-8')
except UnicodeDecodeError:
try:
description = description.decode('latin-1')
except UnicodeDecodeError:
description = "%s" % description
if sys.version > '2.6':
description = description.encode('utf-8', errors="ignore")
description = "%s" % description
descriptionLength = len(description)
imageDescription = struct.pack("%ds" % descriptionLength, description)
nDirectoryEntries += 1
#software
if software is not None:
softwareLength = len(software)
while softwareLength < 4:
software = software + " "
softwareLength = len(software)
if sys.version >= '3.0':
software = bytes(software, 'utf-8')
softwarePackedString = struct.pack("%ds" % softwareLength, software)
nDirectoryEntries += 1
else:
softwareLength = 0
if date is not None:
dateLength = len(date)
if sys.version >= '3.0':
date = bytes(date, 'utf-8')
datePackedString = struct.pack("%ds" % dateLength, date)
dateLength = len(datePackedString)
nDirectoryEntries += 1
else:
dateLength = 0
nRows, nColumns = image.shape
dtype = image.dtype
bitsPerSample = int(dtype.str[-1]) * 8
#only uncompressed data
compression = 1
#interpretation, black is zero
interpretation = 1
#image description
if imageDescription is not None:
descriptionLength = len(imageDescription)
else:
descriptionLength = 0
#strip offsets
#we are putting them after the directory and the directory is
#at the end of the file
self.fd.seek(0, os.SEEK_END)
endOfFile = self.fd.tell()
if endOfFile == 0:
#empty file
endOfFile = 8
#rows per strip
if ALLOW_MULTIPLE_STRIPS:
#try to segment the image in several pieces
if not (nRows % 4):
rowsPerStrip = int(nRows / 4)
elif not (nRows % 10):
rowsPerStrip = int(nRows / 10)
elif not (nRows % 8):
rowsPerStrip = int(nRows / 8)
elif not (nRows % 4):
rowsPerStrip = int(nRows / 4)
elif not (nRows % 2):
rowsPerStrip = int(nRows / 2)
else:
rowsPerStrip = nRows
else:
rowsPerStrip = nRows
#stripByteCounts
stripByteCounts = int(nColumns * rowsPerStrip * bitsPerSample / 8)
if descriptionLength > 4:
stripOffsets0 = endOfFile + dateLength + descriptionLength + \
2 + 12 * nDirectoryEntries + 4
else:
stripOffsets0 = endOfFile + dateLength + \
2 + 12 * nDirectoryEntries + 4
if softwareLength > 4:
stripOffsets0 += softwareLength
stripOffsets = [stripOffsets0]
stripOffsetsLength = 0
stripOffsetsString = None
st = self._structChar
if rowsPerStrip != nRows:
nStripOffsets = int(nRows / rowsPerStrip)
fmt = st + 'I'
stripOffsetsLength = struct.calcsize(fmt) * nStripOffsets
stripOffsets0 += stripOffsetsLength
#the length for the stripByteCounts will be the same
stripOffsets0 += stripOffsetsLength
stripOffsets = []
for i in range(nStripOffsets):
value = stripOffsets0 + i * stripByteCounts
stripOffsets.append(value)
if i == 0:
stripOffsetsString = struct.pack(fmt, value)
stripByteCountsString = struct.pack(fmt, stripByteCounts)
else:
stripOffsetsString += struct.pack(fmt, value)
stripByteCountsString += struct.pack(fmt, stripByteCounts)
if DEBUG:
print("IMAGE WILL START AT %d" % stripOffsets[0])
#sample format
if dtype in [numpy.float32, numpy.float64] or\
dtype.str[-2] == 'f':
sampleFormat = SAMPLE_FORMAT_FLOAT
elif dtype in [numpy.uint8, numpy.uint16, numpy.uint32, numpy.uint64]:
sampleFormat = SAMPLE_FORMAT_UINT
elif dtype in [numpy.int8, numpy.int16, numpy.int32, numpy.int64]:
sampleFormat = SAMPLE_FORMAT_INT
else:
raise ValueError("Unsupported data type %s" % dtype)
info = {}
info["nColumns"] = nColumns
info["nRows"] = nRows
info["nBits"] = bitsPerSample
info["compression"] = compression
info["photometricInterpretation"] = interpretation
info["stripOffsets"] = stripOffsets
info["rowsPerStrip"] = rowsPerStrip
info["stripByteCounts"] = stripByteCounts
info["date"] = date
info["sampleFormat"] = sampleFormat
outputIFD = ""
if sys.version > '2.6':
outputIFD = eval('b""')
fmt = st + "H"
outputIFD += struct.pack(fmt, nDirectoryEntries)
fmt = st + "HHII"
outputIFD += struct.pack(fmt, TAG_NUMBER_OF_COLUMNS,
FIELD_TYPE_OUT['I'],
1,
info["nColumns"])
outputIFD += struct.pack(fmt, TAG_NUMBER_OF_ROWS,
FIELD_TYPE_OUT['I'],
1,
info["nRows"])
fmt = st + 'HHIHH'
outputIFD += struct.pack(fmt, TAG_BITS_PER_SAMPLE,
FIELD_TYPE_OUT['H'],
1,
info["nBits"], 0)
fmt = st + 'HHIHH'
outputIFD += struct.pack(fmt, TAG_COMPRESSION,
FIELD_TYPE_OUT['H'],
1,
info["compression"], 0)
fmt = st + 'HHIHH'
outputIFD += struct.pack(fmt, TAG_PHOTOMETRIC_INTERPRETATION,
FIELD_TYPE_OUT['H'],
1,
info["photometricInterpretation"], 0)
if imageDescription is not None:
descriptionLength = len(imageDescription)
if descriptionLength > 4:
fmt = st + 'HHII'
outputIFD += struct.pack(fmt, TAG_IMAGE_DESCRIPTION,
FIELD_TYPE_OUT['s'],
descriptionLength,
info["stripOffsets"][0] - \
2 * stripOffsetsLength - \
descriptionLength)
else:
#it has to have length 4
fmt = st + 'HHI%ds' % descriptionLength
outputIFD += struct.pack(fmt, TAG_IMAGE_DESCRIPTION,
FIELD_TYPE_OUT['s'],
descriptionLength,
description)
if len(stripOffsets) == 1:
fmt = st + 'HHII'
outputIFD += struct.pack(fmt, TAG_STRIP_OFFSETS,
FIELD_TYPE_OUT['I'],
1,
info["stripOffsets"][0])
else:
fmt = st + 'HHII'
outputIFD += struct.pack(fmt, TAG_STRIP_OFFSETS,
FIELD_TYPE_OUT['I'],
len(stripOffsets),
info["stripOffsets"][0] - 2 * stripOffsetsLength)
fmt = st + 'HHII'
outputIFD += struct.pack(fmt, TAG_ROWS_PER_STRIP,
FIELD_TYPE_OUT['I'],
1,
info["rowsPerStrip"])
if len(stripOffsets) == 1:
fmt = st + 'HHII'
outputIFD += struct.pack(fmt, TAG_STRIP_BYTE_COUNTS,
FIELD_TYPE_OUT['I'],
1,
info["stripByteCounts"])
else:
fmt = st + 'HHII'
outputIFD += struct.pack(fmt, TAG_STRIP_BYTE_COUNTS,
FIELD_TYPE_OUT['I'],
len(stripOffsets),
info["stripOffsets"][0] - stripOffsetsLength)
if software is not None:
if softwareLength > 4:
fmt = st + 'HHII'
outputIFD += struct.pack(fmt, TAG_SOFTWARE,
FIELD_TYPE_OUT['s'],
softwareLength,
info["stripOffsets"][0] - \
2 * stripOffsetsLength - \
descriptionLength - softwareLength - dateLength)
else:
#it has to have length 4
fmt = st + 'HHI%ds' % softwareLength
outputIFD += struct.pack(fmt, TAG_SOFTWARE,
FIELD_TYPE_OUT['s'],
softwareLength,
softwarePackedString)
if date is not None:
fmt = st + 'HHII'
outputIFD += struct.pack(fmt, TAG_DATE,
FIELD_TYPE_OUT['s'],
dateLength,
info["stripOffsets"][0] - \
2 * stripOffsetsLength - \
descriptionLength - dateLength)
fmt = st + 'HHIHH'
outputIFD += struct.pack(fmt, TAG_SAMPLE_FORMAT,
FIELD_TYPE_OUT['H'],
1,
info["sampleFormat"], 0)
fmt = st + 'I'
outputIFD += struct.pack(fmt, 0)
if softwareLength > 4:
outputIFD += softwarePackedString
if date is not None:
outputIFD += datePackedString
if imageDescription is not None:
if descriptionLength > 4:
outputIFD += imageDescription
if stripOffsetsString is not None:
outputIFD += stripOffsetsString
outputIFD += stripByteCountsString
return outputIFD
if __name__ == "__main__":
filename = sys.argv[1]
dtype = numpy.uint16
if not os.path.exists(filename):
print("Testing file creation")
tif = TiffIO(filename, mode='wb+')
data = numpy.arange(10000).astype(dtype)
data.shape = 100, 100
tif.writeImage(data, info={'Title':'1st'})
tif = None
if os.path.exists(filename):
print("Testing image appending")
tif = TiffIO(filename, mode='rb+')
tif.writeImage((data * 2).astype(dtype), info={'Title':'2nd'})
tif = None
tif = TiffIO(filename)
print("Number of images = %d" % tif.getNumberOfImages())
for i in range(tif.getNumberOfImages()):
info = tif.getInfo(i)
for key in info:
if key not in ["colormap"]:
print("%s = %s" % (key, info[key]))
elif info['colormap'] is not None:
print("RED %s = %s" % (key, info[key][0:10, 0]))
print("GREEN %s = %s" % (key, info[key][0:10, 1]))
print("BLUE %s = %s" % (key, info[key][0:10, 2]))
data = tif.getImage(i)[0, 0:10]
print("data [0, 0:10] = ", data)