Package fabio :: Module TiffIO
[hide private]
[frames] | no frames]

Source Code for Module fabio.TiffIO

   1  #/*########################################################################## 
   2  # Copyright (C) 2012 European Synchrotron Radiation Facility 
   3  # 
   4  # This file is part of the PyMca X-ray Fluorescence Toolkit developed at 
   5  # the ESRF by the Software group. 
   6  # 
   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)  
  10  # any later version. 
  11  # 
  12  # PyMca 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 
  15  # details. 
  16  # 
  17  #############################################################################*/ 
  18  __author__ = "V.A. Sole - ESRF Data Analysis" 
  19  __revision__ = 1501 
  20   
  21  import sys 
  22  import os 
  23  import struct 
  24  import numpy 
  25   
  26  DEBUG = 0 
  27  ALLOW_MULTIPLE_STRIPS = False 
  28   
  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 
  39              306:"Date", # ASCII 
  40              320:"Colormap", # Colormap of Palette-color Images  
  41              339:"SampleFormat", # SHORT Interpretation of data in each pixel 
  42              } 
  43   
  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 
  49  TAG_COMPRESSION = 259 
  50  TAG_IMAGE_DESCRIPTION = 270 
  51  TAG_STRIP_OFFSETS = 273 
  52  TAG_ROWS_PER_STRIP = 278 
  53  TAG_STRIP_BYTE_COUNTS = 279 
  54  TAG_SOFTWARE = 305 
  55  TAG_DATE = 306 
  56  TAG_COLORMAP = 320 
  57  TAG_SAMPLE_FORMAT = 339 
  58   
  59  FIELD_TYPE = {1:('BYTE', "B"), 
  60                 2:('ASCII', "s"), #string ending with binary zero 
  61                 3:('SHORT', "H"), 
  62                 4:('LONG', "I"), 
  63                 5:('RATIONAL', "II"), 
  64                 6:('SBYTE', "b"), 
  65                 7:('UNDEFINED', "B"), 
  66                 8:('SSHORT', "h"), 
  67                 9:('SLONG', "i"), 
  68                 10:('SRATIONAL', "ii"), 
  69                 11:('FLOAT', "f"), 
  70                 12:('DOUBLE', "d")} 
  71   
  72  FIELD_TYPE_OUT = { 'B':   1, 
  73                     's':   2, 
  74                     'H':   3, 
  75                     'I':   4, 
  76                     'II':  5, 
  77                     'b':   6, 
  78                     'h':   8, 
  79                     'i':   9, 
  80                     'ii': 10, 
  81                     'f':  11, 
  82                     'd':  12} 
  83   
  84  #sample formats (http://www.awaresystems.be/imaging/tiff/tiffflags/sampleformat.html) 
  85  SAMPLE_FORMAT_UINT = 1 
  86  SAMPLE_FORMAT_INT = 2 
  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 
  91   
  92   
  93   
94 -class TiffIO(object):
95 - def __init__(self, filename, mode=None, cache_length=20, mono_output=False):
96 if mode is None: 97 mode = 'rb' 98 if 'b' not in mode: 99 mode = mode + 'b' 100 if 'a' in mode.lower(): 101 raise IOError("Mode %s makes no sense on TIFF files. Consider 'rb+'" % mode) 102 if ('w' in mode): 103 if '+' not in mode: 104 mode += '+' 105 #if isinstance(filename, file): #does not work in python 3 106 if hasattr(filename, "seek"): 107 fd = filename 108 self._access = None 109 else: 110 #the b is needed for windows and python 3 111 fd = open(filename, mode) 112 self._access = mode 113 114 self._initInternalVariables(fd) 115 self._maxImageCacheLength = cache_length 116 self._forceMonoOutput = mono_output
117
118 - def _initInternalVariables(self, fd=None):
119 if fd is None: 120 fd = self.fd 121 else: 122 self.fd = fd 123 # read the order 124 fd.seek(0) 125 order = fd.read(2).decode() 126 if len(order): 127 if order == "II": 128 #intel, little endian 129 fileOrder = "little" 130 self._structChar = '<' 131 elif order == "MM": 132 #motorola, high endian 133 fileOrder = "big" 134 self._structChar = '>' 135 else: 136 raise IOError("File is not a Mar CCD file, nor a TIFF file") 137 a = fd.read(2) 138 fortyTwo = struct.unpack(self._structChar + "H", a)[0] 139 if fortyTwo != 42: 140 raise IOError("Invalid TIFF version %d" % fortyTwo) 141 else: 142 if DEBUG: 143 print("VALID TIFF VERSION") 144 if sys.byteorder != fileOrder: 145 swap = True 146 else: 147 swap = False 148 else: 149 if sys.byteorder == "little": 150 self._structChar = '<' 151 else: 152 self._structChar = '>' 153 swap = False 154 self._swap = swap 155 self._IFD = [] 156 self._imageDataCacheIndex = [] 157 self._imageDataCache = [] 158 self._imageInfoCacheIndex = [] 159 self._imageInfoCache = [] 160 self.getImageFileDirectories(fd)
161
162 - def __makeSureFileIsOpen(self):
163 if not self.fd.closed: 164 return 165 if DEBUG: 166 print("Reopening closed file") 167 fileName = self.fd.name 168 if self._access is None: 169 #we do not own the file 170 #open in read mode 171 newFile = open(fileName, 'rb') 172 else: 173 newFile = open(fileName, self._access) 174 self.fd = newFile
175
176 - def __makeSureFileIsClosed(self):
177 if self._access is None: 178 #we do not own the file 179 if DEBUG: 180 print("Not closing not owned file") 181 return 182 183 if not self.fd.closed: 184 self.fd.close()
185
186 - def getNumberOfImages(self):
187 #update for the case someone has done anything? 188 self._updateIFD() 189 return len(self._IFD)
190
191 - def _updateIFD(self):
195
196 - def getImageFileDirectories(self, fd=None):
197 if fd is None: 198 fd = self.fd 199 else: 200 self.fd = fd 201 st = self._structChar 202 fd.seek(4) 203 self._IFD = [] 204 nImages = 0 205 fmt = st + 'I' 206 inStr = fd.read(struct.calcsize(fmt)) 207 if not len(inStr): 208 offsetToIFD = 0 209 else: 210 offsetToIFD = struct.unpack(fmt, inStr)[0] 211 if DEBUG: 212 print("Offset to first IFD = %d" % offsetToIFD) 213 while offsetToIFD != 0: 214 self._IFD.append(offsetToIFD) 215 nImages += 1 216 fd.seek(offsetToIFD) 217 fmt = st + 'H' 218 numberOfDirectoryEntries = struct.unpack(fmt, fd.read(struct.calcsize(fmt)))[0] 219 if DEBUG: 220 print("Number of directory entries = %d" % numberOfDirectoryEntries) 221 222 fmt = st + 'I' 223 fd.seek(offsetToIFD + 2 + 12 * numberOfDirectoryEntries) 224 offsetToIFD = struct.unpack(fmt, fd.read(struct.calcsize(fmt)))[0] 225 if DEBUG: 226 print("Next Offset to IFD = %d" % offsetToIFD) 227 #offsetToIFD = 0 228 if DEBUG: 229 print("Number of images found = %d" % nImages) 230 return nImages
231
232 - def _parseImageFileDirectory(self, nImage):
233 offsetToIFD = self._IFD[nImage] 234 st = self._structChar 235 fd = self.fd 236 fd.seek(offsetToIFD) 237 fmt = st + 'H' 238 numberOfDirectoryEntries = struct.unpack(fmt, fd.read(struct.calcsize(fmt)))[0] 239 if DEBUG: 240 print("Number of directory entries = %d" % numberOfDirectoryEntries) 241 242 fmt = st + 'HHI4s' 243 tagIDList = [] 244 fieldTypeList = [] 245 nValuesList = [] 246 valueOffsetList = [] 247 for i in range(numberOfDirectoryEntries): 248 tagID, fieldType, nValues, valueOffset = struct.unpack(fmt, fd.read(12)) 249 tagIDList.append(tagID) 250 fieldTypeList.append(fieldType) 251 nValuesList.append(nValues) 252 if nValues == 1: 253 ftype, vfmt = FIELD_TYPE[fieldType] 254 if ftype not in ['ASCII', 'RATIONAL', 'SRATIONAL']: 255 vfmt = st + vfmt 256 actualValue = struct.unpack(vfmt, valueOffset[0: struct.calcsize(vfmt)])[0] 257 valueOffsetList.append(actualValue) 258 else: 259 valueOffsetList.append(valueOffset) 260 elif (nValues < 5) and (fieldType == 2): 261 ftype, vfmt = FIELD_TYPE[fieldType] 262 vfmt = st + "%d%s" % (nValues, vfmt) 263 actualValue = struct.unpack(vfmt, valueOffset[0: struct.calcsize(vfmt)])[0] 264 valueOffsetList.append(actualValue) 265 else: 266 valueOffsetList.append(valueOffset) 267 if DEBUG: 268 if tagID in TAG_ID: 269 print("tagID = %s" % TAG_ID[tagID]) 270 else: 271 print("tagID = %d" % tagID) 272 print("fieldType = %s" % FIELD_TYPE[fieldType][0]) 273 print("nValues = %d" % nValues) 274 #if nValues == 1: 275 # print("valueOffset = %s" % valueOffset) 276 return tagIDList, fieldTypeList, nValuesList, valueOffsetList
277 278 279
280 - def _readIFDEntry(self, tag, tagIDList, fieldTypeList, nValuesList, valueOffsetList):
281 fd = self.fd 282 st = self._structChar 283 idx = tagIDList.index(tag) 284 nValues = nValuesList[idx] 285 output = [] 286 ftype, vfmt = FIELD_TYPE[fieldTypeList[idx]] 287 vfmt = st + "%d%s" % (nValues, vfmt) 288 requestedBytes = struct.calcsize(vfmt) 289 if nValues == 1: 290 output.append(valueOffsetList[idx]) 291 elif requestedBytes < 5: 292 output.append(valueOffsetList[idx]) 293 else: 294 offset = fd.seek(struct.unpack(st + "I", valueOffsetList[idx])[0]) 295 output = struct.unpack(vfmt, fd.read(requestedBytes)) 296 return output
297
298 - def getData(self, nImage, **kw):
299 if nImage >= len(self._IFD): 300 #update prior to raise an index error error 301 self._updateIFD() 302 return self._readImage(nImage, **kw)
303
304 - def getImage(self, nImage):
305 return self.getData(nImage)
306
307 - def getInfo(self, nImage, **kw):
308 if nImage >= len(self._IFD): 309 #update prior to raise an index error error 310 self._updateIFD() 311 current = self._IFD[nImage] 312 return self._readInfo(nImage)
313
314 - def _readInfo(self, nImage, close=True):
315 if nImage in self._imageInfoCacheIndex: 316 if DEBUG: 317 print("Reading info from cache") 318 return self._imageInfoCache[self._imageInfoCacheIndex.index(nImage)] 319 320 #read the header 321 self.__makeSureFileIsOpen() 322 tagIDList, fieldTypeList, nValuesList, valueOffsetList = self._parseImageFileDirectory(nImage) 323 324 #rows and columns 325 nColumns = valueOffsetList[tagIDList.index(TAG_NUMBER_OF_COLUMNS)] 326 nRows = valueOffsetList[tagIDList.index(TAG_NUMBER_OF_ROWS)] 327 328 #bits per sample 329 idx = tagIDList.index(TAG_BITS_PER_SAMPLE) 330 nBits = valueOffsetList[idx] 331 if nValuesList[idx] != 1: 332 #this happens with RGB and friends, nBits is not a single value 333 nBits = self._readIFDEntry(TAG_BITS_PER_SAMPLE, 334 tagIDList, fieldTypeList, nValuesList, valueOffsetList) 335 336 337 if TAG_COLORMAP in tagIDList: 338 idx = tagIDList.index(TAG_COLORMAP) 339 tmpColormap = self._readIFDEntry(TAG_COLORMAP, 340 tagIDList, fieldTypeList, nValuesList, valueOffsetList) 341 if max(tmpColormap) > 255: 342 tmpColormap = numpy.array(tmpColormap, dtype=numpy.uint16) 343 tmpColormap = (tmpColormap / 256.).astype(numpy.uint8) 344 else: 345 tmpColormap = numpy.array(tmpColormap, dtype=numpy.uint8) 346 tmpColormap.shape = 3, -1 347 colormap = numpy.zeros((tmpColormap.shape[-1], 3), tmpColormap.dtype) 348 colormap[:, :] = tmpColormap.T 349 tmpColormap = None 350 else: 351 colormap = None 352 353 #sample format 354 if TAG_SAMPLE_FORMAT in tagIDList: 355 sampleFormat = valueOffsetList[tagIDList.index(TAG_SAMPLE_FORMAT)] 356 else: 357 #set to unknown 358 sampleFormat = SAMPLE_FORMAT_VOID 359 360 # compression 361 compression = False 362 compression_type = 1 363 if TAG_COMPRESSION in tagIDList: 364 compression_type = valueOffsetList[tagIDList.index(TAG_COMPRESSION)] 365 if compression_type == 1: 366 compression = False 367 else: 368 compression = True 369 370 #photometric interpretation 371 interpretation = 1 372 if TAG_PHOTOMETRIC_INTERPRETATION in tagIDList: 373 interpretation = valueOffsetList[tagIDList.index(TAG_PHOTOMETRIC_INTERPRETATION)] 374 else: 375 print("WARNING: Non standard TIFF. Photometric interpretation TAG missing") 376 helpString = "" 377 if sys.version > '2.6': 378 helpString = eval('b""') 379 380 if TAG_IMAGE_DESCRIPTION in tagIDList: 381 imageDescription = self._readIFDEntry(TAG_IMAGE_DESCRIPTION, 382 tagIDList, fieldTypeList, nValuesList, valueOffsetList) 383 if type(imageDescription) in [type([1]), type((1,))]: 384 imageDescription = helpString.join(imageDescription) 385 else: 386 imageDescription = "%d/%d" % (nImage + 1, len(self._IFD)) 387 388 if sys.version < '3.0': 389 defaultSoftware = "Unknown Software" 390 else: 391 defaultSoftware = bytes("Unknown Software", 392 encoding='utf-8') 393 if TAG_SOFTWARE in tagIDList: 394 software = self._readIFDEntry(TAG_SOFTWARE, 395 tagIDList, fieldTypeList, nValuesList, valueOffsetList) 396 if type(software) in [type([1]), type((1,))]: 397 software = helpString.join(software) 398 else: 399 software = defaultSoftware 400 401 if software == defaultSoftware: 402 try: 403 if sys.version < '3.0': 404 if imageDescription.upper().startswith("IMAGEJ"): 405 software = imageDescription.split("=")[0] 406 else: 407 tmpString = imageDescription.decode() 408 if tmpString.upper().startswith("IMAGEJ"): 409 software = bytes(tmpString.split("=")[0], 410 encoding='utf-8') 411 except: 412 pass 413 414 if TAG_DATE in tagIDList: 415 date = self._readIFDEntry(TAG_DATE, 416 tagIDList, fieldTypeList, nValuesList, valueOffsetList) 417 if type(date) in [type([1]), type((1,))]: 418 date = helpString.join(date) 419 else: 420 date = "Unknown Date" 421 422 stripOffsets = self._readIFDEntry(TAG_STRIP_OFFSETS, 423 tagIDList, fieldTypeList, nValuesList, valueOffsetList) 424 if TAG_ROWS_PER_STRIP in tagIDList: 425 rowsPerStrip = self._readIFDEntry(TAG_ROWS_PER_STRIP, 426 tagIDList, fieldTypeList, nValuesList, valueOffsetList)[0] 427 else: 428 rowsPerStrip = nRows 429 print("WARNING: Non standard TIFF. Rows per strip TAG missing") 430 431 if TAG_STRIP_BYTE_COUNTS in tagIDList: 432 stripByteCounts = self._readIFDEntry(TAG_STRIP_BYTE_COUNTS, 433 tagIDList, fieldTypeList, nValuesList, valueOffsetList) 434 else: 435 print("WARNING: Non standard TIFF. Strip byte counts TAG missing") 436 if hasattr(nBits, 'index'): 437 expectedSum = 0 438 for n in nBits: 439 expectedSum += int(nRows * nColumns * n / 8) 440 else: 441 expectedSum = int(nRows * nColumns * nBits / 8) 442 stripByteCounts = [expectedSum] 443 444 if close: 445 self.__makeSureFileIsClosed() 446 447 if self._forceMonoOutput and (interpretation > 1): 448 #color image but asked monochrome output 449 nBits = 32 450 colormap = None 451 sampleFormat = SAMPLE_FORMAT_FLOAT 452 interpretation = 1 453 #we cannot rely on any cache in this case 454 useInfoCache = False 455 if DEBUG: 456 print("FORCED MONO") 457 else: 458 useInfoCache = True 459 460 info = {} 461 info["nRows"] = nRows 462 info["nColumns"] = nColumns 463 info["nBits"] = nBits 464 info["compression"] = compression 465 info["compression_type"] = compression_type 466 info["imageDescription"] = imageDescription 467 info["stripOffsets"] = stripOffsets #This contains the file offsets to the data positions 468 info["rowsPerStrip"] = rowsPerStrip 469 info["stripByteCounts"] = stripByteCounts #bytes in strip since I do not support compression 470 info["software"] = software 471 info["date"] = date 472 info["colormap"] = colormap 473 info["sampleFormat"] = sampleFormat 474 info["photometricInterpretation"] = interpretation 475 infoDict = {} 476 if sys.version < '3.0': 477 testString = 'PyMca' 478 else: 479 testString = eval('b"PyMca"') 480 if software.startswith(testString): 481 #str to make sure python 2.x sees it as string and not unicode 482 if sys.version < '3.0': 483 descriptionString = imageDescription 484 else: 485 descriptionString = str(imageDescription.decode()) 486 #interpret the image description in terms of supplied 487 #information at writing time 488 items = descriptionString.split('=') 489 for i in range(int(len(items) / 2)): 490 key = "%s" % items[i * 2] 491 #get rid of the \n at the end of the value 492 value = "%s" % items[i * 2 + 1][:-1] 493 infoDict[key] = value 494 info['info'] = infoDict 495 496 if (self._maxImageCacheLength > 0) and useInfoCache: 497 self._imageInfoCacheIndex.insert(0, nImage) 498 self._imageInfoCache.insert(0, info) 499 if len(self._imageInfoCacheIndex) > self._maxImageCacheLength: 500 self._imageInfoCacheIndex = self._imageInfoCacheIndex[:self._maxImageCacheLength] 501 self._imageInfoCache = self._imageInfoCache[:self._maxImageCacheLength] 502 return info
503
504 - def _readImage(self, nImage, **kw):
505 if DEBUG: 506 print("Reading image %d" % nImage) 507 if 'close' in kw: 508 close = kw['close'] 509 else: 510 close = True 511 rowMin = kw.get('rowMin', None) 512 rowMax = kw.get('rowMax', None) 513 if nImage in self._imageDataCacheIndex: 514 if DEBUG: 515 print("Reading image data from cache") 516 return self._imageDataCache[self._imageDataCacheIndex.index(nImage)] 517 518 self.__makeSureFileIsOpen() 519 if self._forceMonoOutput: 520 oldMono = True 521 else: 522 oldMono = False 523 try: 524 self._forceMonoOutput = False 525 info = self._readInfo(nImage, close=False) 526 self._forceMonoOutput = oldMono 527 except: 528 self._forceMonoOutput = oldMono 529 raise 530 compression = info['compression'] 531 compression_type = info['compression_type'] 532 if compression: 533 if compression_type != 32773: 534 raise IOError("Compressed TIFF images not supported except packbits") 535 else: 536 #PackBits compression 537 if DEBUG: 538 print("Using PackBits compression") 539 540 interpretation = info["photometricInterpretation"] 541 if interpretation == 2: 542 #RGB 543 pass 544 #raise IOError("RGB Image. Only grayscale images supported") 545 elif interpretation == 3: 546 #Palette Color Image 547 pass 548 #raise IOError("Palette-color Image. Only grayscale images supported") 549 elif interpretation > 2: 550 #Palette Color Image 551 raise IOError("Only grayscale images supported") 552 553 nRows = info["nRows"] 554 nColumns = info["nColumns"] 555 nBits = info["nBits"] 556 colormap = info["colormap"] 557 sampleFormat = info["sampleFormat"] 558 559 if rowMin is None: 560 rowMin = 0 561 562 if rowMax is None: 563 rowMax = nRows - 1 564 565 if rowMin < 0: 566 rowMin = nRows - rowMin 567 568 if rowMax < 0: 569 rowMax = nRows - rowMax 570 571 if rowMax < rowMin: 572 txt = "Max Row smaller than Min Row. Reverse selection not supported" 573 raise NotImplemented(txt) 574 575 if rowMin >= nRows: 576 raise IndexError("Image only has %d rows" % nRows) 577 578 if rowMax >= nRows: 579 raise IndexError("Image only has %d rows" % nRows) 580 581 if sampleFormat == SAMPLE_FORMAT_FLOAT: 582 if nBits == 32: 583 dtype = numpy.float32 584 elif nBits == 64: 585 dtype = numpy.float64 586 else: 587 raise ValueError("Unsupported number of bits for a float: %d" % nBits) 588 elif sampleFormat in [SAMPLE_FORMAT_UINT, SAMPLE_FORMAT_VOID]: 589 if nBits in [8, (8, 8, 8), [8, 8, 8]]: 590 dtype = numpy.uint8 591 elif nBits in [16, (16, 16, 16), [16, 16, 16]]: 592 dtype = numpy.uint16 593 elif nBits in [32, (32, 32, 32), [32, 32, 32]]: 594 dtype = numpy.uint32 595 elif nBits in [64, (64, 64, 64), [64, 64, 64]]: 596 dtype = numpy.uint64 597 else: 598 raise ValueError("Unsupported number of bits for unsigned int: %s" % (nBits,)) 599 elif sampleFormat == SAMPLE_FORMAT_INT: 600 if nBits in [8, (8, 8, 8), [8, 8, 8]]: 601 dtype = numpy.int8 602 elif nBits in [16, (16, 16, 16), [16, 16, 16]]: 603 dtype = numpy.int16 604 elif nBits in [32, (32, 32, 32), [32, 32, 32]]: 605 dtype = numpy.int32 606 elif nBits in [64, (64, 64, 64), [64, 64, 64]]: 607 dtype = numpy.int64 608 else: 609 raise ValueError("Unsupported number of bits for signed int: %s" % (nBits,)) 610 else: 611 raise ValueError("Unsupported combination. Bits = %s Format = %d" % (nBits, sampleFormat)) 612 if hasattr(nBits, 'index'): 613 image = numpy.zeros((nRows, nColumns, len(nBits)), dtype=dtype) 614 elif colormap is not None: 615 #should I use colormap dtype? 616 image = numpy.zeros((nRows, nColumns, 3), dtype=dtype) 617 else: 618 image = numpy.zeros((nRows, nColumns), dtype=dtype) 619 620 fd = self.fd 621 st = self._structChar 622 stripOffsets = info["stripOffsets"] #This contains the file offsets to the data positions 623 rowsPerStrip = info["rowsPerStrip"] 624 stripByteCounts = info["stripByteCounts"] #bytes in strip since I do not support compression 625 626 rowStart = 0 627 if len(stripOffsets) == 1: 628 bytesPerRow = int(stripByteCounts[0] / rowsPerStrip) 629 fd.seek(stripOffsets[0] + rowMin * bytesPerRow) 630 nBytes = (rowMax - rowMin + 1) * bytesPerRow 631 if self._swap: 632 readout = numpy.fromstring(fd.read(nBytes), dtype).byteswap() 633 else: 634 readout = numpy.fromstring(fd.read(nBytes), dtype) 635 if hasattr(nBits, 'index'): 636 readout.shape = -1, nColumns, len(nBits) 637 elif info['colormap'] is not None: 638 readout = colormap[readout] 639 else: 640 readout.shape = -1, nColumns 641 image[rowMin:rowMax + 1, :] = readout 642 else: 643 for i in range(len(stripOffsets)): 644 #the amount of rows 645 nRowsToRead = rowsPerStrip 646 rowEnd = int(min(rowStart + nRowsToRead, nRows)) 647 if rowEnd < rowMin: 648 rowStart += nRowsToRead 649 continue 650 if (rowStart > rowMax): 651 break 652 #we are in position 653 fd.seek(stripOffsets[i]) 654 #the amount of bytes to read 655 nBytes = stripByteCounts[i] 656 if compression_type == 32773: 657 try: 658 bufferBytes = bytes() 659 except: 660 #python 2.5 ... 661 bufferBytes = "" 662 #packBits 663 readBytes = 0 664 #intermediate buffer 665 tmpBuffer = fd.read(nBytes) 666 while readBytes < nBytes: 667 n = struct.unpack('b', tmpBuffer[readBytes:(readBytes + 1)])[0] 668 readBytes += 1 669 if n >= 0: 670 #should I prevent reading more than the 671 #length of the chain? Let's python raise 672 #the exception... 673 bufferBytes += tmpBuffer[readBytes:\ 674 readBytes + (n + 1)] 675 readBytes += (n + 1) 676 elif n > -128: 677 bufferBytes += (-n + 1) * tmpBuffer[readBytes:(readBytes + 1)] 678 readBytes += 1 679 else: 680 #if read -128 ignore the byte 681 continue 682 if self._swap: 683 readout = numpy.fromstring(bufferBytes, dtype).byteswap() 684 else: 685 readout = numpy.fromstring(bufferBytes, dtype) 686 if hasattr(nBits, 'index'): 687 readout.shape = -1, nColumns, len(nBits) 688 elif info['colormap'] is not None: 689 readout = colormap[readout] 690 readout.shape = -1, nColumns, 3 691 else: 692 readout.shape = -1, nColumns 693 image[rowStart:rowEnd, :] = readout 694 else: 695 if 1: 696 #use numpy 697 if self._swap: 698 readout = numpy.fromstring(fd.read(nBytes), dtype).byteswap() 699 else: 700 readout = numpy.fromstring(fd.read(nBytes), dtype) 701 if hasattr(nBits, 'index'): 702 readout.shape = -1, nColumns, len(nBits) 703 elif colormap is not None: 704 readout = colormap[readout] 705 readout.shape = -1, nColumns, 3 706 else: 707 readout.shape = -1, nColumns 708 image[rowStart:rowEnd, :] = readout 709 else: 710 #using struct 711 readout = numpy.array(struct.unpack(st + "%df" % int(nBytes / 4), fd.read(nBytes)), 712 dtype=dtype) 713 if hasattr(nBits, 'index'): 714 readout.shape = -1, nColumns, len(nBits) 715 elif colormap is not None: 716 readout = colormap[readout] 717 readout.shape = -1, nColumns, 3 718 else: 719 readout.shape = -1, nColumns 720 image[rowStart:rowEnd, :] = readout 721 rowStart += nRowsToRead 722 if close: 723 self.__makeSureFileIsClosed() 724 725 if len(image.shape) == 3: 726 #color image 727 if self._forceMonoOutput: 728 #color image, convert to monochrome 729 image = (image[:, :, 0] * 0.114 + \ 730 image[:, :, 1] * 0.587 + \ 731 image[:, :, 2] * 0.299).astype(numpy.float32) 732 733 if (rowMin == 0) and (rowMax == (nRows - 1)): 734 self._imageDataCacheIndex.insert(0, nImage) 735 self._imageDataCache.insert(0, image) 736 if len(self._imageDataCacheIndex) > self._maxImageCacheLength: 737 self._imageDataCacheIndex = self._imageDataCacheIndex[:self._maxImageCacheLength] 738 self._imageDataCache = self._imageDataCache[:self._maxImageCacheLength] 739 740 return image
741
742 - def writeImage(self, image0, info=None, software=None, date=None):
743 if software is None: 744 software = 'PyMca.TiffIO' 745 #if date is None: 746 # date = time.ctime() 747 748 self.__makeSureFileIsOpen() 749 fd = self.fd 750 #prior to do anything, perform some tests 751 if not len(image0.shape): 752 raise ValueError("Empty image") 753 if len(image0.shape) == 1: 754 #get a different view 755 image = image0[:] 756 image.shape = 1, -1 757 else: 758 image = image0 759 760 if image.dtype == numpy.float64: 761 image = image.astype(numpy.float32) 762 fd.seek(0) 763 mode = fd.mode 764 name = fd.name 765 if 'w' in mode: 766 #we have to overwrite the file 767 self.__makeSureFileIsClosed() 768 fd = None 769 if os.path.exists(name): 770 os.remove(name) 771 fd = open(name, mode='wb+') 772 self._initEmptyFile(fd) 773 self.fd = fd 774 775 #read the file size 776 self.__makeSureFileIsOpen() 777 fd = self.fd 778 fd.seek(0, os.SEEK_END) 779 endOfFile = fd.tell() 780 if fd.tell() == 0: 781 self._initEmptyFile(fd) 782 fd.seek(0, os.SEEK_END) 783 endOfFile = fd.tell() 784 785 #init internal variables 786 self._initInternalVariables(fd) 787 st = self._structChar 788 789 #get the image file directories 790 nImages = self.getImageFileDirectories() 791 if DEBUG: 792 print("File contains %d images" % nImages) 793 if nImages == 0: 794 fd.seek(4) 795 fmt = st + 'I' 796 fd.write(struct.pack(fmt, endOfFile)) 797 else: 798 fd.seek(self._IFD[-1]) 799 fmt = st + 'H' 800 numberOfDirectoryEntries = struct.unpack(fmt, fd.read(struct.calcsize(fmt)))[0] 801 fmt = st + 'I' 802 pos = self._IFD[-1] + 2 + 12 * numberOfDirectoryEntries 803 fd.seek(pos) 804 fmt = st + 'I' 805 fd.write(struct.pack(fmt, endOfFile)) 806 fd.flush() 807 808 #and we can write at the end of the file, find out the file length 809 fd.seek(0, os.SEEK_END) 810 811 #get the description information from the input information 812 if info is None: 813 description = info 814 else: 815 description = "%s" % "" 816 for key in info.keys(): 817 description += "%s=%s\n" % (key, info[key]) 818 819 #get the image file directory 820 outputIFD = self._getOutputIFD(image, description=description, 821 software=software, 822 date=date) 823 824 #write the new IFD 825 fd.write(outputIFD) 826 827 #write the image 828 if self._swap: 829 fd.write(image.byteswap().tostring()) 830 else: 831 fd.write(image.tostring()) 832 833 fd.flush() 834 self.fd = fd 835 self.__makeSureFileIsClosed()
836
837 - def _initEmptyFile(self, fd=None):
838 if fd is None: 839 fd = self.fd 840 if sys.byteorder == "little": 841 order = "II" 842 #intel, little endian 843 fileOrder = "little" 844 self._structChar = '<' 845 else: 846 order = "MM" 847 #motorola, high endian 848 fileOrder = "big" 849 self._structChar = '>' 850 st = self._structChar 851 if fileOrder == sys.byteorder: 852 self._swap = False 853 else: 854 self._swap = True 855 fd.seek(0) 856 if sys.version < '3.0': 857 fd.write(struct.pack(st + '2s', order)) 858 fd.write(struct.pack(st + 'H', 42)) 859 fd.write(struct.pack(st + 'I', 0)) 860 else: 861 fd.write(struct.pack(st + '2s', bytes(order, 'utf-8'))) 862 fd.write(struct.pack(st + 'H', 42)) 863 fd.write(struct.pack(st + 'I', 0)) 864 fd.flush()
865
866 - def _getOutputIFD(self, image, description=None, software=None, date=None):
867 #the tags have to be in order 868 #the very minimum is 869 #256:"NumberOfColumns", # S or L ImageWidth 870 #257:"NumberOfRows", # S or L ImageHeight 871 #258:"BitsPerSample", # S Number of bits per component 872 #259:"Compression", # SHORT (1 - NoCompression, ... 873 #262:"PhotometricInterpretation", # SHORT (0 - WhiteIsZero, 1 -BlackIsZero, 2 - RGB, 3 - Palette color 874 #270:"ImageDescription", # ASCII 875 #273:"StripOffsets", # S or L, for each strip, the byte offset of the strip 876 #278:"RowsPerStrip", # S or L, number of rows in each back may be not for the last 877 #279:"StripByteCounts", # S or L, The number of bytes in the strip AFTER any compression 878 #305:"Software", # ASCII 879 #306:"Date", # ASCII 880 #339:"SampleFormat", # SHORT Interpretation of data in each pixel 881 882 nDirectoryEntries = 9 883 imageDescription = None 884 if description is not None: 885 descriptionLength = len(description) 886 while descriptionLength < 4: 887 description = description + " " 888 descriptionLength = len(description) 889 if sys.version >= '3.0': 890 description = bytes(description, 'utf-8') 891 elif type(description) != type(""): 892 try: 893 description = description.decode('utf-8') 894 except UnicodeDecodeError: 895 try: 896 description = description.decode('latin-1') 897 except UnicodeDecodeError: 898 description = "%s" % description 899 if sys.version > '2.6': 900 description = description.encode('utf-8', errors="ignore") 901 description = "%s" % description 902 descriptionLength = len(description) 903 imageDescription = struct.pack("%ds" % descriptionLength, description) 904 nDirectoryEntries += 1 905 906 #software 907 if software is not None: 908 softwareLength = len(software) 909 while softwareLength < 4: 910 software = software + " " 911 softwareLength = len(software) 912 if sys.version >= '3.0': 913 software = bytes(software, 'utf-8') 914 softwarePackedString = struct.pack("%ds" % softwareLength, software) 915 nDirectoryEntries += 1 916 else: 917 softwareLength = 0 918 919 if date is not None: 920 dateLength = len(date) 921 if sys.version >= '3.0': 922 date = bytes(date, 'utf-8') 923 datePackedString = struct.pack("%ds" % dateLength, date) 924 dateLength = len(datePackedString) 925 nDirectoryEntries += 1 926 else: 927 dateLength = 0 928 929 nRows, nColumns = image.shape 930 dtype = image.dtype 931 bitsPerSample = int(dtype.str[-1]) * 8 932 933 #only uncompressed data 934 compression = 1 935 936 #interpretation, black is zero 937 interpretation = 1 938 939 #image description 940 if imageDescription is not None: 941 descriptionLength = len(imageDescription) 942 else: 943 descriptionLength = 0 944 945 #strip offsets 946 #we are putting them after the directory and the directory is 947 #at the end of the file 948 self.fd.seek(0, os.SEEK_END) 949 endOfFile = self.fd.tell() 950 if endOfFile == 0: 951 #empty file 952 endOfFile = 8 953 954 #rows per strip 955 if ALLOW_MULTIPLE_STRIPS: 956 #try to segment the image in several pieces 957 if not (nRows % 4): 958 rowsPerStrip = int(nRows / 4) 959 elif not (nRows % 10): 960 rowsPerStrip = int(nRows / 10) 961 elif not (nRows % 8): 962 rowsPerStrip = int(nRows / 8) 963 elif not (nRows % 4): 964 rowsPerStrip = int(nRows / 4) 965 elif not (nRows % 2): 966 rowsPerStrip = int(nRows / 2) 967 else: 968 rowsPerStrip = nRows 969 else: 970 rowsPerStrip = nRows 971 972 #stripByteCounts 973 stripByteCounts = int(nColumns * rowsPerStrip * bitsPerSample / 8) 974 975 if descriptionLength > 4: 976 stripOffsets0 = endOfFile + dateLength + descriptionLength + \ 977 2 + 12 * nDirectoryEntries + 4 978 else: 979 stripOffsets0 = endOfFile + dateLength + \ 980 2 + 12 * nDirectoryEntries + 4 981 982 if softwareLength > 4: 983 stripOffsets0 += softwareLength 984 985 stripOffsets = [stripOffsets0] 986 stripOffsetsLength = 0 987 stripOffsetsString = None 988 989 st = self._structChar 990 991 if rowsPerStrip != nRows: 992 nStripOffsets = int(nRows / rowsPerStrip) 993 fmt = st + 'I' 994 stripOffsetsLength = struct.calcsize(fmt) * nStripOffsets 995 stripOffsets0 += stripOffsetsLength 996 #the length for the stripByteCounts will be the same 997 stripOffsets0 += stripOffsetsLength 998 stripOffsets = [] 999 for i in range(nStripOffsets): 1000 value = stripOffsets0 + i * stripByteCounts 1001 stripOffsets.append(value) 1002 if i == 0: 1003 stripOffsetsString = struct.pack(fmt, value) 1004 stripByteCountsString = struct.pack(fmt, stripByteCounts) 1005 else: 1006 stripOffsetsString += struct.pack(fmt, value) 1007 stripByteCountsString += struct.pack(fmt, stripByteCounts) 1008 1009 if DEBUG: 1010 print("IMAGE WILL START AT %d" % stripOffsets[0]) 1011 1012 #sample format 1013 if dtype in [numpy.float32, numpy.float64] or\ 1014 dtype.str[-2] == 'f': 1015 sampleFormat = SAMPLE_FORMAT_FLOAT 1016 elif dtype in [numpy.uint8, numpy.uint16, numpy.uint32, numpy.uint64]: 1017 sampleFormat = SAMPLE_FORMAT_UINT 1018 elif dtype in [numpy.int8, numpy.int16, numpy.int32, numpy.int64]: 1019 sampleFormat = SAMPLE_FORMAT_INT 1020 else: 1021 raise ValueError("Unsupported data type %s" % dtype) 1022 1023 info = {} 1024 info["nColumns"] = nColumns 1025 info["nRows"] = nRows 1026 info["nBits"] = bitsPerSample 1027 info["compression"] = compression 1028 info["photometricInterpretation"] = interpretation 1029 info["stripOffsets"] = stripOffsets 1030 info["rowsPerStrip"] = rowsPerStrip 1031 info["stripByteCounts"] = stripByteCounts 1032 info["date"] = date 1033 info["sampleFormat"] = sampleFormat 1034 1035 outputIFD = "" 1036 if sys.version > '2.6': 1037 outputIFD = eval('b""') 1038 1039 fmt = st + "H" 1040 outputIFD += struct.pack(fmt, nDirectoryEntries) 1041 1042 fmt = st + "HHII" 1043 outputIFD += struct.pack(fmt, TAG_NUMBER_OF_COLUMNS, 1044 FIELD_TYPE_OUT['I'], 1045 1, 1046 info["nColumns"]) 1047 outputIFD += struct.pack(fmt, TAG_NUMBER_OF_ROWS, 1048 FIELD_TYPE_OUT['I'], 1049 1, 1050 info["nRows"]) 1051 1052 fmt = st + 'HHIHH' 1053 outputIFD += struct.pack(fmt, TAG_BITS_PER_SAMPLE, 1054 FIELD_TYPE_OUT['H'], 1055 1, 1056 info["nBits"], 0) 1057 fmt = st + 'HHIHH' 1058 outputIFD += struct.pack(fmt, TAG_COMPRESSION, 1059 FIELD_TYPE_OUT['H'], 1060 1, 1061 info["compression"], 0) 1062 fmt = st + 'HHIHH' 1063 outputIFD += struct.pack(fmt, TAG_PHOTOMETRIC_INTERPRETATION, 1064 FIELD_TYPE_OUT['H'], 1065 1, 1066 info["photometricInterpretation"], 0) 1067 1068 if imageDescription is not None: 1069 descriptionLength = len(imageDescription) 1070 if descriptionLength > 4: 1071 fmt = st + 'HHII' 1072 outputIFD += struct.pack(fmt, TAG_IMAGE_DESCRIPTION, 1073 FIELD_TYPE_OUT['s'], 1074 descriptionLength, 1075 info["stripOffsets"][0] - \ 1076 2 * stripOffsetsLength - \ 1077 descriptionLength) 1078 else: 1079 #it has to have length 4 1080 fmt = st + 'HHI%ds' % descriptionLength 1081 outputIFD += struct.pack(fmt, TAG_IMAGE_DESCRIPTION, 1082 FIELD_TYPE_OUT['s'], 1083 descriptionLength, 1084 description) 1085 1086 if len(stripOffsets) == 1: 1087 fmt = st + 'HHII' 1088 outputIFD += struct.pack(fmt, TAG_STRIP_OFFSETS, 1089 FIELD_TYPE_OUT['I'], 1090 1, 1091 info["stripOffsets"][0]) 1092 else: 1093 fmt = st + 'HHII' 1094 outputIFD += struct.pack(fmt, TAG_STRIP_OFFSETS, 1095 FIELD_TYPE_OUT['I'], 1096 len(stripOffsets), 1097 info["stripOffsets"][0] - 2 * stripOffsetsLength) 1098 1099 fmt = st + 'HHII' 1100 outputIFD += struct.pack(fmt, TAG_ROWS_PER_STRIP, 1101 FIELD_TYPE_OUT['I'], 1102 1, 1103 info["rowsPerStrip"]) 1104 1105 if len(stripOffsets) == 1: 1106 fmt = st + 'HHII' 1107 outputIFD += struct.pack(fmt, TAG_STRIP_BYTE_COUNTS, 1108 FIELD_TYPE_OUT['I'], 1109 1, 1110 info["stripByteCounts"]) 1111 else: 1112 fmt = st + 'HHII' 1113 outputIFD += struct.pack(fmt, TAG_STRIP_BYTE_COUNTS, 1114 FIELD_TYPE_OUT['I'], 1115 len(stripOffsets), 1116 info["stripOffsets"][0] - stripOffsetsLength) 1117 1118 if software is not None: 1119 if softwareLength > 4: 1120 fmt = st + 'HHII' 1121 outputIFD += struct.pack(fmt, TAG_SOFTWARE, 1122 FIELD_TYPE_OUT['s'], 1123 softwareLength, 1124 info["stripOffsets"][0] - \ 1125 2 * stripOffsetsLength - \ 1126 descriptionLength - softwareLength - dateLength) 1127 else: 1128 #it has to have length 4 1129 fmt = st + 'HHI%ds' % softwareLength 1130 outputIFD += struct.pack(fmt, TAG_SOFTWARE, 1131 FIELD_TYPE_OUT['s'], 1132 softwareLength, 1133 softwarePackedString) 1134 1135 if date is not None: 1136 fmt = st + 'HHII' 1137 outputIFD += struct.pack(fmt, TAG_DATE, 1138 FIELD_TYPE_OUT['s'], 1139 dateLength, 1140 info["stripOffsets"][0] - \ 1141 2 * stripOffsetsLength - \ 1142 descriptionLength - dateLength) 1143 1144 fmt = st + 'HHIHH' 1145 outputIFD += struct.pack(fmt, TAG_SAMPLE_FORMAT, 1146 FIELD_TYPE_OUT['H'], 1147 1, 1148 info["sampleFormat"], 0) 1149 fmt = st + 'I' 1150 outputIFD += struct.pack(fmt, 0) 1151 1152 if softwareLength > 4: 1153 outputIFD += softwarePackedString 1154 1155 if date is not None: 1156 outputIFD += datePackedString 1157 1158 if imageDescription is not None: 1159 if descriptionLength > 4: 1160 outputIFD += imageDescription 1161 1162 if stripOffsetsString is not None: 1163 outputIFD += stripOffsetsString 1164 outputIFD += stripByteCountsString 1165 1166 return outputIFD
1167 1168 1169 if __name__ == "__main__": 1170 filename = sys.argv[1] 1171 dtype = numpy.uint16 1172 if not os.path.exists(filename): 1173 print("Testing file creation") 1174 tif = TiffIO(filename, mode='wb+') 1175 data = numpy.arange(10000).astype(dtype) 1176 data.shape = 100, 100 1177 tif.writeImage(data, info={'Title':'1st'}) 1178 tif = None 1179 if os.path.exists(filename): 1180 print("Testing image appending") 1181 tif = TiffIO(filename, mode='rb+') 1182 tif.writeImage((data * 2).astype(dtype), info={'Title':'2nd'}) 1183 tif = None 1184 tif = TiffIO(filename) 1185 print("Number of images = %d" % tif.getNumberOfImages()) 1186 for i in range(tif.getNumberOfImages()): 1187 info = tif.getInfo(i) 1188 for key in info: 1189 if key not in ["colormap"]: 1190 print("%s = %s" % (key, info[key])) 1191 elif info['colormap'] is not None: 1192 print("RED %s = %s" % (key, info[key][0:10, 0])) 1193 print("GREEN %s = %s" % (key, info[key][0:10, 1])) 1194 print("BLUE %s = %s" % (key, info[key][0:10, 2])) 1195 data = tif.getImage(i)[0, 0:10] 1196 print("data [0, 0:10] = ", data) 1197