Package qubx :: Module tifffile
[hide private]
[frames] | no frames]

Source Code for Module qubx.tifffile

   1  #!/usr/bin/env python 
   2  # -*- coding: utf-8 -*- 
   3  # tifffile.py 
   4   
   5  # Copyright (c) 2008-2014, Christoph Gohlke 
   6  # Copyright (c) 2008-2014, The Regents of the University of California 
   7  # Produced at the Laboratory for Fluorescence Dynamics 
   8  # All rights reserved. 
   9  # 
  10  # Redistribution and use in source and binary forms, with or without 
  11  # modification, are permitted provided that the following conditions are met: 
  12  # 
  13  # * Redistributions of source code must retain the above copyright 
  14  #   notice, this list of conditions and the following disclaimer. 
  15  # * Redistributions in binary form must reproduce the above copyright 
  16  #   notice, this list of conditions and the following disclaimer in the 
  17  #   documentation and/or other materials provided with the distribution. 
  18  # * Neither the name of the copyright holders nor the names of any 
  19  #   contributors may be used to endorse or promote products derived 
  20  #   from this software without specific prior written permission. 
  21  # 
  22  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
  23  # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
  24  # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
  25  # ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
  26  # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
  27  # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
  28  # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
  29  # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
  30  # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
  31  # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
  32  # POSSIBILITY OF SUCH DAMAGE. 
  33   
  34  """Read and write image data from and to TIFF files. 
  35   
  36  Image and meta-data can be read from TIFF, BigTIFF, OME-TIFF, STK, LSM, NIH, 
  37  ImageJ, MicroManager, FluoView, SEQ and GEL files. 
  38  Only a subset of the TIFF specification is supported, mainly uncompressed 
  39  and losslessly compressed 2**(0 to 6) bit integer, 16, 32 and 64-bit float, 
  40  grayscale and RGB(A) images, which are commonly used in bio-scientific imaging. 
  41  Specifically, reading JPEG/CCITT compressed image data or EXIF/IPTC/GPS/XMP 
  42  meta-data is not implemented. Only primary info records are read for STK, 
  43  FluoView, MicroManager, and NIH image formats. 
  44   
  45  TIFF, the Tagged Image File Format, is under the control of Adobe Systems. 
  46  BigTIFF allows for files greater than 4 GB. STK, LSM, FluoView, SEQ, GEL, 
  47  and OME-TIFF, are custom extensions defined by MetaMorph, Carl Zeiss 
  48  MicroImaging, Olympus, Media Cybernetics, Molecular Dynamics, and the Open 
  49  Microscopy Environment consortium respectively. 
  50   
  51  For command line usage run ``python tifffile.py --help`` 
  52   
  53  :Author: 
  54    `Christoph Gohlke <http://www.lfd.uci.edu/~gohlke/>`_ 
  55   
  56  :Organization: 
  57    Laboratory for Fluorescence Dynamics, University of California, Irvine 
  58   
  59  :Version: 2013.11.03 
  60   
  61  Requirements 
  62  ------------ 
  63  * `CPython 2.7 or 3.3 <http://www.python.org>`_ 
  64  * `Numpy 1.7 <http://www.numpy.org>`_ 
  65  * `Matplotlib 1.3 <http://www.matplotlib.org>`_  (optional for plotting) 
  66  * `Tifffile.c 2013.01.18 <http://www.lfd.uci.edu/~gohlke/>`_ 
  67    (recommended for faster decoding of PackBits and LZW encoded strings) 
  68   
  69  Notes 
  70  ----- 
  71  The API is not stable yet and might change between revisions. 
  72   
  73  Tested on little-endian platforms only. 
  74   
  75  Other Python packages and modules for reading bio-scientific TIFF files: 
  76  * `Imread <http://luispedro.org/software/imread>`_ 
  77  * `PyLibTiff <http://code.google.com/p/pylibtiff>`_ 
  78  * `SimpleITK <http://www.simpleitk.org>`_ 
  79  * `PyLSM <https://launchpad.net/pylsm>`_ 
  80  * `PyMca.TiffIO.py <http://pymca.sourceforge.net/>`_ 
  81  * `BioImageXD.Readers <http://www.bioimagexd.net/>`_ 
  82  * `Cellcognition.io <http://cellcognition.org/>`_ 
  83  * `CellProfiler.bioformats <http://www.cellprofiler.org/>`_ 
  84   
  85  Acknowledgements 
  86  ---------------- 
  87  *  Egor Zindy, University of Manchester, for cz_lsm_scan_info specifics. 
  88  *  Wim Lewis for a bug fix and some read_cz_lsm functions. 
  89  *  Hadrien Mary for help on reading MicroManager files. 
  90   
  91  References 
  92  ---------- 
  93  (1) TIFF 6.0 Specification and Supplements. Adobe Systems Incorporated. 
  94      http://partners.adobe.com/public/developer/tiff/ 
  95  (2) TIFF File Format FAQ. http://www.awaresystems.be/imaging/tiff/faq.html 
  96  (3) MetaMorph Stack (STK) Image File Format. 
  97      http://support.meta.moleculardevices.com/docs/t10243.pdf 
  98  (4) File Format Description - LSM 5xx Release 2.0. 
  99      http://ibb.gsf.de/homepage/karsten.rodenacker/IDL/Lsmfile.doc 
 100  (5) BioFormats. http://www.loci.wisc.edu/ome/formats.html 
 101  (6) The OME-TIFF format. 
 102      http://www.openmicroscopy.org/site/support/file-formats/ome-tiff 
 103  (7) TiffDecoder.java 
 104      http://rsbweb.nih.gov/ij/developer/source/ij/io/TiffDecoder.java.html 
 105  (8) UltraQuant(r) Version 6.0 for Windows Start-Up Guide. 
 106      http://www.ultralum.com/images%20ultralum/pdf/UQStart%20Up%20Guide.pdf 
 107  (9) Micro-Manager File Formats. 
 108      http://www.micro-manager.org/wiki/Micro-Manager_File_Formats 
 109   
 110  Examples 
 111  -------- 
 112  >>> data = numpy.random.rand(301, 219) 
 113  >>> imsave('temp.tif', data) 
 114  >>> image = imread('temp.tif') 
 115  >>> assert numpy.all(image == data) 
 116   
 117  >>> tif = TiffFile('test.tif') 
 118  >>> images = tif.asarray() 
 119  >>> image0 = tif[0].asarray() 
 120  >>> for page in tif: 
 121  ...     for tag in page.tags.values(): 
 122  ...         t = tag.name, tag.value 
 123  ...     image = page.asarray() 
 124  ...     if page.is_rgb: pass 
 125  ...     if page.is_palette: 
 126  ...         t = page.color_map 
 127  ...     if page.is_stk: 
 128  ...         t = page.mm_uic_tags.number_planes 
 129  ...     if page.is_lsm: 
 130  ...         t = page.cz_lsm_info 
 131  >>> tif.close() 
 132   
 133  """ 
 134   
 135  from __future__ import division, print_function 
 136   
 137  import sys 
 138  import os 
 139  import re 
 140  import glob 
 141  import math 
 142  import zlib 
 143  import time 
 144  import json 
 145  import struct 
 146  import warnings 
 147  import datetime 
 148  import collections 
 149  from fractions import Fraction 
 150  from xml.etree import cElementTree as ElementTree 
 151   
 152  import numpy 
 153   
 154  __version__ = '2013.11.03' 
 155  __docformat__ = 'restructuredtext en' 
 156  __all__ = ['imsave', 'imread', 'imshow', 'TiffFile', 'TiffSequence'] 
157 158 159 -def imsave(filename, data, photometric=None, planarconfig=None, 160 resolution=None, description=None, software='tifffile.py', 161 byteorder=None, bigtiff=False, compress=0, extratags=()):
162 """Write image data to TIFF file. 163 164 Image data are written in one stripe per plane. 165 Dimensions larger than 2 or 3 (depending on photometric mode and 166 planar configuration) are flattened and saved as separate pages. 167 The 'sample_format' and 'bits_per_sample' TIFF tags are derived from 168 the data type. 169 170 Parameters 171 ---------- 172 filename : str 173 Name of file to write. 174 data : array_like 175 Input image. The last dimensions are assumed to be image height, 176 width, and samples. 177 photometric : {'minisblack', 'miniswhite', 'rgb'} 178 The color space of the image data. 179 By default this setting is inferred from the data shape. 180 planarconfig : {'contig', 'planar'} 181 Specifies if samples are stored contiguous or in separate planes. 182 By default this setting is inferred from the data shape. 183 'contig': last dimension contains samples. 184 'planar': third last dimension contains samples. 185 resolution : (float, float) or ((int, int), (int, int)) 186 X and Y resolution in dots per inch as float or rational numbers. 187 description : str 188 The subject of the image. Saved with the first page only. 189 software : str 190 Name of the software used to create the image. 191 Saved with the first page only. 192 byteorder : {'<', '>'} 193 The endianness of the data in the file. 194 By default this is the system's native byte order. 195 bigtiff : bool 196 If True, the BigTIFF format is used. 197 By default the standard TIFF format is used for data less than 2000 MB. 198 compress : int 199 Values from 0 to 9 controlling the level of zlib compression. 200 If 0, data are written uncompressed (default). 201 extratags: sequence of tuples 202 Additional tags as [(code, dtype, count, value, writeonce)]. 203 code : int 204 The TIFF tag Id. 205 dtype : str 206 Data type of items in `value` in Python struct format. 207 One of B, s, H, I, 2I, b, h, i, f, d, Q, or q. 208 count : int 209 Number of data values. Not used for string values. 210 value : sequence 211 `Count` values compatible with `dtype`. 212 writeonce : bool 213 If True, the tag is written to the first page only. 214 215 Examples 216 -------- 217 >>> data = numpy.ones((2, 5, 3, 301, 219), 'float32') * 0.5 218 >>> imsave('temp.tif', data, compress=6) 219 220 >>> data = numpy.ones((5, 301, 219, 3), 'uint8') + 127 221 >>> value = u'{"shape": %s}' % str(list(data.shape)) 222 >>> imsave('temp.tif', data, extratags=[(270, 's', 0, value, True)]) 223 224 """ 225 assert(photometric in (None, 'minisblack', 'miniswhite', 'rgb')) 226 assert(planarconfig in (None, 'contig', 'planar')) 227 assert(byteorder in (None, '<', '>')) 228 assert(0 <= compress <= 9) 229 230 if byteorder is None: 231 byteorder = '<' if sys.byteorder == 'little' else '>' 232 233 data = numpy.asarray(data, dtype=byteorder+data.dtype.char, order='C') 234 data_shape = shape = data.shape 235 data = numpy.atleast_2d(data) 236 237 if not bigtiff and data.size * data.dtype.itemsize < 2000*2**20: 238 bigtiff = False 239 offset_size = 4 240 tag_size = 12 241 numtag_format = 'H' 242 offset_format = 'I' 243 val_format = '4s' 244 else: 245 bigtiff = True 246 offset_size = 8 247 tag_size = 20 248 numtag_format = 'Q' 249 offset_format = 'Q' 250 val_format = '8s' 251 252 # unify shape of data 253 samplesperpixel = 1 254 extrasamples = 0 255 if photometric is None: 256 if data.ndim > 2 and (shape[-3] in (3, 4) or shape[-1] in (3, 4)): 257 photometric = 'rgb' 258 else: 259 photometric = 'minisblack' 260 if photometric == 'rgb': 261 if len(shape) < 3: 262 raise ValueError("not a RGB(A) image") 263 if planarconfig is None: 264 planarconfig = 'planar' if shape[-3] in (3, 4) else 'contig' 265 if planarconfig == 'contig': 266 if shape[-1] not in (3, 4): 267 raise ValueError("not a contiguous RGB(A) image") 268 data = data.reshape((-1, 1) + shape[-3:]) 269 samplesperpixel = shape[-1] 270 else: 271 if shape[-3] not in (3, 4): 272 raise ValueError("not a planar RGB(A) image") 273 data = data.reshape((-1, ) + shape[-3:] + (1, )) 274 samplesperpixel = shape[-3] 275 if samplesperpixel == 4: 276 extrasamples = 1 277 elif planarconfig and len(shape) > 2: 278 if planarconfig == 'contig': 279 data = data.reshape((-1, 1) + shape[-3:]) 280 samplesperpixel = shape[-1] 281 else: 282 data = data.reshape((-1, ) + shape[-3:] + (1, )) 283 samplesperpixel = shape[-3] 284 extrasamples = samplesperpixel - 1 285 else: 286 planarconfig = None 287 # remove trailing 1s 288 while len(shape) > 2 and shape[-1] == 1: 289 shape = shape[:-1] 290 data = data.reshape((-1, 1) + shape[-2:] + (1, )) 291 292 shape = data.shape # (pages, planes, height, width, contig samples) 293 294 bytestr = bytes if sys.version[0] == '2' else ( 295 lambda x: bytes(x, 'utf-8') if isinstance(x, str) else x) 296 tifftypes = {'B': 1, 's': 2, 'H': 3, 'I': 4, '2I': 5, 'b': 6, 297 'h': 8, 'i': 9, 'f': 11, 'd': 12, 'Q': 16, 'q': 17} 298 tifftags = { 299 'new_subfile_type': 254, 'subfile_type': 255, 300 'image_width': 256, 'image_length': 257, 'bits_per_sample': 258, 301 'compression': 259, 'photometric': 262, 'fill_order': 266, 302 'document_name': 269, 'image_description': 270, 'strip_offsets': 273, 303 'orientation': 274, 'samples_per_pixel': 277, 'rows_per_strip': 278, 304 'strip_byte_counts': 279, 'x_resolution': 282, 'y_resolution': 283, 305 'planar_configuration': 284, 'page_name': 285, 'resolution_unit': 296, 306 'software': 305, 'datetime': 306, 'predictor': 317, 'color_map': 320, 307 'extra_samples': 338, 'sample_format': 339} 308 tags = [] # list of (code, ifdentry, ifdvalue, writeonce) 309 310 def pack(fmt, *val): 311 return struct.pack(byteorder+fmt, *val)
312 313 def addtag(code, dtype, count, value, writeonce=False): 314 # compute ifdentry and ifdvalue bytes from code, dtype, count, value 315 # append (code, ifdentry, ifdvalue, writeonce) to tags list 316 code = tifftags[code] if code in tifftags else int(code) 317 tifftype = tifftypes[dtype] # for aggregate values e.g. Rational such as x_resolution, capture the 318 rawcount = count # literal type and count... 319 if dtype not in tifftypes: 320 raise ValueError("unknown dtype %s" % dtype) 321 if dtype == 's': 322 value = bytestr(value) + b'\0' 323 count = rawcount = len(value) 324 value = (value, ) 325 if len(dtype) > 1: 326 count *= int(dtype[:-1]) # ...before changing to numpy-friendly values for internal use 327 dtype = dtype[-1] 328 ifdentry = [pack('HH', code, tifftype), 329 pack(offset_format, rawcount)] 330 ifdvalue = None 331 if count == 1: 332 if isinstance(value, (tuple, list)): 333 value = value[0] 334 ifdentry.append(pack(val_format, pack(dtype, value))) 335 elif struct.calcsize(dtype) * count <= offset_size: 336 ifdentry.append(pack(val_format, pack(str(count)+dtype, *value))) 337 else: 338 ifdentry.append(pack(offset_format, 0)) 339 ifdvalue = pack(str(count)+dtype, *value) 340 tags.append((code, b''.join(ifdentry), ifdvalue, writeonce)) 341 342 def rational(arg, max_denominator=1000000): 343 # return nominator and denominator from float or two integers 344 try: 345 f = Fraction.from_float(arg) 346 except TypeError: 347 f = Fraction(arg[0], arg[1]) 348 f = f.limit_denominator(max_denominator) 349 return f.numerator, f.denominator 350 351 if software: 352 addtag('software', 's', 0, software, writeonce=True) 353 if description: 354 addtag('image_description', 's', 0, description, writeonce=True) 355 elif shape != data_shape: 356 addtag('image_description', 's', 0, 357 "shape=(%s)" % (",".join('%i' % i for i in data_shape)), 358 writeonce=True) 359 addtag('datetime', 's', 0, 360 datetime.datetime.now().strftime("%Y:%m:%d %H:%M:%S"), 361 writeonce=True) 362 addtag('compression', 'H', 1, 32946 if compress else 1) 363 addtag('orientation', 'H', 1, 1) 364 addtag('image_width', 'I', 1, shape[-2]) 365 addtag('image_length', 'I', 1, shape[-3]) 366 addtag('new_subfile_type', 'I', 1, 0 if shape[0] == 1 else 2) 367 addtag('sample_format', 'H', 1, 368 {'u': 1, 'i': 2, 'f': 3, 'c': 6}[data.dtype.kind]) 369 addtag('photometric', 'H', 1, 370 {'miniswhite': 0, 'minisblack': 1, 'rgb': 2}[photometric]) 371 addtag('samples_per_pixel', 'H', 1, samplesperpixel) 372 if planarconfig: 373 addtag('planar_configuration', 'H', 1, 1 if planarconfig=='contig' 374 else 2) 375 addtag('bits_per_sample', 'H', samplesperpixel, 376 (data.dtype.itemsize * 8, ) * samplesperpixel) 377 else: 378 addtag('bits_per_sample', 'H', 1, data.dtype.itemsize * 8) 379 if extrasamples: 380 if photometric == 'rgb': 381 addtag('extra_samples', 'H', 1, 1) # alpha channel 382 else: 383 addtag('extra_samples', 'H', extrasamples, (0, ) * extrasamples) 384 if resolution: 385 addtag('x_resolution', '2I', 1, rational(resolution[0])) 386 addtag('y_resolution', '2I', 1, rational(resolution[1])) 387 addtag('resolution_unit', 'H', 1, 2) 388 addtag('rows_per_strip', 'I', 1, shape[-3]) 389 390 # use one strip per plane 391 strip_byte_counts = (data[0, 0].size * data.dtype.itemsize, ) * shape[1] 392 addtag('strip_byte_counts', offset_format, shape[1], strip_byte_counts) 393 addtag('strip_offsets', offset_format, shape[1], (0, ) * shape[1]) 394 395 # add extra tags from users 396 for t in extratags: 397 addtag(*t) 398 399 # the entries in an IFD must be sorted in ascending order by tag code 400 tags = sorted(tags, key=lambda x: x[0]) 401 402 with open(filename, 'wb') as fh: 403 seek = fh.seek 404 tell = fh.tell 405 406 def write(arg, *args): 407 fh.write(pack(arg, *args) if args else arg) 408 409 write({'<': b'II', '>': b'MM'}[byteorder]) 410 if bigtiff: 411 write('HHH', 43, 8, 0) 412 else: 413 write('H', 42) 414 ifd_offset = tell() 415 write(offset_format, 0) # first IFD 416 417 for pageindex in range(shape[0]): 418 # update pointer at ifd_offset 419 pos = tell() 420 seek(ifd_offset) 421 write(offset_format, pos) 422 seek(pos) 423 424 # write ifdentries 425 write(numtag_format, len(tags)) 426 tag_offset = tell() 427 write(b''.join(t[1] for t in tags)) 428 ifd_offset = tell() 429 write(offset_format, 0) # offset to next IFD 430 431 # write tag values and patch offsets in ifdentries, if necessary 432 for tagindex, tag in enumerate(tags): 433 if tag[2]: 434 pos = tell() 435 seek(tag_offset + tagindex*tag_size + offset_size + 4) 436 write(offset_format, pos) 437 seek(pos) 438 if tag[0] == 273: 439 strip_offsets_offset = pos 440 elif tag[0] == 279: 441 strip_byte_counts_offset = pos 442 write(tag[2]) 443 444 # write image data 445 data_offset = tell() 446 if compress: 447 strip_byte_counts = [] 448 for plane in data[pageindex]: 449 plane = zlib.compress(plane, compress) 450 strip_byte_counts.append(len(plane)) 451 fh.write(plane) 452 else: 453 # if this fails try update Python/numpy 454 data[pageindex].tofile(fh) 455 fh.flush() 456 457 # update strip_offsets and strip_byte_counts if necessary 458 pos = tell() 459 for tagindex, tag in enumerate(tags): 460 if tag[0] == 273: # strip_offsets 461 if tag[2]: 462 seek(strip_offsets_offset) 463 strip_offset = data_offset 464 for size in strip_byte_counts: 465 write(offset_format, strip_offset) 466 strip_offset += size 467 else: 468 seek(tag_offset + tagindex*tag_size + offset_size + 4) 469 write(offset_format, data_offset) 470 elif tag[0] == 279: # strip_byte_counts 471 if compress: 472 if tag[2]: 473 seek(strip_byte_counts_offset) 474 for size in strip_byte_counts: 475 write(offset_format, size) 476 else: 477 seek(tag_offset + tagindex*tag_size + 478 offset_size + 4) 479 write(offset_format, strip_byte_counts[0]) 480 break 481 seek(pos) 482 fh.flush() 483 # remove tags that should be written only once 484 if pageindex == 0: 485 tags = [t for t in tags if not t[-1]] 486
487 488 -def imread(files, *args, **kwargs):
489 """Return image data from TIFF file(s) as numpy array. 490 491 The first image series is returned if no arguments are provided. 492 493 Parameters 494 ---------- 495 files : str or list 496 File name, glob pattern, or list of file names. 497 key : int, slice, or sequence of page indices 498 Defines which pages to return as array. 499 series : int 500 Defines which series of pages in file to return as array. 501 multifile : bool 502 If True (default), OME-TIFF data may include pages from multiple files. 503 pattern : str 504 Regular expression pattern that matches axes names and indices in 505 file names. 506 507 Examples 508 -------- 509 >>> im = imread('test.tif', 0) 510 >>> im.shape 511 (256, 256, 4) 512 >>> ims = imread(['test.tif', 'test.tif']) 513 >>> ims.shape 514 (2, 256, 256, 4) 515 516 """ 517 kwargs_file = {} 518 if 'multifile' in kwargs: 519 kwargs_file['multifile'] = kwargs['multifile'] 520 del kwargs['multifile'] 521 else: 522 kwargs_file['multifile'] = True 523 kwargs_seq = {} 524 if 'pattern' in kwargs: 525 kwargs_seq['pattern'] = kwargs['pattern'] 526 del kwargs['pattern'] 527 528 if isinstance(files, basestring) and any(i in files for i in '?*'): 529 files = glob.glob(files) 530 if not files: 531 raise ValueError('no files found') 532 if len(files) == 1: 533 files = files[0] 534 535 if isinstance(files, basestring): 536 with TiffFile(files, **kwargs_file) as tif: 537 return tif.asarray(*args, **kwargs) 538 else: 539 with TiffSequence(files, **kwargs_seq) as imseq: 540 return imseq.asarray(*args, **kwargs)
541
542 543 -class lazyattr(object):
544 """Lazy object attribute whose value is computed on first access.""" 545 __slots__ = ('func', ) 546
547 - def __init__(self, func):
548 self.func = func
549
550 - def __get__(self, instance, owner):
551 if instance is None: 552 return self 553 value = self.func(instance) 554 if value is NotImplemented: 555 return getattr(super(owner, instance), self.func.__name__) 556 setattr(instance, self.func.__name__, value) 557 return value
558
559 560 -class TiffFile(object):
561 """Read image and meta-data from TIFF, STK, LSM, and FluoView files. 562 563 TiffFile instances must be closed using the close method, which is 564 automatically called when using the 'with' statement. 565 566 Attributes 567 ---------- 568 pages : list 569 All TIFF pages in file. 570 series : list of Records(shape, dtype, axes, TiffPages) 571 TIFF pages with compatible shapes and types. 572 micromanager_metadata: dict 573 Extra MicroManager non-TIFF metadata in the file, if exists. 574 575 All attributes are read-only. 576 577 Examples 578 -------- 579 >>> tif = TiffFile('test.tif') 580 ... try: 581 ... images = tif.asarray() 582 ... except Exception as e: 583 ... print(e) 584 ... finally: 585 ... tif.close() 586 587 """
588 - def __init__(self, arg, name=None, multifile=False):
589 """Initialize instance from file. 590 591 Parameters 592 ---------- 593 arg : str or open file 594 Name of file or open file object. 595 The file objects are closed in TiffFile.close(). 596 name : str 597 Human readable label of open file. 598 multifile : bool 599 If True, series may include pages from multiple files. 600 601 """ 602 if isinstance(arg, basestring): 603 filename = os.path.abspath(arg) 604 self._fh = open(filename, 'rb') 605 else: 606 filename = str(name) 607 self._fh = arg 608 609 self._fh.seek(0, 2) 610 self._fsize = self._fh.tell() 611 self._fh.seek(0) 612 self.fname = os.path.basename(filename) 613 self.fpath = os.path.dirname(filename) 614 self._tiffs = {self.fname: self} # cache of TiffFiles 615 self.offset_size = None 616 self.pages = [] 617 self._multifile = bool(multifile) 618 try: 619 self._fromfile() 620 except Exception: 621 self._fh.close() 622 raise
623
624 - def close(self):
625 """Close open file handle(s).""" 626 for tif in self._tiffs.values(): 627 if tif._fh: 628 tif._fh.close() 629 tif._fh = None 630 self._tiffs = {}
631
632 - def _fromfile(self):
633 """Read TIFF header and all page records from file.""" 634 self._fh.seek(0) 635 try: 636 self.byteorder = {b'II': '<', b'MM': '>'}[self._fh.read(2)] 637 except KeyError: 638 raise ValueError("not a valid TIFF file") 639 version = struct.unpack(self.byteorder+'H', self._fh.read(2))[0] 640 if version == 43: # BigTiff 641 self.offset_size, zero = struct.unpack(self.byteorder+'HH', 642 self._fh.read(4)) 643 if zero or self.offset_size != 8: 644 raise ValueError("not a valid BigTIFF file") 645 elif version == 42: 646 self.offset_size = 4 647 else: 648 raise ValueError("not a TIFF file") 649 self.pages = [] 650 while True: 651 try: 652 page = TiffPage(self) 653 self.pages.append(page) 654 except StopIteration: 655 break 656 if not self.pages: 657 raise ValueError("empty TIFF file") 658 659 if self.is_micromanager: 660 # MicroManager files contain metadata not stored in TIFF tags. 661 self.micromanager_metadata = read_micromanager_metadata(self._fh)
662 663 @lazyattr
664 - def series(self):
665 """Return series of TiffPage with compatible shape and properties.""" 666 series = [] 667 if self.is_ome: 668 series = self._omeseries() 669 elif self.is_fluoview: 670 dims = {b'X': 'X', b'Y': 'Y', b'Z': 'Z', b'T': 'T', 671 b'WAVELENGTH': 'C', b'TIME': 'T', b'XY': 'R', 672 b'EVENT': 'V', b'EXPOSURE': 'L'} 673 mmhd = list(reversed(self.pages[0].mm_header.dimensions)) 674 series = [Record( 675 axes=''.join(dims.get(i[0].strip().upper(), 'Q') 676 for i in mmhd if i[1] > 1), 677 shape=tuple(int(i[1]) for i in mmhd if i[1] > 1), 678 pages=self.pages, dtype=numpy.dtype(self.pages[0].dtype))] 679 elif self.is_lsm: 680 lsmi = self.pages[0].cz_lsm_info 681 axes = CZ_SCAN_TYPES[lsmi.scan_type] 682 if self.pages[0].is_rgb: 683 axes = axes.replace('C', '').replace('XY', 'XYC') 684 axes = axes[::-1] 685 shape = [getattr(lsmi, CZ_DIMENSIONS[i]) for i in axes] 686 pages = [p for p in self.pages if not p.is_reduced] 687 series = [Record(axes=axes, shape=shape, pages=pages, 688 dtype=numpy.dtype(pages[0].dtype))] 689 if len(pages) != len(self.pages): # reduced RGB pages 690 pages = [p for p in self.pages if p.is_reduced] 691 cp = 1 692 i = 0 693 while cp < len(pages) and i < len(shape)-2: 694 cp *= shape[i] 695 i += 1 696 shape = shape[:i] + list(pages[0].shape) 697 axes = axes[:i] + 'CYX' 698 series.append(Record(axes=axes, shape=shape, pages=pages, 699 dtype=numpy.dtype(pages[0].dtype))) 700 elif self.is_imagej: 701 shape = [] 702 axes = [] 703 ij = self.pages[0].imagej_tags 704 if 'frames' in ij: 705 shape.append(ij['frames']) 706 axes.append('T') 707 if 'slices' in ij: 708 shape.append(ij['slices']) 709 axes.append('Z') 710 if 'channels' in ij and not self.is_rgb: 711 shape.append(ij['channels']) 712 axes.append('C') 713 remain = len(self.pages) // (numpy.prod(shape) if shape else 1) 714 if remain > 1: 715 shape.append(remain) 716 axes.append('I') 717 shape.extend(self.pages[0].shape) 718 axes.extend(self.pages[0].axes) 719 axes = ''.join(axes) 720 series = [Record(pages=self.pages, shape=shape, axes=axes, 721 dtype=numpy.dtype(self.pages[0].dtype))] 722 elif self.is_nih: 723 series = [Record(pages=self.pages, 724 shape=(len(self.pages),) + self.pages[0].shape, 725 axes='I' + self.pages[0].axes, 726 dtype=numpy.dtype(self.pages[0].dtype))] 727 elif self.pages[0].is_shaped: 728 shape = self.pages[0].tags['image_description'].value[7:-1] 729 shape = tuple(int(i) for i in shape.split(b',')) 730 series = [Record(pages=self.pages, shape=shape, 731 axes='Q' * len(shape), 732 dtype=numpy.dtype(self.pages[0].dtype))] 733 734 if not series: 735 shapes = [] 736 pages = {} 737 for page in self.pages: 738 if not page.shape: 739 continue 740 shape = page.shape + (page.axes, 741 page.compression in TIFF_DECOMPESSORS) 742 if not shape in pages: 743 shapes.append(shape) 744 pages[shape] = [page] 745 else: 746 pages[shape].append(page) 747 series = [Record(pages=pages[s], 748 axes=(('I' + s[-2]) 749 if len(pages[s]) > 1 else s[-2]), 750 dtype=numpy.dtype(pages[s][0].dtype), 751 shape=((len(pages[s]), ) + s[:-2] 752 if len(pages[s]) > 1 else s[:-2])) 753 for s in shapes] 754 return series
755
756 - def asarray(self, key=None, series=None, memmap=False):
757 """Return image data of multiple TIFF pages as numpy array. 758 759 By default the first image series is returned. 760 761 Parameters 762 ---------- 763 key : int, slice, or sequence of page indices 764 Defines which pages to return as array. 765 series : int 766 Defines which series of pages to return as array. 767 memmap : bool 768 If True, use numpy.memmap to read arrays from file if possible. 769 770 """ 771 if key is None and series is None: 772 series = 0 773 if series is not None: 774 pages = self.series[series].pages 775 else: 776 pages = self.pages 777 778 if key is None: 779 pass 780 elif isinstance(key, int): 781 pages = [pages[key]] 782 elif isinstance(key, slice): 783 pages = pages[key] 784 elif isinstance(key, collections.Iterable): 785 pages = [pages[k] for k in key] 786 else: 787 raise TypeError("key must be an int, slice, or sequence") 788 789 if len(pages) == 1: 790 return pages[0].asarray(memmap=memmap) 791 elif self.is_nih: 792 result = numpy.vstack( 793 p.asarray(colormapped=False, squeeze=False, memmap=memmap) 794 for p in pages) 795 if pages[0].is_palette: 796 result = numpy.take(pages[0].color_map, result, axis=1) 797 result = numpy.swapaxes(result, 0, 1) 798 else: 799 if self.is_ome and any(p is None for p in pages): 800 firstpage = next(p for p in pages if p) 801 nopage = numpy.zeros_like(firstpage.asarray(memmap=memmap)) 802 result = numpy.vstack((p.asarray(memmap=memmap) if p else nopage) 803 for p in pages) 804 if key is None: 805 try: 806 result.shape = self.series[series].shape 807 except ValueError: 808 warnings.warn("failed to reshape %s to %s" % ( 809 result.shape, self.series[series].shape)) 810 result.shape = (-1,) + pages[0].shape 811 else: 812 result.shape = (-1,) + pages[0].shape 813 return result
814
815 - def _omeseries(self):
816 """Return image series in OME-TIFF file(s).""" 817 root = ElementTree.XML(self.pages[0].tags['image_description'].value) 818 uuid = root.attrib.get('UUID', None) 819 self._tiffs = {uuid: self} 820 modulo = {} 821 result = [] 822 for element in root: 823 if element.tag.endswith('BinaryOnly'): 824 warnings.warn("not an OME-TIFF master file") 825 break 826 if element.tag.endswith('StructuredAnnotations'): 827 for annot in element: 828 if not annot.attrib.get('Namespace', 829 '').endswith('modulo'): 830 continue 831 for value in annot: 832 for modul in value: 833 for along in modul: 834 if not along.tag[:-1].endswith('Along'): 835 continue 836 axis = along.tag[-1] 837 newaxis = along.attrib.get('Type', 'other') 838 newaxis = AXES_LABELS[newaxis] 839 if 'Start' in along.attrib: 840 labels = range( 841 int(along.attrib['Start']), 842 int(along.attrib['End']) + 1, 843 int(along.attrib.get('Step', 1))) 844 else: 845 labels = [label.text for label in along 846 if label.tag.endswith('Label')] 847 modulo[axis] = (newaxis, labels) 848 if not element.tag.endswith('Image'): 849 continue 850 for pixels in element: 851 if not pixels.tag.endswith('Pixels'): 852 continue 853 atr = pixels.attrib 854 axes = "".join(reversed(atr['DimensionOrder'])) 855 shape = list(int(atr['Size'+ax]) for ax in axes) 856 size = numpy.prod(shape[:-2]) 857 ifds = [None] * size 858 for data in pixels: 859 if not data.tag.endswith('TiffData'): 860 continue 861 atr = data.attrib 862 ifd = int(atr.get('IFD', 0)) 863 num = int(atr.get('NumPlanes', 1 if 'IFD' in atr else 0)) 864 num = int(atr.get('PlaneCount', num)) 865 idx = [int(atr.get('First'+ax, 0)) for ax in axes[:-2]] 866 idx = numpy.ravel_multi_index(idx, shape[:-2]) 867 for uuid in data: 868 if uuid.tag.endswith('UUID'): 869 if uuid.text not in self._tiffs: 870 if not self._multifile: 871 # abort reading multi file OME series 872 return [] 873 fn = uuid.attrib['FileName'] 874 try: 875 tf = TiffFile(os.path.join(self.fpath, fn)) 876 except (IOError, ValueError): 877 warnings.warn("failed to read %s" % fn) 878 break 879 self._tiffs[uuid.text] = tf 880 pages = self._tiffs[uuid.text].pages 881 try: 882 for i in range(num if num else len(pages)): 883 ifds[idx + i] = pages[ifd + i] 884 except IndexError: 885 warnings.warn("ome-xml: index out of range") 886 break 887 else: 888 pages = self.pages 889 try: 890 for i in range(num if num else len(pages)): 891 ifds[idx + i] = pages[ifd + i] 892 except IndexError: 893 warnings.warn("ome-xml: index out of range") 894 result.append(Record(axes=axes, shape=shape, pages=ifds, 895 dtype=numpy.dtype(ifds[0].dtype))) 896 897 for record in result: 898 for axis, (newaxis, labels) in modulo.items(): 899 i = record.axes.index(axis) 900 size = len(labels) 901 if record.shape[i] == size: 902 record.axes = record.axes.replace(axis, newaxis, 1) 903 else: 904 record.shape[i] //= size 905 record.shape.insert(i+1, size) 906 record.axes = record.axes.replace(axis, axis+newaxis, 1) 907 908 return result
909
910 - def __len__(self):
911 """Return number of image pages in file.""" 912 return len(self.pages)
913
914 - def __getitem__(self, key):
915 """Return specified page.""" 916 return self.pages[key]
917
918 - def __iter__(self):
919 """Return iterator over pages.""" 920 return iter(self.pages)
921
922 - def __str__(self):
923 """Return string containing information about file.""" 924 result = [ 925 self.fname.capitalize(), 926 format_size(self._fsize), 927 {'<': 'little endian', '>': 'big endian'}[self.byteorder]] 928 if self.is_bigtiff: 929 result.append("bigtiff") 930 if len(self.pages) > 1: 931 result.append("%i pages" % len(self.pages)) 932 if len(self.series) > 1: 933 result.append("%i series" % len(self.series)) 934 if len(self._tiffs) > 1: 935 result.append("%i files" % (len(self._tiffs))) 936 return ", ".join(result)
937
938 - def __enter__(self):
939 return self
940
941 - def __exit__(self, exc_type, exc_value, traceback):
942 self.close()
943 944 @lazyattr
945 - def fstat(self):
946 try: 947 return os.fstat(self._fh.fileno()) 948 except Exception: # io.UnsupportedOperation 949 return None
950 951 @lazyattr
952 - def is_bigtiff(self):
953 return self.offset_size != 4
954 955 @lazyattr
956 - def is_rgb(self):
957 return all(p.is_rgb for p in self.pages)
958 959 @lazyattr
960 - def is_palette(self):
961 return all(p.is_palette for p in self.pages)
962 963 @lazyattr
964 - def is_mdgel(self):
965 return any(p.is_mdgel for p in self.pages)
966 967 @lazyattr
968 - def is_mediacy(self):
969 return any(p.is_mediacy for p in self.pages)
970 971 @lazyattr
972 - def is_stk(self):
973 return all(p.is_stk for p in self.pages)
974 975 @lazyattr
976 - def is_lsm(self):
977 return self.pages[0].is_lsm
978 979 @lazyattr
980 - def is_imagej(self):
981 return self.pages[0].is_imagej
982 983 @lazyattr
984 - def is_micromanager(self):
985 return self.pages[0].is_micromanager
986 987 @lazyattr
988 - def is_nih(self):
989 return self.pages[0].is_nih
990 991 @lazyattr
992 - def is_fluoview(self):
993 return self.pages[0].is_fluoview
994 995 @lazyattr
996 - def is_ome(self):
997 return self.pages[0].is_ome
998
999 1000 -class TiffPage(object):
1001 """A TIFF image file directory (IFD). 1002 1003 Attributes 1004 ---------- 1005 index : int 1006 Index of page in file. 1007 dtype : str {TIFF_SAMPLE_DTYPES} 1008 Data type of image, colormapped if applicable. 1009 shape : tuple 1010 Dimensions of the image array in TIFF page, 1011 colormapped and with one alpha channel if applicable. 1012 axes : str 1013 Axes label codes: 1014 'X' width, 'Y' height, 'S' sample, 'P' plane, 'I' image series, 1015 'Z' depth, 'C' color|em-wavelength|channel, 'E' ex-wavelength|lambda, 1016 'T' time, 'R' region|tile, 'A' angle, 'F' phase, 'H' lifetime, 1017 'L' exposure, 'V' event, 'Q' unknown, '_' missing 1018 tags : TiffTags 1019 Dictionary of tags in page. 1020 Tag values are also directly accessible as attributes. 1021 color_map : numpy array 1022 Color look up table, if exists. 1023 mm_uic_tags: Record(dict) 1024 Consolidated MetaMorph mm_uic# tags, if exists. 1025 cz_lsm_scan_info: Record(dict) 1026 LSM scan info attributes, if exists. 1027 imagej_tags: Record(dict) 1028 Consolidated ImageJ description and metadata tags, if exists. 1029 1030 All attributes are read-only. 1031 1032 """
1033 - def __init__(self, parent):
1034 """Initialize instance from file.""" 1035 self.parent = parent 1036 self.index = len(parent.pages) 1037 self.shape = self._shape = () 1038 self.dtype = self._dtype = None 1039 self.axes = "" 1040 self.tags = TiffTags() 1041 1042 self._fromfile() 1043 self._process_tags()
1044
1045 - def _fromfile(self):
1046 """Read TIFF IFD structure and its tags from file. 1047 1048 File cursor must be at storage position of IFD offset and is left at 1049 offset to next IFD. 1050 1051 Raises StopIteration if offset (first bytes read) is 0. 1052 1053 """ 1054 fh = self.parent._fh 1055 byteorder = self.parent.byteorder 1056 offset_size = self.parent.offset_size 1057 1058 fmt = {4: 'I', 8: 'Q'}[offset_size] 1059 offset = struct.unpack(byteorder + fmt, fh.read(offset_size))[0] 1060 if not offset: 1061 raise StopIteration() 1062 1063 # read standard tags 1064 tags = self.tags 1065 fh.seek(offset) 1066 fmt, size = {4: ('H', 2), 8: ('Q', 8)}[offset_size] 1067 try: 1068 numtags = struct.unpack(byteorder + fmt, fh.read(size))[0] 1069 except Exception: 1070 warnings.warn("corrupted page list") 1071 raise StopIteration() 1072 1073 tagcode = 0 1074 for _ in range(numtags): 1075 try: 1076 tag = TiffTag(self.parent) 1077 except TiffTag.Error as e: 1078 warnings.warn(str(e)) 1079 finally: 1080 if tagcode > tag.code: 1081 warnings.warn("tags are not ordered by code") 1082 tagcode = tag.code 1083 if not tag.name in tags: 1084 tags[tag.name] = tag 1085 else: 1086 # some files contain multiple IFD with same code 1087 # e.g. MicroManager files contain two image_description 1088 for ext in ('_1', '_2', '_3'): 1089 name = tag.name + ext 1090 if not name in tags: 1091 tags[name] = tag 1092 break 1093 1094 # read LSM info subrecords 1095 if self.is_lsm: 1096 pos = fh.tell() 1097 for name, reader in CZ_LSM_INFO_READERS.items(): 1098 try: 1099 offset = self.cz_lsm_info['offset_'+name] 1100 except KeyError: 1101 continue 1102 if not offset: 1103 continue 1104 fh.seek(offset) 1105 try: 1106 setattr(self, 'cz_lsm_'+name, reader(fh, byteorder)) 1107 except ValueError: 1108 pass 1109 fh.seek(pos)
1110
1111 - def _process_tags(self):
1112 """Validate standard tags and initialize attributes. 1113 1114 Raise ValueError if tag values are not supported. 1115 1116 """ 1117 tags = self.tags 1118 for code, (name, default, dtype, count, validate) in TIFF_TAGS.items(): 1119 if not (name in tags or default is None): 1120 tags[name] = TiffTag(code, dtype=dtype, count=count, 1121 value=default, name=name) 1122 if name in tags and validate: 1123 try: 1124 if tags[name].count == 1: 1125 setattr(self, name, validate[tags[name].value]) 1126 else: 1127 setattr(self, name, tuple( 1128 validate[value] for value in tags[name].value)) 1129 except KeyError: 1130 raise ValueError("%s.value (%s) not supported" % 1131 (name, tags[name].value)) 1132 1133 tag = tags['bits_per_sample'] 1134 if tag.count == 1: 1135 self.bits_per_sample = tag.value 1136 else: 1137 value = tag.value[:self.samples_per_pixel] 1138 if any((v-value[0] for v in value)): 1139 self.bits_per_sample = value 1140 else: 1141 self.bits_per_sample = value[0] 1142 1143 tag = tags['sample_format'] 1144 if tag.count == 1: 1145 self.sample_format = TIFF_SAMPLE_FORMATS[tag.value] 1146 else: 1147 value = tag.value[:self.samples_per_pixel] 1148 if any((v-value[0] for v in value)): 1149 self.sample_format = [TIFF_SAMPLE_FORMATS[v] for v in value] 1150 else: 1151 self.sample_format = TIFF_SAMPLE_FORMATS[value[0]] 1152 1153 if not 'photometric' in tags: 1154 self.photometric = None 1155 1156 if 'image_length' in tags: 1157 self.strips_per_image = int(math.floor( 1158 float(self.image_length + self.rows_per_strip - 1) / 1159 self.rows_per_strip)) 1160 else: 1161 self.strips_per_image = 0 1162 1163 key = (self.sample_format, self.bits_per_sample) 1164 self.dtype = self._dtype = TIFF_SAMPLE_DTYPES.get(key, None) 1165 1166 if self.is_imagej: 1167 # consolidate imagej meta data 1168 if 'image_description_1' in self.tags: # MicroManager 1169 adict = imagej_description(tags['image_description_1'].value) 1170 else: 1171 adict = imagej_description(tags['image_description'].value) 1172 if 'imagej_metadata' in tags: 1173 try: 1174 adict.update(imagej_metadata( 1175 tags['imagej_metadata'].value, 1176 tags['imagej_byte_counts'].value, 1177 self.parent.byteorder)) 1178 except Exception as e: 1179 warnings.warn(str(e)) 1180 self.imagej_tags = Record(adict) 1181 1182 if not 'image_length' in self.tags or not 'image_width' in self.tags: 1183 # some GEL file pages are missing image data 1184 self.image_length = 0 1185 self.image_width = 0 1186 self.strip_offsets = 0 1187 self._shape = () 1188 self.shape = () 1189 self.axes = '' 1190 1191 if self.is_palette: 1192 self.dtype = self.tags['color_map'].dtype[1] 1193 self.color_map = numpy.array(self.color_map, self.dtype) 1194 dmax = self.color_map.max() 1195 if dmax < 256: 1196 self.dtype = numpy.uint8 1197 self.color_map = self.color_map.astype(self.dtype) 1198 #else: 1199 # self.dtype = numpy.uint8 1200 # self.color_map >>= 8 1201 # self.color_map = self.color_map.astype(self.dtype) 1202 self.color_map.shape = (3, -1) 1203 1204 if self.is_stk: 1205 # consolidate mm_uci tags 1206 planes = tags['mm_uic2'].count 1207 self.mm_uic_tags = Record(tags['mm_uic2'].value) 1208 for key in ('mm_uic3', 'mm_uic4', 'mm_uic1'): 1209 if key in tags: 1210 self.mm_uic_tags.update(tags[key].value) 1211 if self.planar_configuration == 'contig': 1212 self._shape = (planes, 1, self.image_length, self.image_width, 1213 self.samples_per_pixel) 1214 self.shape = tuple(self._shape[i] for i in (0, 2, 3, 4)) 1215 self.axes = 'PYXS' 1216 else: 1217 self._shape = (planes, self.samples_per_pixel, 1218 self.image_length, self.image_width, 1) 1219 self.shape = self._shape[:4] 1220 self.axes = 'PSYX' 1221 if self.is_palette and (self.color_map.shape[1] 1222 >= 2**self.bits_per_sample): 1223 self.shape = (3, planes, self.image_length, self.image_width) 1224 self.axes = 'CPYX' 1225 else: 1226 warnings.warn("palette cannot be applied") 1227 self.is_palette = False 1228 elif self.is_palette: 1229 samples = 1 1230 if 'extra_samples' in self.tags: 1231 samples += len(self.extra_samples) 1232 if self.planar_configuration == 'contig': 1233 self._shape = ( 1234 1, 1, self.image_length, self.image_width, samples) 1235 else: 1236 self._shape = ( 1237 1, samples, self.image_length, self.image_width, 1) 1238 if self.color_map.shape[1] >= 2**self.bits_per_sample: 1239 self.shape = (3, self.image_length, self.image_width) 1240 self.axes = 'CYX' 1241 else: 1242 warnings.warn("palette cannot be applied") 1243 self.is_palette = False 1244 self.shape = (self.image_length, self.image_width) 1245 self.axes = 'YX' 1246 elif self.is_rgb or self.samples_per_pixel > 1: 1247 if self.planar_configuration == 'contig': 1248 self._shape = (1, 1, self.image_length, self.image_width, 1249 self.samples_per_pixel) 1250 self.shape = (self.image_length, self.image_width, 1251 self.samples_per_pixel) 1252 self.axes = 'YXS' 1253 else: 1254 self._shape = (1, self.samples_per_pixel, self.image_length, 1255 self.image_width, 1) 1256 self.shape = self._shape[1:-1] 1257 self.axes = 'SYX' 1258 if self.is_rgb and 'extra_samples' in self.tags: 1259 extra_samples = self.extra_samples 1260 if self.tags['extra_samples'].count == 1: 1261 extra_samples = (extra_samples, ) 1262 for exs in extra_samples: 1263 if exs in ('unassalpha', 'assocalpha', 'unspecified'): 1264 if self.planar_configuration == 'contig': 1265 self.shape = self.shape[:2] + (4,) 1266 else: 1267 self.shape = (4,) + self.shape[1:] 1268 break 1269 else: 1270 self._shape = (1, 1, self.image_length, self.image_width, 1) 1271 self.shape = self._shape[2:4] 1272 self.axes = 'YX' 1273 1274 if not self.compression and not 'strip_byte_counts' in tags: 1275 self.strip_byte_counts = numpy.prod(self.shape) * ( 1276 self.bits_per_sample // 8)
1277
1278 - def asarray(self, squeeze=True, colormapped=True, rgbonly=True, 1279 memmap=False):
1280 """Read image data from file and return as numpy array. 1281 1282 Raise ValueError if format is unsupported. 1283 If any argument is False, the shape of the returned array might be 1284 different from the page shape. 1285 1286 Parameters 1287 ---------- 1288 squeeze : bool 1289 If True, all length-1 dimensions (except X and Y) are 1290 squeezed out from result. 1291 colormapped : bool 1292 If True, color mapping is applied for palette-indexed images. 1293 rgbonly : bool 1294 If True, return RGB(A) image without additional extra samples. 1295 memmap : bool 1296 If True, use numpy.memmap to read array if possible. 1297 1298 """ 1299 fh = self.parent._fh 1300 if not fh: 1301 raise IOError("TIFF file is not open") 1302 if self.dtype is None: 1303 raise ValueError("data type not supported: %s%i" % ( 1304 self.sample_format, self.bits_per_sample)) 1305 if self.compression not in TIFF_DECOMPESSORS: 1306 raise ValueError("cannot decompress %s" % self.compression) 1307 if ('ycbcr_subsampling' in self.tags 1308 and self.tags['ycbcr_subsampling'].value not in (1, (1, 1))): 1309 raise ValueError("YCbCr subsampling not supported") 1310 tag = self.tags['sample_format'] 1311 if tag.count != 1 and any((i-tag.value[0] for i in tag.value)): 1312 raise ValueError("sample formats don't match %s" % str(tag.value)) 1313 1314 dtype = self._dtype 1315 shape = self._shape 1316 1317 if not shape: 1318 return None 1319 1320 image_width = self.image_width 1321 image_length = self.image_length 1322 typecode = self.parent.byteorder + dtype 1323 bits_per_sample = self.bits_per_sample 1324 byteorder_is_native = ({'big': '>', 'little': '<'}[sys.byteorder] == 1325 self.parent.byteorder) 1326 1327 if self.is_tiled: 1328 if 'tile_offsets' in self.tags: 1329 byte_counts = self.tile_byte_counts 1330 offsets = self.tile_offsets 1331 else: 1332 byte_counts = self.strip_byte_counts 1333 offsets = self.strip_offsets 1334 tile_width = self.tile_width 1335 tile_length = self.tile_length 1336 tw = (image_width + tile_width - 1) // tile_width 1337 tl = (image_length + tile_length - 1) // tile_length 1338 shape = shape[:-3] + (tl*tile_length, tw*tile_width, shape[-1]) 1339 tile_shape = (tile_length, tile_width, shape[-1]) 1340 runlen = tile_width 1341 else: 1342 byte_counts = self.strip_byte_counts 1343 offsets = self.strip_offsets 1344 runlen = image_width 1345 1346 try: 1347 offsets[0] 1348 except TypeError: 1349 offsets = (offsets, ) 1350 byte_counts = (byte_counts, ) 1351 if any(o < 2 for o in offsets): 1352 raise ValueError("corrupted page") 1353 1354 if (not self.is_tiled and (self.is_stk or (not self.compression 1355 and bits_per_sample in (8, 16, 32, 64) 1356 and all(offsets[i] == offsets[i+1] - byte_counts[i] 1357 for i in range(len(offsets)-1))))): 1358 # contiguous data 1359 if (memmap and not (self.is_tiled or self.predictor or 1360 ('extra_samples' in self.tags) or 1361 (colormapped and self.is_palette) or 1362 (not byteorder_is_native))): 1363 result = numpy.memmap(fh, typecode, 'r', offsets[0], shape) 1364 else: 1365 fh.seek(offsets[0]) 1366 result = numpy_fromfile(fh, typecode, numpy.prod(shape)) 1367 result = result.astype('=' + dtype) 1368 else: 1369 if self.planar_configuration == 'contig': 1370 runlen *= self.samples_per_pixel 1371 if bits_per_sample in (8, 16, 32, 64, 128): 1372 if (bits_per_sample * runlen) % 8: 1373 raise ValueError("data and sample size mismatch") 1374 1375 def unpack(x): 1376 return numpy.fromstring(x, typecode)
1377 elif isinstance(bits_per_sample, tuple): 1378 def unpack(x): 1379 return unpackrgb(x, typecode, bits_per_sample)
1380 else: 1381 def unpack(x): 1382 return unpackints(x, typecode, bits_per_sample, runlen) 1383 decompress = TIFF_DECOMPESSORS[self.compression] 1384 if self.is_tiled: 1385 result = numpy.empty(shape, dtype) 1386 tw, tl, pl = 0, 0, 0 1387 for offset, bytecount in zip(offsets, byte_counts): 1388 fh.seek(offset) 1389 tile = unpack(decompress(fh.read(bytecount))) 1390 tile.shape = tile_shape 1391 if self.predictor == 'horizontal': 1392 numpy.cumsum(tile, axis=-2, dtype=dtype, out=tile) 1393 result[0, pl, tl:tl+tile_length, 1394 tw:tw+tile_width, :] = tile 1395 del tile 1396 tw += tile_width 1397 if tw >= shape[-2]: 1398 tw, tl = 0, tl + tile_length 1399 if tl >= shape[-3]: 1400 tl, pl = 0, pl + 1 1401 result = result[..., :image_length, :image_width, :] 1402 else: 1403 strip_size = (self.rows_per_strip * self.image_width * 1404 self.samples_per_pixel) 1405 result = numpy.empty(shape, dtype).reshape(-1) 1406 index = 0 1407 for offset, bytecount in zip(offsets, byte_counts): 1408 fh.seek(offset) 1409 strip = fh.read(bytecount) 1410 strip = unpack(decompress(strip)) 1411 size = min(result.size, strip.size, strip_size, 1412 result.size - index) 1413 result[index:index+size] = strip[:size] 1414 del strip 1415 index += size 1416 1417 result.shape = self._shape 1418 1419 if self.predictor == 'horizontal' and not self.is_tiled: 1420 # work around bug in LSM510 software 1421 if not (self.parent.is_lsm and not self.compression): 1422 numpy.cumsum(result, axis=-2, dtype=dtype, out=result) 1423 1424 if colormapped and self.is_palette: 1425 if self.color_map.shape[1] >= 2**bits_per_sample: 1426 # FluoView and LSM might fail here 1427 result = numpy.take(self.color_map, 1428 result[:, 0, :, :, 0], axis=1) 1429 elif rgbonly and self.is_rgb and 'extra_samples' in self.tags: 1430 # return only RGB and first alpha channel if exists 1431 extra_samples = self.extra_samples 1432 if self.tags['extra_samples'].count == 1: 1433 extra_samples = (extra_samples, ) 1434 for i, exs in enumerate(extra_samples): 1435 if exs in ('unassalpha', 'assocalpha', 'unspecified'): 1436 if self.planar_configuration == 'contig': 1437 result = result[..., [0, 1, 2, 3+i]] 1438 else: 1439 result = result[:, [0, 1, 2, 3+i]] 1440 break 1441 else: 1442 if self.planar_configuration == 'contig': 1443 result = result[..., :3] 1444 else: 1445 result = result[:, :3] 1446 1447 if squeeze: 1448 try: 1449 result.shape = self.shape 1450 except ValueError: 1451 warnings.warn("failed to reshape from %s to %s" % ( 1452 str(result.shape), str(self.shape))) 1453 1454 return result 1455
1456 - def __str__(self):
1457 """Return string containing information about page.""" 1458 s = ', '.join(s for s in ( 1459 ' x '.join(str(i) for i in self.shape), 1460 str(numpy.dtype(self.dtype)), 1461 '%s bit' % str(self.bits_per_sample), 1462 self.photometric if 'photometric' in self.tags else '', 1463 self.compression if self.compression else 'raw', 1464 '|'.join(t[3:] for t in ( 1465 'is_stk', 'is_lsm', 'is_nih', 'is_ome', 'is_imagej', 1466 'is_micromanager', 'is_fluoview', 'is_mdgel', 'is_mediacy', 1467 'is_reduced', 'is_tiled') if getattr(self, t))) if s) 1468 return "Page %i: %s" % (self.index, s)
1469
1470 - def __getattr__(self, name):
1471 """Return tag value.""" 1472 if name in self.tags: 1473 value = self.tags[name].value 1474 setattr(self, name, value) 1475 return value 1476 raise AttributeError(name)
1477 1478 @lazyattr
1479 - def is_rgb(self):
1480 """True if page contains a RGB image.""" 1481 return ('photometric' in self.tags and 1482 self.tags['photometric'].value == 2)
1483 1484 @lazyattr
1485 - def is_palette(self):
1486 """True if page contains a palette-colored image.""" 1487 return ('photometric' in self.tags and 1488 self.tags['photometric'].value == 3)
1489 1490 @lazyattr
1491 - def is_tiled(self):
1492 """True if page contains tiled image.""" 1493 return 'tile_width' in self.tags
1494 1495 @lazyattr
1496 - def is_reduced(self):
1497 """True if page is a reduced image of another image.""" 1498 return bool(self.tags['new_subfile_type'].value & 1)
1499 1500 @lazyattr
1501 - def is_mdgel(self):
1502 """True if page contains md_file_tag tag.""" 1503 return 'md_file_tag' in self.tags
1504 1505 @lazyattr
1506 - def is_mediacy(self):
1507 """True if page contains Media Cybernetics Id tag.""" 1508 return ('mc_id' in self.tags and 1509 self.tags['mc_id'].value.startswith(b'MC TIFF'))
1510 1511 @lazyattr
1512 - def is_stk(self):
1513 """True if page contains MM_UIC2 tag.""" 1514 return 'mm_uic2' in self.tags
1515 1516 @lazyattr
1517 - def is_lsm(self):
1518 """True if page contains LSM CZ_LSM_INFO tag.""" 1519 return 'cz_lsm_info' in self.tags
1520 1521 @lazyattr
1522 - def is_fluoview(self):
1523 """True if page contains FluoView MM_STAMP tag.""" 1524 return 'mm_stamp' in self.tags
1525 1526 @lazyattr
1527 - def is_nih(self):
1528 """True if page contains NIH image header.""" 1529 return 'nih_image_header' in self.tags
1530 1531 @lazyattr
1532 - def is_ome(self):
1533 """True if page contains OME-XML in image_description tag.""" 1534 return ('image_description' in self.tags and self.tags[ 1535 'image_description'].value.startswith(b'<?xml version='))
1536 1537 @lazyattr
1538 - def is_shaped(self):
1539 """True if page contains shape in image_description tag.""" 1540 return ('image_description' in self.tags and self.tags[ 1541 'image_description'].value.startswith(b'shape=('))
1542 1543 @lazyattr
1544 - def is_imagej(self):
1545 """True if page contains ImageJ description.""" 1546 return ( 1547 ('image_description' in self.tags and 1548 self.tags['image_description'].value.startswith(b'ImageJ=')) or 1549 ('image_description_1' in self.tags and # Micromanager 1550 self.tags['image_description_1'].value.startswith(b'ImageJ=')))
1551 1552 @lazyattr
1553 - def is_micromanager(self):
1554 """True if page contains Micro-Manager metadata.""" 1555 return 'micromanager_metadata' in self.tags
1556
1557 1558 -class TiffTag(object):
1559 """A TIFF tag structure. 1560 1561 Attributes 1562 ---------- 1563 name : string 1564 Attribute name of tag. 1565 code : int 1566 Decimal code of tag. 1567 dtype : str 1568 Datatype of tag data. One of TIFF_DATA_TYPES. 1569 count : int 1570 Number of values. 1571 value : various types 1572 Tag data as Python object. 1573 value_offset : int 1574 Location of value in file, if any. 1575 1576 All attributes are read-only. 1577 1578 """ 1579 __slots__ = ('code', 'name', 'count', 'dtype', 'value', 'value_offset', 1580 '_offset', '_value') 1581
1582 - class Error(Exception):
1583 pass
1584
1585 - def __init__(self, arg, **kwargs):
1586 """Initialize instance from file or arguments.""" 1587 self._offset = None 1588 if hasattr(arg, '_fh'): 1589 self._fromfile(arg, **kwargs) 1590 else: 1591 self._fromdata(arg, **kwargs)
1592
1593 - def _fromdata(self, code, dtype, count, value, name=None):
1594 """Initialize instance from arguments.""" 1595 self.code = int(code) 1596 self.name = name if name else str(code) 1597 self.dtype = TIFF_DATA_TYPES[dtype] 1598 self.count = int(count) 1599 self.value = value
1600
1601 - def _fromfile(self, parent):
1602 """Read tag structure from open file. Advance file cursor.""" 1603 fh = parent._fh 1604 byteorder = parent.byteorder 1605 self._offset = fh.tell() 1606 self.value_offset = self._offset + parent.offset_size + 4 1607 1608 fmt, size = {4: ('HHI4s', 12), 8: ('HHQ8s', 20)}[parent.offset_size] 1609 data = fh.read(size) 1610 code, dtype = struct.unpack(byteorder + fmt[:2], data[:4]) 1611 count, value = struct.unpack(byteorder + fmt[2:], data[4:]) 1612 self._value = value 1613 1614 if code in TIFF_TAGS: 1615 name = TIFF_TAGS[code][0] 1616 elif code in CUSTOM_TAGS: 1617 name = CUSTOM_TAGS[code][0] 1618 else: 1619 name = str(code) 1620 1621 try: 1622 dtype = TIFF_DATA_TYPES[dtype] 1623 except KeyError: 1624 raise TiffTag.Error("unknown tag data type %i" % dtype) 1625 1626 fmt = '%s%i%s' % (byteorder, count*int(dtype[0]), dtype[1]) 1627 size = struct.calcsize(fmt) 1628 if size > parent.offset_size or code in CUSTOM_TAGS: 1629 pos = fh.tell() 1630 tof = {4: 'I', 8: 'Q'}[parent.offset_size] 1631 self.value_offset = offset = struct.unpack(byteorder+tof, value)[0] 1632 if offset < 0 or offset > parent._fsize: 1633 raise TiffTag.Error("corrupt file - invalid tag value offset") 1634 elif offset < 4: 1635 raise TiffTag.Error("corrupt value offset for tag %i" % code) 1636 fh.seek(offset) 1637 if code in CUSTOM_TAGS: 1638 readfunc = CUSTOM_TAGS[code][1] 1639 value = readfunc(fh, byteorder, dtype, count) 1640 fh.seek(0, 2) # bug in numpy/Python 3.x ? 1641 if isinstance(value, dict): # numpy.core.records.record 1642 value = Record(value) 1643 elif code in TIFF_TAGS or dtype[-1] == 's': 1644 value = struct.unpack(fmt, fh.read(size)) 1645 else: 1646 value = read_numpy(fh, byteorder, dtype, count) 1647 fh.seek(0, 2) # bug in numpy/Python 3.x ? 1648 fh.seek(pos) 1649 else: 1650 value = struct.unpack(fmt, value[:size]) 1651 1652 if not code in CUSTOM_TAGS: 1653 if len(value) == 1: 1654 value = value[0] 1655 1656 if dtype.endswith('s') and isinstance(value, bytes): 1657 value = stripnull(value) 1658 1659 self.code = code 1660 self.name = name 1661 self.dtype = dtype 1662 self.count = count 1663 self.value = value
1664
1665 - def __str__(self):
1666 """Return string containing information about tag.""" 1667 return ' '.join(str(getattr(self, s)) for s in self.__slots__)
1668
1669 1670 -class TiffSequence(object):
1671 """Sequence of image files. 1672 1673 Properties 1674 ---------- 1675 files : list 1676 List of file names. 1677 shape : tuple 1678 Shape of image sequence. 1679 axes : str 1680 Labels of axes in shape. 1681 1682 Examples 1683 -------- 1684 >>> ims = TiffSequence("test.oif.files/*.tif") 1685 >>> ims = ims.asarray() 1686 >>> ims.shape 1687 (2, 100, 256, 256) 1688 1689 """ 1690 _axes_pattern = """ 1691 # matches Olympus OIF and Leica TIFF series 1692 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4})) 1693 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))? 1694 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))? 1695 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))? 1696 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))? 1697 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))? 1698 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))? 1699 """ 1700
1701 - class _ParseError(Exception):
1702 pass
1703
1704 - def __init__(self, files, imread=TiffFile, pattern='axes'):
1705 """Initialize instance from multiple files. 1706 1707 Parameters 1708 ---------- 1709 files : str, or sequence of str 1710 Glob pattern or sequence of file names. 1711 imread : function or class 1712 Image read function or class with asarray function returning numpy 1713 array from single file. 1714 pattern : str 1715 Regular expression pattern that matches axes names and sequence 1716 indices in file names. 1717 1718 """ 1719 if isinstance(files, basestring): 1720 files = natural_sorted(glob.glob(files)) 1721 files = list(files) 1722 if not files: 1723 raise ValueError("no files found") 1724 #if not os.path.isfile(files[0]): 1725 # raise ValueError("file not found") 1726 self.files = files 1727 1728 if hasattr(imread, 'asarray'): 1729 _imread = imread 1730 1731 def imread(fname, *args, **kwargs): 1732 with _imread(fname) as im: 1733 return im.asarray(*args, **kwargs)
1734 1735 self.imread = imread 1736 1737 self.pattern = self._axes_pattern if pattern == 'axes' else pattern 1738 try: 1739 self._parse() 1740 if not self.axes: 1741 self.axes = 'I' 1742 except self._ParseError: 1743 self.axes = 'I' 1744 self.shape = (len(files),) 1745 self._start_index = (0,) 1746 self._indices = ((i,) for i in range(len(files)))
1747
1748 - def __str__(self):
1749 """Return string with information about image sequence.""" 1750 return "\n".join([ 1751 self.files[0], 1752 '* files: %i' % len(self.files), 1753 '* axes: %s' % self.axes, 1754 '* shape: %s' % str(self.shape)])
1755
1756 - def __len__(self):
1757 return len(self.files)
1758
1759 - def __enter__(self):
1760 return self
1761
1762 - def __exit__(self, exc_type, exc_value, traceback):
1763 self.close()
1764
1765 - def close(self):
1766 pass
1767
1768 - def asarray(self, *args, **kwargs):
1769 """Read image data from all files and return as single numpy array. 1770 1771 Raise IndexError if image shapes don't match. 1772 1773 """ 1774 im = self.imread(self.files[0]) 1775 result_shape = self.shape + im.shape 1776 result = numpy.zeros(result_shape, dtype=im.dtype) 1777 result = result.reshape(-1, *im.shape) 1778 for index, fname in zip(self._indices, self.files): 1779 index = [i-j for i, j in zip(index, self._start_index)] 1780 index = numpy.ravel_multi_index(index, self.shape) 1781 im = self.imread(fname, *args, **kwargs) 1782 result[index] = im 1783 result.shape = result_shape 1784 return result
1785
1786 - def _parse(self):
1787 """Get axes and shape from file names.""" 1788 if not self.pattern: 1789 raise self._ParseError("invalid pattern") 1790 pattern = re.compile(self.pattern, re.IGNORECASE | re.VERBOSE) 1791 matches = pattern.findall(self.files[0]) 1792 if not matches: 1793 raise self._ParseError("pattern doesn't match file names") 1794 matches = matches[-1] 1795 if len(matches) % 2: 1796 raise self._ParseError("pattern doesn't match axis name and index") 1797 axes = ''.join(m for m in matches[::2] if m) 1798 if not axes: 1799 raise self._ParseError("pattern doesn't match file names") 1800 1801 indices = [] 1802 for fname in self.files: 1803 matches = pattern.findall(fname)[-1] 1804 if axes != ''.join(m for m in matches[::2] if m): 1805 raise ValueError("axes don't match within the image sequence") 1806 indices.append([int(m) for m in matches[1::2] if m]) 1807 shape = tuple(numpy.max(indices, axis=0)) 1808 start_index = tuple(numpy.min(indices, axis=0)) 1809 shape = tuple(i-j+1 for i, j in zip(shape, start_index)) 1810 if numpy.prod(shape) != len(self.files): 1811 warnings.warn("files are missing. Missing data are zeroed") 1812 1813 self.axes = axes.upper() 1814 self.shape = shape 1815 self._indices = indices 1816 self._start_index = start_index
1817
1818 1819 -class Record(dict):
1820 """Dictionary with attribute access. 1821 1822 Can also be initialized with numpy.core.records.record. 1823 1824 """ 1825 __slots__ = () 1826
1827 - def __init__(self, arg=None, **kwargs):
1828 if kwargs: 1829 arg = kwargs 1830 elif arg is None: 1831 arg = {} 1832 try: 1833 dict.__init__(self, arg) 1834 except (TypeError, ValueError): 1835 for i, name in enumerate(arg.dtype.names): 1836 v = arg[i] 1837 self[name] = v if v.dtype.char != 'S' else stripnull(v)
1838
1839 - def __getattr__(self, name):
1840 return self[name]
1841
1842 - def __setattr__(self, name, value):
1843 self.__setitem__(name, value)
1844
1845 - def __str__(self):
1846 """Pretty print Record.""" 1847 s = [] 1848 lists = [] 1849 for k in sorted(self): 1850 if k.startswith('_'): # does not work with byte 1851 continue 1852 v = self[k] 1853 if isinstance(v, (list, tuple)) and len(v): 1854 if isinstance(v[0], Record): 1855 lists.append((k, v)) 1856 continue 1857 elif isinstance(v[0], TiffPage): 1858 v = [i.index for i in v if i] 1859 s.append( 1860 ("* %s: %s" % (k, str(v))).split("\n", 1)[0] 1861 [:PRINT_LINE_LEN].rstrip()) 1862 for k, v in lists: 1863 l = [] 1864 for i, w in enumerate(v): 1865 l.append("* %s[%i]\n %s" % (k, i, 1866 str(w).replace("\n", "\n "))) 1867 s.append('\n'.join(l)) 1868 return '\n'.join(s)
1869
1870 1871 -class TiffTags(Record):
1872 """Dictionary of TiffTags with attribute access."""
1873 - def __str__(self):
1874 """Return string with information about all tags.""" 1875 s = [] 1876 for tag in sorted(self.values(), key=lambda x: x.code): 1877 typecode = "%i%s" % (tag.count * int(tag.dtype[0]), tag.dtype[1]) 1878 line = "* %i %s (%s) %s" % (tag.code, tag.name, typecode, 1879 str(tag.value).split('\n', 1)[0]) 1880 s.append(line[:PRINT_LINE_LEN].lstrip()) 1881 return '\n'.join(s)
1882
1883 1884 -def read_bytes(fh, byteorder, dtype, count):
1885 """Read tag data from file and return as byte string.""" 1886 return numpy_fromfile(fh, byteorder+dtype[-1], count).tostring()
1887
1888 1889 -def read_numpy(fh, byteorder, dtype, count):
1890 """Read tag data from file and return as numpy array.""" 1891 return numpy_fromfile(fh, byteorder+dtype[-1], count)
1892
1893 1894 -def read_json(fh, byteorder, dtype, count):
1895 """Read tag data from file and return as object.""" 1896 return json.loads(unicode(stripnull(fh.read(count)), 'utf-8'))
1897
1898 1899 -def read_mm_header(fh, byteorder, dtype, count):
1900 """Read MM_HEADER tag from file and return as numpy.rec.array.""" 1901 return numpy.rec.fromfile(fh, MM_HEADER, 1, byteorder=byteorder)[0]
1902
1903 1904 -def read_mm_stamp(fh, byteorder, dtype, count):
1905 """Read MM_STAMP tag from file and return as numpy.array.""" 1906 return numpy_fromfile(fh, byteorder+'8f8', 1)[0]
1907
1908 1909 -def read_mm_uic1(fh, byteorder, dtype, count):
1910 """Read MM_UIC1 tag from file and return as dictionary.""" 1911 t = fh.read(8*count) 1912 t = struct.unpack('%s%iI' % (byteorder, 2*count), t) 1913 return dict((MM_TAG_IDS[k], v) for k, v in zip(t[::2], t[1::2]) 1914 if k in MM_TAG_IDS)
1915
1916 1917 -def read_mm_uic2(fh, byteorder, dtype, count):
1918 """Read MM_UIC2 tag from file and return as dictionary.""" 1919 result = {'number_planes': count} 1920 values = numpy_fromfile(fh, byteorder+'I', 6*count) 1921 result['z_distance'] = values[0::6] // values[1::6] 1922 #result['date_created'] = tuple(values[2::6]) 1923 #result['time_created'] = tuple(values[3::6]) 1924 #result['date_modified'] = tuple(values[4::6]) 1925 #result['time_modified'] = tuple(values[5::6]) 1926 return result
1927
1928 1929 -def read_mm_uic3(fh, byteorder, dtype, count):
1930 """Read MM_UIC3 tag from file and return as dictionary.""" 1931 t = numpy_fromfile(fh, byteorder+'I', 2*count) 1932 return {'wavelengths': t[0::2] // t[1::2]}
1933
1934 1935 -def read_mm_uic4(fh, byteorder, dtype, count):
1936 """Read MM_UIC4 tag from file and return as dictionary.""" 1937 t = struct.unpack(byteorder + 'hI'*count, fh.read(6*count)) 1938 return dict((MM_TAG_IDS[k], v) for k, v in zip(t[::2], t[1::2]) 1939 if k in MM_TAG_IDS)
1940
1941 1942 -def read_cz_lsm_info(fh, byteorder, dtype, count):
1943 """Read CS_LSM_INFO tag from file and return as numpy.rec.array.""" 1944 result = numpy.rec.fromfile(fh, CZ_LSM_INFO, 1, 1945 byteorder=byteorder)[0] 1946 {50350412: '1.3', 67127628: '2.0'}[result.magic_number] # validation 1947 return result
1948
1949 1950 -def read_cz_lsm_time_stamps(fh, byteorder):
1951 """Read LSM time stamps from file and return as list.""" 1952 size, count = struct.unpack(byteorder+'II', fh.read(8)) 1953 if size != (8 + 8 * count): 1954 raise ValueError("lsm_time_stamps block is too short") 1955 return struct.unpack(('%s%dd' % (byteorder, count)), 1956 fh.read(8*count))
1957
1958 1959 -def read_cz_lsm_event_list(fh, byteorder):
1960 """Read LSM events from file and return as list of (time, type, text).""" 1961 count = struct.unpack(byteorder+'II', fh.read(8))[1] 1962 events = [] 1963 while count > 0: 1964 esize, etime, etype = struct.unpack(byteorder+'IdI', fh.read(16)) 1965 etext = stripnull(fh.read(esize - 16)) 1966 events.append((etime, etype, etext)) 1967 count -= 1 1968 return events
1969
1970 1971 -def read_cz_lsm_scan_info(fh, byteorder):
1972 """Read LSM scan information from file and return as Record.""" 1973 block = Record() 1974 blocks = [block] 1975 unpack = struct.unpack 1976 if 0x10000000 != struct.unpack(byteorder+"I", fh.read(4))[0]: 1977 raise ValueError("not a lsm_scan_info structure") 1978 fh.read(8) 1979 while True: 1980 entry, dtype, size = unpack(byteorder+"III", fh.read(12)) 1981 if dtype == 2: 1982 value = stripnull(fh.read(size)) 1983 elif dtype == 4: 1984 value = unpack(byteorder+"i", fh.read(4))[0] 1985 elif dtype == 5: 1986 value = unpack(byteorder+"d", fh.read(8))[0] 1987 else: 1988 value = 0 1989 if entry in CZ_LSM_SCAN_INFO_ARRAYS: 1990 blocks.append(block) 1991 name = CZ_LSM_SCAN_INFO_ARRAYS[entry] 1992 newobj = [] 1993 setattr(block, name, newobj) 1994 block = newobj 1995 elif entry in CZ_LSM_SCAN_INFO_STRUCTS: 1996 blocks.append(block) 1997 newobj = Record() 1998 block.append(newobj) 1999 block = newobj 2000 elif entry in CZ_LSM_SCAN_INFO_ATTRIBUTES: 2001 name = CZ_LSM_SCAN_INFO_ATTRIBUTES[entry] 2002 setattr(block, name, value) 2003 elif entry == 0xffffffff: 2004 block = blocks.pop() 2005 else: 2006 setattr(block, "unknown_%x" % entry, value) 2007 if not blocks: 2008 break 2009 return block
2010
2011 2012 -def read_nih_image_header(fh, byteorder, dtype, count):
2013 """Read NIH_IMAGE_HEADER tag from file and return as numpy.rec.array.""" 2014 a = numpy.rec.fromfile(fh, NIH_IMAGE_HEADER, 1, byteorder=byteorder)[0] 2015 a = a.newbyteorder(byteorder) 2016 a.xunit = a.xunit[:a._xunit_len] 2017 a.um = a.um[:a._um_len] 2018 return a
2019
2020 2021 -def imagej_metadata(data, bytecounts, byteorder):
2022 """Return dict from ImageJ meta data tag value.""" 2023 2024 _str = str if sys.version_info[0] < 3 else lambda x: str(x, 'cp1252') 2025 2026 def read_string(data, byteorder): 2027 return _str(stripnull(data[0 if byteorder == '<' else 1::2]))
2028 2029 def read_double(data, byteorder): 2030 return struct.unpack(byteorder+('d' * (len(data) // 8)), data) 2031 2032 def read_bytes(data, byteorder): 2033 #return struct.unpack('b' * len(data), data) 2034 return numpy.fromstring(data, 'uint8') 2035 2036 metadata_types = { # big endian 2037 b'info': ('info', read_string), 2038 b'labl': ('labels', read_string), 2039 b'rang': ('ranges', read_double), 2040 b'luts': ('luts', read_bytes), 2041 b'roi ': ('roi', read_bytes), 2042 b'over': ('overlays', read_bytes)} 2043 metadata_types.update( # little endian 2044 dict((k[::-1], v) for k, v in metadata_types.items())) 2045 2046 if not bytecounts: 2047 raise ValueError("no ImageJ meta data") 2048 2049 if not data[:4] in (b'IJIJ', b'JIJI'): 2050 raise ValueError("invalid ImageJ meta data") 2051 2052 header_size = bytecounts[0] 2053 if header_size < 12 or header_size > 804: 2054 raise ValueError("invalid ImageJ meta data header size") 2055 2056 ntypes = (header_size - 4) // 8 2057 header = struct.unpack(byteorder+'4sI'*ntypes, data[4:4+ntypes*8]) 2058 pos = 4 + ntypes * 8 2059 counter = 0 2060 result = {} 2061 for mtype, count in zip(header[::2], header[1::2]): 2062 values = [] 2063 name, func = metadata_types.get(mtype, (_str(mtype), read_bytes)) 2064 for _ in range(count): 2065 counter += 1 2066 pos1 = pos + bytecounts[counter] 2067 values.append(func(data[pos:pos1], byteorder)) 2068 pos = pos1 2069 result[name.strip()] = values[0] if count == 1 else values 2070 return result 2071
2072 2073 -def imagej_description(description):
2074 """Return dict from ImageJ image_description tag.""" 2075 def _bool(val): 2076 return {b'true': True, b'false': False}[val.lower()]
2077 2078 _str = str if sys.version_info[0] < 3 else lambda x: str(x, 'cp1252') 2079 result = {} 2080 for line in description.splitlines(): 2081 try: 2082 key, val = line.split(b'=') 2083 except Exception: 2084 continue 2085 key = key.strip() 2086 val = val.strip() 2087 for dtype in (int, float, _bool, _str): 2088 try: 2089 val = dtype(val) 2090 break 2091 except Exception: 2092 pass 2093 result[_str(key)] = val 2094 return result 2095
2096 2097 -def read_micromanager_metadata(fh):
2098 """Read MicroManager non-TIFF settings from open file and return as dict. 2099 2100 The settings can be used to read image data without parsing the TIFF file. 2101 2102 Raise ValueError if file does not contain valid MicroManager metadata. 2103 2104 """ 2105 fh.seek(0) 2106 try: 2107 byteorder = {b'II': '<', b'MM': '>'}[fh.read(2)] 2108 except IndexError: 2109 raise ValueError("not a MicroManager TIFF file") 2110 2111 results = {} 2112 fh.seek(8) 2113 (index_header, index_offset, display_header, display_offset, 2114 comments_header, comments_offset, summary_header, summary_length 2115 ) = struct.unpack(byteorder + "IIIIIIII", fh.read(32)) 2116 2117 if summary_header != 2355492: 2118 raise ValueError("invalid MicroManager summary_header") 2119 results['summary'] = read_json(fh, byteorder, None, summary_length) 2120 2121 if index_header != 54773648: 2122 raise ValueError("invalid MicroManager index_header") 2123 fh.seek(index_offset) 2124 header, count = struct.unpack(byteorder + "II", fh.read(8)) 2125 if header != 3453623: 2126 raise ValueError("invalid MicroManager index_header") 2127 data = struct.unpack(byteorder + "IIIII"*count, fh.read(20*count)) 2128 results['index_map'] = { 2129 'channel': data[::5], 'slice': data[1::5], 'frame': data[2::5], 2130 'position': data[3::5], 'offset': data[4::5]} 2131 2132 if display_header != 483765892: 2133 raise ValueError("invalid MicroManager display_header") 2134 fh.seek(display_offset) 2135 header, count = struct.unpack(byteorder + "II", fh.read(8)) 2136 if header != 347834724: 2137 raise ValueError("invalid MicroManager display_header") 2138 results['display_settings'] = read_json(fh, byteorder, None, count) 2139 2140 if comments_header != 99384722: 2141 raise ValueError("invalid MicroManager comments_header") 2142 fh.seek(comments_offset) 2143 header, count = struct.unpack(byteorder + "II", fh.read(8)) 2144 if header != 84720485: 2145 raise ValueError("invalid MicroManager comments_header") 2146 results['comments'] = read_json(fh, byteorder, None, count) 2147 2148 return results
2149
2150 2151 -def _replace_by(module_function, package=None, warn=True):
2152 """Try replace decorated function by module.function.""" 2153 try: 2154 from importlib import import_module 2155 except ImportError: 2156 warnings.warn('Could not import module importlib') 2157 return lambda func: func 2158 2159 def decorate(func, module_function=module_function, warn=warn): 2160 try: 2161 module, function = module_function.split('.') 2162 if not package: 2163 module = import_module(module) 2164 else: 2165 module = import_module('.' + module, package=package) 2166 func, oldfunc = getattr(module, function), func 2167 globals()['__old_' + func.__name__] = oldfunc 2168 except Exception: 2169 if warn: 2170 warnings.warn("failed to import %s" % module_function) 2171 return func
2172 2173 return decorate 2174
2175 2176 @_replace_by('_tifffile.decodepackbits') 2177 -def decodepackbits(encoded):
2178 """Decompress PackBits encoded byte string. 2179 2180 PackBits is a simple byte-oriented run-length compression scheme. 2181 2182 """ 2183 func = ord if sys.version[0] == '2' else lambda x: x 2184 result = [] 2185 result_extend = result.extend 2186 i = 0 2187 try: 2188 while True: 2189 n = func(encoded[i]) + 1 2190 i += 1 2191 if n < 129: 2192 result_extend(encoded[i:i+n]) 2193 i += n 2194 elif n > 129: 2195 result_extend(encoded[i:i+1] * (258-n)) 2196 i += 1 2197 except IndexError: 2198 pass 2199 return b''.join(result) if sys.version[0] == '2' else bytes(result)
2200
2201 2202 @_replace_by('_tifffile.decodelzw') 2203 -def decodelzw(encoded):
2204 """Decompress LZW (Lempel-Ziv-Welch) encoded TIFF strip (byte string). 2205 2206 The strip must begin with a CLEAR code and end with an EOI code. 2207 2208 This is an implementation of the LZW decoding algorithm described in (1). 2209 It is not compatible with old style LZW compressed files like quad-lzw.tif. 2210 2211 """ 2212 len_encoded = len(encoded) 2213 bitcount_max = len_encoded * 8 2214 unpack = struct.unpack 2215 2216 if sys.version[0] == '2': 2217 newtable = [chr(i) for i in range(256)] 2218 else: 2219 newtable = [bytes([i]) for i in range(256)] 2220 newtable.extend((0, 0)) 2221 2222 def next_code(): 2223 """Return integer of `bitw` bits at `bitcount` position in encoded.""" 2224 start = bitcount // 8 2225 s = encoded[start:start+4] 2226 try: 2227 code = unpack('>I', s)[0] 2228 except Exception: 2229 code = unpack('>I', s + b'\x00'*(4-len(s)))[0] 2230 code <<= bitcount % 8 2231 code &= mask 2232 return code >> shr
2233 2234 switchbitch = { # code: bit-width, shr-bits, bit-mask 2235 255: (9, 23, int(9*'1'+'0'*23, 2)), 2236 511: (10, 22, int(10*'1'+'0'*22, 2)), 2237 1023: (11, 21, int(11*'1'+'0'*21, 2)), 2238 2047: (12, 20, int(12*'1'+'0'*20, 2)), } 2239 bitw, shr, mask = switchbitch[255] 2240 bitcount = 0 2241 2242 if len_encoded < 4: 2243 raise ValueError("strip must be at least 4 characters long") 2244 2245 if next_code() != 256: 2246 raise ValueError("strip must begin with CLEAR code") 2247 2248 code = 0 2249 oldcode = 0 2250 result = [] 2251 result_append = result.append 2252 while True: 2253 code = next_code() # ~5% faster when inlining this function 2254 bitcount += bitw 2255 if code == 257 or bitcount >= bitcount_max: # EOI 2256 break 2257 if code == 256: # CLEAR 2258 table = newtable[:] 2259 table_append = table.append 2260 lentable = 258 2261 bitw, shr, mask = switchbitch[255] 2262 code = next_code() 2263 bitcount += bitw 2264 if code == 257: # EOI 2265 break 2266 result_append(table[code]) 2267 else: 2268 if code < lentable: 2269 decoded = table[code] 2270 newcode = table[oldcode] + decoded[:1] 2271 else: 2272 newcode = table[oldcode] 2273 newcode += newcode[:1] 2274 decoded = newcode 2275 result_append(decoded) 2276 table_append(newcode) 2277 lentable += 1 2278 oldcode = code 2279 if lentable in switchbitch: 2280 bitw, shr, mask = switchbitch[lentable] 2281 2282 if code != 257: 2283 warnings.warn( 2284 "decodelzw encountered unexpected end of stream (code %i)" % code) 2285 2286 return b''.join(result) 2287
2288 2289 @_replace_by('_tifffile.unpackints') 2290 -def unpackints(data, dtype, itemsize, runlen=0):
2291 """Decompress byte string to array of integers of any bit size <= 32. 2292 2293 Parameters 2294 ---------- 2295 data : byte str 2296 Data to decompress. 2297 dtype : numpy.dtype or str 2298 A numpy boolean or integer type. 2299 itemsize : int 2300 Number of bits per integer. 2301 runlen : int 2302 Number of consecutive integers, after which to start at next byte. 2303 2304 """ 2305 if itemsize == 1: # bitarray 2306 data = numpy.fromstring(data, '|B') 2307 data = numpy.unpackbits(data) 2308 if runlen % 8: 2309 data = data.reshape(-1, runlen + (8 - runlen % 8)) 2310 data = data[:, :runlen].reshape(-1) 2311 return data.astype(dtype) 2312 2313 dtype = numpy.dtype(dtype) 2314 if itemsize in (8, 16, 32, 64): 2315 return numpy.fromstring(data, dtype) 2316 if itemsize < 1 or itemsize > 32: 2317 raise ValueError("itemsize out of range: %i" % itemsize) 2318 if dtype.kind not in "biu": 2319 raise ValueError("invalid dtype") 2320 2321 itembytes = next(i for i in (1, 2, 4, 8) if 8 * i >= itemsize) 2322 if itembytes != dtype.itemsize: 2323 raise ValueError("dtype.itemsize too small") 2324 if runlen == 0: 2325 runlen = len(data) // itembytes 2326 skipbits = runlen*itemsize % 8 2327 if skipbits: 2328 skipbits = 8 - skipbits 2329 shrbits = itembytes*8 - itemsize 2330 bitmask = int(itemsize*'1'+'0'*shrbits, 2) 2331 dtypestr = '>' + dtype.char # dtype always big endian? 2332 2333 unpack = struct.unpack 2334 l = runlen * (len(data)*8 // (runlen*itemsize + skipbits)) 2335 result = numpy.empty((l, ), dtype) 2336 bitcount = 0 2337 for i in range(len(result)): 2338 start = bitcount // 8 2339 s = data[start:start+itembytes] 2340 try: 2341 code = unpack(dtypestr, s)[0] 2342 except Exception: 2343 code = unpack(dtypestr, s + b'\x00'*(itembytes-len(s)))[0] 2344 code <<= bitcount % 8 2345 code &= bitmask 2346 result[i] = code >> shrbits 2347 bitcount += itemsize 2348 if (i+1) % runlen == 0: 2349 bitcount += skipbits 2350 return result
2351
2352 2353 -def unpackrgb(data, dtype='<B', bitspersample=(5, 6, 5), rescale=True):
2354 """Return array from byte string containing packed samples. 2355 2356 Use to unpack RGB565 or RGB555 to RGB888 format. 2357 2358 Parameters 2359 ---------- 2360 data : byte str 2361 The data to be decoded. Samples in each pixel are stored consecutively. 2362 Pixels are aligned to 8, 16, or 32 bit boundaries. 2363 dtype : numpy.dtype 2364 The sample data type. The byteorder applies also to the data stream. 2365 bitspersample : tuple 2366 Number of bits for each sample in a pixel. 2367 rescale : bool 2368 Upscale samples to the number of bits in dtype. 2369 2370 Returns 2371 ------- 2372 result : ndarray 2373 Flattened array of unpacked samples of native dtype. 2374 2375 Examples 2376 -------- 2377 >>> data = struct.pack('BBBB', 0x21, 0x08, 0xff, 0xff) 2378 >>> print(unpackrgb(data, '<B', (5, 6, 5), False)) 2379 [ 1 1 1 31 63 31] 2380 >>> print(unpackrgb(data, '<B', (5, 6, 5))) 2381 [ 8 4 8 255 255 255] 2382 >>> print(unpackrgb(data, '<B', (5, 5, 5))) 2383 [ 16 8 8 255 255 255] 2384 2385 """ 2386 dtype = numpy.dtype(dtype) 2387 bits = int(numpy.sum(bitspersample)) 2388 if not (bits <= 32 and all(i <= dtype.itemsize*8 for i in bitspersample)): 2389 raise ValueError("sample size not supported %s" % str(bitspersample)) 2390 dt = next(i for i in 'BHI' if numpy.dtype(i).itemsize*8 >= bits) 2391 data = numpy.fromstring(data, dtype.byteorder+dt) 2392 result = numpy.empty((data.size, len(bitspersample)), dtype.char) 2393 for i, bps in enumerate(bitspersample): 2394 t = data >> int(numpy.sum(bitspersample[i+1:])) 2395 t &= int('0b'+'1'*bps, 2) 2396 if rescale: 2397 o = ((dtype.itemsize * 8) // bps + 1) * bps 2398 if o > data.dtype.itemsize * 8: 2399 t = t.astype('I') 2400 t *= (2**o - 1) // (2**bps - 1) 2401 t //= 2**(o - (dtype.itemsize * 8)) 2402 result[:, i] = t 2403 return result.reshape(-1)
2404
2405 2406 -def reorient(image, orientation):
2407 """Return reoriented view of image array. 2408 2409 Parameters 2410 ---------- 2411 image : numpy array 2412 Non-squeezed output of asarray() functions. 2413 Axes -3 and -2 must be image length and width respectively. 2414 orientation : int or str 2415 One of TIFF_ORIENTATIONS keys or values. 2416 2417 """ 2418 o = TIFF_ORIENTATIONS.get(orientation, orientation) 2419 if o == 'top_left': 2420 return image 2421 elif o == 'top_right': 2422 return image[..., ::-1, :] 2423 elif o == 'bottom_left': 2424 return image[..., ::-1, :, :] 2425 elif o == 'bottom_right': 2426 return image[..., ::-1, ::-1, :] 2427 elif o == 'left_top': 2428 return numpy.swapaxes(image, -3, -2) 2429 elif o == 'right_top': 2430 return numpy.swapaxes(image, -3, -2)[..., ::-1, :] 2431 elif o == 'left_bottom': 2432 return numpy.swapaxes(image, -3, -2)[..., ::-1, :, :] 2433 elif o == 'right_bottom': 2434 return numpy.swapaxes(image, -3, -2)[..., ::-1, ::-1, :]
2435
2436 2437 -def numpy_fromfile(arg, dtype=float, count=-1, sep=''):
2438 """Return array from data in binary file. 2439 2440 Work around numpy issue #2230, "numpy.fromfile does not accept StringIO 2441 object" https://github.com/numpy/numpy/issues/2230. 2442 2443 """ 2444 try: 2445 return numpy.fromfile(arg, dtype, count, sep) 2446 except IOError: 2447 if count < 0: 2448 size = 2**30 2449 else: 2450 size = count * numpy.dtype(dtype).itemsize 2451 data = arg.read(int(size)) 2452 return numpy.fromstring(data, dtype, count, sep)
2453
2454 2455 -def stripnull(string):
2456 """Return string truncated at first null character.""" 2457 i = string.find(b'\x00') 2458 return string if (i < 0) else string[:i]
2459
2460 2461 -def format_size(size):
2462 """Return file size as string from byte size.""" 2463 for unit in ('B', 'KB', 'MB', 'GB', 'TB'): 2464 if size < 2048: 2465 return "%.f %s" % (size, unit) 2466 size /= 1024.0
2467
2468 2469 -def natural_sorted(iterable):
2470 """Return human sorted list of strings. 2471 2472 >>> natural_sorted(['f1', 'f2', 'f10']) 2473 ['f1', 'f2', 'f10'] 2474 2475 """ 2476 def sortkey(x): 2477 return [(int(c) if c.isdigit() else c) for c in re.split(numbers, x)]
2478 numbers = re.compile('(\d+)') 2479 return sorted(iterable, key=sortkey) 2480
2481 2482 -def datetime_from_timestamp(n, epoch=datetime.datetime.fromordinal(693594)):
2483 """Return datetime object from timestamp in Excel serial format. 2484 2485 Examples 2486 -------- 2487 >>> datetime_from_timestamp(40237.029999999795) 2488 datetime.datetime(2010, 2, 28, 0, 43, 11, 999982) 2489 2490 """ 2491 return epoch + datetime.timedelta(n)
2492
2493 2494 -def test_tifffile(directory='testimages', verbose=True):
2495 """Read all images in directory. Print error message on failure. 2496 2497 Examples 2498 -------- 2499 >>> test_tifffile(verbose=False) 2500 2501 """ 2502 successful = 0 2503 failed = 0 2504 start = time.time() 2505 for f in glob.glob(os.path.join(directory, '*.*')): 2506 if verbose: 2507 print("\n%s>\n" % f.lower(), end='') 2508 t0 = time.time() 2509 try: 2510 tif = TiffFile(f, multifile=True) 2511 except Exception as e: 2512 if not verbose: 2513 print(f, end=' ') 2514 print("ERROR:", e) 2515 failed += 1 2516 continue 2517 try: 2518 img = tif.asarray() 2519 except ValueError: 2520 try: 2521 img = tif[0].asarray() 2522 except Exception as e: 2523 if not verbose: 2524 print(f, end=' ') 2525 print("ERROR:", e) 2526 failed += 1 2527 continue 2528 finally: 2529 tif.close() 2530 successful += 1 2531 if verbose: 2532 print("%s, %s %s, %s, %.0f ms" % ( 2533 str(tif), str(img.shape), img.dtype, tif[0].compression, 2534 (time.time()-t0) * 1e3)) 2535 if verbose: 2536 print("\nSuccessfully read %i of %i files in %.3f s\n" % ( 2537 successful, successful+failed, time.time()-start))
2538
2539 2540 -class TIFF_SUBFILE_TYPES(object):
2541 - def __getitem__(self, key):
2542 result = [] 2543 if key & 1: 2544 result.append('reduced_image') 2545 if key & 2: 2546 result.append('page') 2547 if key & 4: 2548 result.append('mask') 2549 return tuple(result)
2550 2551 2552 TIFF_PHOTOMETRICS = { 2553 0: 'miniswhite', 2554 1: 'minisblack', 2555 2: 'rgb', 2556 3: 'palette', 2557 4: 'mask', 2558 5: 'separated', 2559 6: 'cielab', 2560 7: 'icclab', 2561 8: 'itulab', 2562 32844: 'logl', 2563 32845: 'logluv', 2564 } 2565 2566 TIFF_COMPESSIONS = { 2567 1: None, 2568 2: 'ccittrle', 2569 3: 'ccittfax3', 2570 4: 'ccittfax4', 2571 5: 'lzw', 2572 6: 'ojpeg', 2573 7: 'jpeg', 2574 8: 'adobe_deflate', 2575 9: 't85', 2576 10: 't43', 2577 32766: 'next', 2578 32771: 'ccittrlew', 2579 32773: 'packbits', 2580 32809: 'thunderscan', 2581 32895: 'it8ctpad', 2582 32896: 'it8lw', 2583 32897: 'it8mp', 2584 32898: 'it8bl', 2585 32908: 'pixarfilm', 2586 32909: 'pixarlog', 2587 32946: 'deflate', 2588 32947: 'dcs', 2589 34661: 'jbig', 2590 34676: 'sgilog', 2591 34677: 'sgilog24', 2592 34712: 'jp2000', 2593 34713: 'nef', 2594 } 2595 2596 TIFF_DECOMPESSORS = { 2597 None: lambda x: x, 2598 'adobe_deflate': zlib.decompress, 2599 'deflate': zlib.decompress, 2600 'packbits': decodepackbits, 2601 'lzw': decodelzw, 2602 } 2603 2604 TIFF_DATA_TYPES = { 2605 1: '1B', # BYTE 8-bit unsigned integer. 2606 2: '1s', # ASCII 8-bit byte that contains a 7-bit ASCII code; 2607 # the last byte must be NULL (binary zero). 2608 3: '1H', # SHORT 16-bit (2-byte) unsigned integer 2609 4: '1I', # LONG 32-bit (4-byte) unsigned integer. 2610 5: '2I', # RATIONAL Two LONGs: the first represents the numerator of 2611 # a fraction; the second, the denominator. 2612 6: '1b', # SBYTE An 8-bit signed (twos-complement) integer. 2613 7: '1B', # UNDEFINED An 8-bit byte that may contain anything, 2614 # depending on the definition of the field. 2615 8: '1h', # SSHORT A 16-bit (2-byte) signed (twos-complement) integer. 2616 9: '1i', # SLONG A 32-bit (4-byte) signed (twos-complement) integer. 2617 10: '2i', # SRATIONAL Two SLONGs: the first represents the numerator 2618 # of a fraction, the second the denominator. 2619 11: '1f', # FLOAT Single precision (4-byte) IEEE format. 2620 12: '1d', # DOUBLE Double precision (8-byte) IEEE format. 2621 13: '1I', # IFD unsigned 4 byte IFD offset. 2622 #14: '', # UNICODE 2623 #15: '', # COMPLEX 2624 16: '1Q', # LONG8 unsigned 8 byte integer (BigTiff) 2625 17: '1q', # SLONG8 signed 8 byte integer (BigTiff) 2626 18: '1Q', # IFD8 unsigned 8 byte IFD offset (BigTiff) 2627 } 2628 2629 TIFF_SAMPLE_FORMATS = { 2630 1: 'uint', 2631 2: 'int', 2632 3: 'float', 2633 #4: 'void', 2634 #5: 'complex_int', 2635 6: 'complex', 2636 } 2637 2638 TIFF_SAMPLE_DTYPES = { 2639 ('uint', 1): '?', # bitmap 2640 ('uint', 2): 'B', 2641 ('uint', 3): 'B', 2642 ('uint', 4): 'B', 2643 ('uint', 5): 'B', 2644 ('uint', 6): 'B', 2645 ('uint', 7): 'B', 2646 ('uint', 8): 'B', 2647 ('uint', 9): 'H', 2648 ('uint', 10): 'H', 2649 ('uint', 11): 'H', 2650 ('uint', 12): 'H', 2651 ('uint', 13): 'H', 2652 ('uint', 14): 'H', 2653 ('uint', 15): 'H', 2654 ('uint', 16): 'H', 2655 ('uint', 17): 'I', 2656 ('uint', 18): 'I', 2657 ('uint', 19): 'I', 2658 ('uint', 20): 'I', 2659 ('uint', 21): 'I', 2660 ('uint', 22): 'I', 2661 ('uint', 23): 'I', 2662 ('uint', 24): 'I', 2663 ('uint', 25): 'I', 2664 ('uint', 26): 'I', 2665 ('uint', 27): 'I', 2666 ('uint', 28): 'I', 2667 ('uint', 29): 'I', 2668 ('uint', 30): 'I', 2669 ('uint', 31): 'I', 2670 ('uint', 32): 'I', 2671 ('uint', 64): 'Q', 2672 ('int', 8): 'b', 2673 ('int', 16): 'h', 2674 ('int', 32): 'i', 2675 ('int', 64): 'q', 2676 ('float', 16): 'e', 2677 ('float', 32): 'f', 2678 ('float', 64): 'd', 2679 ('complex', 64): 'F', 2680 ('complex', 128): 'D', 2681 ('uint', (5, 6, 5)): 'B', 2682 } 2683 2684 TIFF_ORIENTATIONS = { 2685 1: 'top_left', 2686 2: 'top_right', 2687 3: 'bottom_right', 2688 4: 'bottom_left', 2689 5: 'left_top', 2690 6: 'right_top', 2691 7: 'right_bottom', 2692 8: 'left_bottom', 2693 } 2694 2695 AXES_LABELS = { 2696 'X': 'width', 2697 'Y': 'height', 2698 'Z': 'depth', 2699 'S': 'sample', # rgb(a) 2700 'P': 'plane', # page 2701 'T': 'time', 2702 'C': 'channel', # color, emission wavelength 2703 'A': 'angle', 2704 'F': 'phase', 2705 'R': 'tile', # region, point 2706 'H': 'lifetime', # histogram 2707 'E': 'lambda', # excitation wavelength 2708 'L': 'exposure', # lux 2709 'V': 'event', 2710 'Q': 'other', 2711 } 2712 2713 AXES_LABELS.update(dict((v, k) for k, v in AXES_LABELS.items())) 2714 2715 # NIH Image PicHeader v1.63 2716 NIH_IMAGE_HEADER = [ 2717 ('fileid', 'a8'), 2718 ('nlines', 'i2'), 2719 ('pixelsperline', 'i2'), 2720 ('version', 'i2'), 2721 ('oldlutmode', 'i2'), 2722 ('oldncolors', 'i2'), 2723 ('colors', 'u1', (3, 32)), 2724 ('oldcolorstart', 'i2'), 2725 ('colorwidth', 'i2'), 2726 ('extracolors', 'u2', (6, 3)), 2727 ('nextracolors', 'i2'), 2728 ('foregroundindex', 'i2'), 2729 ('backgroundindex', 'i2'), 2730 ('xscale', 'f8'), 2731 ('_x0', 'i2'), 2732 ('_x1', 'i2'), 2733 ('units_t', 'i2'), 2734 ('p1', [('x', 'i2'), ('y', 'i2')]), 2735 ('p2', [('x', 'i2'), ('y', 'i2')]), 2736 ('curvefit_t', 'i2'), 2737 ('ncoefficients', 'i2'), 2738 ('coeff', 'f8', 6), 2739 ('_um_len', 'u1'), 2740 ('um', 'a15'), 2741 ('_x2', 'u1'), 2742 ('binarypic', 'b1'), 2743 ('slicestart', 'i2'), 2744 ('sliceend', 'i2'), 2745 ('scalemagnification', 'f4'), 2746 ('nslices', 'i2'), 2747 ('slicespacing', 'f4'), 2748 ('currentslice', 'i2'), 2749 ('frameinterval', 'f4'), 2750 ('pixelaspectratio', 'f4'), 2751 ('colorstart', 'i2'), 2752 ('colorend', 'i2'), 2753 ('ncolors', 'i2'), 2754 ('fill1', '3u2'), 2755 ('fill2', '3u2'), 2756 ('colortable_t', 'u1'), 2757 ('lutmode_t', 'u1'), 2758 ('invertedtable', 'b1'), 2759 ('zeroclip', 'b1'), 2760 ('_xunit_len', 'u1'), 2761 ('xunit', 'a11'), 2762 ('stacktype_t', 'i2'), 2763 ] 2764 2765 #NIH_COLORTABLE_TYPE = ( 2766 # 'CustomTable', 'AppleDefault', 'Pseudo20', 'Pseudo32', 'Rainbow', 2767 # 'Fire1', 'Fire2', 'Ice', 'Grays', 'Spectrum') 2768 #NIH_LUTMODE_TYPE = ( 2769 # 'PseudoColor', 'OldAppleDefault', 'OldSpectrum', 'GrayScale', 2770 # 'ColorLut', 'CustomGrayscale') 2771 #NIH_CURVEFIT_TYPE = ( 2772 # 'StraightLine', 'Poly2', 'Poly3', 'Poly4', 'Poly5', 'ExpoFit', 2773 # 'PowerFit', 'LogFit', 'RodbardFit', 'SpareFit1', 'Uncalibrated', 2774 # 'UncalibratedOD') 2775 #NIH_UNITS_TYPE = ( 2776 # 'Nanometers', 'Micrometers', 'Millimeters', 'Centimeters', 'Meters', 2777 # 'Kilometers', 'Inches', 'Feet', 'Miles', 'Pixels', 'OtherUnits') 2778 #NIH_STACKTYPE_TYPE = ( 2779 # 'VolumeStack', 'RGBStack', 'MovieStack', 'HSVStack') 2780 2781 # MetaMorph STK tags 2782 MM_TAG_IDS = { 2783 0: 'auto_scale', 2784 1: 'min_scale', 2785 2: 'max_scale', 2786 3: 'spatial_calibration', 2787 #4: 'x_calibration', 2788 #5: 'y_calibration', 2789 #6: 'calibration_units', 2790 #7: 'name', 2791 8: 'thresh_state', 2792 9: 'thresh_state_red', 2793 11: 'thresh_state_green', 2794 12: 'thresh_state_blue', 2795 13: 'thresh_state_lo', 2796 14: 'thresh_state_hi', 2797 15: 'zoom', 2798 #16: 'create_time', 2799 #17: 'last_saved_time', 2800 18: 'current_buffer', 2801 19: 'gray_fit', 2802 20: 'gray_point_count', 2803 #21: 'gray_x', 2804 #22: 'gray_y', 2805 #23: 'gray_min', 2806 #24: 'gray_max', 2807 #25: 'gray_unit_name', 2808 26: 'standard_lut', 2809 27: 'wavelength', 2810 #28: 'stage_position', 2811 #29: 'camera_chip_offset', 2812 #30: 'overlay_mask', 2813 #31: 'overlay_compress', 2814 #32: 'overlay', 2815 #33: 'special_overlay_mask', 2816 #34: 'special_overlay_compress', 2817 #35: 'special_overlay', 2818 36: 'image_property', 2819 #37: 'stage_label', 2820 #38: 'autoscale_lo_info', 2821 #39: 'autoscale_hi_info', 2822 #40: 'absolute_z', 2823 #41: 'absolute_z_valid', 2824 #42: 'gamma', 2825 #43: 'gamma_red', 2826 #44: 'gamma_green', 2827 #45: 'gamma_blue', 2828 #46: 'camera_bin', 2829 47: 'new_lut', 2830 #48: 'image_property_ex', 2831 49: 'plane_property', 2832 #50: 'user_lut_table', 2833 51: 'red_autoscale_info', 2834 #52: 'red_autoscale_lo_info', 2835 #53: 'red_autoscale_hi_info', 2836 54: 'red_minscale_info', 2837 55: 'red_maxscale_info', 2838 56: 'green_autoscale_info', 2839 #57: 'green_autoscale_lo_info', 2840 #58: 'green_autoscale_hi_info', 2841 59: 'green_minscale_info', 2842 60: 'green_maxscale_info', 2843 61: 'blue_autoscale_info', 2844 #62: 'blue_autoscale_lo_info', 2845 #63: 'blue_autoscale_hi_info', 2846 64: 'blue_min_scale_info', 2847 65: 'blue_max_scale_info', 2848 #66: 'overlay_plane_color' 2849 } 2850 2851 # Olympus FluoView 2852 MM_DIMENSION = [ 2853 ('name', 'a16'), 2854 ('size', 'i4'), 2855 ('origin', 'f8'), 2856 ('resolution', 'f8'), 2857 ('unit', 'a64'), 2858 ] 2859 2860 MM_HEADER = [ 2861 ('header_flag', 'i2'), 2862 ('image_type', 'u1'), 2863 ('image_name', 'a257'), 2864 ('offset_data', 'u4'), 2865 ('palette_size', 'i4'), 2866 ('offset_palette0', 'u4'), 2867 ('offset_palette1', 'u4'), 2868 ('comment_size', 'i4'), 2869 ('offset_comment', 'u4'), 2870 ('dimensions', MM_DIMENSION, 10), 2871 ('offset_position', 'u4'), 2872 ('map_type', 'i2'), 2873 ('map_min', 'f8'), 2874 ('map_max', 'f8'), 2875 ('min_value', 'f8'), 2876 ('max_value', 'f8'), 2877 ('offset_map', 'u4'), 2878 ('gamma', 'f8'), 2879 ('offset', 'f8'), 2880 ('gray_channel', MM_DIMENSION), 2881 ('offset_thumbnail', 'u4'), 2882 ('voice_field', 'i4'), 2883 ('offset_voice_field', 'u4'), 2884 ] 2885 2886 # Carl Zeiss LSM 2887 CZ_LSM_INFO = [ 2888 ('magic_number', 'i4'), 2889 ('structure_size', 'i4'), 2890 ('dimension_x', 'i4'), 2891 ('dimension_y', 'i4'), 2892 ('dimension_z', 'i4'), 2893 ('dimension_channels', 'i4'), 2894 ('dimension_time', 'i4'), 2895 ('dimension_data_type', 'i4'), 2896 ('thumbnail_x', 'i4'), 2897 ('thumbnail_y', 'i4'), 2898 ('voxel_size_x', 'f8'), 2899 ('voxel_size_y', 'f8'), 2900 ('voxel_size_z', 'f8'), 2901 ('origin_x', 'f8'), 2902 ('origin_y', 'f8'), 2903 ('origin_z', 'f8'), 2904 ('scan_type', 'u2'), 2905 ('spectral_scan', 'u2'), 2906 ('data_type', 'u4'), 2907 ('offset_vector_overlay', 'u4'), 2908 ('offset_input_lut', 'u4'), 2909 ('offset_output_lut', 'u4'), 2910 ('offset_channel_colors', 'u4'), 2911 ('time_interval', 'f8'), 2912 ('offset_channel_data_types', 'u4'), 2913 ('offset_scan_information', 'u4'), 2914 ('offset_ks_data', 'u4'), 2915 ('offset_time_stamps', 'u4'), 2916 ('offset_event_list', 'u4'), 2917 ('offset_roi', 'u4'), 2918 ('offset_bleach_roi', 'u4'), 2919 ('offset_next_recording', 'u4'), 2920 ('display_aspect_x', 'f8'), 2921 ('display_aspect_y', 'f8'), 2922 ('display_aspect_z', 'f8'), 2923 ('display_aspect_time', 'f8'), 2924 ('offset_mean_of_roi_overlay', 'u4'), 2925 ('offset_topo_isoline_overlay', 'u4'), 2926 ('offset_topo_profile_overlay', 'u4'), 2927 ('offset_linescan_overlay', 'u4'), 2928 ('offset_toolbar_flags', 'u4'), 2929 ] 2930 2931 # Import functions for LSM_INFO sub-records 2932 CZ_LSM_INFO_READERS = { 2933 'scan_information': read_cz_lsm_scan_info, 2934 'time_stamps': read_cz_lsm_time_stamps, 2935 'event_list': read_cz_lsm_event_list, 2936 } 2937 2938 # Map cz_lsm_info.scan_type to dimension order 2939 CZ_SCAN_TYPES = { 2940 0: 'XYZCT', # x-y-z scan 2941 1: 'XYZCT', # z scan (x-z plane) 2942 2: 'XYZCT', # line scan 2943 3: 'XYTCZ', # time series x-y 2944 4: 'XYZTC', # time series x-z 2945 5: 'XYTCZ', # time series 'Mean of ROIs' 2946 6: 'XYZTC', # time series x-y-z 2947 7: 'XYCTZ', # spline scan 2948 8: 'XYCZT', # spline scan x-z 2949 9: 'XYTCZ', # time series spline plane x-z 2950 10: 'XYZCT', # point mode 2951 } 2952 2953 # Map dimension codes to cz_lsm_info attribute 2954 CZ_DIMENSIONS = { 2955 'X': 'dimension_x', 2956 'Y': 'dimension_y', 2957 'Z': 'dimension_z', 2958 'C': 'dimension_channels', 2959 'T': 'dimension_time', 2960 } 2961 2962 # Descriptions of cz_lsm_info.data_type 2963 CZ_DATA_TYPES = { 2964 0: 'varying data types', 2965 2: '12 bit unsigned integer', 2966 5: '32 bit float', 2967 } 2968 2969 CZ_LSM_SCAN_INFO_ARRAYS = { 2970 0x20000000: "tracks", 2971 0x30000000: "lasers", 2972 0x60000000: "detectionchannels", 2973 0x80000000: "illuminationchannels", 2974 0xa0000000: "beamsplitters", 2975 0xc0000000: "datachannels", 2976 0x13000000: "markers", 2977 0x11000000: "timers", 2978 } 2979 2980 CZ_LSM_SCAN_INFO_STRUCTS = { 2981 0x40000000: "tracks", 2982 0x50000000: "lasers", 2983 0x70000000: "detectionchannels", 2984 0x90000000: "illuminationchannels", 2985 0xb0000000: "beamsplitters", 2986 0xd0000000: "datachannels", 2987 0x14000000: "markers", 2988 0x12000000: "timers", 2989 } 2990 2991 CZ_LSM_SCAN_INFO_ATTRIBUTES = { 2992 0x10000001: "name", 2993 0x10000002: "description", 2994 0x10000003: "notes", 2995 0x10000004: "objective", 2996 0x10000005: "processing_summary", 2997 0x10000006: "special_scan_mode", 2998 0x10000007: "oledb_recording_scan_type", 2999 0x10000008: "oledb_recording_scan_mode", 3000 0x10000009: "number_of_stacks", 3001 0x1000000a: "lines_per_plane", 3002 0x1000000b: "samples_per_line", 3003 0x1000000c: "planes_per_volume", 3004 0x1000000d: "images_width", 3005 0x1000000e: "images_height", 3006 0x1000000f: "images_number_planes", 3007 0x10000010: "images_number_stacks", 3008 0x10000011: "images_number_channels", 3009 0x10000012: "linscan_xy_size", 3010 0x10000013: "scan_direction", 3011 0x10000014: "time_series", 3012 0x10000015: "original_scan_data", 3013 0x10000016: "zoom_x", 3014 0x10000017: "zoom_y", 3015 0x10000018: "zoom_z", 3016 0x10000019: "sample_0x", 3017 0x1000001a: "sample_0y", 3018 0x1000001b: "sample_0z", 3019 0x1000001c: "sample_spacing", 3020 0x1000001d: "line_spacing", 3021 0x1000001e: "plane_spacing", 3022 0x1000001f: "plane_width", 3023 0x10000020: "plane_height", 3024 0x10000021: "volume_depth", 3025 0x10000023: "nutation", 3026 0x10000034: "rotation", 3027 0x10000035: "precession", 3028 0x10000036: "sample_0time", 3029 0x10000037: "start_scan_trigger_in", 3030 0x10000038: "start_scan_trigger_out", 3031 0x10000039: "start_scan_event", 3032 0x10000040: "start_scan_time", 3033 0x10000041: "stop_scan_trigger_in", 3034 0x10000042: "stop_scan_trigger_out", 3035 0x10000043: "stop_scan_event", 3036 0x10000044: "stop_scan_time", 3037 0x10000045: "use_rois", 3038 0x10000046: "use_reduced_memory_rois", 3039 0x10000047: "user", 3040 0x10000048: "use_bccorrection", 3041 0x10000049: "position_bccorrection1", 3042 0x10000050: "position_bccorrection2", 3043 0x10000051: "interpolation_y", 3044 0x10000052: "camera_binning", 3045 0x10000053: "camera_supersampling", 3046 0x10000054: "camera_frame_width", 3047 0x10000055: "camera_frame_height", 3048 0x10000056: "camera_offset_x", 3049 0x10000057: "camera_offset_y", 3050 # lasers 3051 0x50000001: "name", 3052 0x50000002: "acquire", 3053 0x50000003: "power", 3054 # tracks 3055 0x40000001: "multiplex_type", 3056 0x40000002: "multiplex_order", 3057 0x40000003: "sampling_mode", 3058 0x40000004: "sampling_method", 3059 0x40000005: "sampling_number", 3060 0x40000006: "acquire", 3061 0x40000007: "sample_observation_time", 3062 0x4000000b: "time_between_stacks", 3063 0x4000000c: "name", 3064 0x4000000d: "collimator1_name", 3065 0x4000000e: "collimator1_position", 3066 0x4000000f: "collimator2_name", 3067 0x40000010: "collimator2_position", 3068 0x40000011: "is_bleach_track", 3069 0x40000012: "is_bleach_after_scan_number", 3070 0x40000013: "bleach_scan_number", 3071 0x40000014: "trigger_in", 3072 0x40000015: "trigger_out", 3073 0x40000016: "is_ratio_track", 3074 0x40000017: "bleach_count", 3075 0x40000018: "spi_center_wavelength", 3076 0x40000019: "pixel_time", 3077 0x40000021: "condensor_frontlens", 3078 0x40000023: "field_stop_value", 3079 0x40000024: "id_condensor_aperture", 3080 0x40000025: "condensor_aperture", 3081 0x40000026: "id_condensor_revolver", 3082 0x40000027: "condensor_filter", 3083 0x40000028: "id_transmission_filter1", 3084 0x40000029: "id_transmission1", 3085 0x40000030: "id_transmission_filter2", 3086 0x40000031: "id_transmission2", 3087 0x40000032: "repeat_bleach", 3088 0x40000033: "enable_spot_bleach_pos", 3089 0x40000034: "spot_bleach_posx", 3090 0x40000035: "spot_bleach_posy", 3091 0x40000036: "spot_bleach_posz", 3092 0x40000037: "id_tubelens", 3093 0x40000038: "id_tubelens_position", 3094 0x40000039: "transmitted_light", 3095 0x4000003a: "reflected_light", 3096 0x4000003b: "simultan_grab_and_bleach", 3097 0x4000003c: "bleach_pixel_time", 3098 # detection_channels 3099 0x70000001: "integration_mode", 3100 0x70000002: "special_mode", 3101 0x70000003: "detector_gain_first", 3102 0x70000004: "detector_gain_last", 3103 0x70000005: "amplifier_gain_first", 3104 0x70000006: "amplifier_gain_last", 3105 0x70000007: "amplifier_offs_first", 3106 0x70000008: "amplifier_offs_last", 3107 0x70000009: "pinhole_diameter", 3108 0x7000000a: "counting_trigger", 3109 0x7000000b: "acquire", 3110 0x7000000c: "point_detector_name", 3111 0x7000000d: "amplifier_name", 3112 0x7000000e: "pinhole_name", 3113 0x7000000f: "filter_set_name", 3114 0x70000010: "filter_name", 3115 0x70000013: "integrator_name", 3116 0x70000014: "detection_channel_name", 3117 0x70000015: "detection_detector_gain_bc1", 3118 0x70000016: "detection_detector_gain_bc2", 3119 0x70000017: "detection_amplifier_gain_bc1", 3120 0x70000018: "detection_amplifier_gain_bc2", 3121 0x70000019: "detection_amplifier_offset_bc1", 3122 0x70000020: "detection_amplifier_offset_bc2", 3123 0x70000021: "detection_spectral_scan_channels", 3124 0x70000022: "detection_spi_wavelength_start", 3125 0x70000023: "detection_spi_wavelength_stop", 3126 0x70000026: "detection_dye_name", 3127 0x70000027: "detection_dye_folder", 3128 # illumination_channels 3129 0x90000001: "name", 3130 0x90000002: "power", 3131 0x90000003: "wavelength", 3132 0x90000004: "aquire", 3133 0x90000005: "detchannel_name", 3134 0x90000006: "power_bc1", 3135 0x90000007: "power_bc2", 3136 # beam_splitters 3137 0xb0000001: "filter_set", 3138 0xb0000002: "filter", 3139 0xb0000003: "name", 3140 # data_channels 3141 0xd0000001: "name", 3142 0xd0000003: "acquire", 3143 0xd0000004: "color", 3144 0xd0000005: "sample_type", 3145 0xd0000006: "bits_per_sample", 3146 0xd0000007: "ratio_type", 3147 0xd0000008: "ratio_track1", 3148 0xd0000009: "ratio_track2", 3149 0xd000000a: "ratio_channel1", 3150 0xd000000b: "ratio_channel2", 3151 0xd000000c: "ratio_const1", 3152 0xd000000d: "ratio_const2", 3153 0xd000000e: "ratio_const3", 3154 0xd000000f: "ratio_const4", 3155 0xd0000010: "ratio_const5", 3156 0xd0000011: "ratio_const6", 3157 0xd0000012: "ratio_first_images1", 3158 0xd0000013: "ratio_first_images2", 3159 0xd0000014: "dye_name", 3160 0xd0000015: "dye_folder", 3161 0xd0000016: "spectrum", 3162 0xd0000017: "acquire", 3163 # markers 3164 0x14000001: "name", 3165 0x14000002: "description", 3166 0x14000003: "trigger_in", 3167 0x14000004: "trigger_out", 3168 # timers 3169 0x12000001: "name", 3170 0x12000002: "description", 3171 0x12000003: "interval", 3172 0x12000004: "trigger_in", 3173 0x12000005: "trigger_out", 3174 0x12000006: "activation_time", 3175 0x12000007: "activation_number", 3176 } 3177 3178 # Map TIFF tag code to attribute name, default value, type, count, validator 3179 TIFF_TAGS = { 3180 254: ('new_subfile_type', 0, 4, 1, TIFF_SUBFILE_TYPES()), 3181 255: ('subfile_type', None, 3, 1, 3182 {0: 'undefined', 1: 'image', 2: 'reduced_image', 3: 'page'}), 3183 256: ('image_width', None, 4, 1, None), 3184 257: ('image_length', None, 4, 1, None), 3185 258: ('bits_per_sample', 1, 3, 1, None), 3186 259: ('compression', 1, 3, 1, TIFF_COMPESSIONS), 3187 262: ('photometric', None, 3, 1, TIFF_PHOTOMETRICS), 3188 266: ('fill_order', 1, 3, 1, {1: 'msb2lsb', 2: 'lsb2msb'}), 3189 269: ('document_name', None, 2, None, None), 3190 270: ('image_description', None, 2, None, None), 3191 271: ('make', None, 2, None, None), 3192 272: ('model', None, 2, None, None), 3193 273: ('strip_offsets', None, 4, None, None), 3194 274: ('orientation', 1, 3, 1, TIFF_ORIENTATIONS), 3195 277: ('samples_per_pixel', 1, 3, 1, None), 3196 278: ('rows_per_strip', 2**32-1, 4, 1, None), 3197 279: ('strip_byte_counts', None, 4, None, None), 3198 280: ('min_sample_value', None, 3, None, None), 3199 281: ('max_sample_value', None, 3, None, None), # 2**bits_per_sample 3200 282: ('x_resolution', None, 5, 1, None), 3201 283: ('y_resolution', None, 5, 1, None), 3202 284: ('planar_configuration', 1, 3, 1, {1: 'contig', 2: 'separate'}), 3203 285: ('page_name', None, 2, None, None), 3204 286: ('x_position', None, 5, 1, None), 3205 287: ('y_position', None, 5, 1, None), 3206 296: ('resolution_unit', 2, 4, 1, {1: 'none', 2: 'inch', 3: 'centimeter'}), 3207 297: ('page_number', None, 3, 2, None), 3208 305: ('software', None, 2, None, None), 3209 306: ('datetime', None, 2, None, None), 3210 315: ('artist', None, 2, None, None), 3211 316: ('host_computer', None, 2, None, None), 3212 317: ('predictor', 1, 3, 1, {1: None, 2: 'horizontal'}), 3213 320: ('color_map', None, 3, None, None), 3214 322: ('tile_width', None, 4, 1, None), 3215 323: ('tile_length', None, 4, 1, None), 3216 324: ('tile_offsets', None, 4, None, None), 3217 325: ('tile_byte_counts', None, 4, None, None), 3218 338: ('extra_samples', None, 3, None, 3219 {0: 'unspecified', 1: 'assocalpha', 2: 'unassalpha'}), 3220 339: ('sample_format', 1, 3, 1, TIFF_SAMPLE_FORMATS), 3221 347: ('jpeg_tables', None, None, None, None), 3222 530: ('ycbcr_subsampling', 1, 3, 2, None), 3223 531: ('ycbcr_positioning', 1, 3, 1, None), 3224 32997: ('image_depth', None, 4, 1, None), 3225 32998: ('tile_depth', None, 4, 1, None), 3226 33432: ('copyright', None, 1, None, None), 3227 33445: ('md_file_tag', None, 4, 1, None), 3228 33446: ('md_scale_pixel', None, 5, 1, None), 3229 33447: ('md_color_table', None, 3, None, None), 3230 33448: ('md_lab_name', None, 2, None, None), 3231 33449: ('md_sample_info', None, 2, None, None), 3232 33450: ('md_prep_date', None, 2, None, None), 3233 33451: ('md_prep_time', None, 2, None, None), 3234 33452: ('md_file_units', None, 2, None, None), 3235 33550: ('model_pixel_scale', None, 12, 3, None), 3236 33922: ('model_tie_point', None, 12, None, None), 3237 37510: ('user_comment', None, None, None, None), 3238 34665: ('exif_ifd', None, None, 1, None), 3239 34735: ('geo_key_directory', None, 3, None, None), 3240 34736: ('geo_double_params', None, 12, None, None), 3241 34737: ('geo_ascii_params', None, 2, None, None), 3242 34853: ('gps_ifd', None, None, 1, None), 3243 42112: ('gdal_metadata', None, 2, None, None), 3244 42113: ('gdal_nodata', None, 2, None, None), 3245 50838: ('imagej_byte_counts', None, None, None, None), 3246 50289: ('mc_xy_position', None, 12, 2, None), 3247 50290: ('mc_z_position', None, 12, 1, None), 3248 50291: ('mc_xy_calibration', None, 12, 3, None), 3249 50292: ('mc_lens_lem_na_n', None, 12, 3, None), 3250 50293: ('mc_channel_name', None, 1, None, None), 3251 50294: ('mc_ex_wavelength', None, 12, 1, None), 3252 50295: ('mc_time_stamp', None, 12, 1, None), 3253 65200: ('flex_xml', None, 2, None, None), 3254 # code: (attribute name, default value, type, count, validator) 3255 } 3256 3257 # Map custom TIFF tag codes to attribute names and import functions 3258 CUSTOM_TAGS = { 3259 700: ('xmp', read_bytes), 3260 34377: ('photoshop', read_numpy), 3261 33723: ('iptc', read_bytes), 3262 34675: ('icc_profile', read_numpy), 3263 33628: ('mm_uic1', read_mm_uic1), 3264 33629: ('mm_uic2', read_mm_uic2), 3265 33630: ('mm_uic3', read_mm_uic3), 3266 33631: ('mm_uic4', read_mm_uic4), 3267 34361: ('mm_header', read_mm_header), 3268 34362: ('mm_stamp', read_mm_stamp), 3269 34386: ('mm_user_block', read_bytes), 3270 34412: ('cz_lsm_info', read_cz_lsm_info), 3271 43314: ('nih_image_header', read_nih_image_header), 3272 # 40001: ('mc_ipwinscal', read_bytes), 3273 40100: ('mc_id_old', read_bytes), 3274 50288: ('mc_id', read_bytes), 3275 50296: ('mc_frame_properties', read_bytes), 3276 50839: ('imagej_metadata', read_bytes), 3277 51123: ('micromanager_metadata', read_json), 3278 } 3279 3280 # Max line length of printed output 3281 PRINT_LINE_LEN = 79
3282 3283 3284 -def imshow(data, title=None, vmin=0, vmax=None, cmap=None, 3285 bitspersample=None, photometric='rgb', interpolation='nearest', 3286 dpi=96, figure=None, subplot=111, maxdim=8192, **kwargs):
3287 """Plot n-dimensional images using matplotlib.pyplot. 3288 3289 Return figure, subplot and plot axis. 3290 Requires pyplot already imported ``from matplotlib import pyplot``. 3291 3292 Parameters 3293 ---------- 3294 bitspersample : int or None 3295 Number of bits per channel in integer RGB images. 3296 photometric : {'miniswhite', 'minisblack', 'rgb', or 'palette'} 3297 The color space of the image data. 3298 title : str 3299 Window and subplot title. 3300 figure : matplotlib.figure.Figure (optional). 3301 Matplotlib to use for plotting. 3302 subplot : int 3303 A matplotlib.pyplot.subplot axis. 3304 maxdim : int 3305 maximum image size in any dimension. 3306 kwargs : optional 3307 Arguments for matplotlib.pyplot.imshow. 3308 3309 """ 3310 #if photometric not in ('miniswhite', 'minisblack', 'rgb', 'palette'): 3311 # raise ValueError("Can't handle %s photometrics" % photometric) 3312 # TODO: handle photometric == 'separated' (CMYK) 3313 isrgb = photometric in ('rgb', 'palette') 3314 data = numpy.atleast_2d(data.squeeze()) 3315 data = data[(slice(0, maxdim), ) * len(data.shape)] 3316 3317 dims = data.ndim 3318 if dims < 2: 3319 raise ValueError("not an image") 3320 elif dims == 2: 3321 dims = 0 3322 isrgb = False 3323 else: 3324 if isrgb and data.shape[-3] in (3, 4): 3325 data = numpy.swapaxes(data, -3, -2) 3326 data = numpy.swapaxes(data, -2, -1) 3327 elif not isrgb and data.shape[-1] in (3, 4): 3328 data = numpy.swapaxes(data, -3, -1) 3329 data = numpy.swapaxes(data, -2, -1) 3330 isrgb = isrgb and data.shape[-1] in (3, 4) 3331 dims -= 3 if isrgb else 2 3332 3333 if photometric == 'palette' and isrgb: 3334 datamax = data.max() 3335 if datamax > 255: 3336 data >>= 8 # possible precision loss 3337 data = data.astype('B') 3338 elif data.dtype.kind in 'ui': 3339 if not (isrgb and data.dtype.itemsize <= 1) or bitspersample is None: 3340 try: 3341 bitspersample = int(math.ceil(math.log(data.max(), 2))) 3342 except Exception: 3343 bitspersample = data.dtype.itemsize * 8 3344 elif not isinstance(bitspersample, int): 3345 # bitspersample can be tuple, e.g. (5, 6, 5) 3346 bitspersample = data.dtype.itemsize * 8 3347 datamax = 2**bitspersample 3348 if isrgb: 3349 if bitspersample < 8: 3350 data <<= 8 - bitspersample 3351 elif bitspersample > 8: 3352 data >>= bitspersample - 8 # precision loss 3353 data = data.astype('B') 3354 elif data.dtype.kind == 'f': 3355 datamax = data.max() 3356 if isrgb and datamax > 1.0: 3357 if data.dtype.char == 'd': 3358 data = data.astype('f') 3359 data /= datamax 3360 elif data.dtype.kind == 'b': 3361 datamax = 1 3362 elif data.dtype.kind == 'c': 3363 raise NotImplementedError("complex type") # TODO: handle complex types 3364 3365 if not isrgb: 3366 if vmax is None: 3367 vmax = datamax 3368 if vmin is None: 3369 if data.dtype.kind == 'i': 3370 dtmin = numpy.iinfo(data.dtype).min 3371 vmin = numpy.min(data) 3372 if vmin == dtmin: 3373 vmin = numpy.min(data > dtmin) 3374 if data.dtype.kind == 'f': 3375 dtmin = numpy.finfo(data.dtype).min 3376 vmin = numpy.min(data) 3377 if vmin == dtmin: 3378 vmin = numpy.min(data > dtmin) 3379 else: 3380 vmin = 0 3381 3382 pyplot = sys.modules['matplotlib.pyplot'] 3383 3384 if figure is None: 3385 pyplot.rc('font', family='sans-serif', weight='normal', size=8) 3386 figure = pyplot.figure(dpi=dpi, figsize=(10.3, 6.3), frameon=True, 3387 facecolor='1.0', edgecolor='w') 3388 try: 3389 figure.canvas.manager.window.title(title) 3390 except Exception: 3391 pass 3392 pyplot.subplots_adjust(bottom=0.03*(dims+2), top=0.9, 3393 left=0.1, right=0.95, hspace=0.05, wspace=0.0) 3394 subplot = pyplot.subplot(subplot) 3395 3396 if title: 3397 try: 3398 title = unicode(title, 'Windows-1252') 3399 except TypeError: 3400 pass 3401 pyplot.title(title, size=11) 3402 3403 if cmap is None: 3404 if data.dtype.kind in 'ub' and vmin == 0: 3405 cmap = 'gray' 3406 else: 3407 cmap = 'coolwarm' 3408 if photometric == 'miniswhite': 3409 cmap += '_r' 3410 3411 image = pyplot.imshow(data[(0, ) * dims].squeeze(), vmin=vmin, vmax=vmax, 3412 cmap=cmap, interpolation=interpolation, **kwargs) 3413 3414 if not isrgb: 3415 pyplot.colorbar() # panchor=(0.55, 0.5), fraction=0.05 3416 3417 def format_coord(x, y): 3418 # callback function to format coordinate display in toolbar 3419 x = int(x + 0.5) 3420 y = int(y + 0.5) 3421 try: 3422 if dims: 3423 return "%s @ %s [%4i, %4i]" % (cur_ax_dat[1][y, x], 3424 current, x, y) 3425 else: 3426 return "%s @ [%4i, %4i]" % (data[y, x], x, y) 3427 except IndexError: 3428 return ""
3429 3430 pyplot.gca().format_coord = format_coord 3431 3432 if dims: 3433 current = list((0, ) * dims) 3434 cur_ax_dat = [0, data[tuple(current)].squeeze()] 3435 sliders = [pyplot.Slider( 3436 pyplot.axes([0.125, 0.03*(axis+1), 0.725, 0.025]), 3437 'Dimension %i' % axis, 0, data.shape[axis]-1, 0, facecolor='0.5', 3438 valfmt='%%.0f [%i]' % data.shape[axis]) for axis in range(dims)] 3439 for slider in sliders: 3440 slider.drawon = False 3441 3442 def set_image(current, sliders=sliders, data=data): 3443 # change image and redraw canvas 3444 cur_ax_dat[1] = data[tuple(current)].squeeze() 3445 image.set_data(cur_ax_dat[1]) 3446 for ctrl, index in zip(sliders, current): 3447 ctrl.eventson = False 3448 ctrl.set_val(index) 3449 ctrl.eventson = True 3450 figure.canvas.draw() 3451 3452 def on_changed(index, axis, data=data, current=current): 3453 # callback function for slider change event 3454 index = int(round(index)) 3455 cur_ax_dat[0] = axis 3456 if index == current[axis]: 3457 return 3458 if index >= data.shape[axis]: 3459 index = 0 3460 elif index < 0: 3461 index = data.shape[axis] - 1 3462 current[axis] = index 3463 set_image(current) 3464 3465 def on_keypressed(event, data=data, current=current): 3466 # callback function for key press event 3467 key = event.key 3468 axis = cur_ax_dat[0] 3469 if str(key) in '0123456789': 3470 on_changed(key, axis) 3471 elif key == 'right': 3472 on_changed(current[axis] + 1, axis) 3473 elif key == 'left': 3474 on_changed(current[axis] - 1, axis) 3475 elif key == 'up': 3476 cur_ax_dat[0] = 0 if axis == len(data.shape)-1 else axis + 1 3477 elif key == 'down': 3478 cur_ax_dat[0] = len(data.shape)-1 if axis == 0 else axis - 1 3479 elif key == 'end': 3480 on_changed(data.shape[axis] - 1, axis) 3481 elif key == 'home': 3482 on_changed(0, axis) 3483 3484 figure.canvas.mpl_connect('key_press_event', on_keypressed) 3485 for axis, ctrl in enumerate(sliders): 3486 ctrl.on_changed(lambda k, a=axis: on_changed(k, a)) 3487 3488 return figure, subplot, image 3489
3490 3491 -def _app_show():
3492 """Block the GUI. For use as skimage plugin.""" 3493 pyplot = sys.modules['matplotlib.pyplot'] 3494 pyplot.show()
3495
3496 3497 -def main(argv=None):
3498 """Command line usage main function.""" 3499 if float(sys.version[0:3]) < 2.6: 3500 print("This script requires Python version 2.6 or better.") 3501 print("This is Python version %s" % sys.version) 3502 return 0 3503 if argv is None: 3504 argv = sys.argv 3505 3506 import optparse 3507 3508 search_doc = lambda r, d: re.search(r, __doc__).group(1) if __doc__ else d 3509 parser = optparse.OptionParser( 3510 usage="usage: %prog [options] path", 3511 description=search_doc("\n\n([^|]*?)\n\n", ''), 3512 version="%%prog %s" % search_doc(":Version: (.*)", "Unknown")) 3513 opt = parser.add_option 3514 opt('-p', '--page', dest='page', type='int', default=-1, 3515 help="display single page") 3516 opt('-s', '--series', dest='series', type='int', default=-1, 3517 help="display series of pages of same shape") 3518 opt('--nomultifile', dest='nomultifile', action='store_true', 3519 default=False, help="don't read OME series from multiple files") 3520 opt('--noplot', dest='noplot', action='store_true', default=False, 3521 help="don't display images") 3522 opt('--interpol', dest='interpol', metavar='INTERPOL', default='bilinear', 3523 help="image interpolation method") 3524 opt('--dpi', dest='dpi', type='int', default=96, 3525 help="set plot resolution") 3526 opt('--debug', dest='debug', action='store_true', default=False, 3527 help="raise exception on failures") 3528 opt('--test', dest='test', action='store_true', default=False, 3529 help="try read all images in path") 3530 opt('--doctest', dest='doctest', action='store_true', default=False, 3531 help="runs the internal tests") 3532 opt('-v', '--verbose', dest='verbose', action='store_true', default=True) 3533 opt('-q', '--quiet', dest='verbose', action='store_false') 3534 3535 settings, path = parser.parse_args() 3536 path = ' '.join(path) 3537 3538 if settings.doctest: 3539 import doctest 3540 doctest.testmod() 3541 return 0 3542 if not path: 3543 parser.error("No file specified") 3544 if settings.test: 3545 test_tifffile(path, settings.verbose) 3546 return 0 3547 3548 if any(i in path for i in '?*'): 3549 path = glob.glob(path) 3550 if not path: 3551 print('no files match the pattern') 3552 return 0 3553 # TODO: handle image sequences 3554 #if len(path) == 1: 3555 path = path[0] 3556 3557 print("Reading file structure...", end=' ') 3558 start = time.time() 3559 try: 3560 tif = TiffFile(path, multifile=not settings.nomultifile) 3561 except Exception as e: 3562 if settings.debug: 3563 raise 3564 else: 3565 print("\n", e) 3566 sys.exit(0) 3567 print("%.3f ms" % ((time.time()-start) * 1e3)) 3568 3569 if tif.is_ome: 3570 settings.norgb = True 3571 3572 images = [(None, tif[0 if settings.page < 0 else settings.page])] 3573 if not settings.noplot: 3574 print("Reading image data... ", end=' ') 3575 3576 def notnone(x): 3577 return next(i for i in x if i is not None)
3578 start = time.time() 3579 try: 3580 if settings.page >= 0: 3581 images = [(tif.asarray(key=settings.page), 3582 tif[settings.page])] 3583 elif settings.series >= 0: 3584 images = [(tif.asarray(series=settings.series), 3585 notnone(tif.series[settings.series].pages))] 3586 else: 3587 images = [] 3588 for i, s in enumerate(tif.series): 3589 try: 3590 images.append( 3591 (tif.asarray(series=i), notnone(s.pages))) 3592 except ValueError as e: 3593 images.append((None, notnone(s.pages))) 3594 if settings.debug: 3595 raise 3596 else: 3597 print("\n* series %i failed: %s... " % (i, e), 3598 end='') 3599 print("%.3f ms" % ((time.time()-start) * 1e3)) 3600 except Exception as e: 3601 if settings.debug: 3602 raise 3603 else: 3604 print(e) 3605 3606 tif.close() 3607 3608 print("\nTIFF file:", tif) 3609 print() 3610 for i, s in enumerate(tif.series): 3611 print ("Series %i" % i) 3612 print(s) 3613 print() 3614 for i, page in images: 3615 print(page) 3616 print(page.tags) 3617 if page.is_palette: 3618 print("\nColor Map:", page.color_map.shape, page.color_map.dtype) 3619 for attr in ('cz_lsm_info', 'cz_lsm_scan_information', 'mm_uic_tags', 3620 'mm_header', 'imagej_tags', 'micromanager_metadata', 3621 'nih_image_header'): 3622 if hasattr(page, attr): 3623 print("", attr.upper(), Record(getattr(page, attr)), sep="\n") 3624 print() 3625 if page.is_micromanager: 3626 print('MICROMANAGER_FILE_METADATA') 3627 print(Record(tif.micromanager_metadata)) 3628 3629 if images and not settings.noplot: 3630 try: 3631 import matplotlib 3632 matplotlib.use('TkAgg') 3633 from matplotlib import pyplot 3634 except ImportError as e: 3635 warnings.warn("failed to import matplotlib.\n%s" % e) 3636 else: 3637 for img, page in images: 3638 if img is None: 3639 continue 3640 vmin, vmax = None, None 3641 if 'gdal_nodata' in page.tags: 3642 vmin = numpy.min(img[img > float(page.gdal_nodata)]) 3643 if page.is_stk: 3644 try: 3645 vmin = page.mm_uic_tags['min_scale'] 3646 vmax = page.mm_uic_tags['max_scale'] 3647 except KeyError: 3648 pass 3649 else: 3650 if vmax <= vmin: 3651 vmin, vmax = None, None 3652 title = "%s\n %s" % (str(tif), str(page)) 3653 imshow(img, title=title, vmin=vmin, vmax=vmax, 3654 bitspersample=page.bits_per_sample, 3655 photometric=page.photometric, 3656 interpolation=settings.interpol, 3657 dpi=settings.dpi) 3658 pyplot.show() 3659 3660 3661 TIFFfile = TiffFile # backwards compatibility 3662 3663 if sys.version_info[0] > 2: 3664 basestring = str, bytes 3665 unicode = str 3666 3667 if __name__ == "__main__": 3668 sys.exit(main()) 3669