1
#/*##########################################################################
2
# Copyright (C) 2012 European Synchrotron Radiation Facility
4
# This file is part of the PyMca X-ray Fluorescence Toolkit developed at
5
# the ESRF by the Software group.
7
# This file is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU Lesser General Public License as published by the Free
9
# Software Foundation; either version 2 of the License, or (at your option)
12
# This file is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
17
#############################################################################*/
18
__author__ = "V.A. Sole - ESRF Data Analysis"
27
ALLOW_MULTIPLE_STRIPS = False
29
TAG_ID = { 256:"NumberOfColumns", # S or L ImageWidth
30
257:"NumberOfRows", # S or L ImageHeight
31
258:"BitsPerSample", # S Number of bits per component
32
259:"Compression", # SHORT (1 - NoCompression, ...
33
262:"PhotometricInterpretation", # SHORT (0 - WhiteIsZero, 1 -BlackIsZero, 2 - RGB, 3 - Palette color
34
270:"ImageDescription", # ASCII
35
273:"StripOffsets", # S or L, for each strip, the byte offset of the strip
36
278:"RowsPerStrip", # S or L, number of rows in each back may be not for the last
37
279:"StripByteCounts", # S or L, The number of bytes in the strip AFTER any compression
38
305:"Software", # ASCII
40
320:"Colormap", # Colormap of Palette-color Images
41
339:"SampleFormat", # SHORT Interpretation of data in each pixel
44
#TILES ARE TO BE SUPPORTED TOO ...
45
TAG_NUMBER_OF_COLUMNS = 256
46
TAG_NUMBER_OF_ROWS = 257
47
TAG_BITS_PER_SAMPLE = 258
48
TAG_PHOTOMETRIC_INTERPRETATION = 262
50
TAG_IMAGE_DESCRIPTION = 270
51
TAG_STRIP_OFFSETS = 273
52
TAG_ROWS_PER_STRIP = 278
53
TAG_STRIP_BYTE_COUNTS = 279
57
TAG_SAMPLE_FORMAT = 339
59
FIELD_TYPE = {1:('BYTE', "B"),
60
2:('ASCII', "s"), #string ending with binary zero
68
10:('SRATIONAL',"ii"),
72
FIELD_TYPE_OUT = { 'B': 1,
84
#sample formats (http://www.awaresystems.be/imaging/tiff/tiffflags/sampleformat.html)
85
SAMPLE_FORMAT_UINT = 1
87
SAMPLE_FORMAT_FLOAT = 3 #floating point
88
SAMPLE_FORMAT_VOID = 4 #undefined data, usually assumed UINT
89
SAMPLE_FORMAT_COMPLEXINT = 5
90
SAMPLE_FORMAT_COMPLEXIEEEFP = 6
95
def __init__(self, filename, mode=None, cache_length=20, mono_output=False):
100
if 'a' in mode.lower():
101
raise IOError("Mode %s makes no sense on TIFF files. Consider 'rb+'" % mode)
106
if hasattr(filename, "seek") and\
107
hasattr(filename, "read"):
111
#the b is needed for windows and python 3
112
fd = open(filename, mode)
115
self._initInternalVariables(fd)
116
self._maxImageCacheLength = cache_length
117
self._forceMonoOutput = mono_output
119
def _initInternalVariables(self, fd=None):
126
order = fd.read(2).decode()
129
#intel, little endian
131
self._structChar = '<'
133
#motorola, high endian
135
self._structChar = '>'
137
raise IOError("File is not a Mar CCD file, nor a TIFF file")
139
fortyTwo = struct.unpack(self._structChar+"H",a)[0]
141
raise IOError("Invalid TIFF version %d" % fortyTwo)
144
print("VALID TIFF VERSION")
145
if sys.byteorder != fileOrder:
150
if sys.byteorder == "little":
151
self._structChar = '<'
153
self._structChar = '>'
157
self._imageDataCacheIndex = []
158
self._imageDataCache = []
159
self._imageInfoCacheIndex = []
160
self._imageInfoCache = []
161
self.getImageFileDirectories(fd)
163
def __makeSureFileIsOpen(self):
164
if not self.fd.closed:
167
print("Reopening closed file")
168
fileName = self.fd.name
169
if self._access is None:
170
#we do not own the file
172
newFile = open(fileName,'rb')
174
newFile = open(fileName, self._access)
177
def __makeSureFileIsClosed(self):
178
if self._access is None:
179
#we do not own the file
181
print("Not closing not owned file")
184
if not self.fd.closed:
188
return self.__makeSureFileIsClosed()
190
def getNumberOfImages(self):
191
#update for the case someone has done anything?
193
return len(self._IFD)
195
def _updateIFD(self):
196
self.__makeSureFileIsOpen()
197
self.getImageFileDirectories()
198
self.__makeSureFileIsClosed()
200
def getImageFileDirectories(self, fd=None):
205
st = self._structChar
210
inStr = fd.read(struct.calcsize(fmt))
214
offsetToIFD = struct.unpack(fmt, inStr)[0]
216
print("Offset to first IFD = %d" % offsetToIFD)
217
while offsetToIFD != 0:
218
self._IFD.append(offsetToIFD)
222
numberOfDirectoryEntries = struct.unpack(fmt,fd.read(struct.calcsize(fmt)))[0]
224
print("Number of directory entries = %d" % numberOfDirectoryEntries)
227
fd.seek(offsetToIFD + 2 + 12 * numberOfDirectoryEntries)
228
offsetToIFD = struct.unpack(fmt,fd.read(struct.calcsize(fmt)))[0]
230
print("Next Offset to IFD = %d" % offsetToIFD)
233
print("Number of images found = %d" % nImages)
236
def _parseImageFileDirectory(self, nImage):
237
offsetToIFD = self._IFD[nImage]
238
st = self._structChar
242
numberOfDirectoryEntries = struct.unpack(fmt,fd.read(struct.calcsize(fmt)))[0]
244
print("Number of directory entries = %d" % numberOfDirectoryEntries)
251
for i in range(numberOfDirectoryEntries):
252
tagID, fieldType, nValues, valueOffset = struct.unpack(fmt, fd.read(12))
253
tagIDList.append(tagID)
254
fieldTypeList.append(fieldType)
255
nValuesList.append(nValues)
257
ftype, vfmt = FIELD_TYPE[fieldType]
258
if ftype not in ['ASCII', 'RATIONAL', 'SRATIONAL']:
260
actualValue = struct.unpack(vfmt, valueOffset[0: struct.calcsize(vfmt)])[0]
261
valueOffsetList.append(actualValue)
263
valueOffsetList.append(valueOffset)
264
elif (nValues < 5) and (fieldType == 2):
265
ftype, vfmt = FIELD_TYPE[fieldType]
266
vfmt = st + "%d%s" % (nValues,vfmt)
267
actualValue = struct.unpack(vfmt, valueOffset[0: struct.calcsize(vfmt)])[0]
268
valueOffsetList.append(actualValue)
270
valueOffsetList.append(valueOffset)
273
print("tagID = %s" % TAG_ID[tagID])
275
print("tagID = %d" % tagID)
276
print("fieldType = %s" % FIELD_TYPE[fieldType][0])
277
print("nValues = %d" % nValues)
279
# print("valueOffset = %s" % valueOffset)
280
return tagIDList, fieldTypeList, nValuesList, valueOffsetList
284
def _readIFDEntry(self, tag, tagIDList, fieldTypeList, nValuesList, valueOffsetList):
286
st = self._structChar
287
idx = tagIDList.index(tag)
288
nValues = nValuesList[idx]
290
ftype, vfmt = FIELD_TYPE[fieldTypeList[idx]]
291
vfmt = st + "%d%s" % (nValues, vfmt)
292
requestedBytes = struct.calcsize(vfmt)
294
output.append(valueOffsetList[idx])
295
elif requestedBytes < 5:
296
output.append(valueOffsetList[idx])
298
fd.seek(struct.unpack(st+"I", valueOffsetList[idx])[0])
299
output = struct.unpack(vfmt, fd.read(requestedBytes))
302
def getData(self, nImage, **kw):
303
if nImage >= len(self._IFD):
304
#update prior to raise an index error error
306
return self._readImage(nImage, **kw)
308
def getImage(self, nImage):
309
return self.getData(nImage)
311
def getInfo(self, nImage, **kw):
312
if nImage >= len(self._IFD):
313
#update prior to raise an index error error
315
# current = self._IFD[nImage]
316
return self._readInfo(nImage)
318
def _readInfo(self, nImage, close=True):
319
if nImage in self._imageInfoCacheIndex:
321
print("Reading info from cache")
322
return self._imageInfoCache[self._imageInfoCacheIndex.index(nImage)]
325
self.__makeSureFileIsOpen()
326
tagIDList, fieldTypeList, nValuesList, valueOffsetList = self._parseImageFileDirectory(nImage)
329
nColumns = valueOffsetList[tagIDList.index(TAG_NUMBER_OF_COLUMNS)]
330
nRows = valueOffsetList[tagIDList.index(TAG_NUMBER_OF_ROWS)]
333
idx = tagIDList.index(TAG_BITS_PER_SAMPLE)
334
nBits = valueOffsetList[idx]
335
if nValuesList[idx] != 1:
336
#this happens with RGB and friends, nBits is not a single value
337
nBits = self._readIFDEntry(TAG_BITS_PER_SAMPLE,
338
tagIDList, fieldTypeList, nValuesList, valueOffsetList)
341
if TAG_COLORMAP in tagIDList:
342
idx = tagIDList.index(TAG_COLORMAP)
343
tmpColormap = self._readIFDEntry(TAG_COLORMAP,
344
tagIDList, fieldTypeList, nValuesList, valueOffsetList)
345
if max(tmpColormap) > 255:
346
tmpColormap = numpy.array(tmpColormap, dtype=numpy.uint16)
347
tmpColormap = (tmpColormap/256.).astype(numpy.uint8)
349
tmpColormap = numpy.array(tmpColormap, dtype=numpy.uint8)
350
tmpColormap.shape = 3, -1
351
colormap = numpy.zeros((tmpColormap.shape[-1], 3), tmpColormap.dtype)
352
colormap[:,:] = tmpColormap.T
358
if TAG_SAMPLE_FORMAT in tagIDList:
359
sampleFormat = valueOffsetList[tagIDList.index(TAG_SAMPLE_FORMAT)]
362
sampleFormat = SAMPLE_FORMAT_VOID
367
if TAG_COMPRESSION in tagIDList:
368
compression_type = valueOffsetList[tagIDList.index(TAG_COMPRESSION)]
369
if compression_type == 1:
374
#photometric interpretation
376
if TAG_PHOTOMETRIC_INTERPRETATION in tagIDList:
377
interpretation = valueOffsetList[tagIDList.index(TAG_PHOTOMETRIC_INTERPRETATION)]
379
print("WARNING: Non standard TIFF. Photometric interpretation TAG missing")
381
if sys.version > '2.6':
382
helpString = eval('b""')
384
if TAG_IMAGE_DESCRIPTION in tagIDList:
385
imageDescription = self._readIFDEntry(TAG_IMAGE_DESCRIPTION,
386
tagIDList, fieldTypeList, nValuesList, valueOffsetList)
387
if type(imageDescription) in [type([1]), type((1,))]:
388
imageDescription =helpString.join(imageDescription)
390
imageDescription = "%d/%d" % (nImage+1, len(self._IFD))
392
if sys.version < '3.0':
393
defaultSoftware = "Unknown Software"
395
defaultSoftware = bytes("Unknown Software",
397
if TAG_SOFTWARE in tagIDList:
398
software = self._readIFDEntry(TAG_SOFTWARE,
399
tagIDList, fieldTypeList, nValuesList, valueOffsetList)
400
if type(software) in [type([1]), type((1,))]:
401
software =helpString.join(software)
403
software = defaultSoftware
405
if software == defaultSoftware:
407
if sys.version < '3.0':
408
if imageDescription.upper().startswith("IMAGEJ"):
409
software = imageDescription.split("=")[0]
411
tmpString = imageDescription.decode()
412
if tmpString.upper().startswith("IMAGEJ"):
413
software = bytes(tmpString.split("=")[0],
418
if TAG_DATE in tagIDList:
419
date = self._readIFDEntry(TAG_DATE,
420
tagIDList, fieldTypeList, nValuesList, valueOffsetList)
421
if type(date) in [type([1]), type((1,))]:
422
date =helpString.join(date)
424
date = "Unknown Date"
426
stripOffsets = self._readIFDEntry(TAG_STRIP_OFFSETS,
427
tagIDList, fieldTypeList, nValuesList, valueOffsetList)
428
if TAG_ROWS_PER_STRIP in tagIDList:
429
rowsPerStrip = self._readIFDEntry(TAG_ROWS_PER_STRIP,
430
tagIDList, fieldTypeList, nValuesList, valueOffsetList)[0]
433
print("WARNING: Non standard TIFF. Rows per strip TAG missing")
435
if TAG_STRIP_BYTE_COUNTS in tagIDList:
436
stripByteCounts = self._readIFDEntry(TAG_STRIP_BYTE_COUNTS,
437
tagIDList, fieldTypeList, nValuesList, valueOffsetList)
439
print("WARNING: Non standard TIFF. Strip byte counts TAG missing")
440
if hasattr(nBits, 'index'):
443
expectedSum += int(nRows * nColumns * n / 8)
445
expectedSum = int(nRows * nColumns * nBits / 8)
446
stripByteCounts = [expectedSum]
449
self.__makeSureFileIsClosed()
451
if self._forceMonoOutput and (interpretation > 1):
452
#color image but asked monochrome output
455
sampleFormat = SAMPLE_FORMAT_FLOAT
457
#we cannot rely on any cache in this case
465
info["nRows"] = nRows
466
info["nColumns"] = nColumns
467
info["nBits"] = nBits
468
info["compression"] = compression
469
info["compression_type"] = compression_type
470
info["imageDescription"] = imageDescription
471
info["stripOffsets"] = stripOffsets #This contains the file offsets to the data positions
472
info["rowsPerStrip"] = rowsPerStrip
473
info["stripByteCounts"] = stripByteCounts #bytes in strip since I do not support compression
474
info["software"] = software
476
info["colormap"] = colormap
477
info["sampleFormat"] = sampleFormat
478
info["photometricInterpretation"] = interpretation
480
if sys.version < '3.0':
483
testString = eval('b"PyMca"')
484
if software.startswith(testString):
485
#str to make sure python 2.x sees it as string and not unicode
486
if sys.version < '3.0':
487
descriptionString = imageDescription
489
descriptionString = str(imageDescription.decode())
490
#interpret the image description in terms of supplied
491
#information at writing time
492
items = descriptionString.split('=')
493
for i in range(int(len(items)/2)):
494
key = "%s" % items[i*2]
495
#get rid of the \n at the end of the value
496
value = "%s" % items[i*2+1][:-1]
497
infoDict[key] = value
498
info['info'] = infoDict
500
if (self._maxImageCacheLength > 0) and useInfoCache:
501
self._imageInfoCacheIndex.insert(0,nImage)
502
self._imageInfoCache.insert(0, info)
503
if len(self._imageInfoCacheIndex) > self._maxImageCacheLength:
504
self._imageInfoCacheIndex = self._imageInfoCacheIndex[:self._maxImageCacheLength]
505
self._imageInfoCache = self._imageInfoCache[:self._maxImageCacheLength]
508
def _readImage(self, nImage, **kw):
510
print("Reading image %d" % nImage)
515
rowMin = kw.get('rowMin', None)
516
rowMax = kw.get('rowMax', None)
517
if nImage in self._imageDataCacheIndex:
519
print("Reading image data from cache")
520
return self._imageDataCache[self._imageDataCacheIndex.index(nImage)]
522
self.__makeSureFileIsOpen()
523
if self._forceMonoOutput:
528
self._forceMonoOutput = False
529
info = self._readInfo(nImage, close=False)
530
self._forceMonoOutput = oldMono
532
self._forceMonoOutput = oldMono
534
compression = info['compression']
535
compression_type = info['compression_type']
537
if compression_type != 32773:
538
raise IOError("Compressed TIFF images not supported except packbits")
540
#PackBits compression
542
print("Using PackBits compression")
544
interpretation = info["photometricInterpretation"]
545
if interpretation == 2:
548
#raise IOError("RGB Image. Only grayscale images supported")
549
elif interpretation == 3:
552
#raise IOError("Palette-color Image. Only grayscale images supported")
553
elif interpretation > 2:
555
raise IOError("Only grayscale images supported")
557
nRows = info["nRows"]
558
nColumns = info["nColumns"]
559
nBits = info["nBits"]
560
colormap = info["colormap"]
561
sampleFormat = info["sampleFormat"]
570
rowMin = nRows - rowMin
573
rowMax = nRows - rowMax
576
txt = "Max Row smaller than Min Row. Reverse selection not supported"
577
raise NotImplemented(txt)
580
raise IndexError("Image only has %d rows" % nRows)
583
raise IndexError("Image only has %d rows" % nRows)
585
if sampleFormat == SAMPLE_FORMAT_FLOAT:
587
dtype = numpy.float32
589
dtype = numpy.float64
591
raise ValueError("Unsupported number of bits for a float: %d" % nBits)
592
elif sampleFormat in [SAMPLE_FORMAT_UINT, SAMPLE_FORMAT_VOID]:
593
if nBits in [8, (8, 8, 8), [8, 8, 8]]:
595
elif nBits in [16, (16, 16, 16), [16, 16, 16]]:
597
elif nBits in [32, (32, 32, 32), [32, 32, 32]]:
599
elif nBits in [64, (64, 64, 64), [64, 64, 64]]:
602
raise ValueError("Unsupported number of bits for unsigned int: %s" % (nBits,))
603
elif sampleFormat == SAMPLE_FORMAT_INT:
604
if nBits in [8, (8, 8, 8), [8, 8, 8]]:
606
elif nBits in [16, (16, 16, 16), [16, 16, 16]]:
608
elif nBits in [32, (32, 32, 32), [32, 32, 32]]:
610
elif nBits in [64, (64, 64, 64), [64, 64, 64]]:
613
raise ValueError("Unsupported number of bits for signed int: %s" % (nBits,))
615
raise ValueError("Unsupported combination. Bits = %s Format = %d" % (nBits, sampleFormat))
616
if hasattr(nBits, 'index'):
617
image = numpy.zeros((nRows, nColumns, len(nBits)), dtype=dtype)
618
elif colormap is not None:
619
#should I use colormap dtype?
620
image = numpy.zeros((nRows, nColumns, 3), dtype=dtype)
622
image = numpy.zeros((nRows, nColumns), dtype=dtype)
625
st = self._structChar
626
stripOffsets = info["stripOffsets"] #This contains the file offsets to the data positions
627
rowsPerStrip = info["rowsPerStrip"]
628
stripByteCounts = info["stripByteCounts"] #bytes in strip since I do not support compression
631
if len(stripOffsets) == 1:
632
bytesPerRow = int(stripByteCounts[0]/rowsPerStrip)
633
fd.seek(stripOffsets[0] + rowMin * bytesPerRow)
634
nBytes = (rowMax-rowMin+1) * bytesPerRow
636
readout = numpy.fromstring(fd.read(nBytes), dtype).byteswap()
638
readout = numpy.fromstring(fd.read(nBytes), dtype)
639
if hasattr(nBits, 'index'):
640
readout.shape = -1, nColumns, len(nBits)
641
elif info['colormap'] is not None:
642
readout = colormap[readout]
644
readout.shape = -1, nColumns
645
image[rowMin:rowMax+1, :] = readout
647
for i in range(len(stripOffsets)):
649
nRowsToRead = rowsPerStrip
650
rowEnd = int(min(rowStart+nRowsToRead, nRows))
652
rowStart += nRowsToRead
654
if (rowStart > rowMax):
657
fd.seek(stripOffsets[i])
658
#the amount of bytes to read
659
nBytes = stripByteCounts[i]
660
if compression_type == 32773:
662
bufferBytes = bytes()
669
tmpBuffer = fd.read(nBytes)
670
while readBytes < nBytes:
671
n = struct.unpack('b', tmpBuffer[readBytes:(readBytes+1)])[0]
674
#should I prevent reading more than the
675
#length of the chain? Let's python raise
677
bufferBytes += tmpBuffer[readBytes:\
681
bufferBytes += (-n+1) * tmpBuffer[readBytes:(readBytes+1)]
684
#if read -128 ignore the byte
687
readout = numpy.fromstring(bufferBytes, dtype).byteswap()
689
readout = numpy.fromstring(bufferBytes, dtype)
690
if hasattr(nBits, 'index'):
691
readout.shape = -1, nColumns, len(nBits)
692
elif info['colormap'] is not None:
693
readout = colormap[readout]
694
readout.shape = -1, nColumns, 3
696
readout.shape = -1, nColumns
697
image[rowStart:rowEnd, :] = readout
702
readout = numpy.fromstring(fd.read(nBytes), dtype).byteswap()
704
readout = numpy.fromstring(fd.read(nBytes), dtype)
705
if hasattr(nBits, 'index'):
706
readout.shape = -1, nColumns, len(nBits)
707
elif colormap is not None:
708
readout = colormap[readout]
709
readout.shape = -1, nColumns, 3
711
readout.shape = -1, nColumns
712
image[rowStart:rowEnd, :] = readout
715
readout = numpy.array(struct.unpack(st+"%df" % int(nBytes/4), fd.read(nBytes)),
717
if hasattr(nBits, 'index'):
718
readout.shape = -1, nColumns, len(nBits)
719
elif colormap is not None:
720
readout = colormap[readout]
721
readout.shape = -1, nColumns, 3
723
readout.shape = -1, nColumns
724
image[rowStart:rowEnd, :] = readout
725
rowStart += nRowsToRead
727
self.__makeSureFileIsClosed()
729
if len(image.shape) == 3:
731
if self._forceMonoOutput:
732
#color image, convert to monochrome
733
image = (image[:,:,0] * 0.114 +\
734
image[:,:,1] * 0.587 +\
735
image[:,:,2] * 0.299).astype(numpy.float32)
737
if (rowMin == 0) and (rowMax == (nRows-1)):
738
self._imageDataCacheIndex.insert(0,nImage)
739
self._imageDataCache.insert(0, image)
740
if len(self._imageDataCacheIndex) > self._maxImageCacheLength:
741
self._imageDataCacheIndex = self._imageDataCacheIndex[:self._maxImageCacheLength]
742
self._imageDataCache = self._imageDataCache[:self._maxImageCacheLength]
746
def writeImage(self, image0, info=None, software=None, date=None):
748
software = 'PyMca.TiffIO'
750
# date = time.ctime()
752
self.__makeSureFileIsOpen()
754
#prior to do anything, perform some tests
755
if not len(image0.shape):
756
raise ValueError("Empty image")
757
if len(image0.shape) == 1:
758
#get a different view
764
if image.dtype == numpy.float64:
765
image = image.astype(numpy.float32)
770
#we have to overwrite the file
771
self.__makeSureFileIsClosed()
773
if os.path.exists(name):
775
fd = open(name, mode='wb+')
776
self._initEmptyFile(fd)
780
self.__makeSureFileIsOpen()
782
fd.seek(0, os.SEEK_END)
783
endOfFile = fd.tell()
785
self._initEmptyFile(fd)
786
fd.seek(0, os.SEEK_END)
787
endOfFile = fd.tell()
789
#init internal variables
790
self._initInternalVariables(fd)
791
st = self._structChar
793
#get the image file directories
794
nImages = self.getImageFileDirectories()
796
print("File contains %d images" % nImages)
800
fd.write(struct.pack(fmt, endOfFile))
802
fd.seek(self._IFD[-1])
804
numberOfDirectoryEntries = struct.unpack(fmt,fd.read(struct.calcsize(fmt)))[0]
806
pos = self._IFD[-1] + 2 + 12 * numberOfDirectoryEntries
809
fd.write(struct.pack(fmt, endOfFile))
812
#and we can write at the end of the file, find out the file length
813
fd.seek(0, os.SEEK_END)
815
#get the description information from the input information
819
description = "%s" % ""
820
for key in info.keys():
821
description += "%s=%s\n" % (key, info[key])
823
#get the image file directory
824
outputIFD = self._getOutputIFD(image, description=description,
833
fd.write(image.byteswap().tostring())
835
fd.write(image.tostring())
839
self.__makeSureFileIsClosed()
841
def _initEmptyFile(self, fd=None):
844
if sys.byteorder == "little":
846
#intel, little endian
848
self._structChar = '<'
851
#motorola, high endian
853
self._structChar = '>'
854
st = self._structChar
855
if fileOrder == sys.byteorder:
860
if sys.version < '3.0':
861
fd.write(struct.pack(st+'2s', order))
862
fd.write(struct.pack(st+'H', 42))
863
fd.write(struct.pack(st+'I', 0))
865
fd.write(struct.pack(st+'2s', bytes(order,'utf-8')))
866
fd.write(struct.pack(st+'H', 42))
867
fd.write(struct.pack(st+'I', 0))
870
def _getOutputIFD(self, image, description=None, software=None, date=None):
871
#the tags have to be in order
873
#256:"NumberOfColumns", # S or L ImageWidth
874
#257:"NumberOfRows", # S or L ImageHeight
875
#258:"BitsPerSample", # S Number of bits per component
876
#259:"Compression", # SHORT (1 - NoCompression, ...
877
#262:"PhotometricInterpretation", # SHORT (0 - WhiteIsZero, 1 -BlackIsZero, 2 - RGB, 3 - Palette color
878
#270:"ImageDescription", # ASCII
879
#273:"StripOffsets", # S or L, for each strip, the byte offset of the strip
880
#278:"RowsPerStrip", # S or L, number of rows in each back may be not for the last
881
#279:"StripByteCounts", # S or L, The number of bytes in the strip AFTER any compression
882
#305:"Software", # ASCII
884
#339:"SampleFormat", # SHORT Interpretation of data in each pixel
886
nDirectoryEntries = 9
887
imageDescription = None
888
if description is not None:
889
descriptionLength = len(description)
890
while descriptionLength < 4:
891
description = description + " "
892
descriptionLength = len(description)
893
if sys.version >= '3.0':
894
description = bytes(description, 'utf-8')
895
elif type(description) != type(""):
897
description = description.decode('utf-8')
898
except UnicodeDecodeError:
900
description = description.decode('latin-1')
901
except UnicodeDecodeError:
902
description = "%s" % description
903
if sys.version > '2.6':
904
description=description.encode('utf-8', errors="ignore")
905
description = "%s" % description
906
descriptionLength = len(description)
907
imageDescription = struct.pack("%ds" % descriptionLength, description)
908
nDirectoryEntries += 1
911
if software is not None:
912
softwareLength = len(software)
913
while softwareLength < 4:
914
software = software + " "
915
softwareLength = len(software)
916
if sys.version >= '3.0':
917
software = bytes(software, 'utf-8')
918
softwarePackedString = struct.pack("%ds" % softwareLength, software)
919
nDirectoryEntries += 1
924
dateLength = len(date)
925
if sys.version >= '3.0':
926
date = bytes(date, 'utf-8')
927
datePackedString = struct.pack("%ds" % dateLength, date)
928
dateLength = len(datePackedString)
929
nDirectoryEntries += 1
933
nRows, nColumns = image.shape
935
bitsPerSample = int(dtype.str[-1]) * 8
937
#only uncompressed data
940
#interpretation, black is zero
944
if imageDescription is not None:
945
descriptionLength = len(imageDescription)
947
descriptionLength = 0
950
#we are putting them after the directory and the directory is
951
#at the end of the file
952
self.fd.seek(0, os.SEEK_END)
953
endOfFile = self.fd.tell()
959
if ALLOW_MULTIPLE_STRIPS:
960
#try to segment the image in several pieces
962
rowsPerStrip = int(nRows/4)
963
elif not (nRows % 10):
964
rowsPerStrip = int(nRows/10)
965
elif not (nRows % 8):
966
rowsPerStrip = int(nRows/8)
967
elif not (nRows % 4):
968
rowsPerStrip = int(nRows/4)
969
elif not (nRows % 2):
970
rowsPerStrip = int(nRows/2)
977
stripByteCounts = int(nColumns * rowsPerStrip * bitsPerSample / 8)
979
if descriptionLength > 4:
980
stripOffsets0 = endOfFile + dateLength + descriptionLength +\
981
2 + 12 * nDirectoryEntries + 4
983
stripOffsets0 = endOfFile + dateLength + \
984
2 + 12 * nDirectoryEntries + 4
986
if softwareLength > 4:
987
stripOffsets0 += softwareLength
989
stripOffsets = [stripOffsets0]
990
stripOffsetsLength = 0
991
stripOffsetsString = None
993
st = self._structChar
995
if rowsPerStrip != nRows:
996
nStripOffsets = int(nRows/rowsPerStrip)
998
stripOffsetsLength = struct.calcsize(fmt) * nStripOffsets
999
stripOffsets0 += stripOffsetsLength
1000
#the length for the stripByteCounts will be the same
1001
stripOffsets0 += stripOffsetsLength
1003
for i in range(nStripOffsets):
1004
value = stripOffsets0 + i * stripByteCounts
1005
stripOffsets.append(value)
1007
stripOffsetsString = struct.pack(fmt, value)
1008
stripByteCountsString = struct.pack(fmt, stripByteCounts)
1010
stripOffsetsString += struct.pack(fmt, value)
1011
stripByteCountsString += struct.pack(fmt, stripByteCounts)
1014
print("IMAGE WILL START AT %d" % stripOffsets[0])
1017
if dtype in [numpy.float32, numpy.float64] or\
1018
dtype.str[-2] == 'f':
1019
sampleFormat = SAMPLE_FORMAT_FLOAT
1020
elif dtype in [numpy.uint8, numpy.uint16, numpy.uint32, numpy.uint64]:
1021
sampleFormat = SAMPLE_FORMAT_UINT
1022
elif dtype in [numpy.int8, numpy.int16, numpy.int32, numpy.int64]:
1023
sampleFormat = SAMPLE_FORMAT_INT
1025
raise ValueError("Unsupported data type %s" % dtype)
1028
info["nColumns"] = nColumns
1029
info["nRows"] = nRows
1030
info["nBits"] = bitsPerSample
1031
info["compression"] = compression
1032
info["photometricInterpretation"] = interpretation
1033
info["stripOffsets"] = stripOffsets
1034
info["rowsPerStrip"] = rowsPerStrip
1035
info["stripByteCounts"] = stripByteCounts
1037
info["sampleFormat"] = sampleFormat
1040
if sys.version > '2.6':
1041
outputIFD = eval('b""')
1044
outputIFD += struct.pack(fmt, nDirectoryEntries)
1047
outputIFD += struct.pack(fmt, TAG_NUMBER_OF_COLUMNS,
1048
FIELD_TYPE_OUT['I'],
1051
outputIFD += struct.pack(fmt, TAG_NUMBER_OF_ROWS,
1052
FIELD_TYPE_OUT['I'],
1057
outputIFD += struct.pack(fmt, TAG_BITS_PER_SAMPLE,
1058
FIELD_TYPE_OUT['H'],
1062
outputIFD += struct.pack(fmt, TAG_COMPRESSION,
1063
FIELD_TYPE_OUT['H'],
1065
info["compression"],0)
1067
outputIFD += struct.pack(fmt, TAG_PHOTOMETRIC_INTERPRETATION,
1068
FIELD_TYPE_OUT['H'],
1070
info["photometricInterpretation"],0)
1072
if imageDescription is not None:
1073
descriptionLength = len(imageDescription)
1074
if descriptionLength > 4:
1076
outputIFD += struct.pack(fmt, TAG_IMAGE_DESCRIPTION,
1077
FIELD_TYPE_OUT['s'],
1079
info["stripOffsets"][0]-\
1080
2*stripOffsetsLength-\
1083
#it has to have length 4
1084
fmt = st + 'HHI%ds' % descriptionLength
1085
outputIFD += struct.pack(fmt, TAG_IMAGE_DESCRIPTION,
1086
FIELD_TYPE_OUT['s'],
1090
if len(stripOffsets) == 1:
1092
outputIFD += struct.pack(fmt, TAG_STRIP_OFFSETS,
1093
FIELD_TYPE_OUT['I'],
1095
info["stripOffsets"][0])
1098
outputIFD += struct.pack(fmt, TAG_STRIP_OFFSETS,
1099
FIELD_TYPE_OUT['I'],
1101
info["stripOffsets"][0]-2*stripOffsetsLength)
1104
outputIFD += struct.pack(fmt, TAG_ROWS_PER_STRIP,
1105
FIELD_TYPE_OUT['I'],
1107
info["rowsPerStrip"])
1109
if len(stripOffsets) == 1:
1111
outputIFD += struct.pack(fmt, TAG_STRIP_BYTE_COUNTS,
1112
FIELD_TYPE_OUT['I'],
1114
info["stripByteCounts"])
1117
outputIFD += struct.pack(fmt, TAG_STRIP_BYTE_COUNTS,
1118
FIELD_TYPE_OUT['I'],
1120
info["stripOffsets"][0]-stripOffsetsLength)
1122
if software is not None:
1123
if softwareLength > 4:
1125
outputIFD += struct.pack(fmt, TAG_SOFTWARE,
1126
FIELD_TYPE_OUT['s'],
1128
info["stripOffsets"][0]-\
1129
2*stripOffsetsLength-\
1130
descriptionLength-softwareLength-dateLength)
1132
#it has to have length 4
1133
fmt = st + 'HHI%ds' % softwareLength
1134
outputIFD += struct.pack(fmt, TAG_SOFTWARE,
1135
FIELD_TYPE_OUT['s'],
1137
softwarePackedString)
1139
if date is not None:
1141
outputIFD += struct.pack(fmt, TAG_DATE,
1142
FIELD_TYPE_OUT['s'],
1144
info["stripOffsets"][0]-\
1145
2*stripOffsetsLength-\
1146
descriptionLength-dateLength)
1149
outputIFD += struct.pack(fmt, TAG_SAMPLE_FORMAT,
1150
FIELD_TYPE_OUT['H'],
1152
info["sampleFormat"],0)
1154
outputIFD += struct.pack(fmt, 0)
1156
if softwareLength > 4:
1157
outputIFD += softwarePackedString
1159
if date is not None:
1160
outputIFD += datePackedString
1162
if imageDescription is not None:
1163
if descriptionLength > 4:
1164
outputIFD += imageDescription
1166
if stripOffsetsString is not None:
1167
outputIFD += stripOffsetsString
1168
outputIFD += stripByteCountsString
1173
if __name__ == "__main__":
1174
filename = sys.argv[1]
1175
dtype = numpy.uint16
1176
if not os.path.exists(filename):
1177
print("Testing file creation")
1178
tif = TiffIO(filename, mode = 'wb+')
1179
data = numpy.arange(10000).astype(dtype)
1180
data.shape = 100, 100
1181
tif.writeImage(data, info={'Title':'1st'})
1183
if os.path.exists(filename):
1184
print("Testing image appending")
1185
tif = TiffIO(filename, mode = 'rb+')
1186
tif.writeImage((data*2).astype(dtype), info={'Title':'2nd'})
1188
tif = TiffIO(filename)
1189
print("Number of images = %d" % tif.getNumberOfImages())
1190
for i in range(tif.getNumberOfImages()):
1191
info = tif.getInfo(i)
1193
if key not in ["colormap"]:
1194
print("%s = %s" % (key, info[key]))
1195
elif info['colormap'] is not None:
1196
print("RED %s = %s" % (key, info[key][0:10, 0]))
1197
print("GREEN %s = %s" % (key, info[key][0:10, 1]))
1198
print("BLUE %s = %s" % (key, info[key][0:10, 2]))
1199
data = tif.getImage(i)[0, 0:10]
1200
print("data [0, 0:10] = ", data)