1 2 3#! /usr/bin/env python3 4# -*- coding: utf-8 -*- 5# tifffile.py 6 7# Copyright (c) 2008-2018, Christoph Gohlke 8# Copyright (c) 2008-2018, The Regents of the University of California 9# Produced at the Laboratory for Fluorescence Dynamics 10# All rights reserved. 11# 12# Redistribution and use in source and binary forms, with or without 13# modification, are permitted provided that the following conditions are met: 14# 15# * Redistributions of source code must retain the above copyright 16# notice, this list of conditions and the following disclaimer. 17# * Redistributions in binary form must reproduce the above copyright 18# notice, this list of conditions and the following disclaimer in the 19# documentation and/or other materials provided with the distribution. 20# * Neither the name of the copyright holders nor the names of any 21# contributors may be used to endorse or promote products derived 22# from this software without specific prior written permission. 23# 24# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34# POSSIBILITY OF SUCH DAMAGE. 35 36"""Read image and meta data from (bio) TIFF(R) files. Save numpy arrays as TIFF. 37 38Image and metadata can be read from TIFF, BigTIFF, OME-TIFF, STK, LSM, NIH, 39SGI, ImageJ, MicroManager, FluoView, ScanImage, SEQ, GEL, and GeoTIFF files. 40 41Tifffile is not a general-purpose TIFF library. 42Only a subset of the TIFF specification is supported, mainly uncompressed and 43losslessly compressed 1, 8, 16, 32 and 64 bit integer, 16, 32 and 64-bit float, 44grayscale and RGB(A) images, which are commonly used in scientific imaging. 45Specifically, reading slices of image data, image trees defined via SubIFDs, 46CCITT and OJPEG compression, chroma subsampling without JPEG compression, 47or IPTC and XMP metadata are not implemented. 48 49TIFF(R), the tagged Image File Format, is a trademark and under control of 50Adobe Systems Incorporated. BigTIFF allows for files greater than 4 GB. 51STK, LSM, FluoView, SGI, SEQ, GEL, and OME-TIFF, are custom extensions 52defined by Molecular Devices (Universal Imaging Corporation), Carl Zeiss 53MicroImaging, Olympus, Silicon Graphics International, Media Cybernetics, 54Molecular Dynamics, and the Open Microscopy Environment consortium 55respectively. 56 57For command line usage run C{python -m tifffile --help} 58 59:Author: 60 `Christoph Gohlke <https://www.lfd.uci.edu/~gohlke/>`_ 61 62:Organization: 63 Laboratory for Fluorescence Dynamics, University of California, Irvine 64 65:Version: 2018.06.15 66 67Requirements 68------------ 69* `CPython 3.6 64-bit <https://www.python.org>`_ 70* `Numpy 1.14 <http://www.numpy.org>`_ 71* `Matplotlib 2.2 <https://www.matplotlib.org>`_ (optional for plotting) 72* `Tifffile.c 2018.02.10 <https://www.lfd.uci.edu/~gohlke/>`_ 73 (recommended for faster decoding of PackBits and LZW encoded strings) 74* `Tifffile_geodb.py 2018.02.10 <https://www.lfd.uci.edu/~gohlke/>`_ 75 (optional enums for GeoTIFF metadata) 76* Python 2 requires 'futures', 'enum34', 'pathlib'. 77 78Revisions 79--------- 802018.06.15 81 Pass 2680 tests. 82 Towards reading JPEG and other compressions via imagecodecs package (WIP). 83 Add function to validate TIFF using 'jhove -m TIFF-hul'. 84 Save bool arrays as bilevel TIFF. 85 Accept pathlib.Path as filenames. 86 Move 'software' argument from TiffWriter __init__ to save. 87 Raise DOS limit to 16 TB. 88 Lazy load lzma and zstd compressors and decompressors. 89 Add option to save IJMetadata tags. 90 Return correct number of pages for truncated series (bug fix). 91 Move EXIF tags to TIFF.TAG as per TIFF/EP standard. 922018.02.18 93 Pass 2293 tests. 94 Always save RowsPerStrip and Resolution tags as required by TIFF standard. 95 Do not use badly typed ImageDescription. 96 Coherce bad ASCII string tags to bytes. 97 Tuning of __str__ functions. 98 Fix reading 'undefined' tag values (bug fix). 99 Read and write ZSTD compressed data. 100 Use hexdump to print byte strings. 101 Determine TIFF byte order from data dtype in imsave. 102 Add option to specify RowsPerStrip for compressed strips. 103 Allow memory map of arrays with non-native byte order. 104 Attempt to handle ScanImage <= 5.1 files. 105 Restore TiffPageSeries.pages sequence interface. 106 Use numpy.frombuffer instead of fromstring to read from binary data. 107 Parse GeoTIFF metadata. 108 Add option to apply horizontal differencing before compression. 109 Towards reading PerkinElmer QPTIFF (no test files). 110 Do not index out of bounds data in tifffile.c unpackbits and decodelzw. 1112017.09.29 (tentative) 112 Many backwards incompatible changes improving speed and resource usage: 113 Pass 2268 tests. 114 Add detail argument to __str__ function. Remove info functions. 115 Fix potential issue correcting offsets of large LSM files with positions. 116 Remove TiffFile sequence interface; use TiffFile.pages instead. 117 Do not make tag values available as TiffPage attributes. 118 Use str (not bytes) type for tag and metadata strings (WIP). 119 Use documented standard tag and value names (WIP). 120 Use enums for some documented TIFF tag values. 121 Remove 'memmap' and 'tmpfile' options; use out='memmap' instead. 122 Add option to specify output in asarray functions. 123 Add option to concurrently decode image strips or tiles using threads. 124 Add TiffPage.asrgb function (WIP). 125 Do not apply colormap in asarray. 126 Remove 'colormapped', 'rgbonly', and 'scale_mdgel' options from asarray. 127 Consolidate metadata in TiffFile _metadata functions. 128 Remove non-tag metadata properties from TiffPage. 129 Add function to convert LSM to tiled BIN files. 130 Align image data in file. 131 Make TiffPage.dtype a numpy.dtype. 132 Add 'ndim' and 'size' properties to TiffPage and TiffPageSeries. 133 Allow imsave to write non-BigTIFF files up to ~4 GB. 134 Only read one page for shaped series if possible. 135 Add memmap function to create memory-mapped array stored in TIFF file. 136 Add option to save empty arrays to TIFF files. 137 Add option to save truncated TIFF files. 138 Allow single tile images to be saved contiguously. 139 Add optional movie mode for files with uniform pages. 140 Lazy load pages. 141 Use lightweight TiffFrame for IFDs sharing properties with key TiffPage. 142 Move module constants to 'TIFF' namespace (speed up module import). 143 Remove 'fastij' option from TiffFile. 144 Remove 'pages' parameter from TiffFile. 145 Remove TIFFfile alias. 146 Deprecate Python 2. 147 Require enum34 and futures packages on Python 2.7. 148 Remove Record class and return all metadata as dict instead. 149 Add functions to parse STK, MetaSeries, ScanImage, SVS, Pilatus metadata. 150 Read tags from EXIF and GPS IFDs. 151 Use pformat for tag and metadata values. 152 Fix reading some UIC tags (bug fix). 153 Do not modify input array in imshow (bug fix). 154 Fix Python implementation of unpack_ints. 1552017.05.23 156 Pass 1961 tests. 157 Write correct number of SampleFormat values (bug fix). 158 Use Adobe deflate code to write ZIP compressed files. 159 Add option to pass tag values as packed binary data for writing. 160 Defer tag validation to attribute access. 161 Use property instead of lazyattr decorator for simple expressions. 1622017.03.17 163 Write IFDs and tag values on word boundaries. 164 Read ScanImage metadata. 165 Remove is_rgb and is_indexed attributes from TiffFile. 166 Create files used by doctests. 1672017.01.12 168 Read Zeiss SEM metadata. 169 Read OME-TIFF with invalid references to external files. 170 Rewrite C LZW decoder (5x faster). 171 Read corrupted LSM files missing EOI code in LZW stream. 1722017.01.01 173 Add option to append images to existing TIFF files. 174 Read files without pages. 175 Read S-FEG and Helios NanoLab tags created by FEI software. 176 Allow saving Color Filter Array (CFA) images. 177 Add info functions returning more information about TiffFile and TiffPage. 178 Add option to read specific pages only. 179 Remove maxpages argument (backwards incompatible). 180 Remove test_tifffile function. 1812016.10.28 182 Pass 1944 tests. 183 Improve detection of ImageJ hyperstacks. 184 Read TVIPS metadata created by EM-MENU (by Marco Oster). 185 Add option to disable using OME-XML metadata. 186 Allow non-integer range attributes in modulo tags (by Stuart Berg). 1872016.06.21 188 Do not always memmap contiguous data in page series. 1892016.05.13 190 Add option to specify resolution unit. 191 Write grayscale images with extra samples when planarconfig is specified. 192 Do not write RGB color images with 2 samples. 193 Reorder TiffWriter.save keyword arguments (backwards incompatible). 1942016.04.18 195 Pass 1932 tests. 196 TiffWriter, imread, and imsave accept open binary file streams. 1972016.04.13 198 Correctly handle reversed fill order in 2 and 4 bps images (bug fix). 199 Implement reverse_bitorder in C. 2002016.03.18 201 Fix saving additional ImageJ metadata. 2022016.02.22 203 Pass 1920 tests. 204 Write 8 bytes double tag values using offset if necessary (bug fix). 205 Add option to disable writing second image description tag. 206 Detect tags with incorrect counts. 207 Disable color mapping for LSM. 2082015.11.13 209 Read LSM 6 mosaics. 210 Add option to specify directory of memory-mapped files. 211 Add command line options to specify vmin and vmax values for colormapping. 2122015.10.06 213 New helper function to apply colormaps. 214 Renamed is_palette attributes to is_indexed (backwards incompatible). 215 Color-mapped samples are now contiguous (backwards incompatible). 216 Do not color-map ImageJ hyperstacks (backwards incompatible). 217 Towards reading Leica SCN. 2182015.09.25 219 Read images with reversed bit order (FillOrder is LSB2MSB). 2202015.09.21 221 Read RGB OME-TIFF. 222 Warn about malformed OME-XML. 2232015.09.16 224 Detect some corrupted ImageJ metadata. 225 Better axes labels for 'shaped' files. 226 Do not create TiffTag for default values. 227 Chroma subsampling is not supported. 228 Memory-map data in TiffPageSeries if possible (optional). 2292015.08.17 230 Pass 1906 tests. 231 Write ImageJ hyperstacks (optional). 232 Read and write LZMA compressed data. 233 Specify datetime when saving (optional). 234 Save tiled and color-mapped images (optional). 235 Ignore void bytecounts and offsets if possible. 236 Ignore bogus image_depth tag created by ISS Vista software. 237 Decode floating point horizontal differencing (not tiled). 238 Save image data contiguously if possible. 239 Only read first IFD from ImageJ files if possible. 240 Read ImageJ 'raw' format (files larger than 4 GB). 241 TiffPageSeries class for pages with compatible shape and data type. 242 Try to read incomplete tiles. 243 Open file dialog if no filename is passed on command line. 244 Ignore errors when decoding OME-XML. 245 Rename decoder functions (backwards incompatible). 2462014.08.24 247 TiffWriter class for incremental writing images. 248 Simplify examples. 2492014.08.19 250 Add memmap function to FileHandle. 251 Add function to determine if image data in TiffPage is memory-mappable. 252 Do not close files if multifile_close parameter is False. 2532014.08.10 254 Pass 1730 tests. 255 Return all extrasamples by default (backwards incompatible). 256 Read data from series of pages into memory-mapped array (optional). 257 Squeeze OME dimensions (backwards incompatible). 258 Workaround missing EOI code in strips. 259 Support image and tile depth tags (SGI extension). 260 Better handling of STK/UIC tags (backwards incompatible). 261 Disable color mapping for STK. 262 Julian to datetime converter. 263 TIFF ASCII type may be NULL separated. 264 Unwrap strip offsets for LSM files greater than 4 GB. 265 Correct strip byte counts in compressed LSM files. 266 Skip missing files in OME series. 267 Read embedded TIFF files. 2682014.02.05 269 Save rational numbers as type 5 (bug fix). 2702013.12.20 271 Keep other files in OME multi-file series closed. 272 FileHandle class to abstract binary file handle. 273 Disable color mapping for bad OME-TIFF produced by bio-formats. 274 Read bad OME-XML produced by ImageJ when cropping. 2752013.11.03 276 Allow zlib compress data in imsave function (optional). 277 Memory-map contiguous image data (optional). 2782013.10.28 279 Read MicroManager metadata and little-endian ImageJ tag. 280 Save extra tags in imsave function. 281 Save tags in ascending order by code (bug fix). 2822012.10.18 283 Accept file like objects (read from OIB files). 2842012.08.21 285 Rename TIFFfile to TiffFile and TIFFpage to TiffPage. 286 TiffSequence class for reading sequence of TIFF files. 287 Read UltraQuant tags. 288 Allow float numbers as resolution in imsave function. 2892012.08.03 290 Read MD GEL tags and NIH Image header. 2912012.07.25 292 Read ImageJ tags. 293 ... 294 295Notes 296----- 297The API is not stable yet and might change between revisions. 298 299Tested on little-endian platforms only. 300 301Other Python packages and modules for reading (bio) scientific TIFF files: 302 303* `python-bioformats <https://github.com/CellProfiler/python-bioformats>`_ 304* `Imread <https://github.com/luispedro/imread>`_ 305* `PyLibTiff <https://github.com/pearu/pylibtiff>`_ 306* `ITK <https://www.itk.org>`_ 307* `PyLSM <https://launchpad.net/pylsm>`_ 308* `PyMca.TiffIO.py <https://github.com/vasole/pymca>`_ (same as fabio.TiffIO) 309* `BioImageXD.Readers <http://www.bioimagexd.net/>`_ 310* `Cellcognition.io <http://cellcognition.org/>`_ 311* `pymimage <https://github.com/ardoi/pymimage>`_ 312* `pytiff <https://github.com/FZJ-INM1-BDA/pytiff>`_ 313 314Acknowledgements 315---------------- 316* Egor Zindy, University of Manchester, for lsm_scan_info specifics. 317* Wim Lewis for a bug fix and some LSM functions. 318* Hadrien Mary for help on reading MicroManager files. 319* Christian Kliche for help writing tiled and color-mapped files. 320 321References 322---------- 3231) TIFF 6.0 Specification and Supplements. Adobe Systems Incorporated. 324 http://partners.adobe.com/public/developer/tiff/ 3252) TIFF File Format FAQ. http://www.awaresystems.be/imaging/tiff/faq.html 3263) MetaMorph Stack (STK) Image File Format. 327 http://support.meta.moleculardevices.com/docs/t10243.pdf 3284) Image File Format Description LSM 5/7 Release 6.0 (ZEN 2010). 329 Carl Zeiss MicroImaging GmbH. BioSciences. May 10, 2011 3305) The OME-TIFF format. 331 http://www.openmicroscopy.org/site/support/file-formats/ome-tiff 3326) UltraQuant(r) Version 6.0 for Windows Start-Up Guide. 333 http://www.ultralum.com/images%20ultralum/pdf/UQStart%20Up%20Guide.pdf 3347) Micro-Manager File Formats. 335 http://www.micro-manager.org/wiki/Micro-Manager_File_Formats 3368) Tags for TIFF and Related Specifications. Digital Preservation. 337 http://www.digitalpreservation.gov/formats/content/tiff_tags.shtml 3389) ScanImage BigTiff Specification - ScanImage 2016. 339 http://scanimage.vidriotechnologies.com/display/SI2016/ 340 ScanImage+BigTiff+Specification 34110) CIPA DC-008-2016: Exchangeable image file format for digital still cameras: 342 Exif Version 2.31. 343 http://www.cipa.jp/std/documents/e/DC-008-Translation-2016-E.pdf 344 345Examples 346-------- 347>>> # write numpy array to TIFF file 348>>> data = numpy.random.rand(4, 301, 219) 349>>> imsave('temp.tif', data, photometric='minisblack') 350 351>>> # read numpy array from TIFF file 352>>> image = imread('temp.tif') 353>>> numpy.testing.assert_array_equal(image, data) 354 355>>> # iterate over pages and tags in TIFF file 356>>> with TiffFile('temp.tif') as tif: 357... images = tif.asarray() 358... for page in tif.pages: 359... for tag in page.tags.values(): 360... _ = tag.name, tag.value 361... image = page.asarray() 362 363""" 364 365from __future__ import division, print_function 366 367import sys 368import os 369import io 370import re 371import glob 372import math 373import zlib 374import time 375import json 376import enum 377import struct 378import pathlib 379import warnings 380import binascii 381import tempfile 382import datetime 383import threading 384import collections 385import multiprocessing 386import concurrent.futures 387 388import numpy 389 390# delay imports: mmap, pprint, fractions, xml, tkinter, matplotlib, lzma, zstd, 391# subprocess 392 393__version__ = '2018.06.15' 394__docformat__ = 'restructuredtext en' 395__all__ = ( 396 'imsave', 'imread', 'imshow', 'memmap', 397 'TiffFile', 'TiffWriter', 'TiffSequence', 398 # utility functions used by oiffile or czifile 399 'FileHandle', 'lazyattr', 'natural_sorted', 'decode_lzw', 'stripnull', 400 'create_output', 'repeat_nd', 'format_size', 'product', 'xml2dict') 401 402 403def imread(files, **kwargs): 404 """Return image data from TIFF file(s) as numpy array. 405 406 Refer to the TiffFile class and member functions for documentation. 407 408 Parameters 409 ---------- 410 files : str, binary stream, or sequence 411 File name, seekable binary stream, glob pattern, or sequence of 412 file names. 413 kwargs : dict 414 Parameters 'multifile' and 'is_ome' are passed to the TiffFile class. 415 The 'pattern' parameter is passed to the TiffSequence class. 416 Other parameters are passed to the asarray functions. 417 The first image series is returned if no arguments are provided. 418 419 Examples 420 -------- 421 >>> # get image from first page 422 >>> imsave('temp.tif', numpy.random.rand(3, 4, 301, 219)) 423 >>> im = imread('temp.tif', key=0) 424 >>> im.shape 425 (4, 301, 219) 426 427 >>> # get images from sequence of files 428 >>> ims = imread(['temp.tif', 'temp.tif']) 429 >>> ims.shape 430 (2, 3, 4, 301, 219) 431 432 """ 433 kwargs_file = parse_kwargs(kwargs, 'multifile', 'is_ome') 434 kwargs_seq = parse_kwargs(kwargs, 'pattern') 435 436 if isinstance(files, basestring) and any(i in files for i in '?*'): 437 files = glob.glob(files) 438 if not files: 439 raise ValueError('no files found') 440 if not hasattr(files, 'seek') and len(files) == 1: 441 files = files[0] 442 443 if isinstance(files, basestring) or hasattr(files, 'seek'): 444 with TiffFile(files, **kwargs_file) as tif: 445 return tif.asarray(**kwargs) 446 else: 447 with TiffSequence(files, **kwargs_seq) as imseq: 448 return imseq.asarray(**kwargs) 449 450 451def imsave(file, data=None, shape=None, dtype=None, bigsize=2**32-2**25, 452 **kwargs): 453 """Write numpy array to TIFF file. 454 455 Refer to the TiffWriter class and member functions for documentation. 456 457 Parameters 458 ---------- 459 file : str or binary stream 460 File name or writable binary stream, such as an open file or BytesIO. 461 data : array_like 462 Input image. The last dimensions are assumed to be image depth, 463 height, width, and samples. 464 If None, an empty array of the specified shape and dtype is 465 saved to file. 466 Unless 'byteorder' is specified in 'kwargs', the TIFF file byte order 467 is determined from the data's dtype or the dtype argument. 468 shape : tuple 469 If 'data' is None, shape of an empty array to save to the file. 470 dtype : numpy.dtype 471 If 'data' is None, data-type of an empty array to save to the file. 472 bigsize : int 473 Create a BigTIFF file if the size of data in bytes is larger than 474 this threshold and 'imagej' or 'truncate' are not enabled. 475 By default, the threshold is 4 GB minus 32 MB reserved for metadata. 476 Use the 'bigtiff' parameter to explicitly specify the type of 477 file created. 478 kwargs : dict 479 Parameters 'append', 'byteorder', 'bigtiff', and 'imagej', are passed 480 to TiffWriter(). Other parameters are passed to TiffWriter.save(). 481 482 Returns 483 ------- 484 If the image data are written contiguously, return offset and bytecount 485 of image data in the file. 486 487 Examples 488 -------- 489 >>> # save a RGB image 490 >>> data = numpy.random.randint(0, 255, (256, 256, 3), 'uint8') 491 >>> imsave('temp.tif', data, photometric='rgb') 492 493 >>> # save a random array and metadata, using compression 494 >>> data = numpy.random.rand(2, 5, 3, 301, 219) 495 >>> imsave('temp.tif', data, compress=6, metadata={'axes': 'TZCYX'}) 496 497 """ 498 tifargs = parse_kwargs(kwargs, 'append', 'bigtiff', 'byteorder', 'imagej') 499 if data is None: 500 size = product(shape) * numpy.dtype(dtype).itemsize 501 byteorder = numpy.dtype(dtype).byteorder 502 else: 503 try: 504 size = data.nbytes 505 byteorder = data.dtype.byteorder 506 except Exception: 507 size = 0 508 byteorder = None 509 if size > bigsize and 'bigtiff' not in tifargs and not ( 510 tifargs.get('imagej', False) or tifargs.get('truncate', False)): 511 tifargs['bigtiff'] = True 512 if 'byteorder' not in tifargs: 513 tifargs['byteorder'] = byteorder 514 515 with TiffWriter(file, **tifargs) as tif: 516 return tif.save(data, shape, dtype, **kwargs) 517 518 519def memmap(filename, shape=None, dtype=None, page=None, series=0, mode='r+', 520 **kwargs): 521 """Return memory-mapped numpy array stored in TIFF file. 522 523 Memory-mapping requires data stored in native byte order, without tiling, 524 compression, predictors, etc. 525 If 'shape' and 'dtype' are provided, existing files will be overwritten or 526 appended to depending on the 'append' parameter. 527 Otherwise the image data of a specified page or series in an existing 528 file will be memory-mapped. By default, the image data of the first page 529 series is memory-mapped. 530 Call flush() to write any changes in the array to the file. 531 Raise ValueError if the image data in the file is not memory-mappable. 532 533 Parameters 534 ---------- 535 filename : str 536 Name of the TIFF file which stores the array. 537 shape : tuple 538 Shape of the empty array. 539 dtype : numpy.dtype 540 Data-type of the empty array. 541 page : int 542 Index of the page which image data to memory-map. 543 series : int 544 Index of the page series which image data to memory-map. 545 mode : {'r+', 'r', 'c'}, optional 546 The file open mode. Default is to open existing file for reading and 547 writing ('r+'). 548 kwargs : dict 549 Additional parameters passed to imsave() or TiffFile(). 550 551 Examples 552 -------- 553 >>> # create an empty TIFF file and write to memory-mapped image 554 >>> im = memmap('temp.tif', shape=(256, 256), dtype='float32') 555 >>> im[255, 255] = 1.0 556 >>> im.flush() 557 >>> im.shape, im.dtype 558 ((256, 256), dtype('float32')) 559 >>> del im 560 561 >>> # memory-map image data in a TIFF file 562 >>> im = memmap('temp.tif', page=0) 563 >>> im[255, 255] 564 1.0 565 566 """ 567 if shape is not None and dtype is not None: 568 # create a new, empty array 569 kwargs.update(data=None, shape=shape, dtype=dtype, returnoffset=True, 570 align=TIFF.ALLOCATIONGRANULARITY) 571 result = imsave(filename, **kwargs) 572 if result is None: 573 # TODO: fail before creating file or writing data 574 raise ValueError('image data are not memory-mappable') 575 offset = result[0] 576 else: 577 # use existing file 578 with TiffFile(filename, **kwargs) as tif: 579 if page is not None: 580 page = tif.pages[page] 581 if not page.is_memmappable: 582 raise ValueError('image data are not memory-mappable') 583 offset, _ = page.is_contiguous 584 shape = page.shape 585 dtype = page.dtype 586 else: 587 series = tif.series[series] 588 if series.offset is None: 589 raise ValueError('image data are not memory-mappable') 590 shape = series.shape 591 dtype = series.dtype 592 offset = series.offset 593 dtype = tif.byteorder + dtype.char 594 return numpy.memmap(filename, dtype, mode, offset, shape, 'C') 595 596 597class lazyattr(object): 598 """Attribute whose value is computed on first access.""" 599 # TODO: help() doesn't work 600 __slots__ = ('func',) 601 602 def __init__(self, func): 603 self.func = func 604 # self.__name__ = func.__name__ 605 # self.__doc__ = func.__doc__ 606 # self.lock = threading.RLock() 607 608 def __get__(self, instance, owner): 609 # with self.lock: 610 if instance is None: 611 return self 612 try: 613 value = self.func(instance) 614 except AttributeError as e: 615 raise RuntimeError(e) 616 if value is NotImplemented: 617 return getattr(super(owner, instance), self.func.__name__) 618 setattr(instance, self.func.__name__, value) 619 return value 620 621 622class TiffWriter(object): 623 """Write numpy arrays to TIFF file. 624 625 TiffWriter instances must be closed using the 'close' method, which is 626 automatically called when using the 'with' context manager. 627 628 TiffWriter's main purpose is saving nD numpy array's as TIFF, 629 not to create any possible TIFF format. Specifically, JPEG compression, 630 SubIFDs, ExifIFD, or GPSIFD tags are not supported. 631 632 Examples 633 -------- 634 >>> # successively append images to BigTIFF file 635 >>> data = numpy.random.rand(2, 5, 3, 301, 219) 636 >>> with TiffWriter('temp.tif', bigtiff=True) as tif: 637 ... for i in range(data.shape[0]): 638 ... tif.save(data[i], compress=6, photometric='minisblack') 639 640 """ 641 def __init__(self, file, bigtiff=False, byteorder=None, append=False, 642 imagej=False): 643 """Open a TIFF file for writing. 644 645 An empty TIFF file is created if the file does not exist, else the 646 file is overwritten with an empty TIFF file unless 'append' 647 is true. Use bigtiff=True when creating files larger than 4 GB. 648 649 Parameters 650 ---------- 651 file : str, binary stream, or FileHandle 652 File name or writable binary stream, such as an open file 653 or BytesIO. 654 bigtiff : bool 655 If True, the BigTIFF format is used. 656 byteorder : {'<', '>', '=', '|'} 657 The endianness of the data in the file. 658 By default, this is the system's native byte order. 659 append : bool 660 If True and 'file' is an existing standard TIFF file, image data 661 and tags are appended to the file. 662 Appending data may corrupt specifically formatted TIFF files 663 such as LSM, STK, ImageJ, NIH, or FluoView. 664 imagej : bool 665 If True, write an ImageJ hyperstack compatible file. 666 This format can handle data types uint8, uint16, or float32 and 667 data shapes up to 6 dimensions in TZCYXS order. 668 RGB images (S=3 or S=4) must be uint8. 669 ImageJ's default byte order is big-endian but this implementation 670 uses the system's native byte order by default. 671 ImageJ does not support BigTIFF format or LZMA compression. 672 The ImageJ file format is undocumented. 673 674 """ 675 if append: 676 # determine if file is an existing TIFF file that can be extended 677 try: 678 with FileHandle(file, mode='rb', size=0) as fh: 679 pos = fh.tell() 680 try: 681 with TiffFile(fh) as tif: 682 if (append != 'force' and 683 any(getattr(tif, 'is_'+a) for a in ( 684 'lsm', 'stk', 'imagej', 'nih', 685 'fluoview', 'micromanager'))): 686 raise ValueError('file contains metadata') 687 byteorder = tif.byteorder 688 bigtiff = tif.is_bigtiff 689 self._ifdoffset = tif.pages.next_page_offset 690 except Exception as e: 691 raise ValueError('cannot append to file: %s' % str(e)) 692 finally: 693 fh.seek(pos) 694 except (IOError, FileNotFoundError): 695 append = False 696 697 if byteorder in (None, '=', '|'): 698 byteorder = '<' if sys.byteorder == 'little' else '>' 699 elif byteorder not in ('<', '>'): 700 raise ValueError('invalid byteorder %s' % byteorder) 701 if imagej and bigtiff: 702 warnings.warn('writing incompatible BigTIFF ImageJ') 703 704 self._byteorder = byteorder 705 self._imagej = bool(imagej) 706 self._truncate = False 707 self._metadata = None 708 self._colormap = None 709 710 self._descriptionoffset = 0 711 self._descriptionlen = 0 712 self._descriptionlenoffset = 0 713 self._tags = None 714 self._shape = None # normalized shape of data in consecutive pages 715 self._datashape = None # shape of data in consecutive pages 716 self._datadtype = None # data type 717 self._dataoffset = None # offset to data 718 self._databytecounts = None # byte counts per plane 719 self._tagoffsets = None # strip or tile offset tag code 720 721 if bigtiff: 722 self._bigtiff = True 723 self._offsetsize = 8 724 self._tagsize = 20 725 self._tagnoformat = 'Q' 726 self._offsetformat = 'Q' 727 self._valueformat = '8s' 728 else: 729 self._bigtiff = False 730 self._offsetsize = 4 731 self._tagsize = 12 732 self._tagnoformat = 'H' 733 self._offsetformat = 'I' 734 self._valueformat = '4s' 735 736 if append: 737 self._fh = FileHandle(file, mode='r+b', size=0) 738 self._fh.seek(0, 2) 739 else: 740 self._fh = FileHandle(file, mode='wb', size=0) 741 self._fh.write({'<': b'II', '>': b'MM'}[byteorder]) 742 if bigtiff: 743 self._fh.write(struct.pack(byteorder+'HHH', 43, 8, 0)) 744 else: 745 self._fh.write(struct.pack(byteorder+'H', 42)) 746 # first IFD 747 self._ifdoffset = self._fh.tell() 748 self._fh.write(struct.pack(byteorder+self._offsetformat, 0)) 749 750 def save(self, data=None, shape=None, dtype=None, returnoffset=False, 751 photometric=None, planarconfig=None, tile=None, contiguous=True, 752 align=16, truncate=False, compress=0, rowsperstrip=None, 753 predictor=False, colormap=None, description=None, 754 datetime=None, resolution=None, software='tifffile.py', 755 metadata={}, ijmetadata=None, extratags=()): 756 """Write numpy array and tags to TIFF file. 757 758 The data shape's last dimensions are assumed to be image depth, 759 height (length), width, and samples. 760 If a colormap is provided, the data's dtype must be uint8 or uint16 761 and the data values are indices into the last dimension of the 762 colormap. 763 If 'shape' and 'dtype' are specified, an empty array is saved. 764 This option cannot be used with compression or multiple tiles. 765 Image data are written uncompressed in one strip per plane by default. 766 Dimensions larger than 2 to 4 (depending on photometric mode, planar 767 configuration, and SGI mode) are flattened and saved as separate pages. 768 The SampleFormat and BitsPerSample tags are derived from the data type. 769 770 Parameters 771 ---------- 772 data : numpy.ndarray or None 773 Input image array. 774 shape : tuple or None 775 Shape of the empty array to save. Used only if 'data' is None. 776 dtype : numpy.dtype or None 777 Data-type of the empty array to save. Used only if 'data' is None. 778 returnoffset : bool 779 If True and the image data in the file is memory-mappable, return 780 the offset and number of bytes of the image data in the file. 781 photometric : {'MINISBLACK', 'MINISWHITE', 'RGB', 'PALETTE', 'CFA'} 782 The color space of the image data. 783 By default, this setting is inferred from the data shape and the 784 value of colormap. 785 For CFA images, DNG tags must be specified in 'extratags'. 786 planarconfig : {'CONTIG', 'SEPARATE'} 787 Specifies if samples are stored contiguous or in separate planes. 788 By default, this setting is inferred from the data shape. 789 If this parameter is set, extra samples are used to store grayscale 790 images. 791 'CONTIG': last dimension contains samples. 792 'SEPARATE': third last dimension contains samples. 793 tile : tuple of int 794 The shape (depth, length, width) of image tiles to write. 795 If None (default), image data are written in strips. 796 The tile length and width must be a multiple of 16. 797 If the tile depth is provided, the SGI ImageDepth and TileDepth 798 tags are used to save volume data. 799 Unless a single tile is used, tiles cannot be used to write 800 contiguous files. 801 Few software can read the SGI format, e.g. MeVisLab. 802 contiguous : bool 803 If True (default) and the data and parameters are compatible with 804 previous ones, if any, the image data are stored contiguously after 805 the previous one. Parameters 'photometric' and 'planarconfig' 806 are ignored. Parameters 'description', datetime', and 'extratags' 807 are written to the first page of a contiguous series only. 808 align : int 809 Byte boundary on which to align the image data in the file. 810 Default 16. Use mmap.ALLOCATIONGRANULARITY for memory-mapped data. 811 Following contiguous writes are not aligned. 812 truncate : bool 813 If True, only write the first page including shape metadata if 814 possible (uncompressed, contiguous, not tiled). 815 Other TIFF readers will only be able to read part of the data. 816 compress : int or 'LZMA', 'ZSTD' 817 Values from 0 to 9 controlling the level of zlib compression. 818 If 0 (default), data are written uncompressed. 819 Compression cannot be used to write contiguous files. 820 If 'LZMA' or 'ZSTD', LZMA or ZSTD compression is used, which is 821 not available on all platforms. 822 rowsperstrip : int 823 The number of rows per strip used for compression. 824 Uncompressed data are written in one strip per plane. 825 predictor : bool 826 If True, apply horizontal differencing to integer type images 827 before compression. 828 colormap : numpy.ndarray 829 RGB color values for the corresponding data value. 830 Must be of shape (3, 2**(data.itemsize*8)) and dtype uint16. 831 description : str 832 The subject of the image. Must be 7-bit ASCII. Cannot be used with 833 the ImageJ format. Saved with the first page only. 834 datetime : datetime 835 Date and time of image creation in '%Y:%m:%d %H:%M:%S' format. 836 If None (default), the current date and time is used. 837 Saved with the first page only. 838 resolution : (float, float[, str]) or ((int, int), (int, int)[, str]) 839 X and Y resolutions in pixels per resolution unit as float or 840 rational numbers. A third, optional parameter specifies the 841 resolution unit, which must be None (default for ImageJ), 842 'INCH' (default), or 'CENTIMETER'. 843 software : str 844 Name of the software used to create the file. Must be 7-bit ASCII. 845 Saved with the first page only. 846 metadata : dict 847 Additional meta data to be saved along with shape information 848 in JSON or ImageJ formats in an ImageDescription tag. 849 If None, do not write a second ImageDescription tag. 850 Strings must be 7-bit ASCII. Saved with the first page only. 851 ijmetadata : dict 852 Additional meta data to be saved in application specific 853 IJMetadata and IJMetadataByteCounts tags. Refer to the 854 imagej_metadata_tags function for valid keys and values. 855 Saved with the first page only. 856 extratags : sequence of tuples 857 Additional tags as [(code, dtype, count, value, writeonce)]. 858 859 code : int 860 The TIFF tag Id. 861 dtype : str 862 Data type of items in 'value' in Python struct format. 863 One of B, s, H, I, 2I, b, h, i, 2i, f, d, Q, or q. 864 count : int 865 Number of data values. Not used for string or byte string 866 values. 867 value : sequence 868 'Count' values compatible with 'dtype'. 869 Byte strings must contain count values of dtype packed as 870 binary data. 871 writeonce : bool 872 If True, the tag is written to the first page only. 873 874 """ 875 # TODO: refactor this function 876 fh = self._fh 877 byteorder = self._byteorder 878 879 if data is None: 880 if compress: 881 raise ValueError('cannot save compressed empty file') 882 datashape = shape 883 datadtype = numpy.dtype(dtype).newbyteorder(byteorder) 884 datadtypechar = datadtype.char 885 else: 886 data = numpy.asarray(data, byteorder+data.dtype.char, 'C') 887 if data.size == 0: 888 raise ValueError('cannot save empty array') 889 datashape = data.shape 890 datadtype = data.dtype 891 datadtypechar = data.dtype.char 892 893 returnoffset = returnoffset and datadtype.isnative 894 bilevel = datadtypechar == '?' 895 if bilevel: 896 index = -1 if datashape[-1] > 1 else -2 897 datasize = product(datashape[:index]) 898 if datashape[index] % 8: 899 datasize *= datashape[index] // 8 + 1 900 else: 901 datasize *= datashape[index] // 8 902 else: 903 datasize = product(datashape) * datadtype.itemsize 904 905 # just append contiguous data if possible 906 self._truncate = bool(truncate) 907 if self._datashape: 908 if (not contiguous 909 or self._datashape[1:] != datashape 910 or self._datadtype != datadtype 911 or (compress and self._tags) 912 or tile 913 or not numpy.array_equal(colormap, self._colormap)): 914 # incompatible shape, dtype, compression mode, or colormap 915 self._write_remaining_pages() 916 self._write_image_description() 917 self._truncate = False 918 self._descriptionoffset = 0 919 self._descriptionlenoffset = 0 920 self._datashape = None 921 self._colormap = None 922 if self._imagej: 923 raise ValueError( 924 'ImageJ does not support non-contiguous data') 925 else: 926 # consecutive mode 927 self._datashape = (self._datashape[0] + 1,) + datashape 928 if not compress: 929 # write contiguous data, write IFDs/tags later 930 offset = fh.tell() 931 if data is None: 932 fh.write_empty(datasize) 933 else: 934 fh.write_array(data) 935 if returnoffset: 936 return offset, datasize 937 return 938 939 input_shape = datashape 940 tagnoformat = self._tagnoformat 941 valueformat = self._valueformat 942 offsetformat = self._offsetformat 943 offsetsize = self._offsetsize 944 tagsize = self._tagsize 945 946 MINISBLACK = TIFF.PHOTOMETRIC.MINISBLACK 947 RGB = TIFF.PHOTOMETRIC.RGB 948 CFA = TIFF.PHOTOMETRIC.CFA 949 PALETTE = TIFF.PHOTOMETRIC.PALETTE 950 CONTIG = TIFF.PLANARCONFIG.CONTIG 951 SEPARATE = TIFF.PLANARCONFIG.SEPARATE 952 953 # parse input 954 if photometric is not None: 955 photometric = enumarg(TIFF.PHOTOMETRIC, photometric) 956 if planarconfig: 957 planarconfig = enumarg(TIFF.PLANARCONFIG, planarconfig) 958 if not compress: 959 compress = False 960 compresstag = 1 961 predictor = False 962 else: 963 if isinstance(compress, (tuple, list)): 964 compress, compresslevel = compress 965 elif isinstance(compress, int): 966 compress, compresslevel = 'ADOBE_DEFLATE', int(compress) 967 if not 0 <= compresslevel <= 9: 968 raise ValueError('invalid compression level %s' % compress) 969 else: 970 compresslevel = None 971 compress = compress.upper() 972 compresstag = enumarg(TIFF.COMPRESSION, compress) 973 974 # prepare ImageJ format 975 if self._imagej: 976 if compress in ('LZMA', 'ZSTD'): 977 raise ValueError( 978 'ImageJ cannot handle LZMA or ZSTD compression') 979 if description: 980 warnings.warn('not writing description to ImageJ file') 981 description = None 982 volume = False 983 if datadtypechar not in 'BHhf': 984 raise ValueError( 985 'ImageJ does not support data type %s' % datadtypechar) 986 ijrgb = photometric == RGB if photometric else None 987 if datadtypechar not in 'B': 988 ijrgb = False 989 ijshape = imagej_shape(datashape, ijrgb) 990 if ijshape[-1] in (3, 4): 991 photometric = RGB 992 if datadtypechar not in 'B': 993 raise ValueError('ImageJ does not support data type %s ' 994 'for RGB' % datadtypechar) 995 elif photometric is None: 996 photometric = MINISBLACK 997 planarconfig = None 998 if planarconfig == SEPARATE: 999 raise ValueError('ImageJ does not support planar images') 1000 else: 1001 planarconfig = CONTIG if ijrgb else None 1002 1003 # define compress function 1004 if compress: 1005 if compresslevel is None: 1006 compressor, compresslevel = TIFF.COMPESSORS[compresstag] 1007 else: 1008 compressor, _ = TIFF.COMPESSORS[compresstag] 1009 compresslevel = int(compresslevel) 1010 if predictor: 1011 if datadtype.kind not in 'iu': 1012 raise ValueError( 1013 'prediction not implemented for %s' % datadtype) 1014 1015 def compress(data, level=compresslevel): 1016 # horizontal differencing 1017 diff = numpy.diff(data, axis=-2) 1018 data = numpy.insert(diff, 0, data[..., 0, :], axis=-2) 1019 return compressor(data, level) 1020 else: 1021 def compress(data, level=compresslevel): 1022 return compressor(data, level) 1023 1024 # verify colormap and indices 1025 if colormap is not None: 1026 if datadtypechar not in 'BH': 1027 raise ValueError('invalid data dtype for palette mode') 1028 colormap = numpy.asarray(colormap, dtype=byteorder+'H') 1029 if colormap.shape != (3, 2**(datadtype.itemsize * 8)): 1030 raise ValueError('invalid color map shape') 1031 self._colormap = colormap 1032 1033 # verify tile shape 1034 if tile: 1035 tile = tuple(int(i) for i in tile[:3]) 1036 volume = len(tile) == 3 1037 if (len(tile) < 2 or tile[-1] % 16 or tile[-2] % 16 or 1038 any(i < 1 for i in tile)): 1039 raise ValueError('invalid tile shape') 1040 else: 1041 tile = () 1042 volume = False 1043 1044 # normalize data shape to 5D or 6D, depending on volume: 1045 # (pages, planar_samples, [depth,] height, width, contig_samples) 1046 datashape = reshape_nd(datashape, 3 if photometric == RGB else 2) 1047 shape = datashape 1048 ndim = len(datashape) 1049 1050 samplesperpixel = 1 1051 extrasamples = 0 1052 if volume and ndim < 3: 1053 volume = False 1054 if colormap is not None: 1055 photometric = PALETTE 1056 planarconfig = None 1057 if photometric is None: 1058 photometric = MINISBLACK 1059 if bilevel: 1060 photometric = TIFF.PHOTOMETRIC.MINISWHITE 1061 elif planarconfig == CONTIG: 1062 if ndim > 2 and shape[-1] in (3, 4): 1063 photometric = RGB 1064 elif planarconfig == SEPARATE: 1065 if volume and ndim > 3 and shape[-4] in (3, 4): 1066 photometric = RGB 1067 elif ndim > 2 and shape[-3] in (3, 4): 1068 photometric = RGB 1069 elif ndim > 2 and shape[-1] in (3, 4): 1070 photometric = RGB 1071 elif self._imagej: 1072 photometric = MINISBLACK 1073 elif volume and ndim > 3 and shape[-4] in (3, 4): 1074 photometric = RGB 1075 elif ndim > 2 and shape[-3] in (3, 4): 1076 photometric = RGB 1077 if planarconfig and len(shape) <= (3 if volume else 2): 1078 planarconfig = None 1079 photometric = MINISBLACK 1080 if photometric == RGB: 1081 if len(shape) < 3: 1082 raise ValueError('not a RGB(A) image') 1083 if len(shape) < 4: 1084 volume = False 1085 if planarconfig is None: 1086 if shape[-1] in (3, 4): 1087 planarconfig = CONTIG 1088 elif shape[-4 if volume else -3] in (3, 4): 1089 planarconfig = SEPARATE 1090 elif shape[-1] > shape[-4 if volume else -3]: 1091 planarconfig = SEPARATE 1092 else: 1093 planarconfig = CONTIG 1094 if planarconfig == CONTIG: 1095 datashape = (-1, 1) + shape[(-4 if volume else -3):] 1096 samplesperpixel = datashape[-1] 1097 else: 1098 datashape = (-1,) + shape[(-4 if volume else -3):] + (1,) 1099 samplesperpixel = datashape[1] 1100 if samplesperpixel > 3: 1101 extrasamples = samplesperpixel - 3 1102 elif photometric == CFA: 1103 if len(shape) != 2: 1104 raise ValueError('invalid CFA image') 1105 volume = False 1106 planarconfig = None 1107 datashape = (-1, 1) + shape[-2:] + (1,) 1108 if 50706 not in (et[0] for et in extratags): 1109 raise ValueError('must specify DNG tags for CFA image') 1110 elif planarconfig and len(shape) > (3 if volume else 2): 1111 if planarconfig == CONTIG: 1112 datashape = (-1, 1) + shape[(-4 if volume else -3):] 1113 samplesperpixel = datashape[-1] 1114 else: 1115 datashape = (-1,) + shape[(-4 if volume else -3):] + (1,) 1116 samplesperpixel = datashape[1] 1117 extrasamples = samplesperpixel - 1 1118 else: 1119 planarconfig = None 1120 # remove trailing 1s 1121 while len(shape) > 2 and shape[-1] == 1: 1122 shape = shape[:-1] 1123 if len(shape) < 3: 1124 volume = False 1125 datashape = (-1, 1) + shape[(-3 if volume else -2):] + (1,) 1126 1127 # normalize shape to 6D 1128 assert len(datashape) in (5, 6) 1129 if len(datashape) == 5: 1130 datashape = datashape[:2] + (1,) + datashape[2:] 1131 if datashape[0] == -1: 1132 s0 = product(input_shape) // product(datashape[1:]) 1133 datashape = (s0,) + datashape[1:] 1134 shape = datashape 1135 if data is not None: 1136 data = data.reshape(shape) 1137 1138 if tile and not volume: 1139 tile = (1, tile[-2], tile[-1]) 1140 1141 if photometric == PALETTE: 1142 if (samplesperpixel != 1 or extrasamples or 1143 shape[1] != 1 or shape[-1] != 1): 1144 raise ValueError('invalid data shape for palette mode') 1145 1146 if photometric == RGB and samplesperpixel == 2: 1147 raise ValueError('not a RGB image (samplesperpixel=2)') 1148 1149 if bilevel: 1150 if compress: 1151 raise ValueError('cannot save compressed bilevel image') 1152 if tile: 1153 raise ValueError('cannot save tiled bilevel image') 1154 if photometric not in (0, 1): 1155 raise ValueError('cannot save bilevel image as %s' % 1156 str(photometric)) 1157 datashape = list(datashape) 1158 if datashape[-2] % 8: 1159 datashape[-2] = datashape[-2] // 8 + 1 1160 else: 1161 datashape[-2] = datashape[-2] // 8 1162 datashape = tuple(datashape) 1163 assert datasize == product(datashape) 1164 if data is not None: 1165 data = numpy.packbits(data, axis=-2) 1166 assert datashape[-2] == data.shape[-2] 1167 1168 bytestr = bytes if sys.version[0] == '2' else ( 1169 lambda x: bytes(x, 'ascii') if isinstance(x, str) else x) 1170 tags = [] # list of (code, ifdentry, ifdvalue, writeonce) 1171 1172 strip_or_tile = 'Tile' if tile else 'Strip' 1173 tagbytecounts = TIFF.TAG_NAMES[strip_or_tile + 'ByteCounts'] 1174 tag_offsets = TIFF.TAG_NAMES[strip_or_tile + 'Offsets'] 1175 self._tagoffsets = tag_offsets 1176 1177 def pack(fmt, *val): 1178 return struct.pack(byteorder+fmt, *val) 1179 1180 def addtag(code, dtype, count, value, writeonce=False): 1181 # Compute ifdentry & ifdvalue bytes from code, dtype, count, value 1182 # Append (code, ifdentry, ifdvalue, writeonce) to tags list 1183 code = int(TIFF.TAG_NAMES.get(code, code)) 1184 try: 1185 tifftype = TIFF.DATA_DTYPES[dtype] 1186 except KeyError: 1187 raise ValueError('unknown dtype %s' % dtype) 1188 rawcount = count 1189 1190 if dtype == 's': 1191 # strings 1192 value = bytestr(value) + b'\0' 1193 count = rawcount = len(value) 1194 rawcount = value.find(b'\0\0') 1195 if rawcount < 0: 1196 rawcount = count 1197 else: 1198 rawcount += 1 # length of string without buffer 1199 value = (value,) 1200 elif isinstance(value, bytes): 1201 # packed binary data 1202 dtsize = struct.calcsize(dtype) 1203 if len(value) % dtsize: 1204 raise ValueError('invalid packed binary data') 1205 count = len(value) // dtsize 1206 if len(dtype) > 1: 1207 count *= int(dtype[:-1]) 1208 dtype = dtype[-1] 1209 ifdentry = [pack('HH', code, tifftype), 1210 pack(offsetformat, rawcount)] 1211 ifdvalue = None 1212 if struct.calcsize(dtype) * count <= offsetsize: 1213 # value(s) can be written directly 1214 if isinstance(value, bytes): 1215 ifdentry.append(pack(valueformat, value)) 1216 elif count == 1: 1217 if isinstance(value, (tuple, list, numpy.ndarray)): 1218 value = value[0] 1219 ifdentry.append(pack(valueformat, pack(dtype, value))) 1220 else: 1221 ifdentry.append(pack(valueformat, 1222 pack(str(count)+dtype, *value))) 1223 else: 1224 # use offset to value(s) 1225 ifdentry.append(pack(offsetformat, 0)) 1226 if isinstance(value, bytes): 1227 ifdvalue = value 1228 elif isinstance(value, numpy.ndarray): 1229 assert value.size == count 1230 assert value.dtype.char == dtype 1231 ifdvalue = value.tostring() 1232 elif isinstance(value, (tuple, list)): 1233 ifdvalue = pack(str(count)+dtype, *value) 1234 else: 1235 ifdvalue = pack(dtype, value) 1236 tags.append((code, b''.join(ifdentry), ifdvalue, writeonce)) 1237 1238 def rational(arg, max_denominator=1000000): 1239 """"Return nominator and denominator from float or two integers.""" 1240 from fractions import Fraction # delayed import 1241 try: 1242 f = Fraction.from_float(arg) 1243 except TypeError: 1244 f = Fraction(arg[0], arg[1]) 1245 f = f.limit_denominator(max_denominator) 1246 return f.numerator, f.denominator 1247 1248 if description: 1249 # user provided description 1250 addtag('ImageDescription', 's', 0, description, writeonce=True) 1251 1252 # write shape and metadata to ImageDescription 1253 self._metadata = {} if not metadata else metadata.copy() 1254 if self._imagej: 1255 description = imagej_description( 1256 input_shape, shape[-1] in (3, 4), self._colormap is not None, 1257 **self._metadata) 1258 elif metadata or metadata == {}: 1259 if self._truncate: 1260 self._metadata.update(truncated=True) 1261 description = json_description(input_shape, **self._metadata) 1262 else: 1263 description = None 1264 if description: 1265 # add 64 bytes buffer 1266 # the image description might be updated later with the final shape 1267 description = str2bytes(description, 'ascii') 1268 description += b'\0'*64 1269 self._descriptionlen = len(description) 1270 addtag('ImageDescription', 's', 0, description, writeonce=True) 1271 1272 if software: 1273 addtag('Software', 's', 0, software, writeonce=True) 1274 if datetime is None: 1275 datetime = self._now() 1276 addtag('DateTime', 's', 0, datetime.strftime('%Y:%m:%d %H:%M:%S'), 1277 writeonce=True) 1278 addtag('Compression', 'H', 1, compresstag) 1279 if predictor: 1280 addtag('Predictor', 'H', 1, 2) 1281 addtag('ImageWidth', 'I', 1, shape[-2]) 1282 addtag('ImageLength', 'I', 1, shape[-3]) 1283 if tile: 1284 addtag('TileWidth', 'I', 1, tile[-1]) 1285 addtag('TileLength', 'I', 1, tile[-2]) 1286 if tile[0] > 1: 1287 addtag('ImageDepth', 'I', 1, shape[-4]) 1288 addtag('TileDepth', 'I', 1, tile[0]) 1289 addtag('NewSubfileType', 'I', 1, 0) 1290 if not bilevel: 1291 sampleformat = {'u': 1, 'i': 2, 'f': 3, 'c': 6}[datadtype.kind] 1292 addtag('SampleFormat', 'H', samplesperpixel, 1293 (sampleformat,) * samplesperpixel) 1294 addtag('PhotometricInterpretation', 'H', 1, photometric.value) 1295 if colormap is not None: 1296 addtag('ColorMap', 'H', colormap.size, colormap) 1297 addtag('SamplesPerPixel', 'H', 1, samplesperpixel) 1298 if bilevel: 1299 pass 1300 elif planarconfig and samplesperpixel > 1: 1301 addtag('PlanarConfiguration', 'H', 1, planarconfig.value) 1302 addtag('BitsPerSample', 'H', samplesperpixel, 1303 (datadtype.itemsize * 8,) * samplesperpixel) 1304 else: 1305 addtag('BitsPerSample', 'H', 1, datadtype.itemsize * 8) 1306 if extrasamples: 1307 if photometric == RGB and extrasamples == 1: 1308 addtag('ExtraSamples', 'H', 1, 1) # associated alpha channel 1309 else: 1310 addtag('ExtraSamples', 'H', extrasamples, (0,) * extrasamples) 1311 if resolution is not None: 1312 addtag('XResolution', '2I', 1, rational(resolution[0])) 1313 addtag('YResolution', '2I', 1, rational(resolution[1])) 1314 if len(resolution) > 2: 1315 unit = resolution[2] 1316 unit = 1 if unit is None else enumarg(TIFF.RESUNIT, unit) 1317 elif self._imagej: 1318 unit = 1 1319 else: 1320 unit = 2 1321 addtag('ResolutionUnit', 'H', 1, unit) 1322 elif not self._imagej: 1323 addtag('XResolution', '2I', 1, (1, 1)) 1324 addtag('YResolution', '2I', 1, (1, 1)) 1325 addtag('ResolutionUnit', 'H', 1, 1) 1326 if ijmetadata: 1327 for t in imagej_metadata_tags(ijmetadata, byteorder): 1328 addtag(*t) 1329 1330 contiguous = not compress 1331 if tile: 1332 # one chunk per tile per plane 1333 tiles = ((shape[2] + tile[0] - 1) // tile[0], 1334 (shape[3] + tile[1] - 1) // tile[1], 1335 (shape[4] + tile[2] - 1) // tile[2]) 1336 numtiles = product(tiles) * shape[1] 1337 stripbytecounts = [ 1338 product(tile) * shape[-1] * datadtype.itemsize] * numtiles 1339 addtag(tagbytecounts, offsetformat, numtiles, stripbytecounts) 1340 addtag(tag_offsets, offsetformat, numtiles, [0] * numtiles) 1341 contiguous = contiguous and product(tiles) == 1 1342 if not contiguous: 1343 # allocate tile buffer 1344 chunk = numpy.empty(tile + (shape[-1],), dtype=datadtype) 1345 elif contiguous: 1346 # one strip per plane 1347 if bilevel: 1348 stripbytecounts = [product(datashape[2:])] * shape[1] 1349 else: 1350 stripbytecounts = [ 1351 product(datashape[2:]) * datadtype.itemsize] * shape[1] 1352 addtag(tagbytecounts, offsetformat, shape[1], stripbytecounts) 1353 addtag(tag_offsets, offsetformat, shape[1], [0] * shape[1]) 1354 addtag('RowsPerStrip', 'I', 1, shape[-3]) 1355 else: 1356 # compress rowsperstrip or ~64 KB chunks 1357 rowsize = product(shape[-2:]) * datadtype.itemsize 1358 if rowsperstrip is None: 1359 rowsperstrip = 65536 // rowsize 1360 if rowsperstrip < 1: 1361 rowsperstrip = 1 1362 elif rowsperstrip > shape[-3]: 1363 rowsperstrip = shape[-3] 1364 addtag('RowsPerStrip', 'I', 1, rowsperstrip) 1365 1366 numstrips = (shape[-3] + rowsperstrip - 1) // rowsperstrip 1367 numstrips *= shape[1] 1368 stripbytecounts = [0] * numstrips 1369 addtag(tagbytecounts, offsetformat, numstrips, [0] * numstrips) 1370 addtag(tag_offsets, offsetformat, numstrips, [0] * numstrips) 1371 1372 if data is None and not contiguous: 1373 raise ValueError('cannot write non-contiguous empty file') 1374 1375 # add extra tags from user 1376 for t in extratags: 1377 addtag(*t) 1378 1379 # TODO: check TIFFReadDirectoryCheckOrder warning in files containing 1380 # multiple tags of same code 1381 # the entries in an IFD must be sorted in ascending order by tag code 1382 tags = sorted(tags, key=lambda x: x[0]) 1383 1384 if not (self._bigtiff or self._imagej) and ( 1385 fh.tell() + datasize > 2**31-1): 1386 raise ValueError('data too large for standard TIFF file') 1387 1388 # if not compressed or multi-tiled, write the first IFD and then 1389 # all data contiguously; else, write all IFDs and data interleaved 1390 for pageindex in range(1 if contiguous else shape[0]): 1391 # update pointer at ifd_offset 1392 pos = fh.tell() 1393 if pos % 2: 1394 # location of IFD must begin on a word boundary 1395 fh.write(b'\0') 1396 pos += 1 1397 fh.seek(self._ifdoffset) 1398 fh.write(pack(offsetformat, pos)) 1399 fh.seek(pos) 1400 1401 # write ifdentries 1402 fh.write(pack(tagnoformat, len(tags))) 1403 tag_offset = fh.tell() 1404 fh.write(b''.join(t[1] for t in tags)) 1405 self._ifdoffset = fh.tell() 1406 fh.write(pack(offsetformat, 0)) # offset to next IFD 1407 1408 # write tag values and patch offsets in ifdentries, if necessary 1409 for tagindex, tag in enumerate(tags): 1410 if tag[2]: 1411 pos = fh.tell() 1412 if pos % 2: 1413 # tag value is expected to begin on word boundary 1414 fh.write(b'\0') 1415 pos += 1 1416 fh.seek(tag_offset + tagindex*tagsize + offsetsize + 4) 1417 fh.write(pack(offsetformat, pos)) 1418 fh.seek(pos) 1419 if tag[0] == tag_offsets: 1420 stripoffsetsoffset = pos 1421 elif tag[0] == tagbytecounts: 1422 strip_bytecounts_offset = pos 1423 elif tag[0] == 270 and tag[2].endswith(b'\0\0\0\0'): 1424 # image description buffer 1425 self._descriptionoffset = pos 1426 self._descriptionlenoffset = ( 1427 tag_offset + tagindex * tagsize + 4) 1428 fh.write(tag[2]) 1429 1430 # write image data 1431 data_offset = fh.tell() 1432 skip = align - data_offset % align 1433 fh.seek(skip, 1) 1434 data_offset += skip 1435 if contiguous: 1436 if data is None: 1437 fh.write_empty(datasize) 1438 else: 1439 fh.write_array(data) 1440 elif tile: 1441 if data is None: 1442 fh.write_empty(numtiles * stripbytecounts[0]) 1443 else: 1444 stripindex = 0 1445 for plane in data[pageindex]: 1446 for tz in range(tiles[0]): 1447 for ty in range(tiles[1]): 1448 for tx in range(tiles[2]): 1449 c0 = min(tile[0], shape[2] - tz*tile[0]) 1450 c1 = min(tile[1], shape[3] - ty*tile[1]) 1451 c2 = min(tile[2], shape[4] - tx*tile[2]) 1452 chunk[c0:, c1:, c2:] = 0 1453 chunk[:c0, :c1, :c2] = plane[ 1454 tz*tile[0]:tz*tile[0]+c0, 1455 ty*tile[1]:ty*tile[1]+c1, 1456 tx*tile[2]:tx*tile[2]+c2] 1457 if compress: 1458 t = compress(chunk) 1459 fh.write(t) 1460 stripbytecounts[stripindex] = len(t) 1461 stripindex += 1 1462 else: 1463 fh.write_array(chunk) 1464 fh.flush() 1465 elif compress: 1466 # write one strip per rowsperstrip 1467 assert data.shape[2] == 1 # not handling depth 1468 numstrips = (shape[-3] + rowsperstrip - 1) // rowsperstrip 1469 stripindex = 0 1470 for plane in data[pageindex]: 1471 for i in range(numstrips): 1472 strip = plane[0, i*rowsperstrip: (i+1)*rowsperstrip] 1473 strip = compress(strip) 1474 fh.write(strip) 1475 stripbytecounts[stripindex] = len(strip) 1476 stripindex += 1 1477 1478 # update strip/tile offsets and bytecounts if necessary 1479 pos = fh.tell() 1480 for tagindex, tag in enumerate(tags): 1481 if tag[0] == tag_offsets: # strip/tile offsets 1482 if tag[2]: 1483 fh.seek(stripoffsetsoffset) 1484 strip_offset = data_offset 1485 for size in stripbytecounts: 1486 fh.write(pack(offsetformat, strip_offset)) 1487 strip_offset += size 1488 else: 1489 fh.seek(tag_offset + tagindex*tagsize + offsetsize + 4) 1490 fh.write(pack(offsetformat, data_offset)) 1491 elif tag[0] == tagbytecounts: # strip/tile bytecounts 1492 if compress: 1493 if tag[2]: 1494 fh.seek(strip_bytecounts_offset) 1495 for size in stripbytecounts: 1496 fh.write(pack(offsetformat, size)) 1497 else: 1498 fh.seek(tag_offset + tagindex*tagsize + 1499 offsetsize + 4) 1500 fh.write(pack(offsetformat, stripbytecounts[0])) 1501 break 1502 fh.seek(pos) 1503 fh.flush() 1504 1505 # remove tags that should be written only once 1506 if pageindex == 0: 1507 tags = [tag for tag in tags if not tag[-1]] 1508 1509 self._shape = shape 1510 self._datashape = (1,) + input_shape 1511 self._datadtype = datadtype 1512 self._dataoffset = data_offset 1513 self._databytecounts = stripbytecounts 1514 1515 if contiguous: 1516 # write remaining IFDs/tags later 1517 self._tags = tags 1518 # return offset and size of image data 1519 if returnoffset: 1520 return data_offset, sum(stripbytecounts) 1521 1522 def _write_remaining_pages(self): 1523 """Write outstanding IFDs and tags to file.""" 1524 if not self._tags or self._truncate: 1525 return 1526 1527 fh = self._fh 1528 fhpos = fh.tell() 1529 if fhpos % 2: 1530 fh.write(b'\0') 1531 fhpos += 1 1532 byteorder = self._byteorder 1533 offsetformat = self._offsetformat 1534 offsetsize = self._offsetsize 1535 tagnoformat = self._tagnoformat 1536 tagsize = self._tagsize 1537 dataoffset = self._dataoffset 1538 pagedatasize = sum(self._databytecounts) 1539 pageno = self._shape[0] * self._datashape[0] - 1 1540 1541 def pack(fmt, *val): 1542 return struct.pack(byteorder+fmt, *val) 1543 1544 # construct template IFD in memory 1545 # need to patch offsets to next IFD and data before writing to disk 1546 ifd = io.BytesIO() 1547 ifd.write(pack(tagnoformat, len(self._tags))) 1548 tagoffset = ifd.tell() 1549 ifd.write(b''.join(t[1] for t in self._tags)) 1550 ifdoffset = ifd.tell() 1551 ifd.write(pack(offsetformat, 0)) # offset to next IFD 1552 # tag values 1553 for tagindex, tag in enumerate(self._tags): 1554 offset2value = tagoffset + tagindex*tagsize + offsetsize + 4 1555 if tag[2]: 1556 pos = ifd.tell() 1557 if pos % 2: # tag value is expected to begin on word boundary 1558 ifd.write(b'\0') 1559 pos += 1 1560 ifd.seek(offset2value) 1561 try: 1562 ifd.write(pack(offsetformat, pos + fhpos)) 1563 except Exception: # struct.error 1564 if self._imagej: 1565 warnings.warn('truncating ImageJ file') 1566 self._truncate = True 1567 return 1568 raise ValueError('data too large for non-BigTIFF file') 1569 ifd.seek(pos) 1570 ifd.write(tag[2]) 1571 if tag[0] == self._tagoffsets: 1572 # save strip/tile offsets for later updates 1573 stripoffset2offset = offset2value 1574 stripoffset2value = pos 1575 elif tag[0] == self._tagoffsets: 1576 # save strip/tile offsets for later updates 1577 stripoffset2offset = None 1578 stripoffset2value = offset2value 1579 # size to word boundary 1580 if ifd.tell() % 2: 1581 ifd.write(b'\0') 1582 1583 # check if all IFDs fit in file 1584 pos = fh.tell() 1585 if not self._bigtiff and pos + ifd.tell() * pageno > 2**32 - 256: 1586 if self._imagej: 1587 warnings.warn('truncating ImageJ file') 1588 self._truncate = True 1589 return 1590 raise ValueError('data too large for non-BigTIFF file') 1591 1592 # TODO: assemble IFD chain in memory 1593 for _ in range(pageno): 1594 # update pointer at IFD offset 1595 pos = fh.tell() 1596 fh.seek(self._ifdoffset) 1597 fh.write(pack(offsetformat, pos)) 1598 fh.seek(pos) 1599 self._ifdoffset = pos + ifdoffset 1600 # update strip/tile offsets in IFD 1601 dataoffset += pagedatasize # offset to image data 1602 if stripoffset2offset is None: 1603 ifd.seek(stripoffset2value) 1604 ifd.write(pack(offsetformat, dataoffset)) 1605 else: 1606 ifd.seek(stripoffset2offset) 1607 ifd.write(pack(offsetformat, pos + stripoffset2value)) 1608 ifd.seek(stripoffset2value) 1609 stripoffset = dataoffset 1610 for size in self._databytecounts: 1611 ifd.write(pack(offsetformat, stripoffset)) 1612 stripoffset += size 1613 # write IFD entry 1614 fh.write(ifd.getvalue()) 1615 1616 self._tags = None 1617 self._datadtype = None 1618 self._dataoffset = None 1619 self._databytecounts = None 1620 # do not reset _shape or _data_shape 1621 1622 def _write_image_description(self): 1623 """Write meta data to ImageDescription tag.""" 1624 if (not self._datashape or self._datashape[0] == 1 or 1625 self._descriptionoffset <= 0): 1626 return 1627 1628 colormapped = self._colormap is not None 1629 if self._imagej: 1630 isrgb = self._shape[-1] in (3, 4) 1631 description = imagej_description( 1632 self._datashape, isrgb, colormapped, **self._metadata) 1633 else: 1634 description = json_description(self._datashape, **self._metadata) 1635 1636 # rewrite description and its length to file 1637 description = description.encode('utf-8') 1638 description = description[:self._descriptionlen-1] 1639 pos = self._fh.tell() 1640 self._fh.seek(self._descriptionoffset) 1641 self._fh.write(description) 1642 self._fh.seek(self._descriptionlenoffset) 1643 self._fh.write(struct.pack(self._byteorder+self._offsetformat, 1644 len(description)+1)) 1645 self._fh.seek(pos) 1646 1647 self._descriptionoffset = 0 1648 self._descriptionlenoffset = 0 1649 self._descriptionlen = 0 1650 1651 def _now(self): 1652 """Return current date and time.""" 1653 return datetime.datetime.now() 1654 1655 def close(self): 1656 """Write remaining pages and close file handle.""" 1657 if not self._truncate: 1658 self._write_remaining_pages() 1659 self._write_image_description() 1660 self._fh.close() 1661 1662 def __enter__(self): 1663 return self 1664 1665 def __exit__(self, exc_type, exc_value, traceback): 1666 self.close() 1667 1668 1669class TiffFile(object): 1670 """Read image and metadata from TIFF file. 1671 1672 TiffFile instances must be closed using the 'close' method, which is 1673 automatically called when using the 'with' context manager. 1674 1675 Attributes 1676 ---------- 1677 pages : TiffPages 1678 Sequence of TIFF pages in file. 1679 series : list of TiffPageSeries 1680 Sequences of closely related TIFF pages. These are computed 1681 from OME, LSM, ImageJ, etc. metadata or based on similarity 1682 of page properties such as shape, dtype, and compression. 1683 byteorder : '>', '<' 1684 The endianness of data in the file. 1685 '>': big-endian (Motorola). 1686 '>': little-endian (Intel). 1687 is_flag : bool 1688 If True, file is of a certain format. 1689 Flags are: bigtiff, movie, shaped, ome, imagej, stk, lsm, fluoview, 1690 nih, vista, 'micromanager, metaseries, mdgel, mediacy, tvips, fei, 1691 sem, scn, svs, scanimage, andor, epics, pilatus, qptiff. 1692 1693 All attributes are read-only. 1694 1695 Examples 1696 -------- 1697 >>> # read image array from TIFF file 1698 >>> imsave('temp.tif', numpy.random.rand(5, 301, 219)) 1699 >>> with TiffFile('temp.tif') as tif: 1700 ... data = tif.asarray() 1701 >>> data.shape 1702 (5, 301, 219) 1703 1704 """ 1705 def __init__(self, arg, name=None, offset=None, size=None, 1706 multifile=True, movie=None, **kwargs): 1707 """Initialize instance from file. 1708 1709 Parameters 1710 ---------- 1711 arg : str or open file 1712 Name of file or open file object. 1713 The file objects are closed in TiffFile.close(). 1714 name : str 1715 Optional name of file in case 'arg' is a file handle. 1716 offset : int 1717 Optional start position of embedded file. By default, this is 1718 the current file position. 1719 size : int 1720 Optional size of embedded file. By default, this is the number 1721 of bytes from the 'offset' to the end of the file. 1722 multifile : bool 1723 If True (default), series may include pages from multiple files. 1724 Currently applies to OME-TIFF only. 1725 movie : bool 1726 If True, assume that later pages differ from first page only by 1727 data offsets and byte counts. Significantly increases speed and 1728 reduces memory usage when reading movies with thousands of pages. 1729 Enabling this for non-movie files will result in data corruption 1730 or crashes. Python 3 only. 1731 kwargs : bool 1732 'is_ome': If False, disable processing of OME-XML metadata. 1733 1734 """ 1735 if 'fastij' in kwargs: 1736 del kwargs['fastij'] 1737 raise DeprecationWarning('the fastij option will be removed') 1738 for key, value in kwargs.items(): 1739 if key[:3] == 'is_' and key[3:] in TIFF.FILE_FLAGS: 1740 if value is not None and not value: 1741 setattr(self, key, bool(value)) 1742 else: 1743 raise TypeError('unexpected keyword argument: %s' % key) 1744 1745 fh = FileHandle(arg, mode='rb', name=name, offset=offset, size=size) 1746 self._fh = fh 1747 self._multifile = bool(multifile) 1748 self._files = {fh.name: self} # cache of TiffFiles 1749 try: 1750 fh.seek(0) 1751 try: 1752 byteorder = {b'II': '<', b'MM': '>'}[fh.read(2)] 1753 except KeyError: 1754 raise ValueError('not a TIFF file') 1755 sys_byteorder = {'big': '>', 'little': '<'}[sys.byteorder] 1756 self.isnative = byteorder == sys_byteorder 1757 1758 version = struct.unpack(byteorder+'H', fh.read(2))[0] 1759 if version == 43: 1760 # BigTiff 1761 self.is_bigtiff = True 1762 offsetsize, zero = struct.unpack(byteorder+'HH', fh.read(4)) 1763 if zero or offsetsize != 8: 1764 raise ValueError('invalid BigTIFF file') 1765 self.byteorder = byteorder 1766 self.offsetsize = 8 1767 self.offsetformat = byteorder+'Q' 1768 self.tagnosize = 8 1769 self.tagnoformat = byteorder+'Q' 1770 self.tagsize = 20 1771 self.tagformat1 = byteorder+'HH' 1772 self.tagformat2 = byteorder+'Q8s' 1773 elif version == 42: 1774 self.is_bigtiff = False 1775 self.byteorder = byteorder 1776 self.offsetsize = 4 1777 self.offsetformat = byteorder+'I' 1778 self.tagnosize = 2 1779 self.tagnoformat = byteorder+'H' 1780 self.tagsize = 12 1781 self.tagformat1 = byteorder+'HH' 1782 self.tagformat2 = byteorder+'I4s' 1783 else: 1784 raise ValueError('invalid TIFF file') 1785 1786 # file handle is at offset to offset to first page 1787 self.pages = TiffPages(self) 1788 1789 if self.is_lsm and (self.filehandle.size >= 2**32 or 1790 self.pages[0].compression != 1 or 1791 self.pages[1].compression != 1): 1792 self._lsm_load_pages() 1793 self._lsm_fix_strip_offsets() 1794 self._lsm_fix_strip_bytecounts() 1795 elif movie: 1796 self.pages.useframes = True 1797 1798 except Exception: 1799 fh.close() 1800 raise 1801 1802 @property 1803 def filehandle(self): 1804 """Return file handle.""" 1805 return self._fh 1806 1807 @property 1808 def filename(self): 1809 """Return name of file handle.""" 1810 return self._fh.name 1811 1812 @lazyattr 1813 def fstat(self): 1814 """Return status of file handle as stat_result object.""" 1815 try: 1816 return os.fstat(self._fh.fileno()) 1817 except Exception: # io.UnsupportedOperation 1818 return None 1819 1820 def close(self): 1821 """Close open file handle(s).""" 1822 for tif in self._files.values(): 1823 tif.filehandle.close() 1824 self._files = {} 1825 1826 def asarray(self, key=None, series=None, out=None, validate=True, 1827 maxworkers=1): 1828 """Return image data from multiple TIFF pages as numpy array. 1829 1830 By default, the data from the first series is returned. 1831 1832 Parameters 1833 ---------- 1834 key : int, slice, or sequence of page indices 1835 Defines which pages to return as array. 1836 series : int or TiffPageSeries 1837 Defines which series of pages to return as array. 1838 out : numpy.ndarray, str, or file-like object; optional 1839 Buffer where image data will be saved. 1840 If None (default), a new array will be created. 1841 If numpy.ndarray, a writable array of compatible dtype and shape. 1842 If 'memmap', directly memory-map the image data in the TIFF file 1843 if possible; else create a memory-mapped array in a temporary file. 1844 If str or open file, the file name or file object used to 1845 create a memory-map to an array stored in a binary file on disk. 1846 validate : bool 1847 If True (default), validate various tags. 1848 Passed to TiffPage.asarray(). 1849 maxworkers : int 1850 Maximum number of threads to concurrently get data from pages. 1851 Default is 1. If None, up to half the CPU cores are used. 1852 Reading data from file is limited to a single thread. 1853 Using multiple threads can significantly speed up this function 1854 if the bottleneck is decoding compressed data, e.g. in case of 1855 large LZW compressed LSM files. 1856 If the bottleneck is I/O or pure Python code, using multiple 1857 threads might be detrimental. 1858 1859 """ 1860 if not self.pages: 1861 return numpy.array([]) 1862 if key is None and series is None: 1863 series = 0 1864 if series is not None: 1865 try: 1866 series = self.series[series] 1867 except (KeyError, TypeError): 1868 pass 1869 pages = series._pages 1870 else: 1871 pages = self.pages 1872 1873 if key is None: 1874 pass 1875 elif isinstance(key, inttypes): 1876 pages = [pages[key]] 1877 elif isinstance(key, slice): 1878 pages = pages[key] 1879 elif isinstance(key, collections.Iterable): 1880 pages = [pages[k] for k in key] 1881 else: 1882 raise TypeError('key must be an int, slice, or sequence') 1883 1884 if not pages: 1885 raise ValueError('no pages selected') 1886 1887 if self.is_nih: 1888 result = stack_pages(pages, out=out, maxworkers=maxworkers, 1889 squeeze=False) 1890 elif key is None and series and series.offset: 1891 typecode = self.byteorder + series.dtype.char 1892 if out == 'memmap' and pages[0].is_memmappable: 1893 result = self.filehandle.memmap_array( 1894 typecode, series.shape, series.offset) 1895 else: 1896 if out is not None: 1897 out = create_output(out, series.shape, series.dtype) 1898 self.filehandle.seek(series.offset) 1899 result = self.filehandle.read_array( 1900 typecode, product(series.shape), out=out, native=True) 1901 elif len(pages) == 1: 1902 result = pages[0].asarray(out=out, validate=validate) 1903 else: 1904 result = stack_pages(pages, out=out, maxworkers=maxworkers) 1905 1906 if result is None: 1907 return 1908 1909 if key is None: 1910 try: 1911 result.shape = series.shape 1912 except ValueError: 1913 try: 1914 warnings.warn('failed to reshape %s to %s' % ( 1915 result.shape, series.shape)) 1916 # try series of expected shapes 1917 result.shape = (-1,) + series.shape 1918 except ValueError: 1919 # revert to generic shape 1920 result.shape = (-1,) + pages[0].shape 1921 elif len(pages) == 1: 1922 result.shape = pages[0].shape 1923 else: 1924 result.shape = (-1,) + pages[0].shape 1925 return result 1926 1927 @lazyattr 1928 def series(self): 1929 """Return related pages as TiffPageSeries. 1930 1931 Side effect: after calling this function, TiffFile.pages might contain 1932 TiffPage and TiffFrame instances. 1933 1934 """ 1935 if not self.pages: 1936 return [] 1937 1938 useframes = self.pages.useframes 1939 keyframe = self.pages.keyframe 1940 series = [] 1941 for name in 'ome imagej lsm fluoview nih mdgel shaped'.split(): 1942 if getattr(self, 'is_' + name, False): 1943 series = getattr(self, '_%s_series' % name)() 1944 break 1945 self.pages.useframes = useframes 1946 self.pages.keyframe = keyframe 1947 if not series: 1948 series = self._generic_series() 1949 1950 # remove empty series, e.g. in MD Gel files 1951 series = [s for s in series if sum(s.shape) > 0] 1952 1953 for i, s in enumerate(series): 1954 s.index = i 1955 return series 1956 1957 def _generic_series(self): 1958 """Return image series in file.""" 1959 if self.pages.useframes: 1960 # movie mode 1961 page = self.pages[0] 1962 shape = page.shape 1963 axes = page.axes 1964 if len(self.pages) > 1: 1965 shape = (len(self.pages),) + shape 1966 axes = 'I' + axes 1967 return [TiffPageSeries(self.pages[:], shape, page.dtype, axes, 1968 stype='movie')] 1969 1970 self.pages.clear(False) 1971 self.pages.load() 1972 result = [] 1973 keys = [] 1974 series = {} 1975 compressions = TIFF.DECOMPESSORS 1976 for page in self.pages: 1977 if not page.shape: 1978 continue 1979 key = page.shape + (page.axes, page.compression in compressions) 1980 if key in series: 1981 series[key].append(page) 1982 else: 1983 keys.append(key) 1984 series[key] = [page] 1985 for key in keys: 1986 pages = series[key] 1987 page = pages[0] 1988 shape = page.shape 1989 axes = page.axes 1990 if len(pages) > 1: 1991 shape = (len(pages),) + shape 1992 axes = 'I' + axes 1993 result.append(TiffPageSeries(pages, shape, page.dtype, axes, 1994 stype='Generic')) 1995 1996 return result 1997 1998 def _shaped_series(self): 1999 """Return image series in "shaped" file.""" 2000 pages = self.pages 2001 pages.useframes = True 2002 lenpages = len(pages) 2003 2004 def append_series(series, pages, axes, shape, reshape, name, 2005 truncated): 2006 page = pages[0] 2007 if not axes: 2008 shape = page.shape 2009 axes = page.axes 2010 if len(pages) > 1: 2011 shape = (len(pages),) + shape 2012 axes = 'Q' + axes 2013 size = product(shape) 2014 resize = product(reshape) 2015 if page.is_contiguous and resize > size and resize % size == 0: 2016 if truncated is None: 2017 truncated = True 2018 axes = 'Q' + axes 2019 shape = (resize // size,) + shape 2020 try: 2021 axes = reshape_axes(axes, shape, reshape) 2022 shape = reshape 2023 except ValueError as e: 2024 warnings.warn(str(e)) 2025 series.append( 2026 TiffPageSeries(pages, shape, page.dtype, axes, name=name, 2027 stype='Shaped', truncated=truncated)) 2028 2029 keyframe = axes = shape = reshape = name = None 2030 series = [] 2031 index = 0 2032 while True: 2033 if index >= lenpages: 2034 break 2035 # new keyframe; start of new series 2036 pages.keyframe = index 2037 keyframe = pages[index] 2038 if not keyframe.is_shaped: 2039 warnings.warn('invalid shape metadata or corrupted file') 2040 return 2041 # read metadata 2042 axes = None 2043 shape = None 2044 metadata = json_description_metadata(keyframe.is_shaped) 2045 name = metadata.get('name', '') 2046 reshape = metadata['shape'] 2047 truncated = metadata.get('truncated', None) 2048 if 'axes' in metadata: 2049 axes = metadata['axes'] 2050 if len(axes) == len(reshape): 2051 shape = reshape 2052 else: 2053 axes = '' 2054 warnings.warn('axes do not match shape') 2055 # skip pages if possible 2056 spages = [keyframe] 2057 size = product(reshape) 2058 npages, mod = divmod(size, product(keyframe.shape)) 2059 if mod: 2060 warnings.warn('series shape does not match page shape') 2061 return 2062 if 1 < npages <= lenpages - index: 2063 size *= keyframe._dtype.itemsize 2064 if truncated: 2065 npages = 1 2066 elif (keyframe.is_final and 2067 keyframe.offset + size < pages[index+1].offset): 2068 truncated = False 2069 else: 2070 # need to read all pages for series 2071 truncated = False 2072 for j in range(index+1, index+npages): 2073 page = pages[j] 2074 page.keyframe = keyframe 2075 spages.append(page) 2076 append_series(series, spages, axes, shape, reshape, name, 2077 truncated) 2078 index += npages 2079 2080 return series 2081 2082 def _imagej_series(self): 2083 """Return image series in ImageJ file.""" 2084 # ImageJ's dimension order is always TZCYXS 2085 # TODO: fix loading of color, composite, or palette images 2086 self.pages.useframes = True 2087 self.pages.keyframe = 0 2088 2089 ij = self.imagej_metadata 2090 pages = self.pages 2091 page = pages[0] 2092 2093 def is_hyperstack(): 2094 # ImageJ hyperstack store all image metadata in the first page and 2095 # image data are stored contiguously before the second page, if any 2096 if not page.is_final: 2097 return False 2098 images = ij.get('images', 0) 2099 if images <= 1: 2100 return False 2101 offset, count = page.is_contiguous 2102 if (count != product(page.shape) * page.bitspersample // 8 2103 or offset + count*images > self.filehandle.size): 2104 raise ValueError() 2105 # check that next page is stored after data 2106 if len(pages) > 1 and offset + count*images > pages[1].offset: 2107 return False 2108 return True 2109 2110 try: 2111 hyperstack = is_hyperstack() 2112 except ValueError: 2113 warnings.warn('invalid ImageJ metadata or corrupted file') 2114 return 2115 if hyperstack: 2116 # no need to read other pages 2117 pages = [page] 2118 else: 2119 self.pages.load() 2120 2121 shape = [] 2122 axes = [] 2123 if 'frames' in ij: 2124 shape.append(ij['frames']) 2125 axes.append('T') 2126 if 'slices' in ij: 2127 shape.append(ij['slices']) 2128 axes.append('Z') 2129 if 'channels' in ij and not (page.photometric == 2 and not 2130 ij.get('hyperstack', False)): 2131 shape.append(ij['channels']) 2132 axes.append('C') 2133 remain = ij.get('images', len(pages))//(product(shape) if shape else 1) 2134 if remain > 1: 2135 shape.append(remain) 2136 axes.append('I') 2137 if page.axes[0] == 'I': 2138 # contiguous multiple images 2139 shape.extend(page.shape[1:]) 2140 axes.extend(page.axes[1:]) 2141 elif page.axes[:2] == 'SI': 2142 # color-mapped contiguous multiple images 2143 shape = page.shape[0:1] + tuple(shape) + page.shape[2:] 2144 axes = list(page.axes[0]) + axes + list(page.axes[2:]) 2145 else: 2146 shape.extend(page.shape) 2147 axes.extend(page.axes) 2148 2149 truncated = ( 2150 hyperstack and len(self.pages) == 1 and 2151 page.is_contiguous[1] != product(shape) * page.bitspersample // 8) 2152 2153 return [TiffPageSeries(pages, shape, page.dtype, axes, stype='ImageJ', 2154 truncated=truncated)] 2155 2156 def _fluoview_series(self): 2157 """Return image series in FluoView file.""" 2158 self.pages.useframes = True 2159 self.pages.keyframe = 0 2160 self.pages.load() 2161 mm = self.fluoview_metadata 2162 mmhd = list(reversed(mm['Dimensions'])) 2163 axes = ''.join(TIFF.MM_DIMENSIONS.get(i[0].upper(), 'Q') 2164 for i in mmhd if i[1] > 1) 2165 shape = tuple(int(i[1]) for i in mmhd if i[1] > 1) 2166 return [TiffPageSeries(self.pages, shape, self.pages[0].dtype, axes, 2167 name=mm['ImageName'], stype='FluoView')] 2168 2169 def _mdgel_series(self): 2170 """Return image series in MD Gel file.""" 2171 # only a single page, scaled according to metadata in second page 2172 self.pages.useframes = False 2173 self.pages.keyframe = 0 2174 self.pages.load() 2175 md = self.mdgel_metadata 2176 if md['FileTag'] in (2, 128): 2177 dtype = numpy.dtype('float32') 2178 scale = md['ScalePixel'] 2179 scale = scale[0] / scale[1] # rational 2180 if md['FileTag'] == 2: 2181 # squary root data format 2182 def transform(a): 2183 return a.astype('float32')**2 * scale 2184 else: 2185 def transform(a): 2186 return a.astype('float32') * scale 2187 else: 2188 transform = None 2189 page = self.pages[0] 2190 return [TiffPageSeries([page], page.shape, dtype, page.axes, 2191 transform=transform, stype='MDGel')] 2192 2193 def _nih_series(self): 2194 """Return image series in NIH file.""" 2195 self.pages.useframes = True 2196 self.pages.keyframe = 0 2197 self.pages.load() 2198 page0 = self.pages[0] 2199 if len(self.pages) == 1: 2200 shape = page0.shape 2201 axes = page0.axes 2202 else: 2203 shape = (len(self.pages),) + page0.shape 2204 axes = 'I' + page0.axes 2205 return [ 2206 TiffPageSeries(self.pages, shape, page0.dtype, axes, stype='NIH')] 2207 2208 def _ome_series(self): 2209 """Return image series in OME-TIFF file(s).""" 2210 from xml.etree import cElementTree as etree # delayed import 2211 omexml = self.pages[0].description 2212 try: 2213 root = etree.fromstring(omexml) 2214 except etree.ParseError as e: 2215 # TODO: test badly encoded OME-XML 2216 warnings.warn('ome-xml: %s' % e) 2217 try: 2218 # might work on Python 2 2219 omexml = omexml.decode('utf-8', 'ignore').encode('utf-8') 2220 root = etree.fromstring(omexml) 2221 except Exception: 2222 return 2223 2224 self.pages.useframes = True 2225 self.pages.keyframe = 0 2226 self.pages.load() 2227 2228 uuid = root.attrib.get('UUID', None) 2229 self._files = {uuid: self} 2230 dirname = self._fh.dirname 2231 modulo = {} 2232 series = [] 2233 for element in root: 2234 if element.tag.endswith('BinaryOnly'): 2235 # TODO: load OME-XML from master or companion file 2236 warnings.warn('ome-xml: not an ome-tiff master file') 2237 break 2238 if element.tag.endswith('StructuredAnnotations'): 2239 for annot in element: 2240 if not annot.attrib.get('Namespace', 2241 '').endswith('modulo'): 2242 continue 2243 for value in annot: 2244 for modul in value: 2245 for along in modul: 2246 if not along.tag[:-1].endswith('Along'): 2247 continue 2248 axis = along.tag[-1] 2249 newaxis = along.attrib.get('Type', 'other') 2250 newaxis = TIFF.AXES_LABELS[newaxis] 2251 if 'Start' in along.attrib: 2252 step = float(along.attrib.get('Step', 1)) 2253 start = float(along.attrib['Start']) 2254 stop = float(along.attrib['End']) + step 2255 labels = numpy.arange(start, stop, step) 2256 else: 2257 labels = [label.text for label in along 2258 if label.tag.endswith('Label')] 2259 modulo[axis] = (newaxis, labels) 2260 2261 if not element.tag.endswith('Image'): 2262 continue 2263 2264 attr = element.attrib 2265 name = attr.get('Name', None) 2266 2267 for pixels in element: 2268 if not pixels.tag.endswith('Pixels'): 2269 continue 2270 attr = pixels.attrib 2271 dtype = attr.get('PixelType', None) 2272 axes = ''.join(reversed(attr['DimensionOrder'])) 2273 shape = list(int(attr['Size'+ax]) for ax in axes) 2274 size = product(shape[:-2]) 2275 ifds = None 2276 spp = 1 # samples per pixel 2277 # FIXME: this implementation assumes the last two 2278 # dimensions are stored in tiff pages (shape[:-2]). 2279 # Apparently that is not always the case. 2280 for data in pixels: 2281 if data.tag.endswith('Channel'): 2282 attr = data.attrib 2283 if ifds is None: 2284 spp = int(attr.get('SamplesPerPixel', spp)) 2285 ifds = [None] * (size // spp) 2286 elif int(attr.get('SamplesPerPixel', 1)) != spp: 2287 raise ValueError( 2288 "cannot handle differing SamplesPerPixel") 2289 continue 2290 if ifds is None: 2291 ifds = [None] * (size // spp) 2292 if not data.tag.endswith('TiffData'): 2293 continue 2294 attr = data.attrib 2295 ifd = int(attr.get('IFD', 0)) 2296 num = int(attr.get('NumPlanes', 1 if 'IFD' in attr else 0)) 2297 num = int(attr.get('PlaneCount', num)) 2298 idx = [int(attr.get('First'+ax, 0)) for ax in axes[:-2]] 2299 try: 2300 idx = numpy.ravel_multi_index(idx, shape[:-2]) 2301 except ValueError: 2302 # ImageJ produces invalid ome-xml when cropping 2303 warnings.warn('ome-xml: invalid TiffData index') 2304 continue 2305 for uuid in data: 2306 if not uuid.tag.endswith('UUID'): 2307 continue 2308 if uuid.text not in self._files: 2309 if not self._multifile: 2310 # abort reading multifile OME series 2311 # and fall back to generic series 2312 return [] 2313 fname = uuid.attrib['FileName'] 2314 try: 2315 tif = TiffFile(os.path.join(dirname, fname)) 2316 tif.pages.useframes = True 2317 tif.pages.keyframe = 0 2318 tif.pages.load() 2319 except (IOError, FileNotFoundError, ValueError): 2320 warnings.warn( 2321 "ome-xml: failed to read '%s'" % fname) 2322 break 2323 self._files[uuid.text] = tif 2324 tif.close() 2325 pages = self._files[uuid.text].pages 2326 try: 2327 for i in range(num if num else len(pages)): 2328 ifds[idx + i] = pages[ifd + i] 2329 except IndexError: 2330 warnings.warn('ome-xml: index out of range') 2331 # only process first UUID 2332 break 2333 else: 2334 pages = self.pages 2335 try: 2336 for i in range(num if num else len(pages)): 2337 ifds[idx + i] = pages[ifd + i] 2338 except IndexError: 2339 warnings.warn('ome-xml: index out of range') 2340 2341 if all(i is None for i in ifds): 2342 # skip images without data 2343 continue 2344 2345 # set a keyframe on all IFDs 2346 keyframe = None 2347 for i in ifds: 2348 # try find a TiffPage 2349 if i and i == i.keyframe: 2350 keyframe = i 2351 break 2352 if not keyframe: 2353 # reload a TiffPage from file 2354 for i, keyframe in enumerate(ifds): 2355 if keyframe: 2356 keyframe.parent.pages.keyframe = keyframe.index 2357 keyframe = keyframe.parent.pages[keyframe.index] 2358 ifds[i] = keyframe 2359 break 2360 for i in ifds: 2361 if i is not None: 2362 i.keyframe = keyframe 2363 2364 dtype = keyframe.dtype 2365 series.append( 2366 TiffPageSeries(ifds, shape, dtype, axes, parent=self, 2367 name=name, stype='OME')) 2368 for serie in series: 2369 shape = list(serie.shape) 2370 for axis, (newaxis, labels) in modulo.items(): 2371 i = serie.axes.index(axis) 2372 size = len(labels) 2373 if shape[i] == size: 2374 serie.axes = serie.axes.replace(axis, newaxis, 1) 2375 else: 2376 shape[i] //= size 2377 shape.insert(i+1, size) 2378 serie.axes = serie.axes.replace(axis, axis+newaxis, 1) 2379 serie.shape = tuple(shape) 2380 # squeeze dimensions 2381 for serie in series: 2382 serie.shape, serie.axes = squeeze_axes(serie.shape, serie.axes) 2383 return series 2384 2385 def _lsm_series(self): 2386 """Return main image series in LSM file. Skip thumbnails.""" 2387 lsmi = self.lsm_metadata 2388 axes = TIFF.CZ_LSMINFO_SCANTYPE[lsmi['ScanType']] 2389 if self.pages[0].photometric == 2: # RGB; more than one channel 2390 axes = axes.replace('C', '').replace('XY', 'XYC') 2391 if lsmi.get('DimensionP', 0) > 1: 2392 axes += 'P' 2393 if lsmi.get('DimensionM', 0) > 1: 2394 axes += 'M' 2395 axes = axes[::-1] 2396 shape = tuple(int(lsmi[TIFF.CZ_LSMINFO_DIMENSIONS[i]]) for i in axes) 2397 name = lsmi.get('Name', '') 2398 self.pages.keyframe = 0 2399 pages = self.pages[::2] 2400 dtype = pages[0].dtype 2401 series = [TiffPageSeries(pages, shape, dtype, axes, name=name, 2402 stype='LSM')] 2403 2404 if self.pages[1].is_reduced: 2405 self.pages.keyframe = 1 2406 pages = self.pages[1::2] 2407 dtype = pages[0].dtype 2408 cp, i = 1, 0 2409 while cp < len(pages) and i < len(shape)-2: 2410 cp *= shape[i] 2411 i += 1 2412 shape = shape[:i] + pages[0].shape 2413 axes = axes[:i] + 'CYX' 2414 series.append(TiffPageSeries(pages, shape, dtype, axes, name=name, 2415 stype='LSMreduced')) 2416 2417 return series 2418 2419 def _lsm_load_pages(self): 2420 """Load all pages from LSM file.""" 2421 self.pages.cache = True 2422 self.pages.useframes = True 2423 # second series: thumbnails 2424 self.pages.keyframe = 1 2425 keyframe = self.pages[1] 2426 for page in self.pages[1::2]: 2427 page.keyframe = keyframe 2428 # first series: data 2429 self.pages.keyframe = 0 2430 keyframe = self.pages[0] 2431 for page in self.pages[::2]: 2432 page.keyframe = keyframe 2433 2434 def _lsm_fix_strip_offsets(self): 2435 """Unwrap strip offsets for LSM files greater than 4 GB. 2436 2437 Each series and position require separate unwrapping (undocumented). 2438 2439 """ 2440 if self.filehandle.size < 2**32: 2441 return 2442 2443 pages = self.pages 2444 npages = len(pages) 2445 series = self.series[0] 2446 axes = series.axes 2447 2448 # find positions 2449 positions = 1 2450 for i in 0, 1: 2451 if series.axes[i] in 'PM': 2452 positions *= series.shape[i] 2453 2454 # make time axis first 2455 if positions > 1: 2456 ntimes = 0 2457 for i in 1, 2: 2458 if axes[i] == 'T': 2459 ntimes = series.shape[i] 2460 break 2461 if ntimes: 2462 div, mod = divmod(npages, 2*positions*ntimes) 2463 assert mod == 0 2464 shape = (positions, ntimes, div, 2) 2465 indices = numpy.arange(product(shape)).reshape(shape) 2466 indices = numpy.moveaxis(indices, 1, 0) 2467 else: 2468 indices = numpy.arange(npages).reshape(-1, 2) 2469 2470 # images of reduced page might be stored first 2471 if pages[0].dataoffsets[0] > pages[1].dataoffsets[0]: 2472 indices = indices[..., ::-1] 2473 2474 # unwrap offsets 2475 wrap = 0 2476 previousoffset = 0 2477 for i in indices.flat: 2478 page = pages[i] 2479 dataoffsets = [] 2480 for currentoffset in page.dataoffsets: 2481 if currentoffset < previousoffset: 2482 wrap += 2**32 2483 dataoffsets.append(currentoffset + wrap) 2484 previousoffset = currentoffset 2485 page.dataoffsets = tuple(dataoffsets) 2486 2487 def _lsm_fix_strip_bytecounts(self): 2488 """Set databytecounts to size of compressed data. 2489 2490 The StripByteCounts tag in LSM files contains the number of bytes 2491 for the uncompressed data. 2492 2493 """ 2494 pages = self.pages 2495 if pages[0].compression == 1: 2496 return 2497 # sort pages by first strip offset 2498 pages = sorted(pages, key=lambda p: p.dataoffsets[0]) 2499 npages = len(pages) - 1 2500 for i, page in enumerate(pages): 2501 if page.index % 2: 2502 continue 2503 offsets = page.dataoffsets 2504 bytecounts = page.databytecounts 2505 if i < npages: 2506 lastoffset = pages[i+1].dataoffsets[0] 2507 else: 2508 # LZW compressed strips might be longer than uncompressed 2509 lastoffset = min(offsets[-1] + 2*bytecounts[-1], self._fh.size) 2510 offsets = offsets + (lastoffset,) 2511 page.databytecounts = tuple(offsets[j+1] - offsets[j] 2512 for j in range(len(bytecounts))) 2513 2514 def __getattr__(self, name): 2515 """Return 'is_flag' attributes from first page.""" 2516 if name[3:] in TIFF.FILE_FLAGS: 2517 if not self.pages: 2518 return False 2519 value = bool(getattr(self.pages[0], name)) 2520 setattr(self, name, value) 2521 return value 2522 raise AttributeError("'%s' object has no attribute '%s'" % 2523 (self.__class__.__name__, name)) 2524 2525 def __enter__(self): 2526 return self 2527 2528 def __exit__(self, exc_type, exc_value, traceback): 2529 self.close() 2530 2531 def __str__(self, detail=0, width=79): 2532 """Return string containing information about file. 2533 2534 The detail parameter specifies the level of detail returned: 2535 2536 0: file only. 2537 1: all series, first page of series and its tags. 2538 2: large tag values and file metadata. 2539 3: all pages. 2540 2541 """ 2542 info = [ 2543 "TiffFile '%s'", 2544 format_size(self._fh.size), 2545 {'<': 'LittleEndian', '>': 'BigEndian'}[self.byteorder]] 2546 if self.is_bigtiff: 2547 info.append('BigTiff') 2548 info.append('|'.join(f.upper() for f in self.flags)) 2549 if len(self.pages) > 1: 2550 info.append('%i Pages' % len(self.pages)) 2551 if len(self.series) > 1: 2552 info.append('%i Series' % len(self.series)) 2553 if len(self._files) > 1: 2554 info.append('%i Files' % (len(self._files))) 2555 info = ' '.join(info) 2556 info = info.replace(' ', ' ').replace(' ', ' ') 2557 info = info % snipstr(self._fh.name, max(12, width+2-len(info))) 2558 if detail <= 0: 2559 return info 2560 info = [info] 2561 info.append('\n'.join(str(s) for s in self.series)) 2562 if detail >= 3: 2563 info.extend((TiffPage.__str__(p, detail=detail, width=width) 2564 for p in self.pages 2565 if p is not None)) 2566 else: 2567 info.extend((TiffPage.__str__(s.pages[0], detail=detail, 2568 width=width) 2569 for s in self.series 2570 if s.pages[0] is not None)) 2571 if detail >= 2: 2572 for name in sorted(self.flags): 2573 if hasattr(self, name + '_metadata'): 2574 m = getattr(self, name + '_metadata') 2575 if m: 2576 info.append( 2577 '%s_METADATA\n%s' % (name.upper(), 2578 pformat(m, width=width, 2579 height=detail*12))) 2580 return '\n\n'.join(info).replace('\n\n\n', '\n\n') 2581 2582 @lazyattr 2583 def flags(self): 2584 """Return set of file flags.""" 2585 return set(name.lower() for name in sorted(TIFF.FILE_FLAGS) 2586 if getattr(self, 'is_' + name)) 2587 2588 @lazyattr 2589 def is_mdgel(self): 2590 """File has MD Gel format.""" 2591 try: 2592 return self.pages[0].is_mdgel or self.pages[1].is_mdgel 2593 except IndexError: 2594 return False 2595 2596 @property 2597 def is_movie(self): 2598 """Return if file is a movie.""" 2599 return self.pages.useframes 2600 2601 @lazyattr 2602 def shaped_metadata(self): 2603 """Return Tifffile metadata from JSON descriptions as dicts.""" 2604 if not self.is_shaped: 2605 return 2606 return tuple(json_description_metadata(s.pages[0].is_shaped) 2607 for s in self.series if s.stype.lower() == 'shaped') 2608 2609 @lazyattr 2610 def ome_metadata(self): 2611 """Return OME XML as dict.""" 2612 # TODO: remove this or return XML? 2613 if not self.is_ome: 2614 return 2615 return xml2dict(self.pages[0].description)['OME'] 2616 2617 @lazyattr 2618 def qptiff_metadata(self): 2619 """Return PerkinElmer-QPI-ImageDescription XML element as dict.""" 2620 if not self.is_qptiff: 2621 return 2622 root = 'PerkinElmer-QPI-ImageDescription' 2623 xml = self.pages[0].description.replace(' ' + root + ' ', root) 2624 return xml2dict(xml)[root] 2625 2626 @lazyattr 2627 def lsm_metadata(self): 2628 """Return LSM metadata from CZ_LSMINFO tag as dict.""" 2629 if not self.is_lsm: 2630 return 2631 return self.pages[0].tags['CZ_LSMINFO'].value 2632 2633 @lazyattr 2634 def stk_metadata(self): 2635 """Return STK metadata from UIC tags as dict.""" 2636 if not self.is_stk: 2637 return 2638 page = self.pages[0] 2639 tags = page.tags 2640 result = {} 2641 result['NumberPlanes'] = tags['UIC2tag'].count 2642 if page.description: 2643 result['PlaneDescriptions'] = page.description.split('\0') 2644 # result['plane_descriptions'] = stk_description_metadata( 2645 # page.image_description) 2646 if 'UIC1tag' in tags: 2647 result.update(tags['UIC1tag'].value) 2648 if 'UIC3tag' in tags: 2649 result.update(tags['UIC3tag'].value) # wavelengths 2650 if 'UIC4tag' in tags: 2651 result.update(tags['UIC4tag'].value) # override uic1 tags 2652 uic2tag = tags['UIC2tag'].value 2653 result['ZDistance'] = uic2tag['ZDistance'] 2654 result['TimeCreated'] = uic2tag['TimeCreated'] 2655 result['TimeModified'] = uic2tag['TimeModified'] 2656 try: 2657 result['DatetimeCreated'] = numpy.array( 2658 [julian_datetime(*dt) for dt in 2659 zip(uic2tag['DateCreated'], uic2tag['TimeCreated'])], 2660 dtype='datetime64[ns]') 2661 result['DatetimeModified'] = numpy.array( 2662 [julian_datetime(*dt) for dt in 2663 zip(uic2tag['DateModified'], uic2tag['TimeModified'])], 2664 dtype='datetime64[ns]') 2665 except ValueError as e: 2666 warnings.warn('stk_metadata: %s' % e) 2667 return result 2668 2669 @lazyattr 2670 def imagej_metadata(self): 2671 """Return consolidated ImageJ metadata as dict.""" 2672 if not self.is_imagej: 2673 return 2674 page = self.pages[0] 2675 result = imagej_description_metadata(page.is_imagej) 2676 if 'IJMetadata' in page.tags: 2677 try: 2678 result.update(page.tags['IJMetadata'].value) 2679 except Exception: 2680 pass 2681 return result 2682 2683 @lazyattr 2684 def fluoview_metadata(self): 2685 """Return consolidated FluoView metadata as dict.""" 2686 if not self.is_fluoview: 2687 return 2688 result = {} 2689 page = self.pages[0] 2690 result.update(page.tags['MM_Header'].value) 2691 # TODO: read stamps from all pages 2692 result['Stamp'] = page.tags['MM_Stamp'].value 2693 # skip parsing image description; not reliable 2694 # try: 2695 # t = fluoview_description_metadata(page.image_description) 2696 # if t is not None: 2697 # result['ImageDescription'] = t 2698 # except Exception as e: 2699 # warnings.warn( 2700 # "failed to read FluoView image description: %s" % e) 2701 return result 2702 2703 @lazyattr 2704 def nih_metadata(self): 2705 """Return NIH Image metadata from NIHImageHeader tag as dict.""" 2706 if not self.is_nih: 2707 return 2708 return self.pages[0].tags['NIHImageHeader'].value 2709 2710 @lazyattr 2711 def fei_metadata(self): 2712 """Return FEI metadata from SFEG or HELIOS tags as dict.""" 2713 if not self.is_fei: 2714 return 2715 tags = self.pages[0].tags 2716 if 'FEI_SFEG' in tags: 2717 return tags['FEI_SFEG'].value 2718 if 'FEI_HELIOS' in tags: 2719 return tags['FEI_HELIOS'].value 2720 2721 @lazyattr 2722 def sem_metadata(self): 2723 """Return SEM metadata from CZ_SEM tag as dict.""" 2724 if not self.is_sem: 2725 return 2726 return self.pages[0].tags['CZ_SEM'].value 2727 2728 @lazyattr 2729 def mdgel_metadata(self): 2730 """Return consolidated metadata from MD GEL tags as dict.""" 2731 for page in self.pages[:2]: 2732 if 'MDFileTag' in page.tags: 2733 tags = page.tags 2734 break 2735 else: 2736 return 2737 result = {} 2738 for code in range(33445, 33453): 2739 name = TIFF.TAGS[code] 2740 if name not in tags: 2741 continue 2742 result[name[2:]] = tags[name].value 2743 return result 2744 2745 @lazyattr 2746 def andor_metadata(self): 2747 """Return Andor tags as dict.""" 2748 return self.pages[0].andor_tags 2749 2750 @lazyattr 2751 def epics_metadata(self): 2752 """Return EPICS areaDetector tags as dict.""" 2753 return self.pages[0].epics_tags 2754 2755 @lazyattr 2756 def tvips_metadata(self): 2757 """Return TVIPS tag as dict.""" 2758 if not self.is_tvips: 2759 return 2760 return self.pages[0].tags['TVIPS'].value 2761 2762 @lazyattr 2763 def metaseries_metadata(self): 2764 """Return MetaSeries metadata from image description as dict.""" 2765 if not self.is_metaseries: 2766 return 2767 return metaseries_description_metadata(self.pages[0].description) 2768 2769 @lazyattr 2770 def pilatus_metadata(self): 2771 """Return Pilatus metadata from image description as dict.""" 2772 if not self.is_pilatus: 2773 return 2774 return pilatus_description_metadata(self.pages[0].description) 2775 2776 @lazyattr 2777 def micromanager_metadata(self): 2778 """Return consolidated MicroManager metadata as dict.""" 2779 if not self.is_micromanager: 2780 return 2781 # from file header 2782 result = read_micromanager_metadata(self._fh) 2783 # from tag 2784 result.update(self.pages[0].tags['MicroManagerMetadata'].value) 2785 return result 2786 2787 @lazyattr 2788 def scanimage_metadata(self): 2789 """Return ScanImage non-varying frame and ROI metadata as dict.""" 2790 if not self.is_scanimage: 2791 return 2792 result = {} 2793 try: 2794 framedata, roidata = read_scanimage_metadata(self._fh) 2795 result['FrameData'] = framedata 2796 result.update(roidata) 2797 except ValueError: 2798 pass 2799 # TODO: scanimage_artist_metadata 2800 try: 2801 result['Description'] = scanimage_description_metadata( 2802 self.pages[0].description) 2803 except Exception as e: 2804 warnings.warn('scanimage_description_metadata failed: %s' % e) 2805 return result 2806 2807 @property 2808 def geotiff_metadata(self): 2809 """Return GeoTIFF metadata from first page as dict.""" 2810 if not self.is_geotiff: 2811 return 2812 return self.pages[0].geotiff_tags 2813 2814 2815class TiffPages(object): 2816 """Sequence of TIFF image file directories.""" 2817 def __init__(self, parent): 2818 """Initialize instance from file. Read first TiffPage from file. 2819 2820 The file position must be at an offset to an offset to a TiffPage. 2821 2822 """ 2823 self.parent = parent 2824 self.pages = [] # cache of TiffPages, TiffFrames, or their offsets 2825 self.complete = False # True if offsets to all pages were read 2826 self._tiffpage = TiffPage # class for reading tiff pages 2827 self._keyframe = None 2828 self._cache = True 2829 2830 # read offset to first page 2831 fh = parent.filehandle 2832 self._nextpageoffset = fh.tell() 2833 offset = struct.unpack(parent.offsetformat, 2834 fh.read(parent.offsetsize))[0] 2835 2836 if offset == 0: 2837 # warnings.warn('file contains no pages') 2838 self.complete = True 2839 return 2840 if offset >= fh.size: 2841 warnings.warn('invalid page offset (%i)' % offset) 2842 self.complete = True 2843 return 2844 2845 # always read and cache first page 2846 fh.seek(offset) 2847 page = TiffPage(parent, index=0) 2848 self.pages.append(page) 2849 self._keyframe = page 2850 2851 @property 2852 def cache(self): 2853 """Return if pages/frames are currenly being cached.""" 2854 return self._cache 2855 2856 @cache.setter 2857 def cache(self, value): 2858 """Enable or disable caching of pages/frames. Clear cache if False.""" 2859 value = bool(value) 2860 if self._cache and not value: 2861 self.clear() 2862 self._cache = value 2863 2864 @property 2865 def useframes(self): 2866 """Return if currently using TiffFrame (True) or TiffPage (False).""" 2867 return self._tiffpage == TiffFrame and TiffFrame is not TiffPage 2868 2869 @useframes.setter 2870 def useframes(self, value): 2871 """Set to use TiffFrame (True) or TiffPage (False).""" 2872 self._tiffpage = TiffFrame if value else TiffPage 2873 2874 @property 2875 def keyframe(self): 2876 """Return index of current keyframe.""" 2877 return self._keyframe.index 2878 2879 @keyframe.setter 2880 def keyframe(self, index): 2881 """Set current keyframe. Load TiffPage from file if necessary.""" 2882 if self._keyframe.index == index: 2883 return 2884 if self.complete or 0 <= index < len(self.pages): 2885 page = self.pages[index] 2886 if isinstance(page, TiffPage): 2887 self._keyframe = page 2888 return 2889 elif isinstance(page, TiffFrame): 2890 # remove existing frame 2891 self.pages[index] = page.offset 2892 # load TiffPage from file 2893 useframes = self.useframes 2894 self._tiffpage = TiffPage 2895 self._keyframe = self[index] 2896 self.useframes = useframes 2897 2898 @property 2899 def next_page_offset(self): 2900 """Return offset where offset to a new page can be stored.""" 2901 if not self.complete: 2902 self._seek(-1) 2903 return self._nextpageoffset 2904 2905 def load(self): 2906 """Read all remaining pages from file.""" 2907 fh = self.parent.filehandle 2908 keyframe = self._keyframe 2909 pages = self.pages 2910 if not self.complete: 2911 self._seek(-1) 2912 for i, page in enumerate(pages): 2913 if isinstance(page, inttypes): 2914 fh.seek(page) 2915 page = self._tiffpage(self.parent, index=i, keyframe=keyframe) 2916 pages[i] = page 2917 2918 def clear(self, fully=True): 2919 """Delete all but first page from cache. Set keyframe to first page.""" 2920 pages = self.pages 2921 if not self._cache or len(pages) < 1: 2922 return 2923 self._keyframe = pages[0] 2924 if fully: 2925 # delete all but first TiffPage/TiffFrame 2926 for i, page in enumerate(pages[1:]): 2927 if not isinstance(page, inttypes): 2928 pages[i+1] = page.offset 2929 elif TiffFrame is not TiffPage: 2930 # delete only TiffFrames 2931 for i, page in enumerate(pages): 2932 if isinstance(page, TiffFrame): 2933 pages[i] = page.offset 2934 2935 def _seek(self, index, maxpages=2**22): 2936 """Seek file to offset of specified page.""" 2937 pages = self.pages 2938 if not pages: 2939 return 2940 2941 fh = self.parent.filehandle 2942 if fh.closed: 2943 raise RuntimeError('FileHandle is closed') 2944 2945 if self.complete or 0 <= index < len(pages): 2946 page = pages[index] 2947 offset = page if isinstance(page, inttypes) else page.offset 2948 fh.seek(offset) 2949 return 2950 2951 offsetformat = self.parent.offsetformat 2952 offsetsize = self.parent.offsetsize 2953 tagnoformat = self.parent.tagnoformat 2954 tagnosize = self.parent.tagnosize 2955 tagsize = self.parent.tagsize 2956 unpack = struct.unpack 2957 2958 page = pages[-1] 2959 offset = page if isinstance(page, inttypes) else page.offset 2960 2961 while len(pages) < maxpages: 2962 # read offsets to pages from file until index is reached 2963 fh.seek(offset) 2964 # skip tags 2965 try: 2966 tagno = unpack(tagnoformat, fh.read(tagnosize))[0] 2967 if tagno > 4096: 2968 raise ValueError('suspicious number of tags') 2969 except Exception: 2970 warnings.warn('corrupted tag list at offset %i' % offset) 2971 del pages[-1] 2972 self.complete = True 2973 break 2974 self._nextpageoffset = offset + tagnosize + tagno * tagsize 2975 fh.seek(self._nextpageoffset) 2976 2977 # read offset to next page 2978 offset = unpack(offsetformat, fh.read(offsetsize))[0] 2979 if offset == 0: 2980 self.complete = True 2981 break 2982 if offset >= fh.size: 2983 warnings.warn('invalid page offset (%i)' % offset) 2984 self.complete = True 2985 break 2986 2987 pages.append(offset) 2988 if 0 <= index < len(pages): 2989 break 2990 2991 if index >= len(pages): 2992 raise IndexError('list index out of range') 2993 2994 page = pages[index] 2995 fh.seek(page if isinstance(page, inttypes) else page.offset) 2996 2997 def __bool__(self): 2998 """Return True if file contains any pages.""" 2999 return len(self.pages) > 0 3000 3001 def __len__(self): 3002 """Return number of pages in file.""" 3003 if not self.complete: 3004 self._seek(-1) 3005 return len(self.pages) 3006 3007 def __getitem__(self, key): 3008 """Return specified page(s) from cache or file.""" 3009 pages = self.pages 3010 if not pages: 3011 raise IndexError('list index out of range') 3012 if key == 0: 3013 return pages[key] 3014 3015 if isinstance(key, slice): 3016 start, stop, _ = key.indices(2**31-1) 3017 if not self.complete and max(stop, start) > len(pages): 3018 self._seek(-1) 3019 return [self[i] for i in range(*key.indices(len(pages)))] 3020 3021 if self.complete and key >= len(pages): 3022 raise IndexError('list index out of range') 3023 3024 try: 3025 page = pages[key] 3026 except IndexError: 3027 page = 0 3028 if not isinstance(page, inttypes): 3029 return page 3030 3031 self._seek(key) 3032 page = self._tiffpage(self.parent, index=key, keyframe=self._keyframe) 3033 if self._cache: 3034 pages[key] = page 3035 return page 3036 3037 def __iter__(self): 3038 """Return iterator over all pages.""" 3039 i = 0 3040 while True: 3041 try: 3042 yield self[i] 3043 i += 1 3044 except IndexError: 3045 break 3046 3047 3048class TiffPage(object): 3049 """TIFF image file directory (IFD). 3050 3051 Attributes 3052 ---------- 3053 index : int 3054 Index of page in file. 3055 dtype : numpy.dtype or None 3056 Data type (native byte order) of the image in IFD. 3057 shape : tuple 3058 Dimensions of the image in IFD. 3059 axes : str 3060 Axes label codes: 3061 'X' width, 'Y' height, 'S' sample, 'I' image series|page|plane, 3062 'Z' depth, 'C' color|em-wavelength|channel, 'E' ex-wavelength|lambda, 3063 'T' time, 'R' region|tile, 'A' angle, 'P' phase, 'H' lifetime, 3064 'L' exposure, 'V' event, 'Q' unknown, '_' missing 3065 tags : dict 3066 Dictionary of tags in IFD. {tag.name: TiffTag} 3067 colormap : numpy.ndarray 3068 Color look up table, if exists. 3069 3070 All attributes are read-only. 3071 3072 Notes 3073 ----- 3074 The internal, normalized '_shape' attribute is 6 dimensional: 3075 3076 0 : number planes/images (stk, ij). 3077 1 : planar samplesperpixel. 3078 2 : imagedepth Z (sgi). 3079 3 : imagelength Y. 3080 4 : imagewidth X. 3081 5 : contig samplesperpixel. 3082 3083 """ 3084 # default properties; will be updated from tags 3085 imagewidth = 0 3086 imagelength = 0 3087 imagedepth = 1 3088 tilewidth = 0 3089 tilelength = 0 3090 tiledepth = 1 3091 bitspersample = 1 3092 samplesperpixel = 1 3093 sampleformat = 1 3094 rowsperstrip = 2**32-1 3095 compression = 1 3096 planarconfig = 1 3097 fillorder = 1 3098 photometric = 0 3099 predictor = 1 3100 extrasamples = 1 3101 colormap = None 3102 software = '' 3103 description = '' 3104 description1 = '' 3105 3106 def __init__(self, parent, index, keyframe=None): 3107 """Initialize instance from file. 3108 3109 The file handle position must be at offset to a valid IFD. 3110 3111 """ 3112 self.parent = parent 3113 self.index = index 3114 self.shape = () 3115 self._shape = () 3116 self.dtype = None 3117 self._dtype = None 3118 self.axes = '' 3119 self.tags = {} 3120 3121 self.dataoffsets = () 3122 self.databytecounts = () 3123 3124 # read TIFF IFD structure and its tags from file 3125 fh = parent.filehandle 3126 self.offset = fh.tell() # offset to this IFD 3127 try: 3128 tagno = struct.unpack(parent.tagnoformat, 3129 fh.read(parent.tagnosize))[0] 3130 if tagno > 4096: 3131 raise ValueError('suspicious number of tags') 3132 except Exception: 3133 raise ValueError('corrupted tag list at offset %i' % self.offset) 3134 3135 tagsize = parent.tagsize 3136 data = fh.read(tagsize * tagno) 3137 tags = self.tags 3138 index = -tagsize 3139 for _ in range(tagno): 3140 index += tagsize 3141 try: 3142 tag = TiffTag(self.parent, data[index:index+tagsize]) 3143 except TiffTag.Error as e: 3144 warnings.warn(str(e)) 3145 continue 3146 tagname = tag.name 3147 if tagname not in tags: 3148 name = tagname 3149 tags[name] = tag 3150 else: 3151 # some files contain multiple tags with same code 3152 # e.g. MicroManager files contain two ImageDescription tags 3153 i = 1 3154 while True: 3155 name = '%s%i' % (tagname, i) 3156 if name not in tags: 3157 tags[name] = tag 3158 break 3159 name = TIFF.TAG_ATTRIBUTES.get(name, '') 3160 if name: 3161 if (name[:3] in 'sof des' and not isinstance(tag.value, str)): 3162 pass # wrong string type for software, description 3163 else: 3164 setattr(self, name, tag.value) 3165 3166 if not tags: 3167 return # found in FIBICS 3168 3169 # consolidate private tags; remove them from self.tags 3170 if self.is_andor: 3171 self.andor_tags 3172 elif self.is_epics: 3173 self.epics_tags 3174 3175 if self.is_lsm or (self.index and self.parent.is_lsm): 3176 # correct non standard LSM bitspersample tags 3177 self.tags['BitsPerSample']._fix_lsm_bitspersample(self) 3178 3179 if self.is_vista or (self.index and self.parent.is_vista): 3180 # ISS Vista writes wrong ImageDepth tag 3181 self.imagedepth = 1 3182 3183 if self.is_stk and 'UIC1tag' in tags and not tags['UIC1tag'].value: 3184 # read UIC1tag now that plane count is known 3185 uic1tag = tags['UIC1tag'] 3186 fh.seek(uic1tag.valueoffset) 3187 tags['UIC1tag'].value = read_uic1tag( 3188 fh, self.parent.byteorder, uic1tag.dtype, 3189 uic1tag.count, None, tags['UIC2tag'].count) 3190 3191 if 'IJMetadata' in tags: 3192 # decode IJMetadata tag 3193 try: 3194 tags['IJMetadata'].value = imagej_metadata( 3195 tags['IJMetadata'].value, 3196 tags['IJMetadataByteCounts'].value, 3197 self.parent.byteorder) 3198 except Exception as e: 3199 warnings.warn(str(e)) 3200 3201 if 'BitsPerSample' in tags: 3202 tag = tags['BitsPerSample'] 3203 if tag.count == 1: 3204 self.bitspersample = tag.value 3205 else: 3206 # LSM might list more items than samplesperpixel 3207 value = tag.value[:self.samplesperpixel] 3208 if any((v-value[0] for v in value)): 3209 self.bitspersample = value 3210 else: 3211 self.bitspersample = value[0] 3212 3213 if 'SampleFormat' in tags: 3214 tag = tags['SampleFormat'] 3215 if tag.count == 1: 3216 self.sampleformat = tag.value 3217 else: 3218 value = tag.value[:self.samplesperpixel] 3219 if any((v-value[0] for v in value)): 3220 self.sampleformat = value 3221 else: 3222 self.sampleformat = value[0] 3223 3224 if 'ImageLength' in tags: 3225 if 'RowsPerStrip' not in tags or tags['RowsPerStrip'].count > 1: 3226 self.rowsperstrip = self.imagelength 3227 # self.stripsperimage = int(math.floor( 3228 # float(self.imagelength + self.rowsperstrip - 1) / 3229 # self.rowsperstrip)) 3230 3231 # determine dtype 3232 dtype = self.sampleformat, self.bitspersample 3233 dtype = TIFF.SAMPLE_DTYPES.get(dtype, None) 3234 if dtype is not None: 3235 dtype = numpy.dtype(dtype) 3236 self.dtype = self._dtype = dtype 3237 3238 # determine shape of data 3239 imagelength = self.imagelength 3240 imagewidth = self.imagewidth 3241 imagedepth = self.imagedepth 3242 samplesperpixel = self.samplesperpixel 3243 3244 if self.is_stk: 3245 assert self.imagedepth == 1 3246 uictag = tags['UIC2tag'].value 3247 planes = tags['UIC2tag'].count 3248 if self.planarconfig == 1: 3249 self._shape = ( 3250 planes, 1, 1, imagelength, imagewidth, samplesperpixel) 3251 if samplesperpixel == 1: 3252 self.shape = (planes, imagelength, imagewidth) 3253 self.axes = 'YX' 3254 else: 3255 self.shape = ( 3256 planes, imagelength, imagewidth, samplesperpixel) 3257 self.axes = 'YXS' 3258 else: 3259 self._shape = ( 3260 planes, samplesperpixel, 1, imagelength, imagewidth, 1) 3261 if samplesperpixel == 1: 3262 self.shape = (planes, imagelength, imagewidth) 3263 self.axes = 'YX' 3264 else: 3265 self.shape = ( 3266 planes, samplesperpixel, imagelength, imagewidth) 3267 self.axes = 'SYX' 3268 # detect type of series 3269 if planes == 1: 3270 self.shape = self.shape[1:] 3271 elif numpy.all(uictag['ZDistance'] != 0): 3272 self.axes = 'Z' + self.axes 3273 elif numpy.all(numpy.diff(uictag['TimeCreated']) != 0): 3274 self.axes = 'T' + self.axes 3275 else: 3276 self.axes = 'I' + self.axes 3277 elif self.photometric == 2 or samplesperpixel > 1: # PHOTOMETRIC.RGB 3278 if self.planarconfig == 1: 3279 self._shape = ( 3280 1, 1, imagedepth, imagelength, imagewidth, samplesperpixel) 3281 if imagedepth == 1: 3282 self.shape = (imagelength, imagewidth, samplesperpixel) 3283 self.axes = 'YXS' 3284 else: 3285 self.shape = ( 3286 imagedepth, imagelength, imagewidth, samplesperpixel) 3287 self.axes = 'ZYXS' 3288 else: 3289 self._shape = (1, samplesperpixel, imagedepth, 3290 imagelength, imagewidth, 1) 3291 if imagedepth == 1: 3292 self.shape = (samplesperpixel, imagelength, imagewidth) 3293 self.axes = 'SYX' 3294 else: 3295 self.shape = ( 3296 samplesperpixel, imagedepth, imagelength, imagewidth) 3297 self.axes = 'SZYX' 3298 else: 3299 self._shape = (1, 1, imagedepth, imagelength, imagewidth, 1) 3300 if imagedepth == 1: 3301 self.shape = (imagelength, imagewidth) 3302 self.axes = 'YX' 3303 else: 3304 self.shape = (imagedepth, imagelength, imagewidth) 3305 self.axes = 'ZYX' 3306 3307 # dataoffsets and databytecounts 3308 if 'TileOffsets' in tags: 3309 self.dataoffsets = tags['TileOffsets'].value 3310 elif 'StripOffsets' in tags: 3311 self.dataoffsets = tags['StripOffsets'].value 3312 else: 3313 self.dataoffsets = (0,) 3314 3315 if 'TileByteCounts' in tags: 3316 self.databytecounts = tags['TileByteCounts'].value 3317 elif 'StripByteCounts' in tags: 3318 self.databytecounts = tags['StripByteCounts'].value 3319 else: 3320 self.databytecounts = ( 3321 product(self.shape) * (self.bitspersample // 8),) 3322 if self.compression != 1: 3323 warnings.warn('required ByteCounts tag is missing') 3324 3325 assert len(self.shape) == len(self.axes) 3326 3327 def asarray(self, out=None, squeeze=True, lock=None, reopen=True, 3328 maxsize=2**44, validate=True): 3329 """Read image data from file and return as numpy array. 3330 3331 Raise ValueError if format is unsupported. 3332 3333 Parameters 3334 ---------- 3335 out : numpy.ndarray, str, or file-like object; optional 3336 Buffer where image data will be saved. 3337 If None (default), a new array will be created. 3338 If numpy.ndarray, a writable array of compatible dtype and shape. 3339 If 'memmap', directly memory-map the image data in the TIFF file 3340 if possible; else create a memory-mapped array in a temporary file. 3341 If str or open file, the file name or file object used to 3342 create a memory-map to an array stored in a binary file on disk. 3343 squeeze : bool 3344 If True, all length-1 dimensions (except X and Y) are 3345 squeezed out from the array. 3346 If False, the shape of the returned array might be different from 3347 the page.shape. 3348 lock : {RLock, NullContext} 3349 A reentrant lock used to syncronize reads from file. 3350 If None (default), the lock of the parent's filehandle is used. 3351 reopen : bool 3352 If True (default) and the parent file handle is closed, the file 3353 is temporarily re-opened and closed if no exception occurs. 3354 maxsize: int or None 3355 Maximum size of data before a ValueError is raised. 3356 Can be used to catch DOS. Default: 16 TB. 3357 validate : bool 3358 If True (default), validate various parameters. 3359 If None, only validate parameters and return None. 3360 3361 """ 3362 self_ = self 3363 self = self.keyframe # self or keyframe 3364 3365 if not self._shape or product(self._shape) == 0: 3366 return 3367 3368 tags = self.tags 3369 3370 if validate or validate is None: 3371 if maxsize and product(self._shape) > maxsize: 3372 raise ValueError('data are too large %s' % str(self._shape)) 3373 if self.dtype is None: 3374 raise ValueError('data type not supported: %s%i' % ( 3375 self.sampleformat, self.bitspersample)) 3376 if self.compression not in TIFF.DECOMPESSORS: 3377 raise ValueError( 3378 'cannot decompress %s' % self.compression.name) 3379 if 'SampleFormat' in tags: 3380 tag = tags['SampleFormat'] 3381 if tag.count != 1 and any((i-tag.value[0] for i in tag.value)): 3382 raise ValueError( 3383 'sample formats do not match %s' % tag.value) 3384 if self.is_chroma_subsampled and (self.compression != 7 or 3385 self.planarconfig == 2): 3386 raise NotImplementedError('chroma subsampling not supported') 3387 if validate is None: 3388 return 3389 3390 fh = self_.parent.filehandle 3391 lock = fh.lock if lock is None else lock 3392 with lock: 3393 closed = fh.closed 3394 if closed: 3395 if reopen: 3396 fh.open() 3397 else: 3398 raise IOError('file handle is closed') 3399 3400 dtype = self._dtype 3401 shape = self._shape 3402 imagewidth = self.imagewidth 3403 imagelength = self.imagelength 3404 imagedepth = self.imagedepth 3405 bitspersample = self.bitspersample 3406 typecode = self.parent.byteorder + dtype.char 3407 lsb2msb = self.fillorder == 2 3408 offsets, bytecounts = self_.offsets_bytecounts 3409 istiled = self.is_tiled 3410 3411 if istiled: 3412 tilewidth = self.tilewidth 3413 tilelength = self.tilelength 3414 tiledepth = self.tiledepth 3415 tw = (imagewidth + tilewidth - 1) // tilewidth 3416 tl = (imagelength + tilelength - 1) // tilelength 3417 td = (imagedepth + tiledepth - 1) // tiledepth 3418 shape = (shape[0], shape[1], 3419 td*tiledepth, tl*tilelength, tw*tilewidth, shape[-1]) 3420 tileshape = (tiledepth, tilelength, tilewidth, shape[-1]) 3421 runlen = tilewidth 3422 else: 3423 runlen = imagewidth 3424 3425 if self.planarconfig == 1: 3426 runlen *= self.samplesperpixel 3427 3428 if out == 'memmap' and self.is_memmappable: 3429 with lock: 3430 result = fh.memmap_array(typecode, shape, offset=offsets[0]) 3431 elif self.is_contiguous: 3432 if out is not None: 3433 out = create_output(out, shape, dtype) 3434 with lock: 3435 fh.seek(offsets[0]) 3436 result = fh.read_array(typecode, product(shape), out=out) 3437 if out is None and not result.dtype.isnative: 3438 # swap byte order and dtype without copy 3439 result.byteswap(True) 3440 result = result.newbyteorder() 3441 if lsb2msb: 3442 reverse_bitorder(result) 3443 else: 3444 result = create_output(out, shape, dtype) 3445 3446 decompress = TIFF.DECOMPESSORS[self.compression] 3447 3448 if self.compression == 7: # COMPRESSION.JPEG 3449 if bitspersample not in (8, 12): 3450 raise ValueError( 3451 'unsupported JPEG precision %i' % bitspersample) 3452 if 'JPEGTables' in tags: 3453 table = tags['JPEGTables'].value 3454 else: 3455 table = b'' 3456 unpack = identityfunc 3457 colorspace = TIFF.PHOTOMETRIC(self.photometric).name 3458 3459 def decompress(x, func=decompress, table=table, 3460 bitspersample=bitspersample, 3461 colorspace=colorspace): 3462 return func(x, table, bitspersample, 3463 colorspace).reshape(-1) 3464 3465 elif bitspersample in (8, 16, 32, 64, 128): 3466 if (bitspersample * runlen) % 8: 3467 raise ValueError('data and sample size mismatch') 3468 3469 def unpack(x, typecode=typecode): 3470 if self.predictor == 3: # PREDICTOR.FLOATINGPOINT 3471 # the floating point horizontal differencing decoder 3472 # needs the raw byte order 3473 typecode = dtype.char 3474 try: 3475 # read only numpy array 3476 return numpy.frombuffer(x, typecode) 3477 except ValueError: 3478 # strips may be missing EOI 3479 # warnings.warn('unpack: %s' % e) 3480 xlen = ((len(x) // (bitspersample // 8)) * 3481 (bitspersample // 8)) 3482 return numpy.frombuffer(x[:xlen], typecode) 3483 3484 elif isinstance(bitspersample, tuple): 3485 def unpack(x, typecode=typecode, bitspersample=bitspersample): 3486 return unpack_rgb(x, typecode, bitspersample) 3487 else: 3488 def unpack(x, typecode=typecode, bitspersample=bitspersample, 3489 runlen=runlen): 3490 return unpack_ints(x, typecode, bitspersample, runlen) 3491 3492 if istiled: 3493 writable = None 3494 tw, tl, td, pl = 0, 0, 0, 0 3495 for tile in buffered_read(fh, lock, offsets, bytecounts): 3496 if lsb2msb: 3497 tile = reverse_bitorder(tile) 3498 tile = decompress(tile) 3499 tile = unpack(tile) 3500 try: 3501 tile.shape = tileshape 3502 except ValueError: 3503 # incomplete tiles; see gdal issue #1179 3504 warnings.warn('invalid tile data') 3505 t = numpy.zeros(tileshape, dtype).reshape(-1) 3506 s = min(tile.size, t.size) 3507 t[:s] = tile[:s] 3508 tile = t.reshape(tileshape) 3509 if self.predictor == 2: # PREDICTOR.HORIZONTAL 3510 if writable is None: 3511 writable = tile.flags['WRITEABLE'] 3512 if writable: 3513 numpy.cumsum(tile, axis=-2, dtype=dtype, out=tile) 3514 else: 3515 tile = numpy.cumsum(tile, axis=-2, dtype=dtype) 3516 elif self.predictor == 3: # PREDICTOR.FLOATINGPOINT 3517 raise NotImplementedError() 3518 result[0, pl, td:td+tiledepth, 3519 tl:tl+tilelength, tw:tw+tilewidth, :] = tile 3520 del tile 3521 tw += tilewidth 3522 if tw >= shape[4]: 3523 tw, tl = 0, tl + tilelength 3524 if tl >= shape[3]: 3525 tl, td = 0, td + tiledepth 3526 if td >= shape[2]: 3527 td, pl = 0, pl + 1 3528 result = result[..., :imagedepth, :imagelength, :imagewidth, :] 3529 else: 3530 strip_size = self.rowsperstrip * self.imagewidth 3531 if self.planarconfig == 1: 3532 strip_size *= self.samplesperpixel 3533 result = result.reshape(-1) 3534 index = 0 3535 for strip in buffered_read(fh, lock, offsets, bytecounts): 3536 if lsb2msb: 3537 strip = reverse_bitorder(strip) 3538 strip = decompress(strip) 3539 strip = unpack(strip) 3540 size = min(result.size, strip.size, strip_size, 3541 result.size - index) 3542 result[index:index+size] = strip[:size] 3543 del strip 3544 index += size 3545 3546 result.shape = self._shape 3547 3548 if self.predictor != 1 and not (istiled and not self.is_contiguous): 3549 if self.parent.is_lsm and self.compression == 1: 3550 pass # work around bug in LSM510 software 3551 elif self.predictor == 2: # PREDICTOR.HORIZONTAL 3552 numpy.cumsum(result, axis=-2, dtype=dtype, out=result) 3553 elif self.predictor == 3: # PREDICTOR.FLOATINGPOINT 3554 result = decode_floats(result) 3555 3556 if squeeze: 3557 try: 3558 result.shape = self.shape 3559 except ValueError: 3560 warnings.warn('failed to reshape from %s to %s' % ( 3561 str(result.shape), str(self.shape))) 3562 3563 if closed: 3564 # TODO: file should remain open if an exception occurred above 3565 fh.close() 3566 return result 3567 3568 def asrgb(self, uint8=False, alpha=None, colormap=None, 3569 dmin=None, dmax=None, *args, **kwargs): 3570 """Return image data as RGB(A). 3571 3572 Work in progress. 3573 3574 """ 3575 data = self.asarray(*args, **kwargs) 3576 self = self.keyframe # self or keyframe 3577 photometric = self.photometric 3578 PHOTOMETRIC = TIFF.PHOTOMETRIC 3579 3580 if photometric == PHOTOMETRIC.PALETTE: 3581 colormap = self.colormap 3582 if (colormap.shape[1] < 2**self.bitspersample or 3583 self.dtype.char not in 'BH'): 3584 raise ValueError('cannot apply colormap') 3585 if uint8: 3586 if colormap.max() > 255: 3587 colormap >>= 8 3588 colormap = colormap.astype('uint8') 3589 if 'S' in self.axes: 3590 data = data[..., 0] if self.planarconfig == 1 else data[0] 3591 data = apply_colormap(data, colormap) 3592 3593 elif photometric == PHOTOMETRIC.RGB: 3594 if 'ExtraSamples' in self.tags: 3595 if alpha is None: 3596 alpha = TIFF.EXTRASAMPLE 3597 extrasamples = self.extrasamples 3598 if self.tags['ExtraSamples'].count == 1: 3599 extrasamples = (extrasamples,) 3600 for i, exs in enumerate(extrasamples): 3601 if exs in alpha: 3602 if self.planarconfig == 1: 3603 data = data[..., [0, 1, 2, 3+i]] 3604 else: 3605 data = data[:, [0, 1, 2, 3+i]] 3606 break 3607 else: 3608 if self.planarconfig == 1: 3609 data = data[..., :3] 3610 else: 3611 data = data[:, :3] 3612 # TODO: convert to uint8? 3613 3614 elif photometric == PHOTOMETRIC.MINISBLACK: 3615 raise NotImplementedError() 3616 elif photometric == PHOTOMETRIC.MINISWHITE: 3617 raise NotImplementedError() 3618 elif photometric == PHOTOMETRIC.SEPARATED: 3619 raise NotImplementedError() 3620 else: 3621 raise NotImplementedError() 3622 return data 3623 3624 def aspage(self): 3625 return self 3626 3627 @property 3628 def keyframe(self): 3629 return self 3630 3631 @keyframe.setter 3632 def keyframe(self, index): 3633 return 3634 3635 @lazyattr 3636 def offsets_bytecounts(self): 3637 """Return simplified offsets and bytecounts.""" 3638 if self.is_contiguous: 3639 offset, byte_count = self.is_contiguous 3640 return [offset], [byte_count] 3641 return clean_offsets_counts(self.dataoffsets, self.databytecounts) 3642 3643 @lazyattr 3644 def is_contiguous(self): 3645 """Return offset and size of contiguous data, else None. 3646 3647 Excludes prediction and fill_order. 3648 3649 """ 3650 if (self.compression != 1 3651 or self.bitspersample not in (8, 16, 32, 64)): 3652 return 3653 if 'TileWidth' in self.tags: 3654 if (self.imagewidth != self.tilewidth or 3655 self.imagelength % self.tilelength or 3656 self.tilewidth % 16 or self.tilelength % 16): 3657 return 3658 if ('ImageDepth' in self.tags and 'TileDepth' in self.tags and 3659 (self.imagelength != self.tilelength or 3660 self.imagedepth % self.tiledepth)): 3661 return 3662 3663 offsets = self.dataoffsets 3664 bytecounts = self.databytecounts 3665 if len(offsets) == 1: 3666 return offsets[0], bytecounts[0] 3667 if self.is_stk or all((offsets[i] + bytecounts[i] == offsets[i+1] or 3668 bytecounts[i+1] == 0) # no data/ignore offset 3669 for i in range(len(offsets)-1)): 3670 return offsets[0], sum(bytecounts) 3671 3672 @lazyattr 3673 def is_final(self): 3674 """Return if page's image data are stored in final form. 3675 3676 Excludes byte-swapping. 3677 3678 """ 3679 return (self.is_contiguous and self.fillorder == 1 and 3680 self.predictor == 1 and not self.is_chroma_subsampled) 3681 3682 @lazyattr 3683 def is_memmappable(self): 3684 """Return if page's image data in file can be memory-mapped.""" 3685 return (self.parent.filehandle.is_file and self.is_final and 3686 # (self.bitspersample == 8 or self.parent.isnative) and 3687 self.is_contiguous[0] % self.dtype.itemsize == 0) # aligned? 3688 3689 def __str__(self, detail=0, width=79): 3690 """Return string containing information about page.""" 3691 if self.keyframe != self: 3692 return TiffFrame.__str__(self, detail) 3693 attr = '' 3694 for name in ('memmappable', 'final', 'contiguous'): 3695 attr = getattr(self, 'is_'+name) 3696 if attr: 3697 attr = name.upper() 3698 break 3699 info = ' '.join(s for s in ( 3700 'x'.join(str(i) for i in self.shape), 3701 '%s%s' % (TIFF.SAMPLEFORMAT(self.sampleformat).name, 3702 self.bitspersample), 3703 '|'.join(i for i in ( 3704 TIFF.PHOTOMETRIC(self.photometric).name, 3705 'TILED' if self.is_tiled else '', 3706 self.compression.name if self.compression != 1 else '', 3707 self.planarconfig.name if self.planarconfig != 1 else '', 3708 self.predictor.name if self.predictor != 1 else '', 3709 self.fillorder.name if self.fillorder != 1 else '') 3710 if i), 3711 attr, 3712 '|'.join((f.upper() for f in self.flags)) 3713 ) if s) 3714 info = 'TiffPage %i @%i %s' % (self.index, self.offset, info) 3715 if detail <= 0: 3716 return info 3717 info = [info] 3718 tags = self.tags 3719 tlines = [] 3720 vlines = [] 3721 for tag in sorted(tags.values(), key=lambda x: x.code): 3722 value = tag.__str__(width=width+1) 3723 tlines.append(value[:width].strip()) 3724 if detail > 1 and len(value) > width: 3725 name = tag.name.upper() 3726 if detail <= 2 and ('COUNTS' in name or 'OFFSETS' in name): 3727 value = pformat(tag.value, width=width, height=detail*4) 3728 else: 3729 value = pformat(tag.value, width=width, height=detail*12) 3730 vlines.append('%s\n%s' % (tag.name, value)) 3731 info.append('\n'.join(tlines)) 3732 if detail > 1: 3733 info.append('\n\n'.join(vlines)) 3734 if detail > 3: 3735 try: 3736 info.append('DATA\n%s' % pformat( 3737 self.asarray(), width=width, height=detail*8)) 3738 except Exception: 3739 pass 3740 return '\n\n'.join(info) 3741 3742 @lazyattr 3743 def flags(self): 3744 """Return set of flags.""" 3745 return set((name.lower() for name in sorted(TIFF.FILE_FLAGS) 3746 if getattr(self, 'is_' + name))) 3747 3748 @property 3749 def ndim(self): 3750 """Return number of array dimensions.""" 3751 return len(self.shape) 3752 3753 @property 3754 def size(self): 3755 """Return number of elements in array.""" 3756 return product(self.shape) 3757 3758 @lazyattr 3759 def andor_tags(self): 3760 """Return consolidated metadata from Andor tags as dict. 3761 3762 Remove Andor tags from self.tags. 3763 3764 """ 3765 if not self.is_andor: 3766 return 3767 tags = self.tags 3768 result = {'Id': tags['AndorId'].value} 3769 for tag in list(self.tags.values()): 3770 code = tag.code 3771 if not 4864 < code < 5031: 3772 continue 3773 value = tag.value 3774 name = tag.name[5:] if len(tag.name) > 5 else tag.name 3775 result[name] = value 3776 del tags[tag.name] 3777 return result 3778 3779 @lazyattr 3780 def epics_tags(self): 3781 """Return consolidated metadata from EPICS areaDetector tags as dict. 3782 3783 Remove areaDetector tags from self.tags. 3784 3785 """ 3786 if not self.is_epics: 3787 return 3788 result = {} 3789 tags = self.tags 3790 for tag in list(self.tags.values()): 3791 code = tag.code 3792 if not 65000 <= code < 65500: 3793 continue 3794 value = tag.value 3795 if code == 65000: 3796 result['timeStamp'] = datetime.datetime.fromtimestamp( 3797 float(value)) 3798 elif code == 65001: 3799 result['uniqueID'] = int(value) 3800 elif code == 65002: 3801 result['epicsTSSec'] = int(value) 3802 elif code == 65003: 3803 result['epicsTSNsec'] = int(value) 3804 else: 3805 key, value = value.split(':', 1) 3806 result[key] = astype(value) 3807 del tags[tag.name] 3808 return result 3809 3810 @lazyattr 3811 def geotiff_tags(self): 3812 """Return consolidated metadata from GeoTIFF tags as dict.""" 3813 if not self.is_geotiff: 3814 return 3815 tags = self.tags 3816 3817 gkd = tags['GeoKeyDirectoryTag'].value 3818 if gkd[0] != 1: 3819 warnings.warn('invalid GeoKeyDirectoryTag') 3820 return {} 3821 3822 result = { 3823 'KeyDirectoryVersion': gkd[0], 3824 'KeyRevision': gkd[1], 3825 'KeyRevisionMinor': gkd[2], 3826 # 'NumberOfKeys': gkd[3], 3827 } 3828 # deltags = ['GeoKeyDirectoryTag'] 3829 geokeys = TIFF.GEO_KEYS 3830 geocodes = TIFF.GEO_CODES 3831 for index in range(gkd[3]): 3832 keyid, tagid, count, offset = gkd[4 + index * 4: index * 4 + 8] 3833 keyid = geokeys.get(keyid, keyid) 3834 if tagid == 0: 3835 value = offset 3836 else: 3837 tagname = TIFF.TAGS[tagid] 3838 # deltags.append(tagname) 3839 value = tags[tagname].value[offset: offset + count] 3840 if tagid == 34737 and count > 1 and value[-1] == '|': 3841 value = value[:-1] 3842 value = value if count > 1 else value[0] 3843 if keyid in geocodes: 3844 try: 3845 value = geocodes[keyid](value) 3846 except Exception: 3847 pass 3848 result[keyid] = value 3849 3850 if 'IntergraphMatrixTag' in tags: 3851 value = tags['IntergraphMatrixTag'].value 3852 value = numpy.array(value) 3853 if len(value) == 16: 3854 value = value.reshape((4, 4)).tolist() 3855 result['IntergraphMatrix'] = value 3856 if 'ModelPixelScaleTag' in tags: 3857 value = numpy.array(tags['ModelPixelScaleTag'].value).tolist() 3858 result['ModelPixelScale'] = value 3859 if 'ModelTiepointTag' in tags: 3860 value = tags['ModelTiepointTag'].value 3861 value = numpy.array(value).reshape((-1, 6)).squeeze().tolist() 3862 result['ModelTiepoint'] = value 3863 if 'ModelTransformationTag' in tags: 3864 value = tags['ModelTransformationTag'].value 3865 value = numpy.array(value).reshape((4, 4)).tolist() 3866 result['ModelTransformation'] = value 3867 elif False: 3868 # if 'ModelPixelScaleTag' in tags and 'ModelTiepointTag' in tags: 3869 sx, sy, sz = tags['ModelPixelScaleTag'].value 3870 tiepoints = tags['ModelTiepointTag'].value 3871 transforms = [] 3872 for tp in range(0, len(tiepoints), 6): 3873 i, j, k, x, y, z = tiepoints[tp:tp+6] 3874 transforms.append([ 3875 [sx, 0.0, 0.0, x - i * sx], 3876 [0.0, -sy, 0.0, y + j * sy], 3877 [0.0, 0.0, sz, z - k * sz], 3878 [0.0, 0.0, 0.0, 1.0]]) 3879 if len(tiepoints) == 6: 3880 transforms = transforms[0] 3881 result['ModelTransformation'] = transforms 3882 3883 if 'RPCCoefficientTag' in tags: 3884 rpcc = tags['RPCCoefficientTag'].value 3885 result['RPCCoefficient'] = { 3886 'ERR_BIAS': rpcc[0], 3887 'ERR_RAND': rpcc[1], 3888 'LINE_OFF': rpcc[2], 3889 'SAMP_OFF': rpcc[3], 3890 'LAT_OFF': rpcc[4], 3891 'LONG_OFF': rpcc[5], 3892 'HEIGHT_OFF': rpcc[6], 3893 'LINE_SCALE': rpcc[7], 3894 'SAMP_SCALE': rpcc[8], 3895 'LAT_SCALE': rpcc[9], 3896 'LONG_SCALE': rpcc[10], 3897 'HEIGHT_SCALE': rpcc[11], 3898 'LINE_NUM_COEFF': rpcc[12:33], 3899 'LINE_DEN_COEFF ': rpcc[33:53], 3900 'SAMP_NUM_COEFF': rpcc[53:73], 3901 'SAMP_DEN_COEFF': rpcc[73:]} 3902 3903 return result 3904 3905 @property 3906 def is_tiled(self): 3907 """Page contains tiled image.""" 3908 return 'TileWidth' in self.tags 3909 3910 @property 3911 def is_reduced(self): 3912 """Page is reduced image of another image.""" 3913 return ('NewSubfileType' in self.tags and 3914 self.tags['NewSubfileType'].value & 1) 3915 3916 @property 3917 def is_chroma_subsampled(self): 3918 """Page contains chroma subsampled image.""" 3919 return ('YCbCrSubSampling' in self.tags and 3920 self.tags['YCbCrSubSampling'].value != (1, 1)) 3921 3922 @lazyattr 3923 def is_imagej(self): 3924 """Return ImageJ description if exists, else None.""" 3925 for description in (self.description, self.description1): 3926 if not description: 3927 return 3928 if description[:7] == 'ImageJ=': 3929 return description 3930 3931 @lazyattr 3932 def is_shaped(self): 3933 """Return description containing array shape if exists, else None.""" 3934 for description in (self.description, self.description1): 3935 if not description: 3936 return 3937 if description[:1] == '{' and '"shape":' in description: 3938 return description 3939 if description[:6] == 'shape=': 3940 return description 3941 3942 @property 3943 def is_mdgel(self): 3944 """Page contains MDFileTag tag.""" 3945 return 'MDFileTag' in self.tags 3946 3947 @property 3948 def is_mediacy(self): 3949 """Page contains Media Cybernetics Id tag.""" 3950 return ('MC_Id' in self.tags and 3951 self.tags['MC_Id'].value[:7] == b'MC TIFF') 3952 3953 @property 3954 def is_stk(self): 3955 """Page contains UIC2Tag tag.""" 3956 return 'UIC2tag' in self.tags 3957 3958 @property 3959 def is_lsm(self): 3960 """Page contains CZ_LSMINFO tag.""" 3961 return 'CZ_LSMINFO' in self.tags 3962 3963 @property 3964 def is_fluoview(self): 3965 """Page contains FluoView MM_STAMP tag.""" 3966 return 'MM_Stamp' in self.tags 3967 3968 @property 3969 def is_nih(self): 3970 """Page contains NIH image header.""" 3971 return 'NIHImageHeader' in self.tags 3972 3973 @property 3974 def is_sgi(self): 3975 """Page contains SGI image and tile depth tags.""" 3976 return 'ImageDepth' in self.tags and 'TileDepth' in self.tags 3977 3978 @property 3979 def is_vista(self): 3980 """Software tag is 'ISS Vista'.""" 3981 return self.software == 'ISS Vista' 3982 3983 @property 3984 def is_metaseries(self): 3985 """Page contains MDS MetaSeries metadata in ImageDescription tag.""" 3986 if self.index > 1 or self.software != 'MetaSeries': 3987 return False 3988 d = self.description 3989 return d.startswith('<MetaData>') and d.endswith('</MetaData>') 3990 3991 @property 3992 def is_ome(self): 3993 """Page contains OME-XML in ImageDescription tag.""" 3994 if self.index > 1 or not self.description: 3995 return False 3996 d = self.description 3997 return d[:14] == '<?xml version=' and d[-6:] == '</OME>' 3998 3999 @property 4000 def is_scn(self): 4001 """Page contains Leica SCN XML in ImageDescription tag.""" 4002 if self.index > 1 or not self.description: 4003 return False 4004 d = self.description 4005 return d[:14] == '<?xml version=' and d[-6:] == '</scn>' 4006 4007 @property 4008 def is_micromanager(self): 4009 """Page contains Micro-Manager metadata.""" 4010 return 'MicroManagerMetadata' in self.tags 4011 4012 @property 4013 def is_andor(self): 4014 """Page contains Andor Technology tags.""" 4015 return 'AndorId' in self.tags 4016 4017 @property 4018 def is_pilatus(self): 4019 """Page contains Pilatus tags.""" 4020 return (self.software[:8] == 'TVX TIFF' and 4021 self.description[:2] == '# ') 4022 4023 @property 4024 def is_epics(self): 4025 """Page contains EPICS areaDetector tags.""" 4026 return (self.description == 'EPICS areaDetector' or 4027 self.software == 'EPICS areaDetector') 4028 4029 @property 4030 def is_tvips(self): 4031 """Page contains TVIPS metadata.""" 4032 return 'TVIPS' in self.tags 4033 4034 @property 4035 def is_fei(self): 4036 """Page contains SFEG or HELIOS metadata.""" 4037 return 'FEI_SFEG' in self.tags or 'FEI_HELIOS' in self.tags 4038 4039 @property 4040 def is_sem(self): 4041 """Page contains Zeiss SEM metadata.""" 4042 return 'CZ_SEM' in self.tags 4043 4044 @property 4045 def is_svs(self): 4046 """Page contains Aperio metadata.""" 4047 return self.description[:20] == 'Aperio Image Library' 4048 4049 @property 4050 def is_scanimage(self): 4051 """Page contains ScanImage metadata.""" 4052 return (self.description[:12] == 'state.config' or 4053 self.software[:22] == 'SI.LINE_FORMAT_VERSION' or 4054 'scanimage.SI.' in self.description[-256:]) 4055 4056 @property 4057 def is_qptiff(self): 4058 """Page contains PerkinElmer tissue images metadata.""" 4059 # The ImageDescription tag contains XML with a top-level 4060 # <PerkinElmer-QPI-ImageDescription> element 4061 return self.software[:15] == 'PerkinElmer-QPI' 4062 4063 @property 4064 def is_geotiff(self): 4065 """Page contains GeoTIFF metadata.""" 4066 return 'GeoKeyDirectoryTag' in self.tags 4067 4068 4069class TiffFrame(object): 4070 """Lightweight TIFF image file directory (IFD). 4071 4072 Only a limited number of tag values are read from file, e.g. StripOffsets, 4073 and StripByteCounts. Other tag values are assumed to be identical with a 4074 specified TiffPage instance, the keyframe. 4075 4076 TiffFrame is intended to reduce resource usage and speed up reading data 4077 from file, not for introspection of metadata. 4078 4079 Not compatible with Python 2. 4080 4081 """ 4082 __slots__ = ('keyframe', 'parent', 'index', 'offset', 4083 'dataoffsets', 'databytecounts') 4084 4085 is_mdgel = False 4086 tags = {} 4087 4088 def __init__(self, parent, index, keyframe): 4089 """Read specified tags from file. 4090 4091 The file handle position must be at the offset to a valid IFD. 4092 4093 """ 4094 self.keyframe = keyframe 4095 self.parent = parent 4096 self.index = index 4097 self.dataoffsets = None 4098 self.databytecounts = None 4099 4100 unpack = struct.unpack 4101 fh = parent.filehandle 4102 self.offset = fh.tell() 4103 try: 4104 tagno = unpack(parent.tagnoformat, fh.read(parent.tagnosize))[0] 4105 if tagno > 4096: 4106 raise ValueError('suspicious number of tags') 4107 except Exception: 4108 raise ValueError('corrupted page list at offset %i' % self.offset) 4109 4110 # tags = {} 4111 tagcodes = {273, 279, 324, 325} # TIFF.FRAME_TAGS 4112 tagsize = parent.tagsize 4113 codeformat = parent.tagformat1[:2] 4114 4115 data = fh.read(tagsize * tagno) 4116 index = -tagsize 4117 for _ in range(tagno): 4118 index += tagsize 4119 code = unpack(codeformat, data[index:index+2])[0] 4120 if code not in tagcodes: 4121 continue 4122 try: 4123 tag = TiffTag(parent, data[index:index+tagsize]) 4124 except TiffTag.Error as e: 4125 warnings.warn(str(e)) 4126 continue 4127 if code == 273 or code == 324: 4128 setattr(self, 'dataoffsets', tag.value) 4129 elif code == 279 or code == 325: 4130 setattr(self, 'databytecounts', tag.value) 4131 # elif code == 270: 4132 # tagname = tag.name 4133 # if tagname not in tags: 4134 # tags[tagname] = bytes2str(tag.value) 4135 # elif 'ImageDescription1' not in tags: 4136 # tags['ImageDescription1'] = bytes2str(tag.value) 4137 # else: 4138 # tags[tag.name] = tag.value 4139 4140 def aspage(self): 4141 """Return TiffPage from file.""" 4142 self.parent.filehandle.seek(self.offset) 4143 return TiffPage(self.parent, index=self.index, keyframe=None) 4144 4145 def asarray(self, *args, **kwargs): 4146 """Read image data from file and return as numpy array.""" 4147 # TODO: fix TypeError on Python 2 4148 # "TypeError: unbound method asarray() must be called with TiffPage 4149 # instance as first argument (got TiffFrame instance instead)" 4150 kwargs['validate'] = False 4151 return TiffPage.asarray(self, *args, **kwargs) 4152 4153 def asrgb(self, *args, **kwargs): 4154 """Read image data from file and return RGB image as numpy array.""" 4155 kwargs['validate'] = False 4156 return TiffPage.asrgb(self, *args, **kwargs) 4157 4158 @property 4159 def offsets_bytecounts(self): 4160 """Return simplified offsets and bytecounts.""" 4161 if self.keyframe.is_contiguous: 4162 return self.dataoffsets[:1], self.keyframe.is_contiguous[1:] 4163 return clean_offsets_counts(self.dataoffsets, self.databytecounts) 4164 4165 @property 4166 def is_contiguous(self): 4167 """Return offset and size of contiguous data, else None.""" 4168 if self.keyframe.is_contiguous: 4169 return self.dataoffsets[0], self.keyframe.is_contiguous[1] 4170 4171 @property 4172 def is_memmappable(self): 4173 """Return if page's image data in file can be memory-mapped.""" 4174 return self.keyframe.is_memmappable 4175 4176 def __getattr__(self, name): 4177 """Return attribute from keyframe.""" 4178 if name in TIFF.FRAME_ATTRS: 4179 return getattr(self.keyframe, name) 4180 # this error could be raised because an AttributeError was 4181 # raised inside a @property function 4182 raise AttributeError("'%s' object has no attribute '%s'" % 4183 (self.__class__.__name__, name)) 4184 4185 def __str__(self, detail=0): 4186 """Return string containing information about frame.""" 4187 info = ' '.join(s for s in ( 4188 'x'.join(str(i) for i in self.shape), 4189 str(self.dtype))) 4190 return 'TiffFrame %i @%i %s' % (self.index, self.offset, info) 4191 4192 4193class TiffTag(object): 4194 """TIFF tag structure. 4195 4196 Attributes 4197 ---------- 4198 name : string 4199 Name of tag. 4200 code : int 4201 Decimal code of tag. 4202 dtype : str 4203 Datatype of tag data. One of TIFF DATA_FORMATS. 4204 count : int 4205 Number of values. 4206 value : various types 4207 Tag data as Python object. 4208 ImageSourceData : int 4209 Location of value in file. 4210 4211 All attributes are read-only. 4212 4213 """ 4214 __slots__ = ('code', 'count', 'dtype', 'value', 'valueoffset') 4215 4216 class Error(Exception): 4217 pass 4218 4219 def __init__(self, parent, tagheader, **kwargs): 4220 """Initialize instance from tag header.""" 4221 fh = parent.filehandle 4222 byteorder = parent.byteorder 4223 unpack = struct.unpack 4224 offsetsize = parent.offsetsize 4225 4226 self.valueoffset = fh.tell() + offsetsize + 4 4227 code, type_ = unpack(parent.tagformat1, tagheader[:4]) 4228 count, value = unpack(parent.tagformat2, tagheader[4:]) 4229 4230 try: 4231 dtype = TIFF.DATA_FORMATS[type_] 4232 except KeyError: 4233 raise TiffTag.Error('unknown tag data type %i' % type_) 4234 4235 fmt = '%s%i%s' % (byteorder, count * int(dtype[0]), dtype[1]) 4236 size = struct.calcsize(fmt) 4237 if size > offsetsize or code in TIFF.TAG_READERS: 4238 self.valueoffset = offset = unpack(parent.offsetformat, value)[0] 4239 if offset < 8 or offset > fh.size - size: 4240 raise TiffTag.Error('invalid tag value offset') 4241 # if offset % 2: 4242 # warnings.warn('tag value does not begin on word boundary') 4243 fh.seek(offset) 4244 if code in TIFF.TAG_READERS: 4245 readfunc = TIFF.TAG_READERS[code] 4246 value = readfunc(fh, byteorder, dtype, count, offsetsize) 4247 elif type_ == 7 or (count > 1 and dtype[-1] == 'B'): 4248 value = read_bytes(fh, byteorder, dtype, count, offsetsize) 4249 elif code in TIFF.TAGS or dtype[-1] == 's': 4250 value = unpack(fmt, fh.read(size)) 4251 else: 4252 value = read_numpy(fh, byteorder, dtype, count, offsetsize) 4253 elif dtype[-1] == 'B' or type_ == 7: 4254 value = value[:size] 4255 else: 4256 value = unpack(fmt, value[:size]) 4257 4258 process = (code not in TIFF.TAG_READERS and code not in TIFF.TAG_TUPLE 4259 and type_ != 7) 4260 if process and dtype[-1] == 's' and isinstance(value[0], bytes): 4261 # TIFF ASCII fields can contain multiple strings, 4262 # each terminated with a NUL 4263 value = value[0] 4264 try: 4265 value = bytes2str(stripascii(value).strip()) 4266 except UnicodeDecodeError: 4267 warnings.warn('tag %i: coercing invalid ASCII to bytes' % code) 4268 dtype = '1B' 4269 else: 4270 if code in TIFF.TAG_ENUM: 4271 t = TIFF.TAG_ENUM[code] 4272 try: 4273 value = tuple(t(v) for v in value) 4274 except ValueError as e: 4275 warnings.warn(str(e)) 4276 if process: 4277 if len(value) == 1: 4278 value = value[0] 4279 4280 self.code = code 4281 self.dtype = dtype 4282 self.count = count 4283 self.value = value 4284 4285 @property 4286 def name(self): 4287 return TIFF.TAGS.get(self.code, str(self.code)) 4288 4289 def _fix_lsm_bitspersample(self, parent): 4290 """Correct LSM bitspersample tag. 4291 4292 Old LSM writers may use a separate region for two 16-bit values, 4293 although they fit into the tag value element of the tag. 4294 4295 """ 4296 if self.code == 258 and self.count == 2: 4297 # TODO: test this case; need example file 4298 warnings.warn('correcting LSM bitspersample tag') 4299 tof = parent.offsetformat[parent.offsetsize] 4300 self.valueoffset = struct.unpack(tof, self._value)[0] 4301 parent.filehandle.seek(self.valueoffset) 4302 self.value = struct.unpack('<HH', parent.filehandle.read(4)) 4303 4304 def __str__(self, detail=0, width=79): 4305 """Return string containing information about tag.""" 4306 height = 1 if detail <= 0 else 8 * detail 4307 tcode = '%i%s' % (self.count * int(self.dtype[0]), self.dtype[1]) 4308 line = 'TiffTag %i %s %s @%i ' % ( 4309 self.code, self.name, tcode, self.valueoffset)[:width] 4310 4311 if self.code in TIFF.TAG_ENUM: 4312 if self.count == 1: 4313 value = TIFF.TAG_ENUM[self.code](self.value).name 4314 else: 4315 value = pformat(tuple(v.name for v in self.value)) 4316 else: 4317 value = pformat(self.value, width=width, height=height) 4318 4319 if detail <= 0: 4320 line += value 4321 line = line[:width] 4322 else: 4323 line += '\n' + value 4324 return line 4325 4326 4327class TiffPageSeries(object): 4328 """Series of TIFF pages with compatible shape and data type. 4329 4330 Attributes 4331 ---------- 4332 pages : list of TiffPage 4333 Sequence of TiffPages in series. 4334 dtype : numpy.dtype 4335 Data type (native byte order) of the image array in series. 4336 shape : tuple 4337 Dimensions of the image array in series. 4338 axes : str 4339 Labels of axes in shape. See TiffPage.axes. 4340 offset : int or None 4341 Position of image data in file if memory-mappable, else None. 4342 4343 """ 4344 def __init__(self, pages, shape, dtype, axes, parent=None, name=None, 4345 transform=None, stype=None, truncated=False): 4346 """Initialize instance.""" 4347 self.index = 0 4348 self._pages = pages # might contain only first of contiguous pages 4349 self.shape = tuple(shape) 4350 self.axes = ''.join(axes) 4351 self.dtype = numpy.dtype(dtype) 4352 self.stype = stype if stype else '' 4353 self.name = name if name else '' 4354 self.transform = transform 4355 if parent: 4356 self.parent = parent 4357 elif pages: 4358 self.parent = pages[0].parent 4359 else: 4360 self.parent = None 4361 if len(pages) == 1 and not truncated: 4362 self._len = int(product(self.shape) // product(pages[0].shape)) 4363 else: 4364 self._len = len(pages) 4365 4366 def asarray(self, out=None): 4367 """Return image data from series of TIFF pages as numpy array.""" 4368 if self.parent: 4369 result = self.parent.asarray(series=self, out=out) 4370 if self.transform is not None: 4371 result = self.transform(result) 4372 return result 4373 4374 @lazyattr 4375 def offset(self): 4376 """Return offset to series data in file, if any.""" 4377 if not self._pages: 4378 return 4379 4380 pos = 0 4381 for page in self._pages: 4382 if page is None: 4383 return 4384 if not page.is_final: 4385 return 4386 if not pos: 4387 pos = page.is_contiguous[0] + page.is_contiguous[1] 4388 continue 4389 if pos != page.is_contiguous[0]: 4390 return 4391 pos += page.is_contiguous[1] 4392 4393 page = self._pages[0] 4394 offset = page.is_contiguous[0] 4395 if (page.is_imagej or page.is_shaped) and len(self._pages) == 1: 4396 # truncated files 4397 return offset 4398 if pos == offset + product(self.shape) * self.dtype.itemsize: 4399 return offset 4400 4401 @property 4402 def ndim(self): 4403 """Return number of array dimensions.""" 4404 return len(self.shape) 4405 4406 @property 4407 def size(self): 4408 """Return number of elements in array.""" 4409 return int(product(self.shape)) 4410 4411 @property 4412 def pages(self): 4413 """Return sequence of all pages in series.""" 4414 # a workaround to keep the old interface working 4415 return self 4416 4417 def __len__(self): 4418 """Return number of TiffPages in series.""" 4419 return self._len 4420 4421 def __getitem__(self, key): 4422 """Return specified TiffPage.""" 4423 if len(self._pages) == 1 and 0 < key < self._len: 4424 index = self._pages[0].index 4425 return self.parent.pages[index + key] 4426 return self._pages[key] 4427 4428 def __iter__(self): 4429 """Return iterator over TiffPages in series.""" 4430 if len(self._pages) == self._len: 4431 for page in self._pages: 4432 yield page 4433 else: 4434 pages = self.parent.pages 4435 index = self._pages[0].index 4436 for i in range(self._len): 4437 yield pages[index + i] 4438 4439 def __str__(self): 4440 """Return string with information about series.""" 4441 s = ' '.join(s for s in ( 4442 snipstr("'%s'" % self.name, 20) if self.name else '', 4443 'x'.join(str(i) for i in self.shape), 4444 str(self.dtype), 4445 self.axes, 4446 self.stype, 4447 '%i Pages' % len(self.pages), 4448 ('Offset=%i' % self.offset) if self.offset else '') if s) 4449 return 'TiffPageSeries %i %s' % (self.index, s) 4450 4451 4452class TiffSequence(object): 4453 """Sequence of TIFF files. 4454 4455 The image data in all files must match shape, dtype, etc. 4456 4457 Attributes 4458 ---------- 4459 files : list 4460 List of file names. 4461 shape : tuple 4462 Shape of image sequence. Excludes shape of image array. 4463 axes : str 4464 Labels of axes in shape. 4465 4466 Examples 4467 -------- 4468 >>> # read image stack from sequence of TIFF files 4469 >>> imsave('temp_C001T001.tif', numpy.random.rand(64, 64)) 4470 >>> imsave('temp_C001T002.tif', numpy.random.rand(64, 64)) 4471 >>> tifs = TiffSequence('temp_C001*.tif') 4472 >>> tifs.shape 4473 (1, 2) 4474 >>> tifs.axes 4475 'CT' 4476 >>> data = tifs.asarray() 4477 >>> data.shape 4478 (1, 2, 64, 64) 4479 4480 """ 4481 _patterns = { 4482 'axes': r""" 4483 # matches Olympus OIF and Leica TIFF series 4484 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4})) 4485 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))? 4486 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))? 4487 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))? 4488 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))? 4489 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))? 4490 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))? 4491 """} 4492 4493 class ParseError(Exception): 4494 pass 4495 4496 def __init__(self, files, imread=TiffFile, pattern='axes', 4497 *args, **kwargs): 4498 """Initialize instance from multiple files. 4499 4500 Parameters 4501 ---------- 4502 files : str, pathlib.Path, or sequence thereof 4503 Glob pattern or sequence of file names. 4504 Binary streams are not supported. 4505 imread : function or class 4506 Image read function or class with asarray function returning numpy 4507 array from single file. 4508 pattern : str 4509 Regular expression pattern that matches axes names and sequence 4510 indices in file names. 4511 By default, the pattern matches Olympus OIF and Leica TIFF series. 4512 4513 """ 4514 if isinstance(files, pathlib.Path): 4515 files = str(files) 4516 if isinstance(files, basestring): 4517 files = natural_sorted(glob.glob(files)) 4518 files = list(files) 4519 if not files: 4520 raise ValueError('no files found') 4521 if isinstance(files[0], pathlib.Path): 4522 files = [str(pathlib.Path(f)) for f in files] 4523 elif not isinstance(files[0], basestring): 4524 raise ValueError('not a file name') 4525 self.files = files 4526 4527 if hasattr(imread, 'asarray'): 4528 # redefine imread 4529 _imread = imread 4530 4531 def imread(fname, *args, **kwargs): 4532 with _imread(fname) as im: 4533 return im.asarray(*args, **kwargs) 4534 4535 self.imread = imread 4536 4537 self.pattern = self._patterns.get(pattern, pattern) 4538 try: 4539 self._parse() 4540 if not self.axes: 4541 self.axes = 'I' 4542 except self.ParseError: 4543 self.axes = 'I' 4544 self.shape = (len(files),) 4545 self._startindex = (0,) 4546 self._indices = tuple((i,) for i in range(len(files))) 4547 4548 def __str__(self): 4549 """Return string with information about image sequence.""" 4550 return '\n'.join([ 4551 self.files[0], 4552 ' size: %i' % len(self.files), 4553 ' axes: %s' % self.axes, 4554 ' shape: %s' % str(self.shape)]) 4555 4556 def __len__(self): 4557 return len(self.files) 4558 4559 def __enter__(self): 4560 return self 4561 4562 def __exit__(self, exc_type, exc_value, traceback): 4563 self.close() 4564 4565 def close(self): 4566 pass 4567 4568 def asarray(self, out=None, *args, **kwargs): 4569 """Read image data from all files and return as numpy array. 4570 4571 The args and kwargs parameters are passed to the imread function. 4572 4573 Raise IndexError or ValueError if image shapes do not match. 4574 4575 """ 4576 im = self.imread(self.files[0], *args, **kwargs) 4577 shape = self.shape + im.shape 4578 result = create_output(out, shape, dtype=im.dtype) 4579 result = result.reshape(-1, *im.shape) 4580 for index, fname in zip(self._indices, self.files): 4581 index = [i-j for i, j in zip(index, self._startindex)] 4582 index = numpy.ravel_multi_index(index, self.shape) 4583 im = self.imread(fname, *args, **kwargs) 4584 result[index] = im 4585 result.shape = shape 4586 return result 4587 4588 def _parse(self): 4589 """Get axes and shape from file names.""" 4590 if not self.pattern: 4591 raise self.ParseError('invalid pattern') 4592 pattern = re.compile(self.pattern, re.IGNORECASE | re.VERBOSE) 4593 matches = pattern.findall(self.files[0]) 4594 if not matches: 4595 raise self.ParseError('pattern does not match file names') 4596 matches = matches[-1] 4597 if len(matches) % 2: 4598 raise self.ParseError('pattern does not match axis name and index') 4599 axes = ''.join(m for m in matches[::2] if m) 4600 if not axes: 4601 raise self.ParseError('pattern does not match file names') 4602 4603 indices = [] 4604 for fname in self.files: 4605 matches = pattern.findall(fname)[-1] 4606 if axes != ''.join(m for m in matches[::2] if m): 4607 raise ValueError('axes do not match within the image sequence') 4608 indices.append([int(m) for m in matches[1::2] if m]) 4609 shape = tuple(numpy.max(indices, axis=0)) 4610 startindex = tuple(numpy.min(indices, axis=0)) 4611 shape = tuple(i-j+1 for i, j in zip(shape, startindex)) 4612 if product(shape) != len(self.files): 4613 warnings.warn('files are missing. Missing data are zeroed') 4614 4615 self.axes = axes.upper() 4616 self.shape = shape 4617 self._indices = indices 4618 self._startindex = startindex 4619 4620 4621class FileHandle(object): 4622 """Binary file handle. 4623 4624 A limited, special purpose file handler that can: 4625 4626 * handle embedded files (for CZI within CZI files) 4627 * re-open closed files (for multi-file formats, such as OME-TIFF) 4628 * read and write numpy arrays and records from file like objects 4629 4630 Only 'rb' and 'wb' modes are supported. Concurrently reading and writing 4631 of the same stream is untested. 4632 4633 When initialized from another file handle, do not use it unless this 4634 FileHandle is closed. 4635 4636 Attributes 4637 ---------- 4638 name : str 4639 Name of the file. 4640 path : str 4641 Absolute path to file. 4642 size : int 4643 Size of file in bytes. 4644 is_file : bool 4645 If True, file has a filno and can be memory-mapped. 4646 4647 All attributes are read-only. 4648 4649 """ 4650 __slots__ = ('_fh', '_file', '_mode', '_name', '_dir', '_lock', 4651 '_offset', '_size', '_close', 'is_file') 4652 4653 def __init__(self, file, mode='rb', name=None, offset=None, size=None): 4654 """Initialize file handle from file name or another file handle. 4655 4656 Parameters 4657 ---------- 4658 file : str, pathlib.Path, binary stream, or FileHandle 4659 File name or seekable binary stream, such as an open file 4660 or BytesIO. 4661 mode : str 4662 File open mode in case 'file' is a file name. Must be 'rb' or 'wb'. 4663 name : str 4664 Optional name of file in case 'file' is a binary stream. 4665 offset : int 4666 Optional start position of embedded file. By default, this is 4667 the current file position. 4668 size : int 4669 Optional size of embedded file. By default, this is the number 4670 of bytes from the 'offset' to the end of the file. 4671 4672 """ 4673 self._file = file 4674 self._fh = None 4675 self._mode = mode 4676 self._name = name 4677 self._dir = '' 4678 self._offset = offset 4679 self._size = size 4680 self._close = True 4681 self.is_file = False 4682 self._lock = NullContext() 4683 self.open() 4684 4685 def open(self): 4686 """Open or re-open file.""" 4687 if self._fh: 4688 return # file is open 4689 4690 if isinstance(self._file, pathlib.Path): 4691 self._file = str(self._file) 4692 if isinstance(self._file, basestring): 4693 # file name 4694 self._file = os.path.realpath(self._file) 4695 self._dir, self._name = os.path.split(self._file) 4696 self._fh = open(self._file, self._mode) 4697 self._close = True 4698 if self._offset is None: 4699 self._offset = 0 4700 elif isinstance(self._file, FileHandle): 4701 # FileHandle 4702 self._fh = self._file._fh 4703 if self._offset is None: 4704 self._offset = 0 4705 self._offset += self._file._offset 4706 self._close = False 4707 if not self._name: 4708 if self._offset: 4709 name, ext = os.path.splitext(self._file._name) 4710 self._name = '%s@%i%s' % (name, self._offset, ext) 4711 else: 4712 self._name = self._file._name 4713 if self._mode and self._mode != self._file._mode: 4714 raise ValueError('FileHandle has wrong mode') 4715 self._mode = self._file._mode 4716 self._dir = self._file._dir 4717 elif hasattr(self._file, 'seek'): 4718 # binary stream: open file, BytesIO 4719 try: 4720 self._file.tell() 4721 except Exception: 4722 raise ValueError('binary stream is not seekable') 4723 self._fh = self._file 4724 if self._offset is None: 4725 self._offset = self._file.tell() 4726 self._close = False 4727 if not self._name: 4728 try: 4729 self._dir, self._name = os.path.split(self._fh.name) 4730 except AttributeError: 4731 self._name = 'Unnamed binary stream' 4732 try: 4733 self._mode = self._fh.mode 4734 except AttributeError: 4735 pass 4736 else: 4737 raise ValueError('The first parameter must be a file name, ' 4738 'seekable binary stream, or FileHandle') 4739 4740 if self._offset: 4741 self._fh.seek(self._offset) 4742 4743 if self._size is None: 4744 pos = self._fh.tell() 4745 self._fh.seek(self._offset, 2) 4746 self._size = self._fh.tell() 4747 self._fh.seek(pos) 4748 4749 try: 4750 self._fh.fileno() 4751 self.is_file = True 4752 except Exception: 4753 self.is_file = False 4754 4755 def read(self, size=-1): 4756 """Read 'size' bytes from file, or until EOF is reached.""" 4757 if size < 0 and self._offset: 4758 size = self._size 4759 return self._fh.read(size) 4760 4761 def write(self, bytestring): 4762 """Write bytestring to file.""" 4763 return self._fh.write(bytestring) 4764 4765 def flush(self): 4766 """Flush write buffers if applicable.""" 4767 return self._fh.flush() 4768 4769 def memmap_array(self, dtype, shape, offset=0, mode='r', order='C'): 4770 """Return numpy.memmap of data stored in file.""" 4771 if not self.is_file: 4772 raise ValueError('Cannot memory-map file without fileno') 4773 return numpy.memmap(self._fh, dtype=dtype, mode=mode, 4774 offset=self._offset + offset, 4775 shape=shape, order=order) 4776 4777 def read_array(self, dtype, count=-1, sep='', chunksize=2**25, out=None, 4778 native=False): 4779 """Return numpy array from file. 4780 4781 Work around numpy issue #2230, "numpy.fromfile does not accept 4782 StringIO object" https://github.com/numpy/numpy/issues/2230. 4783 4784 """ 4785 fh = self._fh 4786 dtype = numpy.dtype(dtype) 4787 size = self._size if count < 0 else count * dtype.itemsize 4788 4789 if out is None: 4790 try: 4791 result = numpy.fromfile(fh, dtype, count, sep) 4792 except IOError: 4793 # ByteIO 4794 data = fh.read(size) 4795 result = numpy.frombuffer(data, dtype, count).copy() 4796 if native and not result.dtype.isnative: 4797 # swap byte order and dtype without copy 4798 result.byteswap(True) 4799 result = result.newbyteorder() 4800 return result 4801 4802 # Read data from file in chunks and copy to output array 4803 shape = out.shape 4804 size = min(out.nbytes, size) 4805 out = out.reshape(-1) 4806 index = 0 4807 while size > 0: 4808 data = fh.read(min(chunksize, size)) 4809 datasize = len(data) 4810 if datasize == 0: 4811 break 4812 size -= datasize 4813 data = numpy.frombuffer(data, dtype) 4814 out[index:index+data.size] = data 4815 index += data.size 4816 4817 if hasattr(out, 'flush'): 4818 out.flush() 4819 return out.reshape(shape) 4820 4821 def read_record(self, dtype, shape=1, byteorder=None): 4822 """Return numpy record from file.""" 4823 rec = numpy.rec 4824 try: 4825 record = rec.fromfile(self._fh, dtype, shape, byteorder=byteorder) 4826 except Exception: 4827 dtype = numpy.dtype(dtype) 4828 if shape is None: 4829 shape = self._size // dtype.itemsize 4830 size = product(sequence(shape)) * dtype.itemsize 4831 data = self._fh.read(size) 4832 record = rec.fromstring(data, dtype, shape, byteorder=byteorder) 4833 return record[0] if shape == 1 else record 4834 4835 def write_empty(self, size): 4836 """Append size bytes to file. Position must be at end of file.""" 4837 if size < 1: 4838 return 4839 self._fh.seek(size-1, 1) 4840 self._fh.write(b'\x00') 4841 4842 def write_array(self, data): 4843 """Write numpy array to binary file.""" 4844 try: 4845 data.tofile(self._fh) 4846 except Exception: 4847 # BytesIO 4848 self._fh.write(data.tostring()) 4849 4850 def tell(self): 4851 """Return file's current position.""" 4852 return self._fh.tell() - self._offset 4853 4854 def seek(self, offset, whence=0): 4855 """Set file's current position.""" 4856 if self._offset: 4857 if whence == 0: 4858 self._fh.seek(self._offset + offset, whence) 4859 return 4860 elif whence == 2 and self._size > 0: 4861 self._fh.seek(self._offset + self._size + offset, 0) 4862 return 4863 self._fh.seek(offset, whence) 4864 4865 def close(self): 4866 """Close file.""" 4867 if self._close and self._fh: 4868 self._fh.close() 4869 self._fh = None 4870 4871 def __enter__(self): 4872 return self 4873 4874 def __exit__(self, exc_type, exc_value, traceback): 4875 self.close() 4876 4877 def __getattr__(self, name): 4878 """Return attribute from underlying file object.""" 4879 if self._offset: 4880 warnings.warn( 4881 "FileHandle: '%s' not implemented for embedded files" % name) 4882 return getattr(self._fh, name) 4883 4884 @property 4885 def name(self): 4886 return self._name 4887 4888 @property 4889 def dirname(self): 4890 return self._dir 4891 4892 @property 4893 def path(self): 4894 return os.path.join(self._dir, self._name) 4895 4896 @property 4897 def size(self): 4898 return self._size 4899 4900 @property 4901 def closed(self): 4902 return self._fh is None 4903 4904 @property 4905 def lock(self): 4906 return self._lock 4907 4908 @lock.setter 4909 def lock(self, value): 4910 self._lock = threading.RLock() if value else NullContext() 4911 4912 4913class NullContext(object): 4914 """Null context manager. 4915 4916 >>> with NullContext(): 4917 ... pass 4918 4919 """ 4920 def __enter__(self): 4921 return self 4922 4923 def __exit__(self, exc_type, exc_value, traceback): 4924 pass 4925 4926 4927class OpenFileCache(object): 4928 """Keep files open.""" 4929 4930 __slots__ = ('files', 'past', 'lock', 'size') 4931 4932 def __init__(self, size, lock=None): 4933 """Initialize open file cache.""" 4934 self.past = [] # FIFO of opened files 4935 self.files = {} # refcounts of opened files 4936 self.lock = NullContext() if lock is None else lock 4937 self.size = int(size) 4938 4939 def open(self, filehandle): 4940 """Re-open file if necessary.""" 4941 with self.lock: 4942 if filehandle in self.files: 4943 self.files[filehandle] += 1 4944 elif filehandle.closed: 4945 filehandle.open() 4946 self.files[filehandle] = 1 4947 self.past.append(filehandle) 4948 4949 def close(self, filehandle): 4950 """Close openend file if no longer used.""" 4951 with self.lock: 4952 if filehandle in self.files: 4953 self.files[filehandle] -= 1 4954 # trim the file cache 4955 index = 0 4956 size = len(self.past) 4957 while size > self.size and index < size: 4958 filehandle = self.past[index] 4959 if self.files[filehandle] == 0: 4960 filehandle.close() 4961 del self.files[filehandle] 4962 del self.past[index] 4963 size -= 1 4964 else: 4965 index += 1 4966 4967 def clear(self): 4968 """Close all opened files if not in use.""" 4969 with self.lock: 4970 for filehandle, refcount in list(self.files.items()): 4971 if refcount == 0: 4972 filehandle.close() 4973 del self.files[filehandle] 4974 del self.past[self.past.index(filehandle)] 4975 4976 4977class LazyConst(object): 4978 """Class whose attributes are computed on first access from its methods.""" 4979 def __init__(self, cls): 4980 self._cls = cls 4981 self.__doc__ = getattr(cls, '__doc__') 4982 4983 def __getattr__(self, name): 4984 func = getattr(self._cls, name) 4985 if not callable(func): 4986 return func 4987 try: 4988 value = func() 4989 except TypeError: 4990 # Python 2 unbound method 4991 value = func.__func__() 4992 setattr(self, name, value) 4993 return value 4994 4995 4996@LazyConst 4997class TIFF(object): 4998 """Namespace for module constants.""" 4999 5000 def TAGS(): 5001 # TIFF tag codes and names from TIFF6, TIFF/EP, EXIF, and other specs 5002 return { 5003 11: 'ProcessingSoftware', 5004 254: 'NewSubfileType', 5005 255: 'SubfileType', 5006 256: 'ImageWidth', 5007 257: 'ImageLength', 5008 258: 'BitsPerSample', 5009 259: 'Compression', 5010 262: 'PhotometricInterpretation', 5011 263: 'Thresholding', 5012 264: 'CellWidth', 5013 265: 'CellLength', 5014 266: 'FillOrder', 5015 269: 'DocumentName', 5016 270: 'ImageDescription', 5017 271: 'Make', 5018 272: 'Model', 5019 273: 'StripOffsets', 5020 274: 'Orientation', 5021 277: 'SamplesPerPixel', 5022 278: 'RowsPerStrip', 5023 279: 'StripByteCounts', 5024 280: 'MinSampleValue', 5025 281: 'MaxSampleValue', 5026 282: 'XResolution', 5027 283: 'YResolution', 5028 284: 'PlanarConfiguration', 5029 285: 'PageName', 5030 286: 'XPosition', 5031 287: 'YPosition', 5032 288: 'FreeOffsets', 5033 289: 'FreeByteCounts', 5034 290: 'GrayResponseUnit', 5035 291: 'GrayResponseCurve', 5036 292: 'T4Options', 5037 293: 'T6Options', 5038 296: 'ResolutionUnit', 5039 297: 'PageNumber', 5040 300: 'ColorResponseUnit', 5041 301: 'TransferFunction', 5042 305: 'Software', 5043 306: 'DateTime', 5044 315: 'Artist', 5045 316: 'HostComputer', 5046 317: 'Predictor', 5047 318: 'WhitePoint', 5048 319: 'PrimaryChromaticities', 5049 320: 'ColorMap', 5050 321: 'HalftoneHints', 5051 322: 'TileWidth', 5052 323: 'TileLength', 5053 324: 'TileOffsets', 5054 325: 'TileByteCounts', 5055 326: 'BadFaxLines', 5056 327: 'CleanFaxData', 5057 328: 'ConsecutiveBadFaxLines', 5058 330: 'SubIFDs', 5059 332: 'InkSet', 5060 333: 'InkNames', 5061 334: 'NumberOfInks', 5062 336: 'DotRange', 5063 337: 'TargetPrinter', 5064 338: 'ExtraSamples', 5065 339: 'SampleFormat', 5066 340: 'SMinSampleValue', 5067 341: 'SMaxSampleValue', 5068 342: 'TransferRange', 5069 343: 'ClipPath', 5070 344: 'XClipPathUnits', 5071 345: 'YClipPathUnits', 5072 346: 'Indexed', 5073 347: 'JPEGTables', 5074 351: 'OPIProxy', 5075 400: 'GlobalParametersIFD', 5076 401: 'ProfileType', 5077 402: 'FaxProfile', 5078 403: 'CodingMethods', 5079 404: 'VersionYear', 5080 405: 'ModeNumber', 5081 433: 'Decode', 5082 434: 'DefaultImageColor', 5083 435: 'T82Options', 5084 437: 'JPEGTables_', # 347 5085 512: 'JPEGProc', 5086 513: 'JPEGInterchangeFormat', 5087 514: 'JPEGInterchangeFormatLength', 5088 515: 'JPEGRestartInterval', 5089 517: 'JPEGLosslessPredictors', 5090 518: 'JPEGPointTransforms', 5091 519: 'JPEGQTables', 5092 520: 'JPEGDCTables', 5093 521: 'JPEGACTables', 5094 529: 'YCbCrCoefficients', 5095 530: 'YCbCrSubSampling', 5096 531: 'YCbCrPositioning', 5097 532: 'ReferenceBlackWhite', 5098 559: 'StripRowCounts', 5099 700: 'XMP', # XMLPacket 5100 769: 'GDIGamma', # GDI+ 5101 770: 'ICCProfileDescriptor', # GDI+ 5102 771: 'SRGBRenderingIntent', # GDI+ 5103 800: 'ImageTitle', # GDI+ 5104 999: 'USPTO_Miscellaneous', 5105 4864: 'AndorId', # TODO: Andor Technology 4864 - 5030 5106 4869: 'AndorTemperature', 5107 4876: 'AndorExposureTime', 5108 4878: 'AndorKineticCycleTime', 5109 4879: 'AndorAccumulations', 5110 4881: 'AndorAcquisitionCycleTime', 5111 4882: 'AndorReadoutTime', 5112 4884: 'AndorPhotonCounting', 5113 4885: 'AndorEmDacLevel', 5114 4890: 'AndorFrames', 5115 4896: 'AndorHorizontalFlip', 5116 4897: 'AndorVerticalFlip', 5117 4898: 'AndorClockwise', 5118 4899: 'AndorCounterClockwise', 5119 4904: 'AndorVerticalClockVoltage', 5120 4905: 'AndorVerticalShiftSpeed', 5121 4907: 'AndorPreAmpSetting', 5122 4908: 'AndorCameraSerial', 5123 4911: 'AndorActualTemperature', 5124 4912: 'AndorBaselineClamp', 5125 4913: 'AndorPrescans', 5126 4914: 'AndorModel', 5127 4915: 'AndorChipSizeX', 5128 4916: 'AndorChipSizeY', 5129 4944: 'AndorBaselineOffset', 5130 4966: 'AndorSoftwareVersion', 5131 18246: 'Rating', 5132 18247: 'XP_DIP_XML', 5133 18248: 'StitchInfo', 5134 18249: 'RatingPercent', 5135 20481: 'ResolutionXUnit', # GDI+ 5136 20482: 'ResolutionYUnit', # GDI+ 5137 20483: 'ResolutionXLengthUnit', # GDI+ 5138 20484: 'ResolutionYLengthUnit', # GDI+ 5139 20485: 'PrintFlags', # GDI+ 5140 20486: 'PrintFlagsVersion', # GDI+ 5141 20487: 'PrintFlagsCrop', # GDI+ 5142 20488: 'PrintFlagsBleedWidth', # GDI+ 5143 20489: 'PrintFlagsBleedWidthScale', # GDI+ 5144 20490: 'HalftoneLPI', # GDI+ 5145 20491: 'HalftoneLPIUnit', # GDI+ 5146 20492: 'HalftoneDegree', # GDI+ 5147 20493: 'HalftoneShape', # GDI+ 5148 20494: 'HalftoneMisc', # GDI+ 5149 20495: 'HalftoneScreen', # GDI+ 5150 20496: 'JPEGQuality', # GDI+ 5151 20497: 'GridSize', # GDI+ 5152 20498: 'ThumbnailFormat', # GDI+ 5153 20499: 'ThumbnailWidth', # GDI+ 5154 20500: 'ThumbnailHeight', # GDI+ 5155 20501: 'ThumbnailColorDepth', # GDI+ 5156 20502: 'ThumbnailPlanes', # GDI+ 5157 20503: 'ThumbnailRawBytes', # GDI+ 5158 20504: 'ThumbnailSize', # GDI+ 5159 20505: 'ThumbnailCompressedSize', # GDI+ 5160 20506: 'ColorTransferFunction', # GDI+ 5161 20507: 'ThumbnailData', 5162 20512: 'ThumbnailImageWidth', # GDI+ 5163 20513: 'ThumbnailImageHeight', # GDI+ 5164 20514: 'ThumbnailBitsPerSample', # GDI+ 5165 20515: 'ThumbnailCompression', 5166 20516: 'ThumbnailPhotometricInterp', # GDI+ 5167 20517: 'ThumbnailImageDescription', # GDI+ 5168 20518: 'ThumbnailEquipMake', # GDI+ 5169 20519: 'ThumbnailEquipModel', # GDI+ 5170 20520: 'ThumbnailStripOffsets', # GDI+ 5171 20521: 'ThumbnailOrientation', # GDI+ 5172 20522: 'ThumbnailSamplesPerPixel', # GDI+ 5173 20523: 'ThumbnailRowsPerStrip', # GDI+ 5174 20524: 'ThumbnailStripBytesCount', # GDI+ 5175 20525: 'ThumbnailResolutionX', 5176 20526: 'ThumbnailResolutionY', 5177 20527: 'ThumbnailPlanarConfig', # GDI+ 5178 20528: 'ThumbnailResolutionUnit', 5179 20529: 'ThumbnailTransferFunction', 5180 20530: 'ThumbnailSoftwareUsed', # GDI+ 5181 20531: 'ThumbnailDateTime', # GDI+ 5182 20532: 'ThumbnailArtist', # GDI+ 5183 20533: 'ThumbnailWhitePoint', # GDI+ 5184 20534: 'ThumbnailPrimaryChromaticities', # GDI+ 5185 20535: 'ThumbnailYCbCrCoefficients', # GDI+ 5186 20536: 'ThumbnailYCbCrSubsampling', # GDI+ 5187 20537: 'ThumbnailYCbCrPositioning', 5188 20538: 'ThumbnailRefBlackWhite', # GDI+ 5189 20539: 'ThumbnailCopyRight', # GDI+ 5190 20545: 'InteroperabilityIndex', 5191 20546: 'InteroperabilityVersion', 5192 20624: 'LuminanceTable', 5193 20625: 'ChrominanceTable', 5194 20736: 'FrameDelay', # GDI+ 5195 20737: 'LoopCount', # GDI+ 5196 20738: 'GlobalPalette', # GDI+ 5197 20739: 'IndexBackground', # GDI+ 5198 20740: 'IndexTransparent', # GDI+ 5199 20752: 'PixelUnit', # GDI+ 5200 20753: 'PixelPerUnitX', # GDI+ 5201 20754: 'PixelPerUnitY', # GDI+ 5202 20755: 'PaletteHistogram', # GDI+ 5203 28672: 'SonyRawFileType', # Sony ARW 5204 28722: 'VignettingCorrParams', # Sony ARW 5205 28725: 'ChromaticAberrationCorrParams', # Sony ARW 5206 28727: 'DistortionCorrParams', # Sony ARW 5207 # Private tags >= 32768 5208 32781: 'ImageID', 5209 32931: 'WangTag1', 5210 32932: 'WangAnnotation', 5211 32933: 'WangTag3', 5212 32934: 'WangTag4', 5213 32953: 'ImageReferencePoints', 5214 32954: 'RegionXformTackPoint', 5215 32955: 'WarpQuadrilateral', 5216 32956: 'AffineTransformMat', 5217 32995: 'Matteing', 5218 32996: 'DataType', 5219 32997: 'ImageDepth', 5220 32998: 'TileDepth', 5221 33300: 'ImageFullWidth', 5222 33301: 'ImageFullLength', 5223 33302: 'TextureFormat', 5224 33303: 'TextureWrapModes', 5225 33304: 'FieldOfViewCotangent', 5226 33305: 'MatrixWorldToScreen', 5227 33306: 'MatrixWorldToCamera', 5228 33405: 'Model2', 5229 33421: 'CFARepeatPatternDim', 5230 33422: 'CFAPattern', 5231 33423: 'BatteryLevel', 5232 33424: 'KodakIFD', 5233 33434: 'ExposureTime', 5234 33437: 'FNumber', 5235 33432: 'Copyright', 5236 33445: 'MDFileTag', 5237 33446: 'MDScalePixel', 5238 33447: 'MDColorTable', 5239 33448: 'MDLabName', 5240 33449: 'MDSampleInfo', 5241 33450: 'MDPrepDate', 5242 33451: 'MDPrepTime', 5243 33452: 'MDFileUnits', 5244 33550: 'ModelPixelScaleTag', 5245 33589: 'AdventScale', 5246 33590: 'AdventRevision', 5247 33628: 'UIC1tag', # Metamorph Universal Imaging Corp STK 5248 33629: 'UIC2tag', 5249 33630: 'UIC3tag', 5250 33631: 'UIC4tag', 5251 33723: 'IPTCNAA', 5252 33858: 'ExtendedTagsOffset', # DEFF points IFD with private tags 5253 33918: 'IntergraphPacketData', # INGRPacketDataTag 5254 33919: 'IntergraphFlagRegisters', # INGRFlagRegisters 5255 33920: 'IntergraphMatrixTag', # IrasBTransformationMatrix 5256 33921: 'INGRReserved', 5257 33922: 'ModelTiepointTag', 5258 33923: 'LeicaMagic', 5259 34016: 'Site', 5260 34017: 'ColorSequence', 5261 34018: 'IT8Header', 5262 34019: 'RasterPadding', 5263 34020: 'BitsPerRunLength', 5264 34021: 'BitsPerExtendedRunLength', 5265 34022: 'ColorTable', 5266 34023: 'ImageColorIndicator', 5267 34024: 'BackgroundColorIndicator', 5268 34025: 'ImageColorValue', 5269 34026: 'BackgroundColorValue', 5270 34027: 'PixelIntensityRange', 5271 34028: 'TransparencyIndicator', 5272 34029: 'ColorCharacterization', 5273 34030: 'HCUsage', 5274 34031: 'TrapIndicator', 5275 34032: 'CMYKEquivalent', 5276 34118: 'CZ_SEM', # Zeiss SEM 5277 34152: 'AFCP_IPTC', 5278 34232: 'PixelMagicJBIGOptions', 5279 34263: 'JPLCartoIFD', 5280 34122: 'IPLAB', # number of images 5281 34264: 'ModelTransformationTag', 5282 34306: 'WB_GRGBLevels', # Leaf MOS 5283 34310: 'LeafData', 5284 34361: 'MM_Header', 5285 34362: 'MM_Stamp', 5286 34363: 'MM_Unknown', 5287 34377: 'ImageResources', # Photoshop 5288 34386: 'MM_UserBlock', 5289 34412: 'CZ_LSMINFO', 5290 34665: 'ExifTag', 5291 34675: 'InterColorProfile', # ICCProfile 5292 34680: 'FEI_SFEG', # 5293 34682: 'FEI_HELIOS', # 5294 34683: 'FEI_TITAN', # 5295 34687: 'FXExtensions', 5296 34688: 'MultiProfiles', 5297 34689: 'SharedData', 5298 34690: 'T88Options', 5299 34710: 'MarCCD', # offset to MarCCD header 5300 34732: 'ImageLayer', 5301 34735: 'GeoKeyDirectoryTag', 5302 34736: 'GeoDoubleParamsTag', 5303 34737: 'GeoAsciiParamsTag', 5304 34750: 'JBIGOptions', 5305 34821: 'PIXTIFF', # ? Pixel Translations Inc 5306 34850: 'ExposureProgram', 5307 34852: 'SpectralSensitivity', 5308 34853: 'GPSTag', # GPSIFD 5309 34855: 'ISOSpeedRatings', 5310 34856: 'OECF', 5311 34857: 'Interlace', 5312 34858: 'TimeZoneOffset', 5313 34859: 'SelfTimerMode', 5314 34864: 'SensitivityType', 5315 34865: 'StandardOutputSensitivity', 5316 34866: 'RecommendedExposureIndex', 5317 34867: 'ISOSpeed', 5318 34868: 'ISOSpeedLatitudeyyy', 5319 34869: 'ISOSpeedLatitudezzz', 5320 34908: 'HylaFAXFaxRecvParams', 5321 34909: 'HylaFAXFaxSubAddress', 5322 34910: 'HylaFAXFaxRecvTime', 5323 34911: 'FaxDcs', 5324 34929: 'FedexEDR', 5325 34954: 'LeafSubIFD', 5326 34959: 'Aphelion1', 5327 34960: 'Aphelion2', 5328 34961: 'AphelionInternal', # ADCIS 5329 36864: 'ExifVersion', 5330 36867: 'DateTimeOriginal', 5331 36868: 'DateTimeDigitized', 5332 36873: 'GooglePlusUploadCode', 5333 36880: 'OffsetTime', 5334 36881: 'OffsetTimeOriginal', 5335 36882: 'OffsetTimeDigitized', 5336 # TODO: Pilatus/CHESS/TV6 36864..37120 conflicting with Exif tags 5337 # 36864: 'TVX ?', 5338 # 36865: 'TVX_NumExposure', 5339 # 36866: 'TVX_NumBackground', 5340 # 36867: 'TVX_ExposureTime', 5341 # 36868: 'TVX_BackgroundTime', 5342 # 36870: 'TVX ?', 5343 # 36873: 'TVX_SubBpp', 5344 # 36874: 'TVX_SubWide', 5345 # 36875: 'TVX_SubHigh', 5346 # 36876: 'TVX_BlackLevel', 5347 # 36877: 'TVX_DarkCurrent', 5348 # 36878: 'TVX_ReadNoise', 5349 # 36879: 'TVX_DarkCurrentNoise', 5350 # 36880: 'TVX_BeamMonitor', 5351 # 37120: 'TVX_UserVariables', # A/D values 5352 37121: 'ComponentsConfiguration', 5353 37122: 'CompressedBitsPerPixel', 5354 37377: 'ShutterSpeedValue', 5355 37378: 'ApertureValue', 5356 37379: 'BrightnessValue', 5357 37380: 'ExposureBiasValue', 5358 37381: 'MaxApertureValue', 5359 37382: 'SubjectDistance', 5360 37383: 'MeteringMode', 5361 37384: 'LightSource', 5362 37385: 'Flash', 5363 37386: 'FocalLength', 5364 37387: 'FlashEnergy_', # 37387 5365 37388: 'SpatialFrequencyResponse_', # 37388 5366 37389: 'Noise', 5367 37390: 'FocalPlaneXResolution', 5368 37391: 'FocalPlaneYResolution', 5369 37392: 'FocalPlaneResolutionUnit', 5370 37393: 'ImageNumber', 5371 37394: 'SecurityClassification', 5372 37395: 'ImageHistory', 5373 37396: 'SubjectLocation', 5374 37397: 'ExposureIndex', 5375 37398: 'TIFFEPStandardID', 5376 37399: 'SensingMethod', 5377 37434: 'CIP3DataFile', 5378 37435: 'CIP3Sheet', 5379 37436: 'CIP3Side', 5380 37439: 'StoNits', 5381 37500: 'MakerNote', 5382 37510: 'UserComment', 5383 37520: 'SubsecTime', 5384 37521: 'SubsecTimeOriginal', 5385 37522: 'SubsecTimeDigitized', 5386 37679: 'MODIText', # Microsoft Office Document Imaging 5387 37680: 'MODIOLEPropertySetStorage', 5388 37681: 'MODIPositioning', 5389 37706: 'TVIPS', # offset to TemData structure 5390 37707: 'TVIPS1', 5391 37708: 'TVIPS2', # same TemData structure as undefined 5392 37724: 'ImageSourceData', # Photoshop 5393 37888: 'Temperature', 5394 37889: 'Humidity', 5395 37890: 'Pressure', 5396 37891: 'WaterDepth', 5397 37892: 'Acceleration', 5398 37893: 'CameraElevationAngle', 5399 40001: 'MC_IpWinScal', # Media Cybernetics 5400 40100: 'MC_IdOld', 5401 40965: 'InteroperabilityTag', # InteropOffset 5402 40091: 'XPTitle', 5403 40092: 'XPComment', 5404 40093: 'XPAuthor', 5405 40094: 'XPKeywords', 5406 40095: 'XPSubject', 5407 40960: 'FlashpixVersion', 5408 40961: 'ColorSpace', 5409 40962: 'PixelXDimension', 5410 40963: 'PixelYDimension', 5411 40964: 'RelatedSoundFile', 5412 40976: 'SamsungRawPointersOffset', 5413 40977: 'SamsungRawPointersLength', 5414 41217: 'SamsungRawByteOrder', 5415 41218: 'SamsungRawUnknown', 5416 41483: 'FlashEnergy', 5417 41484: 'SpatialFrequencyResponse', 5418 41485: 'Noise_', # 37389 5419 41486: 'FocalPlaneXResolution_', # 37390 5420 41487: 'FocalPlaneYResolution_', # 37391 5421 41488: 'FocalPlaneResolutionUnit_', # 37392 5422 41489: 'ImageNumber_', # 37393 5423 41490: 'SecurityClassification_', # 37394 5424 41491: 'ImageHistory_', # 37395 5425 41492: 'SubjectLocation_', # 37395 5426 41493: 'ExposureIndex_ ', # 37397 5427 41494: 'TIFF-EPStandardID', 5428 41495: 'SensingMethod_', # 37399 5429 41728: 'FileSource', 5430 41729: 'SceneType', 5431 41730: 'CFAPattern_', # 33422 5432 41985: 'CustomRendered', 5433 41986: 'ExposureMode', 5434 41987: 'WhiteBalance', 5435 41988: 'DigitalZoomRatio', 5436 41989: 'FocalLengthIn35mmFilm', 5437 41990: 'SceneCaptureType', 5438 41991: 'GainControl', 5439 41992: 'Contrast', 5440 41993: 'Saturation', 5441 41994: 'Sharpness', 5442 41995: 'DeviceSettingDescription', 5443 41996: 'SubjectDistanceRange', 5444 42016: 'ImageUniqueID', 5445 42032: 'CameraOwnerName', 5446 42033: 'BodySerialNumber', 5447 42034: 'LensSpecification', 5448 42035: 'LensMake', 5449 42036: 'LensModel', 5450 42037: 'LensSerialNumber', 5451 42112: 'GDAL_METADATA', 5452 42113: 'GDAL_NODATA', 5453 42240: 'Gamma', 5454 43314: 'NIHImageHeader', 5455 44992: 'ExpandSoftware', 5456 44993: 'ExpandLens', 5457 44994: 'ExpandFilm', 5458 44995: 'ExpandFilterLens', 5459 44996: 'ExpandScanner', 5460 44997: 'ExpandFlashLamp', 5461 48129: 'PixelFormat', # HDP and WDP 5462 48130: 'Transformation', 5463 48131: 'Uncompressed', 5464 48132: 'ImageType', 5465 48256: 'ImageWidth_', # 256 5466 48257: 'ImageHeight_', 5467 48258: 'WidthResolution', 5468 48259: 'HeightResolution', 5469 48320: 'ImageOffset', 5470 48321: 'ImageByteCount', 5471 48322: 'AlphaOffset', 5472 48323: 'AlphaByteCount', 5473 48324: 'ImageDataDiscard', 5474 48325: 'AlphaDataDiscard', 5475 50215: 'OceScanjobDescription', 5476 50216: 'OceApplicationSelector', 5477 50217: 'OceIdentificationNumber', 5478 50218: 'OceImageLogicCharacteristics', 5479 50255: 'Annotations', 5480 50288: 'MC_Id', # Media Cybernetics 5481 50289: 'MC_XYPosition', 5482 50290: 'MC_ZPosition', 5483 50291: 'MC_XYCalibration', 5484 50292: 'MC_LensCharacteristics', 5485 50293: 'MC_ChannelName', 5486 50294: 'MC_ExcitationWavelength', 5487 50295: 'MC_TimeStamp', 5488 50296: 'MC_FrameProperties', 5489 50341: 'PrintImageMatching', 5490 50495: 'PCO_RAW', # TODO: PCO CamWare 5491 50547: 'OriginalFileName', 5492 50560: 'USPTO_OriginalContentType', # US Patent Office 5493 50561: 'USPTO_RotationCode', 5494 50656: 'CR2CFAPattern', 5495 50706: 'DNGVersion', # DNG 50706 .. 51112 5496 50707: 'DNGBackwardVersion', 5497 50708: 'UniqueCameraModel', 5498 50709: 'LocalizedCameraModel', 5499 50710: 'CFAPlaneColor', 5500 50711: 'CFALayout', 5501 50712: 'LinearizationTable', 5502 50713: 'BlackLevelRepeatDim', 5503 50714: 'BlackLevel', 5504 50715: 'BlackLevelDeltaH', 5505 50716: 'BlackLevelDeltaV', 5506 50717: 'WhiteLevel', 5507 50718: 'DefaultScale', 5508 50719: 'DefaultCropOrigin', 5509 50720: 'DefaultCropSize', 5510 50721: 'ColorMatrix1', 5511 50722: 'ColorMatrix2', 5512 50723: 'CameraCalibration1', 5513 50724: 'CameraCalibration2', 5514 50725: 'ReductionMatrix1', 5515 50726: 'ReductionMatrix2', 5516 50727: 'AnalogBalance', 5517 50728: 'AsShotNeutral', 5518 50729: 'AsShotWhiteXY', 5519 50730: 'BaselineExposure', 5520 50731: 'BaselineNoise', 5521 50732: 'BaselineSharpness', 5522 50733: 'BayerGreenSplit', 5523 50734: 'LinearResponseLimit', 5524 50735: 'CameraSerialNumber', 5525 50736: 'LensInfo', 5526 50737: 'ChromaBlurRadius', 5527 50738: 'AntiAliasStrength', 5528 50739: 'ShadowScale', 5529 50740: 'DNGPrivateData', 5530 50741: 'MakerNoteSafety', 5531 50752: 'RawImageSegmentation', 5532 50778: 'CalibrationIlluminant1', 5533 50779: 'CalibrationIlluminant2', 5534 50780: 'BestQualityScale', 5535 50781: 'RawDataUniqueID', 5536 50784: 'AliasLayerMetadata', 5537 50827: 'OriginalRawFileName', 5538 50828: 'OriginalRawFileData', 5539 50829: 'ActiveArea', 5540 50830: 'MaskedAreas', 5541 50831: 'AsShotICCProfile', 5542 50832: 'AsShotPreProfileMatrix', 5543 50833: 'CurrentICCProfile', 5544 50834: 'CurrentPreProfileMatrix', 5545 50838: 'IJMetadataByteCounts', 5546 50839: 'IJMetadata', 5547 50844: 'RPCCoefficientTag', 5548 50879: 'ColorimetricReference', 5549 50885: 'SRawType', 5550 50898: 'PanasonicTitle', 5551 50899: 'PanasonicTitle2', 5552 50931: 'CameraCalibrationSignature', 5553 50932: 'ProfileCalibrationSignature', 5554 50933: 'ProfileIFD', 5555 50934: 'AsShotProfileName', 5556 50935: 'NoiseReductionApplied', 5557 50936: 'ProfileName', 5558 50937: 'ProfileHueSatMapDims', 5559 50938: 'ProfileHueSatMapData1', 5560 50939: 'ProfileHueSatMapData2', 5561 50940: 'ProfileToneCurve', 5562 50941: 'ProfileEmbedPolicy', 5563 50942: 'ProfileCopyright', 5564 50964: 'ForwardMatrix1', 5565 50965: 'ForwardMatrix2', 5566 50966: 'PreviewApplicationName', 5567 50967: 'PreviewApplicationVersion', 5568 50968: 'PreviewSettingsName', 5569 50969: 'PreviewSettingsDigest', 5570 50970: 'PreviewColorSpace', 5571 50971: 'PreviewDateTime', 5572 50972: 'RawImageDigest', 5573 50973: 'OriginalRawFileDigest', 5574 50974: 'SubTileBlockSize', 5575 50975: 'RowInterleaveFactor', 5576 50981: 'ProfileLookTableDims', 5577 50982: 'ProfileLookTableData', 5578 51008: 'OpcodeList1', 5579 51009: 'OpcodeList2', 5580 51022: 'OpcodeList3', 5581 51023: 'FibicsXML', # 5582 51041: 'NoiseProfile', 5583 51043: 'TimeCodes', 5584 51044: 'FrameRate', 5585 51058: 'TStop', 5586 51081: 'ReelName', 5587 51089: 'OriginalDefaultFinalSize', 5588 51090: 'OriginalBestQualitySize', 5589 51091: 'OriginalDefaultCropSize', 5590 51105: 'CameraLabel', 5591 51107: 'ProfileHueSatMapEncoding', 5592 51108: 'ProfileLookTableEncoding', 5593 51109: 'BaselineExposureOffset', 5594 51110: 'DefaultBlackRender', 5595 51111: 'NewRawImageDigest', 5596 51112: 'RawToPreviewGain', 5597 51125: 'DefaultUserCrop', 5598 51123: 'MicroManagerMetadata', 5599 59932: 'Padding', 5600 59933: 'OffsetSchema', 5601 # Reusable Tags 65000-65535 5602 # 65000: Dimap_Document XML 5603 # 65000-65112: Photoshop Camera RAW EXIF tags 5604 # 65000: 'OwnerName', 5605 # 65001: 'SerialNumber', 5606 # 65002: 'Lens', 5607 # 65024: 'KDC_IFD', 5608 # 65100: 'RawFile', 5609 # 65101: 'Converter', 5610 # 65102: 'WhiteBalance', 5611 # 65105: 'Exposure', 5612 # 65106: 'Shadows', 5613 # 65107: 'Brightness', 5614 # 65108: 'Contrast', 5615 # 65109: 'Saturation', 5616 # 65110: 'Sharpness', 5617 # 65111: 'Smoothness', 5618 # 65112: 'MoireFilter', 5619 65200: 'FlexXML', # 5620 65563: 'PerSample', 5621 } 5622 5623 def TAG_NAMES(): 5624 return {v: c for c, v in TIFF.TAGS.items()} 5625 5626 def TAG_READERS(): 5627 # Map TIFF tag codes to import functions 5628 return { 5629 320: read_colormap, 5630 # 700: read_bytes, # read_utf8, 5631 # 34377: read_bytes, 5632 33723: read_bytes, 5633 # 34675: read_bytes, 5634 33628: read_uic1tag, # Universal Imaging Corp STK 5635 33629: read_uic2tag, 5636 33630: read_uic3tag, 5637 33631: read_uic4tag, 5638 34118: read_cz_sem, # Carl Zeiss SEM 5639 34361: read_mm_header, # Olympus FluoView 5640 34362: read_mm_stamp, 5641 34363: read_numpy, # MM_Unknown 5642 34386: read_numpy, # MM_UserBlock 5643 34412: read_cz_lsminfo, # Carl Zeiss LSM 5644 34680: read_fei_metadata, # S-FEG 5645 34682: read_fei_metadata, # Helios NanoLab 5646 37706: read_tvips_header, # TVIPS EMMENU 5647 37724: read_bytes, # ImageSourceData 5648 33923: read_bytes, # read_leica_magic 5649 43314: read_nih_image_header, 5650 # 40001: read_bytes, 5651 40100: read_bytes, 5652 50288: read_bytes, 5653 50296: read_bytes, 5654 50839: read_bytes, 5655 51123: read_json, 5656 34665: read_exif_ifd, 5657 34853: read_gps_ifd, 5658 40965: read_interoperability_ifd, 5659 } 5660 5661 def TAG_TUPLE(): 5662 # Tags whose values must be stored as tuples 5663 return frozenset((273, 279, 324, 325, 530, 531, 34736)) 5664 5665 def TAG_ATTRIBUTES(): 5666 # Map tag codes to TiffPage attribute names 5667 return { 5668 'ImageWidth': 'imagewidth', 5669 'ImageLength': 'imagelength', 5670 'BitsPerSample': 'bitspersample', 5671 'Compression': 'compression', 5672 'PlanarConfiguration': 'planarconfig', 5673 'FillOrder': 'fillorder', 5674 'PhotometricInterpretation': 'photometric', 5675 'ColorMap': 'colormap', 5676 'ImageDescription': 'description', 5677 'ImageDescription1': 'description1', 5678 'SamplesPerPixel': 'samplesperpixel', 5679 'RowsPerStrip': 'rowsperstrip', 5680 'Software': 'software', 5681 'Predictor': 'predictor', 5682 'TileWidth': 'tilewidth', 5683 'TileLength': 'tilelength', 5684 'ExtraSamples': 'extrasamples', 5685 'SampleFormat': 'sampleformat', 5686 'ImageDepth': 'imagedepth', 5687 'TileDepth': 'tiledepth', 5688 } 5689 5690 def TAG_ENUM(): 5691 return { 5692 # 254: TIFF.FILETYPE, 5693 255: TIFF.OFILETYPE, 5694 259: TIFF.COMPRESSION, 5695 262: TIFF.PHOTOMETRIC, 5696 263: TIFF.THRESHHOLD, 5697 266: TIFF.FILLORDER, 5698 274: TIFF.ORIENTATION, 5699 284: TIFF.PLANARCONFIG, 5700 290: TIFF.GRAYRESPONSEUNIT, 5701 # 292: TIFF.GROUP3OPT, 5702 # 293: TIFF.GROUP4OPT, 5703 296: TIFF.RESUNIT, 5704 300: TIFF.COLORRESPONSEUNIT, 5705 317: TIFF.PREDICTOR, 5706 338: TIFF.EXTRASAMPLE, 5707 339: TIFF.SAMPLEFORMAT, 5708 # 512: TIFF.JPEGPROC, 5709 # 531: TIFF.YCBCRPOSITION, 5710 } 5711 5712 def FILETYPE(): 5713 class FILETYPE(enum.IntFlag): 5714 # Python 3.6 only 5715 UNDEFINED = 0 5716 REDUCEDIMAGE = 1 5717 PAGE = 2 5718 MASK = 4 5719 return FILETYPE 5720 5721 def OFILETYPE(): 5722 class OFILETYPE(enum.IntEnum): 5723 UNDEFINED = 0 5724 IMAGE = 1 5725 REDUCEDIMAGE = 2 5726 PAGE = 3 5727 return OFILETYPE 5728 5729 def COMPRESSION(): 5730 class COMPRESSION(enum.IntEnum): 5731 NONE = 1 # Uncompressed 5732 CCITTRLE = 2 # CCITT 1D 5733 CCITT_T4 = 3 # 'T4/Group 3 Fax', 5734 CCITT_T6 = 4 # 'T6/Group 4 Fax', 5735 LZW = 5 5736 OJPEG = 6 # old-style JPEG 5737 JPEG = 7 5738 ADOBE_DEFLATE = 8 5739 JBIG_BW = 9 5740 JBIG_COLOR = 10 5741 JPEG_99 = 99 5742 KODAK_262 = 262 5743 NEXT = 32766 5744 SONY_ARW = 32767 5745 PACKED_RAW = 32769 5746 SAMSUNG_SRW = 32770 5747 CCIRLEW = 32771 5748 SAMSUNG_SRW2 = 32772 5749 PACKBITS = 32773 5750 THUNDERSCAN = 32809 5751 IT8CTPAD = 32895 5752 IT8LW = 32896 5753 IT8MP = 32897 5754 IT8BL = 32898 5755 PIXARFILM = 32908 5756 PIXARLOG = 32909 5757 DEFLATE = 32946 5758 DCS = 32947 5759 APERIO_JP2000_YCBC = 33003 # Leica Aperio 5760 APERIO_JP2000_RGB = 33005 # Leica Aperio 5761 JBIG = 34661 5762 SGILOG = 34676 5763 SGILOG24 = 34677 5764 JPEG2000 = 34712 5765 NIKON_NEF = 34713 5766 JBIG2 = 34715 5767 MDI_BINARY = 34718 # 'Microsoft Document Imaging 5768 MDI_PROGRESSIVE = 34719 # 'Microsoft Document Imaging 5769 MDI_VECTOR = 34720 # 'Microsoft Document Imaging 5770 JPEG_LOSSY = 34892 5771 LZMA = 34925 5772 ZSTD = 34926 5773 OPS_PNG = 34933 # Objective Pathology Services 5774 OPS_JPEGXR = 34934 # Objective Pathology Services 5775 PIXTIFF = 50013 5776 KODAK_DCR = 65000 5777 PENTAX_PEF = 65535 5778 # def __bool__(self): return self != 1 # Python 3.6 only 5779 return COMPRESSION 5780 5781 def PHOTOMETRIC(): 5782 class PHOTOMETRIC(enum.IntEnum): 5783 MINISWHITE = 0 5784 MINISBLACK = 1 5785 RGB = 2 5786 PALETTE = 3 5787 MASK = 4 5788 SEPARATED = 5 # CMYK 5789 YCBCR = 6 5790 CIELAB = 8 5791 ICCLAB = 9 5792 ITULAB = 10 5793 CFA = 32803 # Color Filter Array 5794 LOGL = 32844 5795 LOGLUV = 32845 5796 LINEAR_RAW = 34892 5797 return PHOTOMETRIC 5798 5799 def THRESHHOLD(): 5800 class THRESHHOLD(enum.IntEnum): 5801 BILEVEL = 1 5802 HALFTONE = 2 5803 ERRORDIFFUSE = 3 5804 return THRESHHOLD 5805 5806 def FILLORDER(): 5807 class FILLORDER(enum.IntEnum): 5808 MSB2LSB = 1 5809 LSB2MSB = 2 5810 return FILLORDER 5811 5812 def ORIENTATION(): 5813 class ORIENTATION(enum.IntEnum): 5814 TOPLEFT = 1 5815 TOPRIGHT = 2 5816 BOTRIGHT = 3 5817 BOTLEFT = 4 5818 LEFTTOP = 5 5819 RIGHTTOP = 6 5820 RIGHTBOT = 7 5821 LEFTBOT = 8 5822 return ORIENTATION 5823 5824 def PLANARCONFIG(): 5825 class PLANARCONFIG(enum.IntEnum): 5826 CONTIG = 1 5827 SEPARATE = 2 5828 return PLANARCONFIG 5829 5830 def GRAYRESPONSEUNIT(): 5831 class GRAYRESPONSEUNIT(enum.IntEnum): 5832 _10S = 1 5833 _100S = 2 5834 _1000S = 3 5835 _10000S = 4 5836 _100000S = 5 5837 return GRAYRESPONSEUNIT 5838 5839 def GROUP4OPT(): 5840 class GROUP4OPT(enum.IntEnum): 5841 UNCOMPRESSED = 2 5842 return GROUP4OPT 5843 5844 def RESUNIT(): 5845 class RESUNIT(enum.IntEnum): 5846 NONE = 1 5847 INCH = 2 5848 CENTIMETER = 3 5849 # def __bool__(self): return self != 1 # Python 3.6 only 5850 return RESUNIT 5851 5852 def COLORRESPONSEUNIT(): 5853 class COLORRESPONSEUNIT(enum.IntEnum): 5854 _10S = 1 5855 _100S = 2 5856 _1000S = 3 5857 _10000S = 4 5858 _100000S = 5 5859 return COLORRESPONSEUNIT 5860 5861 def PREDICTOR(): 5862 class PREDICTOR(enum.IntEnum): 5863 NONE = 1 5864 HORIZONTAL = 2 5865 FLOATINGPOINT = 3 5866 # def __bool__(self): return self != 1 # Python 3.6 only 5867 return PREDICTOR 5868 5869 def EXTRASAMPLE(): 5870 class EXTRASAMPLE(enum.IntEnum): 5871 UNSPECIFIED = 0 5872 ASSOCALPHA = 1 5873 UNASSALPHA = 2 5874 return EXTRASAMPLE 5875 5876 def SAMPLEFORMAT(): 5877 class SAMPLEFORMAT(enum.IntEnum): 5878 UINT = 1 5879 INT = 2 5880 IEEEFP = 3 5881 VOID = 4 5882 COMPLEXINT = 5 5883 COMPLEXIEEEFP = 6 5884 return SAMPLEFORMAT 5885 5886 def DATATYPES(): 5887 class DATATYPES(enum.IntEnum): 5888 NOTYPE = 0 5889 BYTE = 1 5890 ASCII = 2 5891 SHORT = 3 5892 LONG = 4 5893 RATIONAL = 5 5894 SBYTE = 6 5895 UNDEFINED = 7 5896 SSHORT = 8 5897 SLONG = 9 5898 SRATIONAL = 10 5899 FLOAT = 11 5900 DOUBLE = 12 5901 IFD = 13 5902 UNICODE = 14 5903 COMPLEX = 15 5904 LONG8 = 16 5905 SLONG8 = 17 5906 IFD8 = 18 5907 return DATATYPES 5908 5909 def DATA_FORMATS(): 5910 # Map TIFF DATATYPES to Python struct formats 5911 return { 5912 1: '1B', # BYTE 8-bit unsigned integer. 5913 2: '1s', # ASCII 8-bit byte that contains a 7-bit ASCII code; 5914 # the last byte must be NULL (binary zero). 5915 3: '1H', # SHORT 16-bit (2-byte) unsigned integer 5916 4: '1I', # LONG 32-bit (4-byte) unsigned integer. 5917 5: '2I', # RATIONAL Two LONGs: the first represents the numerator 5918 # of a fraction; the second, the denominator. 5919 6: '1b', # SBYTE An 8-bit signed (twos-complement) integer. 5920 7: '1B', # UNDEFINED An 8-bit byte that may contain anything, 5921 # depending on the definition of the field. 5922 8: '1h', # SSHORT A 16-bit (2-byte) signed (twos-complement) 5923 # integer. 5924 9: '1i', # SLONG A 32-bit (4-byte) signed (twos-complement) 5925 # integer. 5926 10: '2i', # SRATIONAL Two SLONGs: the first represents the 5927 # numerator of a fraction, the second the denominator. 5928 11: '1f', # FLOAT Single precision (4-byte) IEEE format. 5929 12: '1d', # DOUBLE Double precision (8-byte) IEEE format. 5930 13: '1I', # IFD unsigned 4 byte IFD offset. 5931 # 14: '', # UNICODE 5932 # 15: '', # COMPLEX 5933 16: '1Q', # LONG8 unsigned 8 byte integer (BigTiff) 5934 17: '1q', # SLONG8 signed 8 byte integer (BigTiff) 5935 18: '1Q', # IFD8 unsigned 8 byte IFD offset (BigTiff) 5936 } 5937 5938 def DATA_DTYPES(): 5939 # Map numpy dtypes to TIFF DATATYPES 5940 return {'B': 1, 's': 2, 'H': 3, 'I': 4, '2I': 5, 'b': 6, 5941 'h': 8, 'i': 9, '2i': 10, 'f': 11, 'd': 12, 'Q': 16, 'q': 17} 5942 5943 def SAMPLE_DTYPES(): 5944 # Map TIFF SampleFormats and BitsPerSample to numpy dtype 5945 return { 5946 (1, 1): '?', # bitmap 5947 (1, 2): 'B', 5948 (1, 3): 'B', 5949 (1, 4): 'B', 5950 (1, 5): 'B', 5951 (1, 6): 'B', 5952 (1, 7): 'B', 5953 (1, 8): 'B', 5954 (1, 9): 'H', 5955 (1, 10): 'H', 5956 (1, 11): 'H', 5957 (1, 12): 'H', 5958 (1, 13): 'H', 5959 (1, 14): 'H', 5960 (1, 15): 'H', 5961 (1, 16): 'H', 5962 (1, 17): 'I', 5963 (1, 18): 'I', 5964 (1, 19): 'I', 5965 (1, 20): 'I', 5966 (1, 21): 'I', 5967 (1, 22): 'I', 5968 (1, 23): 'I', 5969 (1, 24): 'I', 5970 (1, 25): 'I', 5971 (1, 26): 'I', 5972 (1, 27): 'I', 5973 (1, 28): 'I', 5974 (1, 29): 'I', 5975 (1, 30): 'I', 5976 (1, 31): 'I', 5977 (1, 32): 'I', 5978 (1, 64): 'Q', 5979 (2, 8): 'b', 5980 (2, 16): 'h', 5981 (2, 32): 'i', 5982 (2, 64): 'q', 5983 (3, 16): 'e', 5984 (3, 32): 'f', 5985 (3, 64): 'd', 5986 (6, 64): 'F', 5987 (6, 128): 'D', 5988 (1, (5, 6, 5)): 'B', 5989 } 5990 5991 def COMPESSORS(): 5992 # Map COMPRESSION to compress functions and default compression levels 5993 5994 class Compressors(object): 5995 """Delay import compressor functions.""" 5996 def __init__(self): 5997 self._compressors = {8: (zlib.compress, 6), 5998 32946: (zlib.compress, 6)} 5999 6000 def __getitem__(self, key): 6001 if key in self._compressors: 6002 return self._compressors[key] 6003 6004 if key == 34925: 6005 try: 6006 import lzma # delayed import 6007 except ImportError: 6008 try: 6009 import backports.lzma as lzma # delayed import 6010 except ImportError: 6011 raise KeyError 6012 6013 def lzma_compress(x, level): 6014 return lzma.compress(x) 6015 6016 self._compressors[key] = lzma_compress, 0 6017 return lzma_compress, 0 6018 6019 if key == 34926: 6020 try: 6021 import zstd # delayed import 6022 except ImportError: 6023 raise KeyError 6024 self._compressors[key] = zstd.compress, 9 6025 return zstd.compress, 9 6026 6027 raise KeyError 6028 6029 def __contains__(self, key): 6030 try: 6031 self[key] 6032 return True 6033 except KeyError: 6034 return False 6035 6036 return Compressors() 6037 6038 def DECOMPESSORS(): 6039 # Map COMPRESSION to decompress functions 6040 6041 class Decompressors(object): 6042 """Delay import decompressor functions.""" 6043 def __init__(self): 6044 self._decompressors = {None: identityfunc, 6045 1: identityfunc, 6046 5: decode_lzw, 6047 8: zlib.decompress, 6048 32773: decode_packbits, 6049 32946: zlib.decompress} 6050 6051 def __getitem__(self, key): 6052 if key in self._decompressors: 6053 return self._decompressors[key] 6054 6055 if key == 7: 6056 try: 6057 from imagecodecs import jpeg, jpeg_12 6058 except ImportError: 6059 raise KeyError 6060 6061 def decode_jpeg(x, table, bps, colorspace=None): 6062 if bps == 8: 6063 return jpeg.decode_jpeg(x, table, colorspace) 6064 elif bps == 12: 6065 return jpeg_12.decode_jpeg_12(x, table, colorspace) 6066 else: 6067 raise ValueError('bitspersample not supported') 6068 6069 self._decompressors[key] = decode_jpeg 6070 return decode_jpeg 6071 6072 if key == 34925: 6073 try: 6074 import lzma # delayed import 6075 except ImportError: 6076 try: 6077 import backports.lzma as lzma # delayed import 6078 except ImportError: 6079 raise KeyError 6080 self._decompressors[key] = lzma.decompress 6081 return lzma.decompress 6082 6083 if key == 34926: 6084 try: 6085 import zstd # delayed import 6086 except ImportError: 6087 raise KeyError 6088 self._decompressors[key] = zstd.decompress 6089 return zstd.decompress 6090 raise KeyError 6091 6092 def __contains__(self, item): 6093 try: 6094 self[item] 6095 return True 6096 except KeyError: 6097 return False 6098 6099 return Decompressors() 6100 6101 def FRAME_ATTRS(): 6102 # Attributes that a TiffFrame shares with its keyframe 6103 return set('shape ndim size dtype axes is_final'.split()) 6104 6105 def FILE_FLAGS(): 6106 # TiffFile and TiffPage 'is_\*' attributes 6107 exclude = set('reduced final memmappable contiguous tiled ' 6108 'chroma_subsampled'.split()) 6109 return set(a[3:] for a in dir(TiffPage) 6110 if a[:3] == 'is_' and a[3:] not in exclude) 6111 6112 def FILE_EXTENSIONS(): 6113 # TIFF file extensions 6114 return tuple('tif tiff ome.tif lsm stk qptiff pcoraw ' 6115 'gel seq svs bif tf8 tf2 btf'.split()) 6116 6117 def FILEOPEN_FILTER(): 6118 # String for use in Windows File Open box 6119 return [('%s files' % ext.upper(), '*.%s' % ext) 6120 for ext in TIFF.FILE_EXTENSIONS] + [('allfiles', '*')] 6121 6122 def AXES_LABELS(): 6123 # TODO: is there a standard for character axes labels? 6124 axes = { 6125 'X': 'width', 6126 'Y': 'height', 6127 'Z': 'depth', 6128 'S': 'sample', # rgb(a) 6129 'I': 'series', # general sequence, plane, page, IFD 6130 'T': 'time', 6131 'C': 'channel', # color, emission wavelength 6132 'A': 'angle', 6133 'P': 'phase', # formerly F # P is Position in LSM! 6134 'R': 'tile', # region, point, mosaic 6135 'H': 'lifetime', # histogram 6136 'E': 'lambda', # excitation wavelength 6137 'L': 'exposure', # lux 6138 'V': 'event', 6139 'Q': 'other', 6140 'M': 'mosaic', # LSM 6 6141 } 6142 axes.update(dict((v, k) for k, v in axes.items())) 6143 return axes 6144 6145 def ANDOR_TAGS(): 6146 # Andor Technology tags #4864 - 5030 6147 return set(range(4864, 5030)) 6148 6149 def EXIF_TAGS(): 6150 tags = { 6151 # 65000 - 65112 Photoshop Camera RAW EXIF tags 6152 65000: 'OwnerName', 6153 65001: 'SerialNumber', 6154 65002: 'Lens', 6155 65100: 'RawFile', 6156 65101: 'Converter', 6157 65102: 'WhiteBalance', 6158 65105: 'Exposure', 6159 65106: 'Shadows', 6160 65107: 'Brightness', 6161 65108: 'Contrast', 6162 65109: 'Saturation', 6163 65110: 'Sharpness', 6164 65111: 'Smoothness', 6165 65112: 'MoireFilter', 6166 } 6167 tags.update(TIFF.TAGS) 6168 return tags 6169 6170 def GPS_TAGS(): 6171 return { 6172 0: 'GPSVersionID', 6173 1: 'GPSLatitudeRef', 6174 2: 'GPSLatitude', 6175 3: 'GPSLongitudeRef', 6176 4: 'GPSLongitude', 6177 5: 'GPSAltitudeRef', 6178 6: 'GPSAltitude', 6179 7: 'GPSTimeStamp', 6180 8: 'GPSSatellites', 6181 9: 'GPSStatus', 6182 10: 'GPSMeasureMode', 6183 11: 'GPSDOP', 6184 12: 'GPSSpeedRef', 6185 13: 'GPSSpeed', 6186 14: 'GPSTrackRef', 6187 15: 'GPSTrack', 6188 16: 'GPSImgDirectionRef', 6189 17: 'GPSImgDirection', 6190 18: 'GPSMapDatum', 6191 19: 'GPSDestLatitudeRef', 6192 20: 'GPSDestLatitude', 6193 21: 'GPSDestLongitudeRef', 6194 22: 'GPSDestLongitude', 6195 23: 'GPSDestBearingRef', 6196 24: 'GPSDestBearing', 6197 25: 'GPSDestDistanceRef', 6198 26: 'GPSDestDistance', 6199 27: 'GPSProcessingMethod', 6200 28: 'GPSAreaInformation', 6201 29: 'GPSDateStamp', 6202 30: 'GPSDifferential', 6203 31: 'GPSHPositioningError', 6204 } 6205 6206 def IOP_TAGS(): 6207 return { 6208 1: 'InteroperabilityIndex', 6209 2: 'InteroperabilityVersion', 6210 4096: 'RelatedImageFileFormat', 6211 4097: 'RelatedImageWidth', 6212 4098: 'RelatedImageLength', 6213 } 6214 6215 def GEO_KEYS(): 6216 return { 6217 1024: 'GTModelTypeGeoKey', 6218 1025: 'GTRasterTypeGeoKey', 6219 1026: 'GTCitationGeoKey', 6220 2048: 'GeographicTypeGeoKey', 6221 2049: 'GeogCitationGeoKey', 6222 2050: 'GeogGeodeticDatumGeoKey', 6223 2051: 'GeogPrimeMeridianGeoKey', 6224 2052: 'GeogLinearUnitsGeoKey', 6225 2053: 'GeogLinearUnitSizeGeoKey', 6226 2054: 'GeogAngularUnitsGeoKey', 6227 2055: 'GeogAngularUnitsSizeGeoKey', 6228 2056: 'GeogEllipsoidGeoKey', 6229 2057: 'GeogSemiMajorAxisGeoKey', 6230 2058: 'GeogSemiMinorAxisGeoKey', 6231 2059: 'GeogInvFlatteningGeoKey', 6232 2060: 'GeogAzimuthUnitsGeoKey', 6233 2061: 'GeogPrimeMeridianLongGeoKey', 6234 2062: 'GeogTOWGS84GeoKey', 6235 3059: 'ProjLinearUnitsInterpCorrectGeoKey', # GDAL 6236 3072: 'ProjectedCSTypeGeoKey', 6237 3073: 'PCSCitationGeoKey', 6238 3074: 'ProjectionGeoKey', 6239 3075: 'ProjCoordTransGeoKey', 6240 3076: 'ProjLinearUnitsGeoKey', 6241 3077: 'ProjLinearUnitSizeGeoKey', 6242 3078: 'ProjStdParallel1GeoKey', 6243 3079: 'ProjStdParallel2GeoKey', 6244 3080: 'ProjNatOriginLongGeoKey', 6245 3081: 'ProjNatOriginLatGeoKey', 6246 3082: 'ProjFalseEastingGeoKey', 6247 3083: 'ProjFalseNorthingGeoKey', 6248 3084: 'ProjFalseOriginLongGeoKey', 6249 3085: 'ProjFalseOriginLatGeoKey', 6250 3086: 'ProjFalseOriginEastingGeoKey', 6251 3087: 'ProjFalseOriginNorthingGeoKey', 6252 3088: 'ProjCenterLongGeoKey', 6253 3089: 'ProjCenterLatGeoKey', 6254 3090: 'ProjCenterEastingGeoKey', 6255 3091: 'ProjFalseOriginNorthingGeoKey', 6256 3092: 'ProjScaleAtNatOriginGeoKey', 6257 3093: 'ProjScaleAtCenterGeoKey', 6258 3094: 'ProjAzimuthAngleGeoKey', 6259 3095: 'ProjStraightVertPoleLongGeoKey', 6260 3096: 'ProjRectifiedGridAngleGeoKey', 6261 4096: 'VerticalCSTypeGeoKey', 6262 4097: 'VerticalCitationGeoKey', 6263 4098: 'VerticalDatumGeoKey', 6264 4099: 'VerticalUnitsGeoKey', 6265 } 6266 6267 def GEO_CODES(): 6268 try: 6269 from .tifffile_geodb import GEO_CODES # delayed import 6270 except (ImportError, ValueError): 6271 try: 6272 from tifffile_geodb import GEO_CODES # delayed import 6273 except (ImportError, ValueError): 6274 GEO_CODES = {} 6275 return GEO_CODES 6276 6277 def CZ_LSMINFO(): 6278 return [ 6279 ('MagicNumber', 'u4'), 6280 ('StructureSize', 'i4'), 6281 ('DimensionX', 'i4'), 6282 ('DimensionY', 'i4'), 6283 ('DimensionZ', 'i4'), 6284 ('DimensionChannels', 'i4'), 6285 ('DimensionTime', 'i4'), 6286 ('DataType', 'i4'), # DATATYPES 6287 ('ThumbnailX', 'i4'), 6288 ('ThumbnailY', 'i4'), 6289 ('VoxelSizeX', 'f8'), 6290 ('VoxelSizeY', 'f8'), 6291 ('VoxelSizeZ', 'f8'), 6292 ('OriginX', 'f8'), 6293 ('OriginY', 'f8'), 6294 ('OriginZ', 'f8'), 6295 ('ScanType', 'u2'), 6296 ('SpectralScan', 'u2'), 6297 ('TypeOfData', 'u4'), # TYPEOFDATA 6298 ('OffsetVectorOverlay', 'u4'), 6299 ('OffsetInputLut', 'u4'), 6300 ('OffsetOutputLut', 'u4'), 6301 ('OffsetChannelColors', 'u4'), 6302 ('TimeIntervall', 'f8'), 6303 ('OffsetChannelDataTypes', 'u4'), 6304 ('OffsetScanInformation', 'u4'), # SCANINFO 6305 ('OffsetKsData', 'u4'), 6306 ('OffsetTimeStamps', 'u4'), 6307 ('OffsetEventList', 'u4'), 6308 ('OffsetRoi', 'u4'), 6309 ('OffsetBleachRoi', 'u4'), 6310 ('OffsetNextRecording', 'u4'), 6311 # LSM 2.0 ends here 6312 ('DisplayAspectX', 'f8'), 6313 ('DisplayAspectY', 'f8'), 6314 ('DisplayAspectZ', 'f8'), 6315 ('DisplayAspectTime', 'f8'), 6316 ('OffsetMeanOfRoisOverlay', 'u4'), 6317 ('OffsetTopoIsolineOverlay', 'u4'), 6318 ('OffsetTopoProfileOverlay', 'u4'), 6319 ('OffsetLinescanOverlay', 'u4'), 6320 ('ToolbarFlags', 'u4'), 6321 ('OffsetChannelWavelength', 'u4'), 6322 ('OffsetChannelFactors', 'u4'), 6323 ('ObjectiveSphereCorrection', 'f8'), 6324 ('OffsetUnmixParameters', 'u4'), 6325 # LSM 3.2, 4.0 end here 6326 ('OffsetAcquisitionParameters', 'u4'), 6327 ('OffsetCharacteristics', 'u4'), 6328 ('OffsetPalette', 'u4'), 6329 ('TimeDifferenceX', 'f8'), 6330 ('TimeDifferenceY', 'f8'), 6331 ('TimeDifferenceZ', 'f8'), 6332 ('InternalUse1', 'u4'), 6333 ('DimensionP', 'i4'), 6334 ('DimensionM', 'i4'), 6335 ('DimensionsReserved', '16i4'), 6336 ('OffsetTilePositions', 'u4'), 6337 ('', '9u4'), # Reserved 6338 ('OffsetPositions', 'u4'), 6339 # ('', '21u4'), # must be 0 6340 ] 6341 6342 def CZ_LSMINFO_READERS(): 6343 # Import functions for CZ_LSMINFO sub-records 6344 # TODO: read more CZ_LSMINFO sub-records 6345 return { 6346 'ScanInformation': read_lsm_scaninfo, 6347 'TimeStamps': read_lsm_timestamps, 6348 'EventList': read_lsm_eventlist, 6349 'ChannelColors': read_lsm_channelcolors, 6350 'Positions': read_lsm_floatpairs, 6351 'TilePositions': read_lsm_floatpairs, 6352 'VectorOverlay': None, 6353 'InputLut': None, 6354 'OutputLut': None, 6355 'TimeIntervall': None, 6356 'ChannelDataTypes': None, 6357 'KsData': None, 6358 'Roi': None, 6359 'BleachRoi': None, 6360 'NextRecording': None, 6361 'MeanOfRoisOverlay': None, 6362 'TopoIsolineOverlay': None, 6363 'TopoProfileOverlay': None, 6364 'ChannelWavelength': None, 6365 'SphereCorrection': None, 6366 'ChannelFactors': None, 6367 'UnmixParameters': None, 6368 'AcquisitionParameters': None, 6369 'Characteristics': None, 6370 } 6371 6372 def CZ_LSMINFO_SCANTYPE(): 6373 # Map CZ_LSMINFO.ScanType to dimension order 6374 return { 6375 0: 'XYZCT', # 'Stack' normal x-y-z-scan 6376 1: 'XYZCT', # 'Z-Scan' x-z-plane Y=1 6377 2: 'XYZCT', # 'Line' 6378 3: 'XYTCZ', # 'Time Series Plane' time series x-y XYCTZ ? Z=1 6379 4: 'XYZTC', # 'Time Series z-Scan' time series x-z 6380 5: 'XYTCZ', # 'Time Series Mean-of-ROIs' 6381 6: 'XYZTC', # 'Time Series Stack' time series x-y-z 6382 7: 'XYCTZ', # Spline Scan 6383 8: 'XYCZT', # Spline Plane x-z 6384 9: 'XYTCZ', # Time Series Spline Plane x-z 6385 10: 'XYZCT', # 'Time Series Point' point mode 6386 } 6387 6388 def CZ_LSMINFO_DIMENSIONS(): 6389 # Map dimension codes to CZ_LSMINFO attribute 6390 return { 6391 'X': 'DimensionX', 6392 'Y': 'DimensionY', 6393 'Z': 'DimensionZ', 6394 'C': 'DimensionChannels', 6395 'T': 'DimensionTime', 6396 'P': 'DimensionP', 6397 'M': 'DimensionM', 6398 } 6399 6400 def CZ_LSMINFO_DATATYPES(): 6401 # Description of CZ_LSMINFO.DataType 6402 return { 6403 0: 'varying data types', 6404 1: '8 bit unsigned integer', 6405 2: '12 bit unsigned integer', 6406 5: '32 bit float', 6407 } 6408 6409 def CZ_LSMINFO_TYPEOFDATA(): 6410 # Description of CZ_LSMINFO.TypeOfData 6411 return { 6412 0: 'Original scan data', 6413 1: 'Calculated data', 6414 2: '3D reconstruction', 6415 3: 'Topography height map', 6416 } 6417 6418 def CZ_LSMINFO_SCANINFO_ARRAYS(): 6419 return { 6420 0x20000000: 'Tracks', 6421 0x30000000: 'Lasers', 6422 0x60000000: 'DetectionChannels', 6423 0x80000000: 'IlluminationChannels', 6424 0xa0000000: 'BeamSplitters', 6425 0xc0000000: 'DataChannels', 6426 0x11000000: 'Timers', 6427 0x13000000: 'Markers', 6428 } 6429 6430 def CZ_LSMINFO_SCANINFO_STRUCTS(): 6431 return { 6432 # 0x10000000: 'Recording', 6433 0x40000000: 'Track', 6434 0x50000000: 'Laser', 6435 0x70000000: 'DetectionChannel', 6436 0x90000000: 'IlluminationChannel', 6437 0xb0000000: 'BeamSplitter', 6438 0xd0000000: 'DataChannel', 6439 0x12000000: 'Timer', 6440 0x14000000: 'Marker', 6441 } 6442 6443 def CZ_LSMINFO_SCANINFO_ATTRIBUTES(): 6444 return { 6445 # Recording 6446 0x10000001: 'Name', 6447 0x10000002: 'Description', 6448 0x10000003: 'Notes', 6449 0x10000004: 'Objective', 6450 0x10000005: 'ProcessingSummary', 6451 0x10000006: 'SpecialScanMode', 6452 0x10000007: 'ScanType', 6453 0x10000008: 'ScanMode', 6454 0x10000009: 'NumberOfStacks', 6455 0x1000000a: 'LinesPerPlane', 6456 0x1000000b: 'SamplesPerLine', 6457 0x1000000c: 'PlanesPerVolume', 6458 0x1000000d: 'ImagesWidth', 6459 0x1000000e: 'ImagesHeight', 6460 0x1000000f: 'ImagesNumberPlanes', 6461 0x10000010: 'ImagesNumberStacks', 6462 0x10000011: 'ImagesNumberChannels', 6463 0x10000012: 'LinscanXySize', 6464 0x10000013: 'ScanDirection', 6465 0x10000014: 'TimeSeries', 6466 0x10000015: 'OriginalScanData', 6467 0x10000016: 'ZoomX', 6468 0x10000017: 'ZoomY', 6469 0x10000018: 'ZoomZ', 6470 0x10000019: 'Sample0X', 6471 0x1000001a: 'Sample0Y', 6472 0x1000001b: 'Sample0Z', 6473 0x1000001c: 'SampleSpacing', 6474 0x1000001d: 'LineSpacing', 6475 0x1000001e: 'PlaneSpacing', 6476 0x1000001f: 'PlaneWidth', 6477 0x10000020: 'PlaneHeight', 6478 0x10000021: 'VolumeDepth', 6479 0x10000023: 'Nutation', 6480 0x10000034: 'Rotation', 6481 0x10000035: 'Precession', 6482 0x10000036: 'Sample0time', 6483 0x10000037: 'StartScanTriggerIn', 6484 0x10000038: 'StartScanTriggerOut', 6485 0x10000039: 'StartScanEvent', 6486 0x10000040: 'StartScanTime', 6487 0x10000041: 'StopScanTriggerIn', 6488 0x10000042: 'StopScanTriggerOut', 6489 0x10000043: 'StopScanEvent', 6490 0x10000044: 'StopScanTime', 6491 0x10000045: 'UseRois', 6492 0x10000046: 'UseReducedMemoryRois', 6493 0x10000047: 'User', 6494 0x10000048: 'UseBcCorrection', 6495 0x10000049: 'PositionBcCorrection1', 6496 0x10000050: 'PositionBcCorrection2', 6497 0x10000051: 'InterpolationY', 6498 0x10000052: 'CameraBinning', 6499 0x10000053: 'CameraSupersampling', 6500 0x10000054: 'CameraFrameWidth', 6501 0x10000055: 'CameraFrameHeight', 6502 0x10000056: 'CameraOffsetX', 6503 0x10000057: 'CameraOffsetY', 6504 0x10000059: 'RtBinning', 6505 0x1000005a: 'RtFrameWidth', 6506 0x1000005b: 'RtFrameHeight', 6507 0x1000005c: 'RtRegionWidth', 6508 0x1000005d: 'RtRegionHeight', 6509 0x1000005e: 'RtOffsetX', 6510 0x1000005f: 'RtOffsetY', 6511 0x10000060: 'RtZoom', 6512 0x10000061: 'RtLinePeriod', 6513 0x10000062: 'Prescan', 6514 0x10000063: 'ScanDirectionZ', 6515 # Track 6516 0x40000001: 'MultiplexType', # 0 After Line; 1 After Frame 6517 0x40000002: 'MultiplexOrder', 6518 0x40000003: 'SamplingMode', # 0 Sample; 1 Line Avg; 2 Frame Avg 6519 0x40000004: 'SamplingMethod', # 1 Mean; 2 Sum 6520 0x40000005: 'SamplingNumber', 6521 0x40000006: 'Acquire', 6522 0x40000007: 'SampleObservationTime', 6523 0x4000000b: 'TimeBetweenStacks', 6524 0x4000000c: 'Name', 6525 0x4000000d: 'Collimator1Name', 6526 0x4000000e: 'Collimator1Position', 6527 0x4000000f: 'Collimator2Name', 6528 0x40000010: 'Collimator2Position', 6529 0x40000011: 'IsBleachTrack', 6530 0x40000012: 'IsBleachAfterScanNumber', 6531 0x40000013: 'BleachScanNumber', 6532 0x40000014: 'TriggerIn', 6533 0x40000015: 'TriggerOut', 6534 0x40000016: 'IsRatioTrack', 6535 0x40000017: 'BleachCount', 6536 0x40000018: 'SpiCenterWavelength', 6537 0x40000019: 'PixelTime', 6538 0x40000021: 'CondensorFrontlens', 6539 0x40000023: 'FieldStopValue', 6540 0x40000024: 'IdCondensorAperture', 6541 0x40000025: 'CondensorAperture', 6542 0x40000026: 'IdCondensorRevolver', 6543 0x40000027: 'CondensorFilter', 6544 0x40000028: 'IdTransmissionFilter1', 6545 0x40000029: 'IdTransmission1', 6546 0x40000030: 'IdTransmissionFilter2', 6547 0x40000031: 'IdTransmission2', 6548 0x40000032: 'RepeatBleach', 6549 0x40000033: 'EnableSpotBleachPos', 6550 0x40000034: 'SpotBleachPosx', 6551 0x40000035: 'SpotBleachPosy', 6552 0x40000036: 'SpotBleachPosz', 6553 0x40000037: 'IdTubelens', 6554 0x40000038: 'IdTubelensPosition', 6555 0x40000039: 'TransmittedLight', 6556 0x4000003a: 'ReflectedLight', 6557 0x4000003b: 'SimultanGrabAndBleach', 6558 0x4000003c: 'BleachPixelTime', 6559 # Laser 6560 0x50000001: 'Name', 6561 0x50000002: 'Acquire', 6562 0x50000003: 'Power', 6563 # DetectionChannel 6564 0x70000001: 'IntegrationMode', 6565 0x70000002: 'SpecialMode', 6566 0x70000003: 'DetectorGainFirst', 6567 0x70000004: 'DetectorGainLast', 6568 0x70000005: 'AmplifierGainFirst', 6569 0x70000006: 'AmplifierGainLast', 6570 0x70000007: 'AmplifierOffsFirst', 6571 0x70000008: 'AmplifierOffsLast', 6572 0x70000009: 'PinholeDiameter', 6573 0x7000000a: 'CountingTrigger', 6574 0x7000000b: 'Acquire', 6575 0x7000000c: 'PointDetectorName', 6576 0x7000000d: 'AmplifierName', 6577 0x7000000e: 'PinholeName', 6578 0x7000000f: 'FilterSetName', 6579 0x70000010: 'FilterName', 6580 0x70000013: 'IntegratorName', 6581 0x70000014: 'ChannelName', 6582 0x70000015: 'DetectorGainBc1', 6583 0x70000016: 'DetectorGainBc2', 6584 0x70000017: 'AmplifierGainBc1', 6585 0x70000018: 'AmplifierGainBc2', 6586 0x70000019: 'AmplifierOffsetBc1', 6587 0x70000020: 'AmplifierOffsetBc2', 6588 0x70000021: 'SpectralScanChannels', 6589 0x70000022: 'SpiWavelengthStart', 6590 0x70000023: 'SpiWavelengthStop', 6591 0x70000026: 'DyeName', 6592 0x70000027: 'DyeFolder', 6593 # IlluminationChannel 6594 0x90000001: 'Name', 6595 0x90000002: 'Power', 6596 0x90000003: 'Wavelength', 6597 0x90000004: 'Aquire', 6598 0x90000005: 'DetchannelName', 6599 0x90000006: 'PowerBc1', 6600 0x90000007: 'PowerBc2', 6601 # BeamSplitter 6602 0xb0000001: 'FilterSet', 6603 0xb0000002: 'Filter', 6604 0xb0000003: 'Name', 6605 # DataChannel 6606 0xd0000001: 'Name', 6607 0xd0000003: 'Acquire', 6608 0xd0000004: 'Color', 6609 0xd0000005: 'SampleType', 6610 0xd0000006: 'BitsPerSample', 6611 0xd0000007: 'RatioType', 6612 0xd0000008: 'RatioTrack1', 6613 0xd0000009: 'RatioTrack2', 6614 0xd000000a: 'RatioChannel1', 6615 0xd000000b: 'RatioChannel2', 6616 0xd000000c: 'RatioConst1', 6617 0xd000000d: 'RatioConst2', 6618 0xd000000e: 'RatioConst3', 6619 0xd000000f: 'RatioConst4', 6620 0xd0000010: 'RatioConst5', 6621 0xd0000011: 'RatioConst6', 6622 0xd0000012: 'RatioFirstImages1', 6623 0xd0000013: 'RatioFirstImages2', 6624 0xd0000014: 'DyeName', 6625 0xd0000015: 'DyeFolder', 6626 0xd0000016: 'Spectrum', 6627 0xd0000017: 'Acquire', 6628 # Timer 6629 0x12000001: 'Name', 6630 0x12000002: 'Description', 6631 0x12000003: 'Interval', 6632 0x12000004: 'TriggerIn', 6633 0x12000005: 'TriggerOut', 6634 0x12000006: 'ActivationTime', 6635 0x12000007: 'ActivationNumber', 6636 # Marker 6637 0x14000001: 'Name', 6638 0x14000002: 'Description', 6639 0x14000003: 'TriggerIn', 6640 0x14000004: 'TriggerOut', 6641 } 6642 6643 def NIH_IMAGE_HEADER(): 6644 return [ 6645 ('FileID', 'a8'), 6646 ('nLines', 'i2'), 6647 ('PixelsPerLine', 'i2'), 6648 ('Version', 'i2'), 6649 ('OldLutMode', 'i2'), 6650 ('OldnColors', 'i2'), 6651 ('Colors', 'u1', (3, 32)), 6652 ('OldColorStart', 'i2'), 6653 ('ColorWidth', 'i2'), 6654 ('ExtraColors', 'u2', (6, 3)), 6655 ('nExtraColors', 'i2'), 6656 ('ForegroundIndex', 'i2'), 6657 ('BackgroundIndex', 'i2'), 6658 ('XScale', 'f8'), 6659 ('Unused2', 'i2'), 6660 ('Unused3', 'i2'), 6661 ('UnitsID', 'i2'), # NIH_UNITS_TYPE 6662 ('p1', [('x', 'i2'), ('y', 'i2')]), 6663 ('p2', [('x', 'i2'), ('y', 'i2')]), 6664 ('CurveFitType', 'i2'), # NIH_CURVEFIT_TYPE 6665 ('nCoefficients', 'i2'), 6666 ('Coeff', 'f8', 6), 6667 ('UMsize', 'u1'), 6668 ('UM', 'a15'), 6669 ('UnusedBoolean', 'u1'), 6670 ('BinaryPic', 'b1'), 6671 ('SliceStart', 'i2'), 6672 ('SliceEnd', 'i2'), 6673 ('ScaleMagnification', 'f4'), 6674 ('nSlices', 'i2'), 6675 ('SliceSpacing', 'f4'), 6676 ('CurrentSlice', 'i2'), 6677 ('FrameInterval', 'f4'), 6678 ('PixelAspectRatio', 'f4'), 6679 ('ColorStart', 'i2'), 6680 ('ColorEnd', 'i2'), 6681 ('nColors', 'i2'), 6682 ('Fill1', '3u2'), 6683 ('Fill2', '3u2'), 6684 ('Table', 'u1'), # NIH_COLORTABLE_TYPE 6685 ('LutMode', 'u1'), # NIH_LUTMODE_TYPE 6686 ('InvertedTable', 'b1'), 6687 ('ZeroClip', 'b1'), 6688 ('XUnitSize', 'u1'), 6689 ('XUnit', 'a11'), 6690 ('StackType', 'i2'), # NIH_STACKTYPE_TYPE 6691 # ('UnusedBytes', 'u1', 200) 6692 ] 6693 6694 def NIH_COLORTABLE_TYPE(): 6695 return ('CustomTable', 'AppleDefault', 'Pseudo20', 'Pseudo32', 6696 'Rainbow', 'Fire1', 'Fire2', 'Ice', 'Grays', 'Spectrum') 6697 6698 def NIH_LUTMODE_TYPE(): 6699 return ('PseudoColor', 'OldAppleDefault', 'OldSpectrum', 'GrayScale', 6700 'ColorLut', 'CustomGrayscale') 6701 6702 def NIH_CURVEFIT_TYPE(): 6703 return ('StraightLine', 'Poly2', 'Poly3', 'Poly4', 'Poly5', 'ExpoFit', 6704 'PowerFit', 'LogFit', 'RodbardFit', 'SpareFit1', 6705 'Uncalibrated', 'UncalibratedOD') 6706 6707 def NIH_UNITS_TYPE(): 6708 return ('Nanometers', 'Micrometers', 'Millimeters', 'Centimeters', 6709 'Meters', 'Kilometers', 'Inches', 'Feet', 'Miles', 'Pixels', 6710 'OtherUnits') 6711 6712 def NIH_STACKTYPE_TYPE(): 6713 return ('VolumeStack', 'RGBStack', 'MovieStack', 'HSVStack') 6714 6715 def TVIPS_HEADER_V1(): 6716 # TVIPS TemData structure from EMMENU Help file 6717 return [ 6718 ('Version', 'i4'), 6719 ('CommentV1', 'a80'), 6720 ('HighTension', 'i4'), 6721 ('SphericalAberration', 'i4'), 6722 ('IlluminationAperture', 'i4'), 6723 ('Magnification', 'i4'), 6724 ('PostMagnification', 'i4'), 6725 ('FocalLength', 'i4'), 6726 ('Defocus', 'i4'), 6727 ('Astigmatism', 'i4'), 6728 ('AstigmatismDirection', 'i4'), 6729 ('BiprismVoltage', 'i4'), 6730 ('SpecimenTiltAngle', 'i4'), 6731 ('SpecimenTiltDirection', 'i4'), 6732 ('IlluminationTiltDirection', 'i4'), 6733 ('IlluminationTiltAngle', 'i4'), 6734 ('ImageMode', 'i4'), 6735 ('EnergySpread', 'i4'), 6736 ('ChromaticAberration', 'i4'), 6737 ('ShutterType', 'i4'), 6738 ('DefocusSpread', 'i4'), 6739 ('CcdNumber', 'i4'), 6740 ('CcdSize', 'i4'), 6741 ('OffsetXV1', 'i4'), 6742 ('OffsetYV1', 'i4'), 6743 ('PhysicalPixelSize', 'i4'), 6744 ('Binning', 'i4'), 6745 ('ReadoutSpeed', 'i4'), 6746 ('GainV1', 'i4'), 6747 ('SensitivityV1', 'i4'), 6748 ('ExposureTimeV1', 'i4'), 6749 ('FlatCorrected', 'i4'), 6750 ('DeadPxCorrected', 'i4'), 6751 ('ImageMean', 'i4'), 6752 ('ImageStd', 'i4'), 6753 ('DisplacementX', 'i4'), 6754 ('DisplacementY', 'i4'), 6755 ('DateV1', 'i4'), 6756 ('TimeV1', 'i4'), 6757 ('ImageMin', 'i4'), 6758 ('ImageMax', 'i4'), 6759 ('ImageStatisticsQuality', 'i4'), 6760 ] 6761 6762 def TVIPS_HEADER_V2(): 6763 return [ 6764 ('ImageName', 'V160'), # utf16 6765 ('ImageFolder', 'V160'), 6766 ('ImageSizeX', 'i4'), 6767 ('ImageSizeY', 'i4'), 6768 ('ImageSizeZ', 'i4'), 6769 ('ImageSizeE', 'i4'), 6770 ('ImageDataType', 'i4'), 6771 ('Date', 'i4'), 6772 ('Time', 'i4'), 6773 ('Comment', 'V1024'), 6774 ('ImageHistory', 'V1024'), 6775 ('Scaling', '16f4'), 6776 ('ImageStatistics', '16c16'), 6777 ('ImageType', 'i4'), 6778 ('ImageDisplaType', 'i4'), 6779 ('PixelSizeX', 'f4'), # distance between two px in x, [nm] 6780 ('PixelSizeY', 'f4'), # distance between two px in y, [nm] 6781 ('ImageDistanceZ', 'f4'), 6782 ('ImageDistanceE', 'f4'), 6783 ('ImageMisc', '32f4'), 6784 ('TemType', 'V160'), 6785 ('TemHighTension', 'f4'), 6786 ('TemAberrations', '32f4'), 6787 ('TemEnergy', '32f4'), 6788 ('TemMode', 'i4'), 6789 ('TemMagnification', 'f4'), 6790 ('TemMagnificationCorrection', 'f4'), 6791 ('PostMagnification', 'f4'), 6792 ('TemStageType', 'i4'), 6793 ('TemStagePosition', '5f4'), # x, y, z, a, b 6794 ('TemImageShift', '2f4'), 6795 ('TemBeamShift', '2f4'), 6796 ('TemBeamTilt', '2f4'), 6797 ('TilingParameters', '7f4'), # 0: tiling? 1:x 2:y 3: max x 6798 # 4: max y 5: overlap x 6: overlap y 6799 ('TemIllumination', '3f4'), # 0: spotsize 1: intensity 6800 ('TemShutter', 'i4'), 6801 ('TemMisc', '32f4'), 6802 ('CameraType', 'V160'), 6803 ('PhysicalPixelSizeX', 'f4'), 6804 ('PhysicalPixelSizeY', 'f4'), 6805 ('OffsetX', 'i4'), 6806 ('OffsetY', 'i4'), 6807 ('BinningX', 'i4'), 6808 ('BinningY', 'i4'), 6809 ('ExposureTime', 'f4'), 6810 ('Gain', 'f4'), 6811 ('ReadoutRate', 'f4'), 6812 ('FlatfieldDescription', 'V160'), 6813 ('Sensitivity', 'f4'), 6814 ('Dose', 'f4'), 6815 ('CamMisc', '32f4'), 6816 ('FeiMicroscopeInformation', 'V1024'), 6817 ('FeiSpecimenInformation', 'V1024'), 6818 ('Magic', 'u4'), 6819 ] 6820 6821 def MM_HEADER(): 6822 # Olympus FluoView MM_Header 6823 MM_DIMENSION = [ 6824 ('Name', 'a16'), 6825 ('Size', 'i4'), 6826 ('Origin', 'f8'), 6827 ('Resolution', 'f8'), 6828 ('Unit', 'a64')] 6829 return [ 6830 ('HeaderFlag', 'i2'), 6831 ('ImageType', 'u1'), 6832 ('ImageName', 'a257'), 6833 ('OffsetData', 'u4'), 6834 ('PaletteSize', 'i4'), 6835 ('OffsetPalette0', 'u4'), 6836 ('OffsetPalette1', 'u4'), 6837 ('CommentSize', 'i4'), 6838 ('OffsetComment', 'u4'), 6839 ('Dimensions', MM_DIMENSION, 10), 6840 ('OffsetPosition', 'u4'), 6841 ('MapType', 'i2'), 6842 ('MapMin', 'f8'), 6843 ('MapMax', 'f8'), 6844 ('MinValue', 'f8'), 6845 ('MaxValue', 'f8'), 6846 ('OffsetMap', 'u4'), 6847 ('Gamma', 'f8'), 6848 ('Offset', 'f8'), 6849 ('GrayChannel', MM_DIMENSION), 6850 ('OffsetThumbnail', 'u4'), 6851 ('VoiceField', 'i4'), 6852 ('OffsetVoiceField', 'u4'), 6853 ] 6854 6855 def MM_DIMENSIONS(): 6856 # Map FluoView MM_Header.Dimensions to axes characters 6857 return { 6858 'X': 'X', 6859 'Y': 'Y', 6860 'Z': 'Z', 6861 'T': 'T', 6862 'CH': 'C', 6863 'WAVELENGTH': 'C', 6864 'TIME': 'T', 6865 'XY': 'R', 6866 'EVENT': 'V', 6867 'EXPOSURE': 'L', 6868 } 6869 6870 def UIC_TAGS(): 6871 # Map Universal Imaging Corporation MetaMorph internal tag ids to 6872 # name and type 6873 from fractions import Fraction # delayed import 6874 6875 return [ 6876 ('AutoScale', int), 6877 ('MinScale', int), 6878 ('MaxScale', int), 6879 ('SpatialCalibration', int), 6880 ('XCalibration', Fraction), 6881 ('YCalibration', Fraction), 6882 ('CalibrationUnits', str), 6883 ('Name', str), 6884 ('ThreshState', int), 6885 ('ThreshStateRed', int), 6886 ('tagid_10', None), # undefined 6887 ('ThreshStateGreen', int), 6888 ('ThreshStateBlue', int), 6889 ('ThreshStateLo', int), 6890 ('ThreshStateHi', int), 6891 ('Zoom', int), 6892 ('CreateTime', julian_datetime), 6893 ('LastSavedTime', julian_datetime), 6894 ('currentBuffer', int), 6895 ('grayFit', None), 6896 ('grayPointCount', None), 6897 ('grayX', Fraction), 6898 ('grayY', Fraction), 6899 ('grayMin', Fraction), 6900 ('grayMax', Fraction), 6901 ('grayUnitName', str), 6902 ('StandardLUT', int), 6903 ('wavelength', int), 6904 ('StagePosition', '(%i,2,2)u4'), # N xy positions as fract 6905 ('CameraChipOffset', '(%i,2,2)u4'), # N xy offsets as fract 6906 ('OverlayMask', None), 6907 ('OverlayCompress', None), 6908 ('Overlay', None), 6909 ('SpecialOverlayMask', None), 6910 ('SpecialOverlayCompress', None), 6911 ('SpecialOverlay', None), 6912 ('ImageProperty', read_uic_image_property), 6913 ('StageLabel', '%ip'), # N str 6914 ('AutoScaleLoInfo', Fraction), 6915 ('AutoScaleHiInfo', Fraction), 6916 ('AbsoluteZ', '(%i,2)u4'), # N fractions 6917 ('AbsoluteZValid', '(%i,)u4'), # N long 6918 ('Gamma', 'I'), # 'I' uses offset 6919 ('GammaRed', 'I'), 6920 ('GammaGreen', 'I'), 6921 ('GammaBlue', 'I'), 6922 ('CameraBin', '2I'), 6923 ('NewLUT', int), 6924 ('ImagePropertyEx', None), 6925 ('PlaneProperty', int), 6926 ('UserLutTable', '(256,3)u1'), 6927 ('RedAutoScaleInfo', int), 6928 ('RedAutoScaleLoInfo', Fraction), 6929 ('RedAutoScaleHiInfo', Fraction), 6930 ('RedMinScaleInfo', int), 6931 ('RedMaxScaleInfo', int), 6932 ('GreenAutoScaleInfo', int), 6933 ('GreenAutoScaleLoInfo', Fraction), 6934 ('GreenAutoScaleHiInfo', Fraction), 6935 ('GreenMinScaleInfo', int), 6936 ('GreenMaxScaleInfo', int), 6937 ('BlueAutoScaleInfo', int), 6938 ('BlueAutoScaleLoInfo', Fraction), 6939 ('BlueAutoScaleHiInfo', Fraction), 6940 ('BlueMinScaleInfo', int), 6941 ('BlueMaxScaleInfo', int), 6942 # ('OverlayPlaneColor', read_uic_overlay_plane_color), 6943 ] 6944 6945 def PILATUS_HEADER(): 6946 # PILATUS CBF Header Specification, Version 1.4 6947 # Map key to [value_indices], type 6948 return { 6949 'Detector': ([slice(1, None)], str), 6950 'Pixel_size': ([1, 4], float), 6951 'Silicon': ([3], float), 6952 'Exposure_time': ([1], float), 6953 'Exposure_period': ([1], float), 6954 'Tau': ([1], float), 6955 'Count_cutoff': ([1], int), 6956 'Threshold_setting': ([1], float), 6957 'Gain_setting': ([1, 2], str), 6958 'N_excluded_pixels': ([1], int), 6959 'Excluded_pixels': ([1], str), 6960 'Flat_field': ([1], str), 6961 'Trim_file': ([1], str), 6962 'Image_path': ([1], str), 6963 # optional 6964 'Wavelength': ([1], float), 6965 'Energy_range': ([1, 2], float), 6966 'Detector_distance': ([1], float), 6967 'Detector_Voffset': ([1], float), 6968 'Beam_xy': ([1, 2], float), 6969 'Flux': ([1], str), 6970 'Filter_transmission': ([1], float), 6971 'Start_angle': ([1], float), 6972 'Angle_increment': ([1], float), 6973 'Detector_2theta': ([1], float), 6974 'Polarization': ([1], float), 6975 'Alpha': ([1], float), 6976 'Kappa': ([1], float), 6977 'Phi': ([1], float), 6978 'Phi_increment': ([1], float), 6979 'Chi': ([1], float), 6980 'Chi_increment': ([1], float), 6981 'Oscillation_axis': ([slice(1, None)], str), 6982 'N_oscillations': ([1], int), 6983 'Start_position': ([1], float), 6984 'Position_increment': ([1], float), 6985 'Shutter_time': ([1], float), 6986 'Omega': ([1], float), 6987 'Omega_increment': ([1], float) 6988 } 6989 6990 def REVERSE_BITORDER_BYTES(): 6991 # Bytes with reversed bitorder 6992 return ( 6993 b'\x00\x80@\xc0 \xa0`\xe0\x10\x90P\xd00\xb0p\xf0\x08\x88H\xc8(' 6994 b'\xa8h\xe8\x18\x98X\xd88\xb8x\xf8\x04\x84D\xc4$\xa4d\xe4\x14' 6995 b'\x94T\xd44\xb4t\xf4\x0c\x8cL\xcc,\xacl\xec\x1c\x9c\\\xdc<\xbc|' 6996 b'\xfc\x02\x82B\xc2"\xa2b\xe2\x12\x92R\xd22\xb2r\xf2\n\x8aJ\xca*' 6997 b'\xaaj\xea\x1a\x9aZ\xda:\xbaz\xfa\x06\x86F\xc6&\xa6f\xe6\x16' 6998 b'\x96V\xd66\xb6v\xf6\x0e\x8eN\xce.\xaen\xee\x1e\x9e^\xde>\xbe~' 6999 b'\xfe\x01\x81A\xc1!\xa1a\xe1\x11\x91Q\xd11\xb1q\xf1\t\x89I\xc9)' 7000 b'\xa9i\xe9\x19\x99Y\xd99\xb9y\xf9\x05\x85E\xc5%\xa5e\xe5\x15' 7001 b'\x95U\xd55\xb5u\xf5\r\x8dM\xcd-\xadm\xed\x1d\x9d]\xdd=\xbd}' 7002 b'\xfd\x03\x83C\xc3#\xa3c\xe3\x13\x93S\xd33\xb3s\xf3\x0b\x8bK' 7003 b'\xcb+\xabk\xeb\x1b\x9b[\xdb;\xbb{\xfb\x07\x87G\xc7\'\xa7g\xe7' 7004 b'\x17\x97W\xd77\xb7w\xf7\x0f\x8fO\xcf/\xafo\xef\x1f\x9f_' 7005 b'\xdf?\xbf\x7f\xff') 7006 7007 def REVERSE_BITORDER_ARRAY(): 7008 # Numpy array of bytes with reversed bitorder 7009 return numpy.frombuffer(TIFF.REVERSE_BITORDER_BYTES, dtype='uint8') 7010 7011 def ALLOCATIONGRANULARITY(): 7012 # alignment for writing contiguous data to TIFF 7013 import mmap # delayed import 7014 return mmap.ALLOCATIONGRANULARITY 7015 7016 7017def read_tags(fh, byteorder, offsetsize, tagnames, 7018 customtags=None, maxifds=None): 7019 """Read tags from chain of IFDs and return as list of dicts. 7020 7021 The file handle position must be at a valid IFD header. 7022 7023 """ 7024 if offsetsize == 4: 7025 offsetformat = byteorder+'I' 7026 tagnosize = 2 7027 tagnoformat = byteorder+'H' 7028 tagsize = 12 7029 tagformat1 = byteorder+'HH' 7030 tagformat2 = byteorder+'I4s' 7031 elif offsetsize == 8: 7032 offsetformat = byteorder+'Q' 7033 tagnosize = 8 7034 tagnoformat = byteorder+'Q' 7035 tagsize = 20 7036 tagformat1 = byteorder+'HH' 7037 tagformat2 = byteorder+'Q8s' 7038 else: 7039 raise ValueError('invalid offset size') 7040 7041 if customtags is None: 7042 customtags = {} 7043 if maxifds is None: 7044 maxifds = 2**32 7045 7046 result = [] 7047 unpack = struct.unpack 7048 offset = fh.tell() 7049 while len(result) < maxifds: 7050 # loop over IFDs 7051 try: 7052 tagno = unpack(tagnoformat, fh.read(tagnosize))[0] 7053 if tagno > 4096: 7054 raise ValueError('suspicious number of tags') 7055 except Exception: 7056 warnings.warn('corrupted tag list at offset %i' % offset) 7057 break 7058 7059 tags = {} 7060 data = fh.read(tagsize * tagno) 7061 pos = fh.tell() 7062 index = 0 7063 for _ in range(tagno): 7064 code, type_ = unpack(tagformat1, data[index:index+4]) 7065 count, value = unpack(tagformat2, data[index+4:index+tagsize]) 7066 index += tagsize 7067 name = tagnames.get(code, str(code)) 7068 try: 7069 dtype = TIFF.DATA_FORMATS[type_] 7070 except KeyError: 7071 raise TiffTag.Error('unknown tag data type %i' % type_) 7072 7073 fmt = '%s%i%s' % (byteorder, count * int(dtype[0]), dtype[1]) 7074 size = struct.calcsize(fmt) 7075 if size > offsetsize or code in customtags: 7076 offset = unpack(offsetformat, value)[0] 7077 if offset < 8 or offset > fh.size - size: 7078 raise TiffTag.Error('invalid tag value offset %i' % offset) 7079 fh.seek(offset) 7080 if code in customtags: 7081 readfunc = customtags[code][1] 7082 value = readfunc(fh, byteorder, dtype, count, offsetsize) 7083 elif type_ == 7 or (count > 1 and dtype[-1] == 'B'): 7084 value = read_bytes(fh, byteorder, dtype, count, offsetsize) 7085 elif code in tagnames or dtype[-1] == 's': 7086 value = unpack(fmt, fh.read(size)) 7087 else: 7088 value = read_numpy(fh, byteorder, dtype, count, offsetsize) 7089 elif dtype[-1] == 'B' or type_ == 7: 7090 value = value[:size] 7091 else: 7092 value = unpack(fmt, value[:size]) 7093 7094 if code not in customtags and code not in TIFF.TAG_TUPLE: 7095 if len(value) == 1: 7096 value = value[0] 7097 if type_ != 7 and dtype[-1] == 's' and isinstance(value, bytes): 7098 # TIFF ASCII fields can contain multiple strings, 7099 # each terminated with a NUL 7100 try: 7101 value = bytes2str(stripascii(value).strip()) 7102 except UnicodeDecodeError: 7103 warnings.warn( 7104 'tag %i: coercing invalid ASCII to bytes' % code) 7105 7106 tags[name] = value 7107 7108 result.append(tags) 7109 # read offset to next page 7110 fh.seek(pos) 7111 offset = unpack(offsetformat, fh.read(offsetsize))[0] 7112 if offset == 0: 7113 break 7114 if offset >= fh.size: 7115 warnings.warn('invalid page offset %i' % offset) 7116 break 7117 fh.seek(offset) 7118 7119 if result and maxifds == 1: 7120 result = result[0] 7121 return result 7122 7123 7124def read_exif_ifd(fh, byteorder, dtype, count, offsetsize): 7125 """Read EXIF tags from file and return as dict.""" 7126 exif = read_tags(fh, byteorder, offsetsize, TIFF.EXIF_TAGS, maxifds=1) 7127 for name in ('ExifVersion', 'FlashpixVersion'): 7128 try: 7129 exif[name] = bytes2str(exif[name]) 7130 except Exception: 7131 pass 7132 if 'UserComment' in exif: 7133 idcode = exif['UserComment'][:8] 7134 try: 7135 if idcode == b'ASCII\x00\x00\x00': 7136 exif['UserComment'] = bytes2str(exif['UserComment'][8:]) 7137 elif idcode == b'UNICODE\x00': 7138 exif['UserComment'] = exif['UserComment'][8:].decode('utf-16') 7139 except Exception: 7140 pass 7141 return exif 7142 7143 7144def read_gps_ifd(fh, byteorder, dtype, count, offsetsize): 7145 """Read GPS tags from file and return as dict.""" 7146 return read_tags(fh, byteorder, offsetsize, TIFF.GPS_TAGS, maxifds=1) 7147 7148 7149def read_interoperability_ifd(fh, byteorder, dtype, count, offsetsize): 7150 """Read Interoperability tags from file and return as dict.""" 7151 tag_names = {1: 'InteroperabilityIndex'} 7152 return read_tags(fh, byteorder, offsetsize, tag_names, maxifds=1) 7153 7154 7155def read_bytes(fh, byteorder, dtype, count, offsetsize): 7156 """Read tag data from file and return as byte string.""" 7157 dtype = 'B' if dtype[-1] == 's' else byteorder+dtype[-1] 7158 count *= numpy.dtype(dtype).itemsize 7159 data = fh.read(count) 7160 if len(data) != count: 7161 warnings.warn('failed to read all bytes: %i, %i' % (len(data), count)) 7162 return data 7163 7164 7165def read_utf8(fh, byteorder, dtype, count, offsetsize): 7166 """Read tag data from file and return as unicode string.""" 7167 return fh.read(count).decode('utf-8') 7168 7169 7170def read_numpy(fh, byteorder, dtype, count, offsetsize): 7171 """Read tag data from file and return as numpy array.""" 7172 dtype = 'b' if dtype[-1] == 's' else byteorder+dtype[-1] 7173 return fh.read_array(dtype, count) 7174 7175 7176def read_colormap(fh, byteorder, dtype, count, offsetsize): 7177 """Read ColorMap data from file and return as numpy array.""" 7178 cmap = fh.read_array(byteorder+dtype[-1], count) 7179 cmap.shape = (3, -1) 7180 return cmap 7181 7182 7183def read_json(fh, byteorder, dtype, count, offsetsize): 7184 """Read JSON tag data from file and return as object.""" 7185 data = fh.read(count) 7186 try: 7187 return json.loads(unicode(stripnull(data), 'utf-8')) 7188 except ValueError: 7189 warnings.warn("invalid JSON '%s'" % data) 7190 7191 7192def read_mm_header(fh, byteorder, dtype, count, offsetsize): 7193 """Read FluoView mm_header tag from file and return as dict.""" 7194 mmh = fh.read_record(TIFF.MM_HEADER, byteorder=byteorder) 7195 mmh = recarray2dict(mmh) 7196 mmh['Dimensions'] = [ 7197 (bytes2str(d[0]).strip(), d[1], d[2], d[3], bytes2str(d[4]).strip()) 7198 for d in mmh['Dimensions']] 7199 d = mmh['GrayChannel'] 7200 mmh['GrayChannel'] = ( 7201 bytes2str(d[0]).strip(), d[1], d[2], d[3], bytes2str(d[4]).strip()) 7202 return mmh 7203 7204 7205def read_mm_stamp(fh, byteorder, dtype, count, offsetsize): 7206 """Read FluoView mm_stamp tag from file and return as numpy.ndarray.""" 7207 return fh.read_array(byteorder+'f8', 8) 7208 7209 7210def read_uic1tag(fh, byteorder, dtype, count, offsetsize, planecount=None): 7211 """Read MetaMorph STK UIC1Tag from file and return as dict. 7212 7213 Return empty dictionary if planecount is unknown. 7214 7215 """ 7216 assert dtype in ('2I', '1I') and byteorder == '<' 7217 result = {} 7218 if dtype == '2I': 7219 # pre MetaMorph 2.5 (not tested) 7220 values = fh.read_array('<u4', 2*count).reshape(count, 2) 7221 result = {'ZDistance': values[:, 0] / values[:, 1]} 7222 elif planecount: 7223 for _ in range(count): 7224 tagid = struct.unpack('<I', fh.read(4))[0] 7225 if tagid in (28, 29, 37, 40, 41): 7226 # silently skip unexpected tags 7227 fh.read(4) 7228 continue 7229 name, value = read_uic_tag(fh, tagid, planecount, offset=True) 7230 result[name] = value 7231 return result 7232 7233 7234def read_uic2tag(fh, byteorder, dtype, planecount, offsetsize): 7235 """Read MetaMorph STK UIC2Tag from file and return as dict.""" 7236 assert dtype == '2I' and byteorder == '<' 7237 values = fh.read_array('<u4', 6*planecount).reshape(planecount, 6) 7238 return { 7239 'ZDistance': values[:, 0] / values[:, 1], 7240 'DateCreated': values[:, 2], # julian days 7241 'TimeCreated': values[:, 3], # milliseconds 7242 'DateModified': values[:, 4], # julian days 7243 'TimeModified': values[:, 5]} # milliseconds 7244 7245 7246def read_uic3tag(fh, byteorder, dtype, planecount, offsetsize): 7247 """Read MetaMorph STK UIC3Tag from file and return as dict.""" 7248 assert dtype == '2I' and byteorder == '<' 7249 values = fh.read_array('<u4', 2*planecount).reshape(planecount, 2) 7250 return {'Wavelengths': values[:, 0] / values[:, 1]} 7251 7252 7253def read_uic4tag(fh, byteorder, dtype, planecount, offsetsize): 7254 """Read MetaMorph STK UIC4Tag from file and return as dict.""" 7255 assert dtype == '1I' and byteorder == '<' 7256 result = {} 7257 while True: 7258 tagid = struct.unpack('<H', fh.read(2))[0] 7259 if tagid == 0: 7260 break 7261 name, value = read_uic_tag(fh, tagid, planecount, offset=False) 7262 result[name] = value 7263 return result 7264 7265 7266def read_uic_tag(fh, tagid, planecount, offset): 7267 """Read a single UIC tag value from file and return tag name and value. 7268 7269 UIC1Tags use an offset. 7270 7271 """ 7272 def read_int(count=1): 7273 value = struct.unpack('<%iI' % count, fh.read(4*count)) 7274 return value[0] if count == 1 else value 7275 7276 try: 7277 name, dtype = TIFF.UIC_TAGS[tagid] 7278 except IndexError: 7279 # unknown tag 7280 return '_TagId%i' % tagid, read_int() 7281 7282 Fraction = TIFF.UIC_TAGS[4][1] 7283 7284 if offset: 7285 pos = fh.tell() 7286 if dtype not in (int, None): 7287 off = read_int() 7288 if off < 8: 7289 if dtype is str: 7290 return name, '' 7291 warnings.warn("invalid offset for uic tag '%s': %i" % 7292 (name, off)) 7293 return name, off 7294 fh.seek(off) 7295 7296 if dtype is None: 7297 # skip 7298 name = '_' + name 7299 value = read_int() 7300 elif dtype is int: 7301 # int 7302 value = read_int() 7303 elif dtype is Fraction: 7304 # fraction 7305 value = read_int(2) 7306 value = value[0] / value[1] 7307 elif dtype is julian_datetime: 7308 # datetime 7309 value = julian_datetime(*read_int(2)) 7310 elif dtype is read_uic_image_property: 7311 # ImagePropertyEx 7312 value = read_uic_image_property(fh) 7313 elif dtype is str: 7314 # pascal string 7315 size = read_int() 7316 if 0 <= size < 2**10: 7317 value = struct.unpack('%is' % size, fh.read(size))[0][:-1] 7318 value = bytes2str(stripnull(value)) 7319 elif offset: 7320 value = '' 7321 warnings.warn("corrupt string in uic tag '%s'" % name) 7322 else: 7323 raise ValueError('invalid string size: %i' % size) 7324 elif dtype == '%ip': 7325 # sequence of pascal strings 7326 value = [] 7327 for _ in range(planecount): 7328 size = read_int() 7329 if 0 <= size < 2**10: 7330 string = struct.unpack('%is' % size, fh.read(size))[0][:-1] 7331 string = bytes2str(stripnull(string)) 7332 value.append(string) 7333 elif offset: 7334 warnings.warn("corrupt string in uic tag '%s'" % name) 7335 else: 7336 raise ValueError('invalid string size: %i' % size) 7337 else: 7338 # struct or numpy type 7339 dtype = '<' + dtype 7340 if '%i' in dtype: 7341 dtype = dtype % planecount 7342 if '(' in dtype: 7343 # numpy type 7344 value = fh.read_array(dtype, 1)[0] 7345 if value.shape[-1] == 2: 7346 # assume fractions 7347 value = value[..., 0] / value[..., 1] 7348 else: 7349 # struct format 7350 value = struct.unpack(dtype, fh.read(struct.calcsize(dtype))) 7351 if len(value) == 1: 7352 value = value[0] 7353 7354 if offset: 7355 fh.seek(pos + 4) 7356 7357 return name, value 7358 7359 7360def read_uic_image_property(fh): 7361 """Read UIC ImagePropertyEx tag from file and return as dict.""" 7362 # TODO: test this 7363 size = struct.unpack('B', fh.read(1))[0] 7364 name = struct.unpack('%is' % size, fh.read(size))[0][:-1] 7365 flags, prop = struct.unpack('<IB', fh.read(5)) 7366 if prop == 1: 7367 value = struct.unpack('II', fh.read(8)) 7368 value = value[0] / value[1] 7369 else: 7370 size = struct.unpack('B', fh.read(1))[0] 7371 value = struct.unpack('%is' % size, fh.read(size))[0] 7372 return dict(name=name, flags=flags, value=value) 7373 7374 7375def read_cz_lsminfo(fh, byteorder, dtype, count, offsetsize): 7376 """Read CZ_LSMINFO tag from file and return as dict.""" 7377 assert byteorder == '<' 7378 magic_number, structure_size = struct.unpack('<II', fh.read(8)) 7379 if magic_number not in (50350412, 67127628): 7380 raise ValueError('invalid CZ_LSMINFO structure') 7381 fh.seek(-8, 1) 7382 7383 if structure_size < numpy.dtype(TIFF.CZ_LSMINFO).itemsize: 7384 # adjust structure according to structure_size 7385 lsminfo = [] 7386 size = 0 7387 for name, dtype in TIFF.CZ_LSMINFO: 7388 size += numpy.dtype(dtype).itemsize 7389 if size > structure_size: 7390 break 7391 lsminfo.append((name, dtype)) 7392 else: 7393 lsminfo = TIFF.CZ_LSMINFO 7394 7395 lsminfo = fh.read_record(lsminfo, byteorder=byteorder) 7396 lsminfo = recarray2dict(lsminfo) 7397 7398 # read LSM info subrecords at offsets 7399 for name, reader in TIFF.CZ_LSMINFO_READERS.items(): 7400 if reader is None: 7401 continue 7402 offset = lsminfo.get('Offset' + name, 0) 7403 if offset < 8: 7404 continue 7405 fh.seek(offset) 7406 try: 7407 lsminfo[name] = reader(fh) 7408 except ValueError: 7409 pass 7410 return lsminfo 7411 7412 7413def read_lsm_floatpairs(fh): 7414 """Read LSM sequence of float pairs from file and return as list.""" 7415 size = struct.unpack('<i', fh.read(4))[0] 7416 return fh.read_array('<2f8', count=size) 7417 7418 7419def read_lsm_positions(fh): 7420 """Read LSM positions from file and return as list.""" 7421 size = struct.unpack('<I', fh.read(4))[0] 7422 return fh.read_array('<2f8', count=size) 7423 7424 7425def read_lsm_timestamps(fh): 7426 """Read LSM time stamps from file and return as list.""" 7427 size, count = struct.unpack('<ii', fh.read(8)) 7428 if size != (8 + 8 * count): 7429 warnings.warn('invalid LSM TimeStamps block') 7430 return [] 7431 # return struct.unpack('<%dd' % count, fh.read(8*count)) 7432 return fh.read_array('<f8', count=count) 7433 7434 7435def read_lsm_eventlist(fh): 7436 """Read LSM events from file and return as list of (time, type, text).""" 7437 count = struct.unpack('<II', fh.read(8))[1] 7438 events = [] 7439 while count > 0: 7440 esize, etime, etype = struct.unpack('<IdI', fh.read(16)) 7441 etext = bytes2str(stripnull(fh.read(esize - 16))) 7442 events.append((etime, etype, etext)) 7443 count -= 1 7444 return events 7445 7446 7447def read_lsm_channelcolors(fh): 7448 """Read LSM ChannelColors structure from file and return as dict.""" 7449 result = {'Mono': False, 'Colors': [], 'ColorNames': []} 7450 pos = fh.tell() 7451 (size, ncolors, nnames, 7452 coffset, noffset, mono) = struct.unpack('<IIIIII', fh.read(24)) 7453 if ncolors != nnames: 7454 warnings.warn('invalid LSM ChannelColors structure') 7455 return result 7456 result['Mono'] = bool(mono) 7457 # Colors 7458 fh.seek(pos + coffset) 7459 colors = fh.read_array('uint8', count=ncolors*4).reshape((ncolors, 4)) 7460 result['Colors'] = colors.tolist() 7461 # ColorNames 7462 fh.seek(pos + noffset) 7463 buffer = fh.read(size - noffset) 7464 names = [] 7465 while len(buffer) > 4: 7466 size = struct.unpack('<I', buffer[:4])[0] 7467 names.append(bytes2str(buffer[4:3+size])) 7468 buffer = buffer[4+size:] 7469 result['ColorNames'] = names 7470 return result 7471 7472 7473def read_lsm_scaninfo(fh): 7474 """Read LSM ScanInfo structure from file and return as dict.""" 7475 block = {} 7476 blocks = [block] 7477 unpack = struct.unpack 7478 if struct.unpack('<I', fh.read(4))[0] != 0x10000000: 7479 # not a Recording sub block 7480 warnings.warn('invalid LSM ScanInfo structure') 7481 return block 7482 fh.read(8) 7483 while True: 7484 entry, dtype, size = unpack('<III', fh.read(12)) 7485 if dtype == 2: 7486 # ascii 7487 value = bytes2str(stripnull(fh.read(size))) 7488 elif dtype == 4: 7489 # long 7490 value = unpack('<i', fh.read(4))[0] 7491 elif dtype == 5: 7492 # rational 7493 value = unpack('<d', fh.read(8))[0] 7494 else: 7495 value = 0 7496 if entry in TIFF.CZ_LSMINFO_SCANINFO_ARRAYS: 7497 blocks.append(block) 7498 name = TIFF.CZ_LSMINFO_SCANINFO_ARRAYS[entry] 7499 newobj = [] 7500 block[name] = newobj 7501 block = newobj 7502 elif entry in TIFF.CZ_LSMINFO_SCANINFO_STRUCTS: 7503 blocks.append(block) 7504 newobj = {} 7505 block.append(newobj) 7506 block = newobj 7507 elif entry in TIFF.CZ_LSMINFO_SCANINFO_ATTRIBUTES: 7508 name = TIFF.CZ_LSMINFO_SCANINFO_ATTRIBUTES[entry] 7509 block[name] = value 7510 elif entry == 0xffffffff: 7511 # end sub block 7512 block = blocks.pop() 7513 else: 7514 # unknown entry 7515 block['Entry0x%x' % entry] = value 7516 if not blocks: 7517 break 7518 return block 7519 7520 7521def read_tvips_header(fh, byteorder, dtype, count, offsetsize): 7522 """Read TVIPS EM-MENU headers and return as dict.""" 7523 result = {} 7524 header = fh.read_record(TIFF.TVIPS_HEADER_V1, byteorder=byteorder) 7525 for name, typestr in TIFF.TVIPS_HEADER_V1: 7526 result[name] = header[name].tolist() 7527 if header['Version'] == 2: 7528 header = fh.read_record(TIFF.TVIPS_HEADER_V2, byteorder=byteorder) 7529 if header['Magic'] != int(0xaaaaaaaa): 7530 warnings.warn('invalid TVIPS v2 magic number') 7531 return {} 7532 # decode utf16 strings 7533 for name, typestr in TIFF.TVIPS_HEADER_V2: 7534 if typestr.startswith('V'): 7535 s = header[name].tostring().decode('utf16', errors='ignore') 7536 result[name] = stripnull(s, null='\0') 7537 else: 7538 result[name] = header[name].tolist() 7539 # convert nm to m 7540 for axis in 'XY': 7541 header['PhysicalPixelSize' + axis] /= 1e9 7542 header['PixelSize' + axis] /= 1e9 7543 elif header.version != 1: 7544 warnings.warn('unknown TVIPS header version') 7545 return {} 7546 return result 7547 7548 7549def read_fei_metadata(fh, byteorder, dtype, count, offsetsize): 7550 """Read FEI SFEG/HELIOS headers and return as dict.""" 7551 result = {} 7552 section = {} 7553 data = bytes2str(fh.read(count)) 7554 for line in data.splitlines(): 7555 line = line.strip() 7556 if line.startswith('['): 7557 section = {} 7558 result[line[1:-1]] = section 7559 continue 7560 try: 7561 key, value = line.split('=') 7562 except ValueError: 7563 continue 7564 section[key] = astype(value) 7565 return result 7566 7567 7568def read_cz_sem(fh, byteorder, dtype, count, offsetsize): 7569 """Read Zeiss SEM tag and return as dict.""" 7570 result = {'': ()} 7571 key = None 7572 data = bytes2str(fh.read(count)) 7573 for line in data.splitlines(): 7574 if line.isupper(): 7575 key = line.lower() 7576 elif key: 7577 try: 7578 name, value = line.split('=') 7579 except ValueError: 7580 continue 7581 value = value.strip() 7582 unit = '' 7583 try: 7584 v, u = value.split() 7585 number = astype(v, (int, float)) 7586 if number != v: 7587 value = number 7588 unit = u 7589 except Exception: 7590 number = astype(value, (int, float)) 7591 if number != value: 7592 value = number 7593 if value in ('No', 'Off'): 7594 value = False 7595 elif value in ('Yes', 'On'): 7596 value = True 7597 result[key] = (name.strip(), value) 7598 if unit: 7599 result[key] += (unit,) 7600 key = None 7601 else: 7602 result[''] += (astype(line, (int, float)),) 7603 return result 7604 7605 7606def read_nih_image_header(fh, byteorder, dtype, count, offsetsize): 7607 """Read NIH_IMAGE_HEADER tag from file and return as dict.""" 7608 a = fh.read_record(TIFF.NIH_IMAGE_HEADER, byteorder=byteorder) 7609 a = a.newbyteorder(byteorder) 7610 a = recarray2dict(a) 7611 a['XUnit'] = a['XUnit'][:a['XUnitSize']] 7612 a['UM'] = a['UM'][:a['UMsize']] 7613 return a 7614 7615 7616def read_scanimage_metadata(fh): 7617 """Read ScanImage BigTIFF v3 static and ROI metadata from open file. 7618 7619 Return non-varying frame data as dict and ROI group data as JSON. 7620 7621 The settings can be used to read image data and metadata without parsing 7622 the TIFF file. 7623 7624 Raise ValueError if file does not contain valid ScanImage v3 metadata. 7625 7626 """ 7627 fh.seek(0) 7628 try: 7629 byteorder, version = struct.unpack('<2sH', fh.read(4)) 7630 if byteorder != b'II' or version != 43: 7631 raise Exception 7632 fh.seek(16) 7633 magic, version, size0, size1 = struct.unpack('<IIII', fh.read(16)) 7634 if magic != 117637889 or version != 3: 7635 raise Exception 7636 except Exception: 7637 raise ValueError('not a ScanImage BigTIFF v3 file') 7638 7639 frame_data = matlabstr2py(bytes2str(fh.read(size0)[:-1])) 7640 roi_data = read_json(fh, '<', None, size1, None) if size1 > 1 else {} 7641 return frame_data, roi_data 7642 7643 7644def read_micromanager_metadata(fh): 7645 """Read MicroManager non-TIFF settings from open file and return as dict. 7646 7647 The settings can be used to read image data without parsing the TIFF file. 7648 7649 Raise ValueError if the file does not contain valid MicroManager metadata. 7650 7651 """ 7652 fh.seek(0) 7653 try: 7654 byteorder = {b'II': '<', b'MM': '>'}[fh.read(2)] 7655 except IndexError: 7656 raise ValueError('not a MicroManager TIFF file') 7657 7658 result = {} 7659 fh.seek(8) 7660 (index_header, index_offset, display_header, display_offset, 7661 comments_header, comments_offset, summary_header, summary_length 7662 ) = struct.unpack(byteorder + 'IIIIIIII', fh.read(32)) 7663 7664 if summary_header != 2355492: 7665 raise ValueError('invalid MicroManager summary header') 7666 result['Summary'] = read_json(fh, byteorder, None, summary_length, None) 7667 7668 if index_header != 54773648: 7669 raise ValueError('invalid MicroManager index header') 7670 fh.seek(index_offset) 7671 header, count = struct.unpack(byteorder + 'II', fh.read(8)) 7672 if header != 3453623: 7673 raise ValueError('invalid MicroManager index header') 7674 data = struct.unpack(byteorder + 'IIIII'*count, fh.read(20*count)) 7675 result['IndexMap'] = {'Channel': data[::5], 7676 'Slice': data[1::5], 7677 'Frame': data[2::5], 7678 'Position': data[3::5], 7679 'Offset': data[4::5]} 7680 7681 if display_header != 483765892: 7682 raise ValueError('invalid MicroManager display header') 7683 fh.seek(display_offset) 7684 header, count = struct.unpack(byteorder + 'II', fh.read(8)) 7685 if header != 347834724: 7686 raise ValueError('invalid MicroManager display header') 7687 result['DisplaySettings'] = read_json(fh, byteorder, None, count, None) 7688 7689 if comments_header != 99384722: 7690 raise ValueError('invalid MicroManager comments header') 7691 fh.seek(comments_offset) 7692 header, count = struct.unpack(byteorder + 'II', fh.read(8)) 7693 if header != 84720485: 7694 raise ValueError('invalid MicroManager comments header') 7695 result['Comments'] = read_json(fh, byteorder, None, count, None) 7696 7697 return result 7698 7699 7700def read_metaseries_catalog(fh): 7701 """Read MetaSeries non-TIFF hint catalog from file. 7702 7703 Raise ValueError if the file does not contain a valid hint catalog. 7704 7705 """ 7706 # TODO: implement read_metaseries_catalog 7707 raise NotImplementedError() 7708 7709 7710def imagej_metadata_tags(metadata, byteorder): 7711 """Return IJMetadata and IJMetadataByteCounts tags from metadata dict. 7712 7713 The tags can be passed to the TiffWriter.save function as extratags. 7714 7715 The metadata dict may contain the following keys and values: 7716 7717 Info : str 7718 Human-readable information as string. 7719 Labels : sequence of str 7720 Human-readable labels for each channel. 7721 Ranges : sequence of doubles 7722 Lower and upper values for each channel. 7723 LUTs : sequence of (3, 256) uint8 ndarrays 7724 Color palettes for each channel. 7725 Plot : bytes 7726 Undocumented ImageJ internal format. 7727 ROI: bytes 7728 Undocumented ImageJ internal region of interest format. 7729 Overlays : bytes 7730 Undocumented ImageJ internal format. 7731 7732 """ 7733 header = [{'>': b'IJIJ', '<': b'JIJI'}[byteorder]] 7734 bytecounts = [0] 7735 body = [] 7736 7737 def _string(data, byteorder): 7738 return data.encode('utf-16' + {'>': 'be', '<': 'le'}[byteorder]) 7739 7740 def _doubles(data, byteorder): 7741 return struct.pack(byteorder+('d' * len(data)), *data) 7742 7743 def _ndarray(data, byteorder): 7744 return data.tobytes() 7745 7746 def _bytes(data, byteorder): 7747 return data 7748 7749 metadata_types = ( 7750 ('Info', b'info', 1, _string), 7751 ('Labels', b'labl', None, _string), 7752 ('Ranges', b'rang', 1, _doubles), 7753 ('LUTs', b'luts', None, _ndarray), 7754 ('Plot', b'plot', 1, _bytes), 7755 ('ROI', b'roi ', 1, _bytes), 7756 ('Overlays', b'over', None, _bytes)) 7757 7758 for key, mtype, count, func in metadata_types: 7759 if key.lower() in metadata: 7760 key = key.lower() 7761 elif key not in metadata: 7762 continue 7763 if byteorder == '<': 7764 mtype = mtype[::-1] 7765 values = metadata[key] 7766 if count is None: 7767 count = len(values) 7768 else: 7769 values = [values] 7770 header.append(mtype + struct.pack(byteorder+'I', count)) 7771 for value in values: 7772 data = func(value, byteorder) 7773 body.append(data) 7774 bytecounts.append(len(data)) 7775 7776 if not body: 7777 return () 7778 body = b''.join(body) 7779 header = b''.join(header) 7780 data = header + body 7781 bytecounts[0] = len(header) 7782 bytecounts = struct.pack(byteorder+('I' * len(bytecounts)), *bytecounts) 7783 return ((50839, 'B', len(data), data, True), 7784 (50838, 'I', len(bytecounts)//4, bytecounts, True)) 7785 7786 7787def imagej_metadata(data, bytecounts, byteorder): 7788 """Return IJMetadata tag value as dict. 7789 7790 The 'Info' string can have multiple formats, e.g. OIF or ScanImage, 7791 that might be parsed into dicts using the matlabstr2py or 7792 oiffile.SettingsFile functions. 7793 7794 """ 7795 def _string(data, byteorder): 7796 return data.decode('utf-16' + {'>': 'be', '<': 'le'}[byteorder]) 7797 7798 def _doubles(data, byteorder): 7799 return struct.unpack(byteorder+('d' * (len(data) // 8)), data) 7800 7801 def _lut(data, byteorder): 7802 return numpy.frombuffer(data, 'uint8').reshape(-1, 256) 7803 7804 def _bytes(data, byteorder): 7805 return data 7806 7807 metadata_types = { # big-endian 7808 b'info': ('Info', _string), 7809 b'labl': ('Labels', _string), 7810 b'rang': ('Ranges', _doubles), 7811 b'luts': ('LUTs', _lut), 7812 b'plot': ('Plots', _bytes), 7813 b'roi ': ('ROI', _bytes), 7814 b'over': ('Overlays', _bytes)} 7815 metadata_types.update( # little-endian 7816 dict((k[::-1], v) for k, v in metadata_types.items())) 7817 7818 if not bytecounts: 7819 raise ValueError('no ImageJ metadata') 7820 7821 if data[:4] not in (b'IJIJ', b'JIJI'): 7822 raise ValueError('invalid ImageJ metadata') 7823 7824 header_size = bytecounts[0] 7825 if header_size < 12 or header_size > 804: 7826 raise ValueError('invalid ImageJ metadata header size') 7827 7828 ntypes = (header_size - 4) // 8 7829 header = struct.unpack(byteorder+'4sI'*ntypes, data[4:4+ntypes*8]) 7830 pos = 4 + ntypes * 8 7831 counter = 0 7832 result = {} 7833 for mtype, count in zip(header[::2], header[1::2]): 7834 values = [] 7835 name, func = metadata_types.get(mtype, (bytes2str(mtype), read_bytes)) 7836 for _ in range(count): 7837 counter += 1 7838 pos1 = pos + bytecounts[counter] 7839 values.append(func(data[pos:pos1], byteorder)) 7840 pos = pos1 7841 result[name.strip()] = values[0] if count == 1 else values 7842 return result 7843 7844 7845def imagej_description_metadata(description): 7846 """Return metatata from ImageJ image description as dict. 7847 7848 Raise ValueError if not a valid ImageJ description. 7849 7850 >>> description = 'ImageJ=1.11a\\nimages=510\\nhyperstack=true\\n' 7851 >>> imagej_description_metadata(description) # doctest: +SKIP 7852 {'ImageJ': '1.11a', 'images': 510, 'hyperstack': True} 7853 7854 """ 7855 def _bool(val): 7856 return {'true': True, 'false': False}[val.lower()] 7857 7858 result = {} 7859 for line in description.splitlines(): 7860 try: 7861 key, val = line.split('=') 7862 except Exception: 7863 continue 7864 key = key.strip() 7865 val = val.strip() 7866 for dtype in (int, float, _bool): 7867 try: 7868 val = dtype(val) 7869 break 7870 except Exception: 7871 pass 7872 result[key] = val 7873 7874 if 'ImageJ' not in result: 7875 raise ValueError('not a ImageJ image description') 7876 return result 7877 7878 7879def imagej_description(shape, rgb=None, colormaped=False, version='1.11a', 7880 hyperstack=None, mode=None, loop=None, **kwargs): 7881 """Return ImageJ image description from data shape. 7882 7883 ImageJ can handle up to 6 dimensions in order TZCYXS. 7884 7885 >>> imagej_description((51, 5, 2, 196, 171)) # doctest: +SKIP 7886 ImageJ=1.11a 7887 images=510 7888 channels=2 7889 slices=5 7890 frames=51 7891 hyperstack=true 7892 mode=grayscale 7893 loop=false 7894 7895 """ 7896 if colormaped: 7897 raise NotImplementedError('ImageJ colormapping not supported') 7898 shape = imagej_shape(shape, rgb=rgb) 7899 rgb = shape[-1] in (3, 4) 7900 7901 result = ['ImageJ=%s' % version] 7902 append = [] 7903 result.append('images=%i' % product(shape[:-3])) 7904 if hyperstack is None: 7905 hyperstack = True 7906 append.append('hyperstack=true') 7907 else: 7908 append.append('hyperstack=%s' % bool(hyperstack)) 7909 if shape[2] > 1: 7910 result.append('channels=%i' % shape[2]) 7911 if mode is None and not rgb: 7912 mode = 'grayscale' 7913 if hyperstack and mode: 7914 append.append('mode=%s' % mode) 7915 if shape[1] > 1: 7916 result.append('slices=%i' % shape[1]) 7917 if shape[0] > 1: 7918 result.append('frames=%i' % shape[0]) 7919 if loop is None: 7920 append.append('loop=false') 7921 if loop is not None: 7922 append.append('loop=%s' % bool(loop)) 7923 for key, value in kwargs.items(): 7924 append.append('%s=%s' % (key.lower(), value)) 7925 7926 return '\n'.join(result + append + ['']) 7927 7928 7929def imagej_shape(shape, rgb=None): 7930 """Return shape normalized to 6D ImageJ hyperstack TZCYXS. 7931 7932 Raise ValueError if not a valid ImageJ hyperstack shape. 7933 7934 >>> imagej_shape((2, 3, 4, 5, 3), False) 7935 (2, 3, 4, 5, 3, 1) 7936 7937 """ 7938 shape = tuple(int(i) for i in shape) 7939 ndim = len(shape) 7940 if 1 > ndim > 6: 7941 raise ValueError('invalid ImageJ hyperstack: not 2 to 6 dimensional') 7942 if rgb is None: 7943 rgb = shape[-1] in (3, 4) and ndim > 2 7944 if rgb and shape[-1] not in (3, 4): 7945 raise ValueError('invalid ImageJ hyperstack: not a RGB image') 7946 if not rgb and ndim == 6 and shape[-1] != 1: 7947 raise ValueError('invalid ImageJ hyperstack: not a non-RGB image') 7948 if rgb or shape[-1] == 1: 7949 return (1, ) * (6 - ndim) + shape 7950 return (1, ) * (5 - ndim) + shape + (1,) 7951 7952 7953def json_description(shape, **metadata): 7954 """Return JSON image description from data shape and other meta data. 7955 7956 Return UTF-8 encoded JSON. 7957 7958 >>> json_description((256, 256, 3), axes='YXS') # doctest: +SKIP 7959 b'{"shape": [256, 256, 3], "axes": "YXS"}' 7960 7961 """ 7962 metadata.update(shape=shape) 7963 return json.dumps(metadata) # .encode('utf-8') 7964 7965 7966def json_description_metadata(description): 7967 """Return metatata from JSON formated image description as dict. 7968 7969 Raise ValuError if description is of unknown format. 7970 7971 >>> description = '{"shape": [256, 256, 3], "axes": "YXS"}' 7972 >>> json_description_metadata(description) # doctest: +SKIP 7973 {'shape': [256, 256, 3], 'axes': 'YXS'} 7974 >>> json_description_metadata('shape=(256, 256, 3)') 7975 {'shape': (256, 256, 3)} 7976 7977 """ 7978 if description[:6] == 'shape=': 7979 # old style 'shaped' description; not JSON 7980 shape = tuple(int(i) for i in description[7:-1].split(',')) 7981 return dict(shape=shape) 7982 if description[:1] == '{' and description[-1:] == '}': 7983 # JSON description 7984 return json.loads(description) 7985 raise ValueError('invalid JSON image description', description) 7986 7987 7988def fluoview_description_metadata(description, ignoresections=None): 7989 """Return metatata from FluoView image description as dict. 7990 7991 The FluoView image description format is unspecified. Expect failures. 7992 7993 >>> descr = ('[Intensity Mapping]\\nMap Ch0: Range=00000 to 02047\\n' 7994 ... '[Intensity Mapping End]') 7995 >>> fluoview_description_metadata(descr) 7996 {'Intensity Mapping': {'Map Ch0: Range': '00000 to 02047'}} 7997 7998 """ 7999 if not description.startswith('['): 8000 raise ValueError('invalid FluoView image description') 8001 if ignoresections is None: 8002 ignoresections = {'Region Info (Fields)', 'Protocol Description'} 8003 8004 result = {} 8005 sections = [result] 8006 comment = False 8007 for line in description.splitlines(): 8008 if not comment: 8009 line = line.strip() 8010 if not line: 8011 continue 8012 if line[0] == '[': 8013 if line[-5:] == ' End]': 8014 # close section 8015 del sections[-1] 8016 section = sections[-1] 8017 name = line[1:-5] 8018 if comment: 8019 section[name] = '\n'.join(section[name]) 8020 if name[:4] == 'LUT ': 8021 a = numpy.array(section[name], dtype='uint8') 8022 a.shape = -1, 3 8023 section[name] = a 8024 continue 8025 # new section 8026 comment = False 8027 name = line[1:-1] 8028 if name[:4] == 'LUT ': 8029 section = [] 8030 elif name in ignoresections: 8031 section = [] 8032 comment = True 8033 else: 8034 section = {} 8035 sections.append(section) 8036 result[name] = section 8037 continue 8038 # add entry 8039 if comment: 8040 section.append(line) 8041 continue 8042 line = line.split('=', 1) 8043 if len(line) == 1: 8044 section[line[0].strip()] = None 8045 continue 8046 key, value = line 8047 if key[:4] == 'RGB ': 8048 section.extend(int(rgb) for rgb in value.split()) 8049 else: 8050 section[key.strip()] = astype(value.strip()) 8051 return result 8052 8053 8054def pilatus_description_metadata(description): 8055 """Return metatata from Pilatus image description as dict. 8056 8057 Return metadata from Pilatus pixel array detectors by Dectris, created 8058 by camserver or TVX software. 8059 8060 >>> pilatus_description_metadata('# Pixel_size 172e-6 m x 172e-6 m') 8061 {'Pixel_size': (0.000172, 0.000172)} 8062 8063 """ 8064 result = {} 8065 if not description.startswith('# '): 8066 return result 8067 for c in '#:=,()': 8068 description = description.replace(c, ' ') 8069 for line in description.split('\n'): 8070 if line[:2] != ' ': 8071 continue 8072 line = line.split() 8073 name = line[0] 8074 if line[0] not in TIFF.PILATUS_HEADER: 8075 try: 8076 result['DateTime'] = datetime.datetime.strptime( 8077 ' '.join(line), '%Y-%m-%dT%H %M %S.%f') 8078 except Exception: 8079 result[name] = ' '.join(line[1:]) 8080 continue 8081 indices, dtype = TIFF.PILATUS_HEADER[line[0]] 8082 if isinstance(indices[0], slice): 8083 # assumes one slice 8084 values = line[indices[0]] 8085 else: 8086 values = [line[i] for i in indices] 8087 if dtype is float and values[0] == 'not': 8088 values = ['NaN'] 8089 values = tuple(dtype(v) for v in values) 8090 if dtype == str: 8091 values = ' '.join(values) 8092 elif len(values) == 1: 8093 values = values[0] 8094 result[name] = values 8095 return result 8096 8097 8098def svs_description_metadata(description): 8099 """Return metatata from Aperio image description as dict. 8100 8101 The Aperio image description format is unspecified. Expect failures. 8102 8103 >>> svs_description_metadata('Aperio Image Library v1.0') 8104 {'Aperio Image Library': 'v1.0'} 8105 8106 """ 8107 if not description.startswith('Aperio Image Library '): 8108 raise ValueError('invalid Aperio image description') 8109 result = {} 8110 lines = description.split('\n') 8111 key, value = lines[0].strip().rsplit(None, 1) # 'Aperio Image Library' 8112 result[key.strip()] = value.strip() 8113 if len(lines) == 1: 8114 return result 8115 items = lines[1].split('|') 8116 result[''] = items[0].strip() # TODO: parse this? 8117 for item in items[1:]: 8118 key, value = item.split(' = ') 8119 result[key.strip()] = astype(value.strip()) 8120 return result 8121 8122 8123def stk_description_metadata(description): 8124 """Return metadata from MetaMorph image description as list of dict. 8125 8126 The MetaMorph image description format is unspecified. Expect failures. 8127 8128 """ 8129 description = description.strip() 8130 if not description: 8131 return [] 8132 try: 8133 description = bytes2str(description) 8134 except UnicodeDecodeError: 8135 warnings.warn('failed to parse MetaMorph image description') 8136 return [] 8137 result = [] 8138 for plane in description.split('\x00'): 8139 d = {} 8140 for line in plane.split('\r\n'): 8141 line = line.split(':', 1) 8142 if len(line) > 1: 8143 name, value = line 8144 d[name.strip()] = astype(value.strip()) 8145 else: 8146 value = line[0].strip() 8147 if value: 8148 if '' in d: 8149 d[''].append(value) 8150 else: 8151 d[''] = [value] 8152 result.append(d) 8153 return result 8154 8155 8156def metaseries_description_metadata(description): 8157 """Return metatata from MetaSeries image description as dict.""" 8158 if not description.startswith('<MetaData>'): 8159 raise ValueError('invalid MetaSeries image description') 8160 8161 from xml.etree import cElementTree as etree # delayed import 8162 root = etree.fromstring(description) 8163 types = {'float': float, 'int': int, 8164 'bool': lambda x: asbool(x, 'on', 'off')} 8165 8166 def parse(root, result): 8167 # recursive 8168 for child in root: 8169 attrib = child.attrib 8170 if not attrib: 8171 result[child.tag] = parse(child, {}) 8172 continue 8173 if 'id' in attrib: 8174 i = attrib['id'] 8175 t = attrib['type'] 8176 v = attrib['value'] 8177 if t in types: 8178 result[i] = types[t](v) 8179 else: 8180 result[i] = v 8181 return result 8182 8183 adict = parse(root, {}) 8184 if 'Description' in adict: 8185 adict['Description'] = adict['Description'].replace(' ', '\n') 8186 return adict 8187 8188 8189def scanimage_description_metadata(description): 8190 """Return metatata from ScanImage image description as dict.""" 8191 return matlabstr2py(description) 8192 8193 8194def scanimage_artist_metadata(artist): 8195 """Return metatata from ScanImage artist tag as dict.""" 8196 try: 8197 return json.loads(artist) 8198 except ValueError: 8199 warnings.warn("invalid JSON '%s'" % artist) 8200 8201 8202def _replace_by(module_function, package=__package__, warn=None, prefix='_'): 8203 """Try replace decorated function by module.function.""" 8204 return lambda f: f # imageio: just use what's in here 8205 def _warn(e, warn): 8206 if warn is None: 8207 warn = '\n Functionality might be degraded or be slow.\n' 8208 elif warn is True: 8209 warn = '' 8210 elif not warn: 8211 return 8212 warnings.warn('%s%s' % (e, warn)) 8213 8214 try: 8215 from importlib import import_module 8216 except ImportError as e: 8217 _warn(e, warn) 8218 return identityfunc 8219 8220 def decorate(func, module_function=module_function, warn=warn): 8221 module, function = module_function.split('.') 8222 try: 8223 if package: 8224 module = import_module('.' + module, package=package) 8225 else: 8226 module = import_module(module) 8227 except Exception as e: 8228 _warn(e, warn) 8229 return func 8230 try: 8231 func, oldfunc = getattr(module, function), func 8232 except Exception as e: 8233 _warn(e, warn) 8234 return func 8235 globals()[prefix + func.__name__] = oldfunc 8236 return func 8237 8238 return decorate 8239 8240 8241def decode_floats(data): 8242 """Decode floating point horizontal differencing. 8243 8244 The TIFF predictor type 3 reorders the bytes of the image values and 8245 applies horizontal byte differencing to improve compression of floating 8246 point images. The ordering of interleaved color channels is preserved. 8247 8248 Parameters 8249 ---------- 8250 data : numpy.ndarray 8251 The image to be decoded. The dtype must be a floating point. 8252 The shape must include the number of contiguous samples per pixel 8253 even if 1. 8254 8255 """ 8256 shape = data.shape 8257 dtype = data.dtype 8258 if len(shape) < 3: 8259 raise ValueError('invalid data shape') 8260 if dtype.char not in 'dfe': 8261 raise ValueError('not a floating point image') 8262 littleendian = data.dtype.byteorder == '<' or ( 8263 sys.byteorder == 'little' and data.dtype.byteorder == '=') 8264 # undo horizontal byte differencing 8265 data = data.view('uint8') 8266 data.shape = shape[:-2] + (-1,) + shape[-1:] 8267 numpy.cumsum(data, axis=-2, dtype='uint8', out=data) 8268 # reorder bytes 8269 if littleendian: 8270 data.shape = shape[:-2] + (-1,) + shape[-2:] 8271 data = numpy.swapaxes(data, -3, -2) 8272 data = numpy.swapaxes(data, -2, -1) 8273 data = data[..., ::-1] 8274 # back to float 8275 data = numpy.ascontiguousarray(data) 8276 data = data.view(dtype) 8277 data.shape = shape 8278 return data 8279 8280 8281@_replace_by('_tifffile.decode_packbits') 8282def decode_packbits(encoded): 8283 """Decompress PackBits encoded byte string. 8284 8285 PackBits is a simple byte-oriented run-length compression scheme. 8286 8287 """ 8288 func = ord if sys.version[0] == '2' else identityfunc 8289 result = [] 8290 result_extend = result.extend 8291 i = 0 8292 try: 8293 while True: 8294 n = func(encoded[i]) + 1 8295 i += 1 8296 if n < 129: 8297 result_extend(encoded[i:i+n]) 8298 i += n 8299 elif n > 129: 8300 result_extend(encoded[i:i+1] * (258-n)) 8301 i += 1 8302 except IndexError: 8303 pass 8304 return b''.join(result) if sys.version[0] == '2' else bytes(result) 8305 8306 8307@_replace_by('_tifffile.decode_lzw') 8308def decode_lzw(encoded): 8309 """Decompress LZW (Lempel-Ziv-Welch) encoded TIFF strip (byte string). 8310 8311 The strip must begin with a CLEAR code and end with an EOI code. 8312 8313 This implementation of the LZW decoding algorithm is described in (1) and 8314 is not compatible with old style LZW compressed files like quad-lzw.tif. 8315 8316 """ 8317 len_encoded = len(encoded) 8318 bitcount_max = len_encoded * 8 8319 unpack = struct.unpack 8320 8321 if sys.version[0] == '2': 8322 newtable = [chr(i) for i in range(256)] 8323 else: 8324 newtable = [bytes([i]) for i in range(256)] 8325 newtable.extend((0, 0)) 8326 8327 def next_code(): 8328 """Return integer of 'bitw' bits at 'bitcount' position in encoded.""" 8329 start = bitcount // 8 8330 s = encoded[start:start+4] 8331 try: 8332 code = unpack('>I', s)[0] 8333 except Exception: 8334 code = unpack('>I', s + b'\x00'*(4-len(s)))[0] 8335 code <<= bitcount % 8 8336 code &= mask 8337 return code >> shr 8338 8339 switchbitch = { # code: bit-width, shr-bits, bit-mask 8340 255: (9, 23, int(9*'1'+'0'*23, 2)), 8341 511: (10, 22, int(10*'1'+'0'*22, 2)), 8342 1023: (11, 21, int(11*'1'+'0'*21, 2)), 8343 2047: (12, 20, int(12*'1'+'0'*20, 2)), } 8344 bitw, shr, mask = switchbitch[255] 8345 bitcount = 0 8346 8347 if len_encoded < 4: 8348 raise ValueError('strip must be at least 4 characters long') 8349 8350 if next_code() != 256: 8351 raise ValueError('strip must begin with CLEAR code') 8352 8353 code = 0 8354 oldcode = 0 8355 result = [] 8356 result_append = result.append 8357 while True: 8358 code = next_code() # ~5% faster when inlining this function 8359 bitcount += bitw 8360 if code == 257 or bitcount >= bitcount_max: # EOI 8361 break 8362 if code == 256: # CLEAR 8363 table = newtable[:] 8364 table_append = table.append 8365 lentable = 258 8366 bitw, shr, mask = switchbitch[255] 8367 code = next_code() 8368 bitcount += bitw 8369 if code == 257: # EOI 8370 break 8371 result_append(table[code]) 8372 else: 8373 if code < lentable: 8374 decoded = table[code] 8375 newcode = table[oldcode] + decoded[:1] 8376 else: 8377 newcode = table[oldcode] 8378 newcode += newcode[:1] 8379 decoded = newcode 8380 result_append(decoded) 8381 table_append(newcode) 8382 lentable += 1 8383 oldcode = code 8384 if lentable in switchbitch: 8385 bitw, shr, mask = switchbitch[lentable] 8386 8387 if code != 257: 8388 warnings.warn('unexpected end of LZW stream (code %i)' % code) 8389 8390 return b''.join(result) 8391 8392 8393@_replace_by('_tifffile.unpack_ints') 8394def unpack_ints(data, dtype, itemsize, runlen=0): 8395 """Decompress byte string to array of integers of any bit size <= 32. 8396 8397 This Python implementation is slow and only handles itemsizes 1, 2, 4, 8, 8398 16, 32, and 64. 8399 8400 Parameters 8401 ---------- 8402 data : byte str 8403 Data to decompress. 8404 dtype : numpy.dtype or str 8405 A numpy boolean or integer type. 8406 itemsize : int 8407 Number of bits per integer. 8408 runlen : int 8409 Number of consecutive integers, after which to start at next byte. 8410 8411 Examples 8412 -------- 8413 >>> unpack_ints(b'a', 'B', 1) 8414 array([0, 1, 1, 0, 0, 0, 0, 1], dtype=uint8) 8415 >>> unpack_ints(b'ab', 'B', 2) 8416 array([1, 2, 0, 1, 1, 2, 0, 2], dtype=uint8) 8417 8418 """ 8419 if itemsize == 1: # bitarray 8420 data = numpy.frombuffer(data, '|B') 8421 data = numpy.unpackbits(data) 8422 if runlen % 8: 8423 data = data.reshape(-1, runlen + (8 - runlen % 8)) 8424 data = data[:, :runlen].reshape(-1) 8425 return data.astype(dtype) 8426 8427 dtype = numpy.dtype(dtype) 8428 if itemsize in (8, 16, 32, 64): 8429 return numpy.frombuffer(data, dtype) 8430 if itemsize not in (1, 2, 4, 8, 16, 32): 8431 raise ValueError('itemsize not supported: %i' % itemsize) 8432 if dtype.kind not in 'biu': 8433 raise ValueError('invalid dtype') 8434 8435 itembytes = next(i for i in (1, 2, 4, 8) if 8 * i >= itemsize) 8436 if itembytes != dtype.itemsize: 8437 raise ValueError('dtype.itemsize too small') 8438 if runlen == 0: 8439 runlen = (8 * len(data)) // itemsize 8440 skipbits = runlen * itemsize % 8 8441 if skipbits: 8442 skipbits = 8 - skipbits 8443 shrbits = itembytes*8 - itemsize 8444 bitmask = int(itemsize*'1'+'0'*shrbits, 2) 8445 dtypestr = '>' + dtype.char # dtype always big-endian? 8446 8447 unpack = struct.unpack 8448 size = runlen * (len(data)*8 // (runlen*itemsize + skipbits)) 8449 result = numpy.empty((size,), dtype) 8450 bitcount = 0 8451 for i in range(size): 8452 start = bitcount // 8 8453 s = data[start:start+itembytes] 8454 try: 8455 code = unpack(dtypestr, s)[0] 8456 except Exception: 8457 code = unpack(dtypestr, s + b'\x00'*(itembytes-len(s)))[0] 8458 code <<= bitcount % 8 8459 code &= bitmask 8460 result[i] = code >> shrbits 8461 bitcount += itemsize 8462 if (i+1) % runlen == 0: 8463 bitcount += skipbits 8464 return result 8465 8466 8467def unpack_rgb(data, dtype='<B', bitspersample=(5, 6, 5), rescale=True): 8468 """Return array from byte string containing packed samples. 8469 8470 Use to unpack RGB565 or RGB555 to RGB888 format. 8471 8472 Parameters 8473 ---------- 8474 data : byte str 8475 The data to be decoded. Samples in each pixel are stored consecutively. 8476 Pixels are aligned to 8, 16, or 32 bit boundaries. 8477 dtype : numpy.dtype 8478 The sample data type. The byteorder applies also to the data stream. 8479 bitspersample : tuple 8480 Number of bits for each sample in a pixel. 8481 rescale : bool 8482 Upscale samples to the number of bits in dtype. 8483 8484 Returns 8485 ------- 8486 result : ndarray 8487 Flattened array of unpacked samples of native dtype. 8488 8489 Examples 8490 -------- 8491 >>> data = struct.pack('BBBB', 0x21, 0x08, 0xff, 0xff) 8492 >>> print(unpack_rgb(data, '<B', (5, 6, 5), False)) 8493 [ 1 1 1 31 63 31] 8494 >>> print(unpack_rgb(data, '<B', (5, 6, 5))) 8495 [ 8 4 8 255 255 255] 8496 >>> print(unpack_rgb(data, '<B', (5, 5, 5))) 8497 [ 16 8 8 255 255 255] 8498 8499 """ 8500 dtype = numpy.dtype(dtype) 8501 bits = int(numpy.sum(bitspersample)) 8502 if not (bits <= 32 and all(i <= dtype.itemsize*8 for i in bitspersample)): 8503 raise ValueError('sample size not supported: %s' % str(bitspersample)) 8504 dt = next(i for i in 'BHI' if numpy.dtype(i).itemsize*8 >= bits) 8505 data = numpy.frombuffer(data, dtype.byteorder+dt) 8506 result = numpy.empty((data.size, len(bitspersample)), dtype.char) 8507 for i, bps in enumerate(bitspersample): 8508 t = data >> int(numpy.sum(bitspersample[i+1:])) 8509 t &= int('0b'+'1'*bps, 2) 8510 if rescale: 8511 o = ((dtype.itemsize * 8) // bps + 1) * bps 8512 if o > data.dtype.itemsize * 8: 8513 t = t.astype('I') 8514 t *= (2**o - 1) // (2**bps - 1) 8515 t //= 2**(o - (dtype.itemsize * 8)) 8516 result[:, i] = t 8517 return result.reshape(-1) 8518 8519 8520@_replace_by('_tifffile.reverse_bitorder') 8521def reverse_bitorder(data): 8522 """Reverse bits in each byte of byte string or numpy array. 8523 8524 Decode data where pixels with lower column values are stored in the 8525 lower-order bits of the bytes (FillOrder is LSB2MSB). 8526 8527 Parameters 8528 ---------- 8529 data : byte string or ndarray 8530 The data to be bit reversed. If byte string, a new bit-reversed byte 8531 string is returned. Numpy arrays are bit-reversed in-place. 8532 8533 Examples 8534 -------- 8535 >>> reverse_bitorder(b'\\x01\\x64') 8536 b'\\x80&' 8537 >>> data = numpy.array([1, 666], dtype='uint16') 8538 >>> reverse_bitorder(data) 8539 >>> data 8540 array([ 128, 16473], dtype=uint16) 8541 8542 """ 8543 try: 8544 view = data.view('uint8') 8545 numpy.take(TIFF.REVERSE_BITORDER_ARRAY, view, out=view) 8546 except AttributeError: 8547 return data.translate(TIFF.REVERSE_BITORDER_BYTES) 8548 except ValueError: 8549 raise NotImplementedError('slices of arrays not supported') 8550 8551 8552def apply_colormap(image, colormap, contig=True): 8553 """Return palette-colored image. 8554 8555 The image values are used to index the colormap on axis 1. The returned 8556 image is of shape image.shape+colormap.shape[0] and dtype colormap.dtype. 8557 8558 Parameters 8559 ---------- 8560 image : numpy.ndarray 8561 Indexes into the colormap. 8562 colormap : numpy.ndarray 8563 RGB lookup table aka palette of shape (3, 2**bits_per_sample). 8564 contig : bool 8565 If True, return a contiguous array. 8566 8567 Examples 8568 -------- 8569 >>> image = numpy.arange(256, dtype='uint8') 8570 >>> colormap = numpy.vstack([image, image, image]).astype('uint16') * 256 8571 >>> apply_colormap(image, colormap)[-1] 8572 array([65280, 65280, 65280], dtype=uint16) 8573 8574 """ 8575 image = numpy.take(colormap, image, axis=1) 8576 image = numpy.rollaxis(image, 0, image.ndim) 8577 if contig: 8578 image = numpy.ascontiguousarray(image) 8579 return image 8580 8581 8582def reorient(image, orientation): 8583 """Return reoriented view of image array. 8584 8585 Parameters 8586 ---------- 8587 image : numpy.ndarray 8588 Non-squeezed output of asarray() functions. 8589 Axes -3 and -2 must be image length and width respectively. 8590 orientation : int or str 8591 One of TIFF.ORIENTATION names or values. 8592 8593 """ 8594 ORIENTATION = TIFF.ORIENTATION 8595 orientation = enumarg(ORIENTATION, orientation) 8596 8597 if orientation == ORIENTATION.TOPLEFT: 8598 return image 8599 elif orientation == ORIENTATION.TOPRIGHT: 8600 return image[..., ::-1, :] 8601 elif orientation == ORIENTATION.BOTLEFT: 8602 return image[..., ::-1, :, :] 8603 elif orientation == ORIENTATION.BOTRIGHT: 8604 return image[..., ::-1, ::-1, :] 8605 elif orientation == ORIENTATION.LEFTTOP: 8606 return numpy.swapaxes(image, -3, -2) 8607 elif orientation == ORIENTATION.RIGHTTOP: 8608 return numpy.swapaxes(image, -3, -2)[..., ::-1, :] 8609 elif orientation == ORIENTATION.RIGHTBOT: 8610 return numpy.swapaxes(image, -3, -2)[..., ::-1, :, :] 8611 elif orientation == ORIENTATION.LEFTBOT: 8612 return numpy.swapaxes(image, -3, -2)[..., ::-1, ::-1, :] 8613 8614 8615def repeat_nd(a, repeats): 8616 """Return read-only view into input array with elements repeated. 8617 8618 Zoom nD image by integer factors using nearest neighbor interpolation 8619 (box filter). 8620 8621 Parameters 8622 ---------- 8623 a : array_like 8624 Input array. 8625 repeats : sequence of int 8626 The number of repetitions to apply along each dimension of input array. 8627 8628 Example 8629 ------- 8630 >>> repeat_nd([[1, 2], [3, 4]], (2, 2)) 8631 array([[1, 1, 2, 2], 8632 [1, 1, 2, 2], 8633 [3, 3, 4, 4], 8634 [3, 3, 4, 4]]) 8635 8636 """ 8637 a = numpy.asarray(a) 8638 reshape = [] 8639 shape = [] 8640 strides = [] 8641 for i, j, k in zip(a.strides, a.shape, repeats): 8642 shape.extend((j, k)) 8643 strides.extend((i, 0)) 8644 reshape.append(j * k) 8645 return numpy.lib.stride_tricks.as_strided( 8646 a, shape, strides, writeable=False).reshape(reshape) 8647 8648 8649def reshape_nd(data_or_shape, ndim): 8650 """Return image array or shape with at least ndim dimensions. 8651 8652 Prepend 1s to image shape as necessary. 8653 8654 >>> reshape_nd(numpy.empty(0), 1).shape 8655 (0,) 8656 >>> reshape_nd(numpy.empty(1), 2).shape 8657 (1, 1) 8658 >>> reshape_nd(numpy.empty((2, 3)), 3).shape 8659 (1, 2, 3) 8660 >>> reshape_nd(numpy.empty((3, 4, 5)), 3).shape 8661 (3, 4, 5) 8662 >>> reshape_nd((2, 3), 3) 8663 (1, 2, 3) 8664 8665 """ 8666 is_shape = isinstance(data_or_shape, tuple) 8667 shape = data_or_shape if is_shape else data_or_shape.shape 8668 if len(shape) >= ndim: 8669 return data_or_shape 8670 shape = (1,) * (ndim - len(shape)) + shape 8671 return shape if is_shape else data_or_shape.reshape(shape) 8672 8673 8674def squeeze_axes(shape, axes, skip='XY'): 8675 """Return shape and axes with single-dimensional entries removed. 8676 8677 Remove unused dimensions unless their axes are listed in 'skip'. 8678 8679 >>> squeeze_axes((5, 1, 2, 1, 1), 'TZYXC') 8680 ((5, 2, 1), 'TYX') 8681 8682 """ 8683 if len(shape) != len(axes): 8684 raise ValueError('dimensions of axes and shape do not match') 8685 shape, axes = zip(*(i for i in zip(shape, axes) 8686 if i[0] > 1 or i[1] in skip)) 8687 return tuple(shape), ''.join(axes) 8688 8689 8690def transpose_axes(image, axes, asaxes='CTZYX'): 8691 """Return image with its axes permuted to match specified axes. 8692 8693 A view is returned if possible. 8694 8695 >>> transpose_axes(numpy.zeros((2, 3, 4, 5)), 'TYXC', asaxes='CTZYX').shape 8696 (5, 2, 1, 3, 4) 8697 8698 """ 8699 for ax in axes: 8700 if ax not in asaxes: 8701 raise ValueError('unknown axis %s' % ax) 8702 # add missing axes to image 8703 shape = image.shape 8704 for ax in reversed(asaxes): 8705 if ax not in axes: 8706 axes = ax + axes 8707 shape = (1,) + shape 8708 image = image.reshape(shape) 8709 # transpose axes 8710 image = image.transpose([axes.index(ax) for ax in asaxes]) 8711 return image 8712 8713 8714def reshape_axes(axes, shape, newshape, unknown='Q'): 8715 """Return axes matching new shape. 8716 8717 Unknown dimensions are labelled 'Q'. 8718 8719 >>> reshape_axes('YXS', (219, 301, 1), (219, 301)) 8720 'YX' 8721 >>> reshape_axes('IYX', (12, 219, 301), (3, 4, 219, 1, 301, 1)) 8722 'QQYQXQ' 8723 8724 """ 8725 shape = tuple(shape) 8726 newshape = tuple(newshape) 8727 if len(axes) != len(shape): 8728 raise ValueError('axes do not match shape') 8729 8730 size = product(shape) 8731 newsize = product(newshape) 8732 if size != newsize: 8733 raise ValueError('cannot reshape %s to %s' % (shape, newshape)) 8734 if not axes or not newshape: 8735 return '' 8736 8737 lendiff = max(0, len(shape) - len(newshape)) 8738 if lendiff: 8739 newshape = newshape + (1,) * lendiff 8740 8741 i = len(shape)-1 8742 prodns = 1 8743 prods = 1 8744 result = [] 8745 for ns in newshape[::-1]: 8746 prodns *= ns 8747 while i > 0 and shape[i] == 1 and ns != 1: 8748 i -= 1 8749 if ns == shape[i] and prodns == prods*shape[i]: 8750 prods *= shape[i] 8751 result.append(axes[i]) 8752 i -= 1 8753 else: 8754 result.append(unknown) 8755 8756 return ''.join(reversed(result[lendiff:])) 8757 8758 8759def stack_pages(pages, out=None, maxworkers=1, *args, **kwargs): 8760 """Read data from sequence of TiffPage and stack them vertically. 8761 8762 Additional parameters are passsed to the TiffPage.asarray function. 8763 8764 """ 8765 npages = len(pages) 8766 if npages == 0: 8767 raise ValueError('no pages') 8768 8769 if npages == 1: 8770 return pages[0].asarray(out=out, *args, **kwargs) 8771 8772 page0 = next(p for p in pages if p is not None) 8773 page0.asarray(validate=None) # ThreadPoolExecutor swallows exceptions 8774 shape = (npages,) + page0.keyframe.shape 8775 dtype = page0.keyframe.dtype 8776 out = create_output(out, shape, dtype) 8777 8778 if maxworkers is None: 8779 maxworkers = multiprocessing.cpu_count() // 2 8780 page0.parent.filehandle.lock = maxworkers > 1 8781 8782 filecache = OpenFileCache(size=max(4, maxworkers), 8783 lock=page0.parent.filehandle.lock) 8784 8785 def func(page, index, out=out, filecache=filecache, 8786 args=args, kwargs=kwargs): 8787 """Read, decode, and copy page data.""" 8788 if page is not None: 8789 filecache.open(page.parent.filehandle) 8790 out[index] = page.asarray(lock=filecache.lock, reopen=False, 8791 validate=False, *args, **kwargs) 8792 filecache.close(page.parent.filehandle) 8793 8794 if maxworkers < 2: 8795 for i, page in enumerate(pages): 8796 func(page, i) 8797 else: 8798 with concurrent.futures.ThreadPoolExecutor(maxworkers) as executor: 8799 executor.map(func, pages, range(npages)) 8800 8801 filecache.clear() 8802 page0.parent.filehandle.lock = None 8803 8804 return out 8805 8806 8807def clean_offsets_counts(offsets, counts): 8808 """Return cleaned offsets and byte counts. 8809 8810 Remove zero offsets and counts. Use to sanitize _offsets and _bytecounts 8811 tag values for strips or tiles. 8812 8813 """ 8814 offsets = list(offsets) 8815 counts = list(counts) 8816 assert len(offsets) == len(counts) 8817 j = 0 8818 for i, (o, b) in enumerate(zip(offsets, counts)): 8819 if o > 0 and b > 0: 8820 if i > j: 8821 offsets[j] = o 8822 counts[j] = b 8823 j += 1 8824 elif b > 0 and o <= 0: 8825 raise ValueError('invalid offset') 8826 else: 8827 warnings.warn('empty byte count') 8828 if j == 0: 8829 j = 1 8830 return offsets[:j], counts[:j] 8831 8832 8833def buffered_read(fh, lock, offsets, bytecounts, buffersize=2**26): 8834 """Return iterator over blocks read from file.""" 8835 length = len(offsets) 8836 i = 0 8837 while i < length: 8838 data = [] 8839 with lock: 8840 size = 0 8841 while size < buffersize and i < length: 8842 fh.seek(offsets[i]) 8843 bytecount = bytecounts[i] 8844 data.append(fh.read(bytecount)) 8845 size += bytecount 8846 i += 1 8847 for block in data: 8848 yield block 8849 8850 8851def create_output(out, shape, dtype, mode='w+', suffix='.memmap'): 8852 """Return numpy array where image data of shape and dtype can be copied. 8853 8854 The 'out' parameter may have the following values or types: 8855 8856 None 8857 An empty array of shape and dtype is created and returned. 8858 numpy.ndarray 8859 An existing writable array of compatible dtype and shape. A view of 8860 the same array is returned after verification. 8861 'memmap' or 'memmap:tempdir' 8862 A memory-map to an array stored in a temporary binary file on disk 8863 is created and returned. 8864 str or open file 8865 The file name or file object used to create a memory-map to an array 8866 stored in a binary file on disk. The created memory-mapped array is 8867 returned. 8868 8869 """ 8870 if out is None: 8871 return numpy.zeros(shape, dtype) 8872 if isinstance(out, str) and out[:6] == 'memmap': 8873 tempdir = out[7:] if len(out) > 7 else None 8874 with tempfile.NamedTemporaryFile(dir=tempdir, suffix=suffix) as fh: 8875 return numpy.memmap(fh, shape=shape, dtype=dtype, mode=mode) 8876 if isinstance(out, numpy.ndarray): 8877 if product(shape) != product(out.shape): 8878 raise ValueError('incompatible output shape') 8879 if not numpy.can_cast(dtype, out.dtype): 8880 raise ValueError('incompatible output dtype') 8881 return out.reshape(shape) 8882 if isinstance(out, pathlib.Path): 8883 out = str(out) 8884 return numpy.memmap(out, shape=shape, dtype=dtype, mode=mode) 8885 8886 8887def matlabstr2py(string): 8888 """Return Python object from Matlab string representation. 8889 8890 Return str, bool, int, float, list (Matlab arrays or cells), or 8891 dict (Matlab structures) types. 8892 8893 Use to access ScanImage metadata. 8894 8895 >>> matlabstr2py('1') 8896 1 8897 >>> matlabstr2py("['x y z' true false; 1 2.0 -3e4; NaN Inf @class]") 8898 [['x y z', True, False], [1, 2.0, -30000.0], [nan, inf, '@class']] 8899 >>> d = matlabstr2py("SI.hChannels.channelType = {'stripe' 'stripe'}\\n" 8900 ... "SI.hChannels.channelsActive = 2") 8901 >>> d['SI.hChannels.channelType'] 8902 ['stripe', 'stripe'] 8903 8904 """ 8905 # TODO: handle invalid input 8906 # TODO: review unboxing of multidimensional arrays 8907 8908 def lex(s): 8909 # return sequence of tokens from matlab string representation 8910 tokens = ['['] 8911 while True: 8912 t, i = next_token(s) 8913 if t is None: 8914 break 8915 if t == ';': 8916 tokens.extend((']', '[')) 8917 elif t == '[': 8918 tokens.extend(('[', '[')) 8919 elif t == ']': 8920 tokens.extend((']', ']')) 8921 else: 8922 tokens.append(t) 8923 s = s[i:] 8924 tokens.append(']') 8925 return tokens 8926 8927 def next_token(s): 8928 # return next token in matlab string 8929 length = len(s) 8930 if length == 0: 8931 return None, 0 8932 i = 0 8933 while i < length and s[i] == ' ': 8934 i += 1 8935 if i == length: 8936 return None, i 8937 if s[i] in '{[;]}': 8938 return s[i], i + 1 8939 if s[i] == "'": 8940 j = i + 1 8941 while j < length and s[j] != "'": 8942 j += 1 8943 return s[i: j+1], j + 1 8944 if s[i] == '<': 8945 j = i + 1 8946 while j < length and s[j] != '>': 8947 j += 1 8948 return s[i: j+1], j + 1 8949 j = i 8950 while j < length and s[j] not in ' {[;]}': 8951 j += 1 8952 return s[i:j], j 8953 8954 def value(s, fail=False): 8955 # return Python value of token 8956 s = s.strip() 8957 if not s: 8958 return s 8959 if len(s) == 1: 8960 try: 8961 return int(s) 8962 except Exception: 8963 if fail: 8964 raise ValueError() 8965 return s 8966 if s[0] == "'": 8967 if fail and s[-1] != "'" or "'" in s[1:-1]: 8968 raise ValueError() 8969 return s[1:-1] 8970 if s[0] == '<': 8971 if fail and s[-1] != '>' or '<' in s[1:-1]: 8972 raise ValueError() 8973 return s 8974 if fail and any(i in s for i in " ';[]{}"): 8975 raise ValueError() 8976 if s[0] == '@': 8977 return s 8978 if s in ('true', 'True'): 8979 return True 8980 if s in ('false', 'False'): 8981 return False 8982 if s[:6] == 'zeros(': 8983 return numpy.zeros([int(i) for i in s[6:-1].split(',')]).tolist() 8984 if s[:5] == 'ones(': 8985 return numpy.ones([int(i) for i in s[5:-1].split(',')]).tolist() 8986 if '.' in s or 'e' in s: 8987 try: 8988 return float(s) 8989 except Exception: 8990 pass 8991 try: 8992 return int(s) 8993 except Exception: 8994 pass 8995 try: 8996 return float(s) # nan, inf 8997 except Exception: 8998 if fail: 8999 raise ValueError() 9000 return s 9001 9002 def parse(s): 9003 # return Python value from string representation of Matlab value 9004 s = s.strip() 9005 try: 9006 return value(s, fail=True) 9007 except ValueError: 9008 pass 9009 result = add2 = [] 9010 levels = [add2] 9011 for t in lex(s): 9012 if t in '[{': 9013 add2 = [] 9014 levels.append(add2) 9015 elif t in ']}': 9016 x = levels.pop() 9017 if len(x) == 1 and isinstance(x[0], (list, str)): 9018 x = x[0] 9019 add2 = levels[-1] 9020 add2.append(x) 9021 else: 9022 add2.append(value(t)) 9023 if len(result) == 1 and isinstance(result[0], (list, str)): 9024 result = result[0] 9025 return result 9026 9027 if '\r' in string or '\n' in string: 9028 # structure 9029 d = {} 9030 for line in string.splitlines(): 9031 line = line.strip() 9032 if not line or line[0] == '%': 9033 continue 9034 k, v = line.split('=', 1) 9035 k = k.strip() 9036 if any(c in k for c in " ';[]{}<>"): 9037 continue 9038 d[k] = parse(v) 9039 return d 9040 return parse(string) 9041 9042 9043def stripnull(string, null=b'\x00'): 9044 """Return string truncated at first null character. 9045 9046 Clean NULL terminated C strings. For unicode strings use null='\\0'. 9047 9048 >>> stripnull(b'string\\x00') 9049 b'string' 9050 >>> stripnull('string\\x00', null='\\0') 9051 'string' 9052 9053 """ 9054 i = string.find(null) 9055 return string if (i < 0) else string[:i] 9056 9057 9058def stripascii(string): 9059 """Return string truncated at last byte that is 7-bit ASCII. 9060 9061 Clean NULL separated and terminated TIFF strings. 9062 9063 >>> stripascii(b'string\\x00string\\n\\x01\\x00') 9064 b'string\\x00string\\n' 9065 >>> stripascii(b'\\x00') 9066 b'' 9067 9068 """ 9069 # TODO: pythonize this 9070 i = len(string) 9071 while i: 9072 i -= 1 9073 if 8 < byte2int(string[i]) < 127: 9074 break 9075 else: 9076 i = -1 9077 return string[:i+1] 9078 9079 9080def asbool(value, true=(b'true', u'true'), false=(b'false', u'false')): 9081 """Return string as bool if possible, else raise TypeError. 9082 9083 >>> asbool(b' False ') 9084 False 9085 9086 """ 9087 value = value.strip().lower() 9088 if value in true: # might raise UnicodeWarning/BytesWarning 9089 return True 9090 if value in false: 9091 return False 9092 raise TypeError() 9093 9094 9095def astype(value, types=None): 9096 """Return argument as one of types if possible. 9097 9098 >>> astype('42') 9099 42 9100 >>> astype('3.14') 9101 3.14 9102 >>> astype('True') 9103 True 9104 >>> astype(b'Neee-Wom') 9105 'Neee-Wom' 9106 9107 """ 9108 if types is None: 9109 types = int, float, asbool, bytes2str 9110 for typ in types: 9111 try: 9112 return typ(value) 9113 except (ValueError, AttributeError, TypeError, UnicodeEncodeError): 9114 pass 9115 return value 9116 9117 9118def format_size(size, threshold=1536): 9119 """Return file size as string from byte size. 9120 9121 >>> format_size(1234) 9122 '1234 B' 9123 >>> format_size(12345678901) 9124 '11.50 GiB' 9125 9126 """ 9127 if size < threshold: 9128 return "%i B" % size 9129 for unit in ('KiB', 'MiB', 'GiB', 'TiB', 'PiB'): 9130 size /= 1024.0 9131 if size < threshold: 9132 return "%.2f %s" % (size, unit) 9133 9134 9135def identityfunc(arg): 9136 """Single argument identity function. 9137 9138 >>> identityfunc('arg') 9139 'arg' 9140 9141 """ 9142 return arg 9143 9144 9145def nullfunc(*args, **kwargs): 9146 """Null function. 9147 9148 >>> nullfunc('arg', kwarg='kwarg') 9149 9150 """ 9151 return 9152 9153 9154def sequence(value): 9155 """Return tuple containing value if value is not a sequence. 9156 9157 >>> sequence(1) 9158 (1,) 9159 >>> sequence([1]) 9160 [1] 9161 9162 """ 9163 try: 9164 len(value) 9165 return value 9166 except TypeError: 9167 return (value,) 9168 9169 9170def product(iterable): 9171 """Return product of sequence of numbers. 9172 9173 Equivalent of functools.reduce(operator.mul, iterable, 1). 9174 Multiplying numpy integers might overflow. 9175 9176 >>> product([2**8, 2**30]) 9177 274877906944 9178 >>> product([]) 9179 1 9180 9181 """ 9182 prod = 1 9183 for i in iterable: 9184 prod *= i 9185 return prod 9186 9187 9188def natural_sorted(iterable): 9189 """Return human sorted list of strings. 9190 9191 E.g. for sorting file names. 9192 9193 >>> natural_sorted(['f1', 'f2', 'f10']) 9194 ['f1', 'f2', 'f10'] 9195 9196 """ 9197 def sortkey(x): 9198 return [(int(c) if c.isdigit() else c) for c in re.split(numbers, x)] 9199 9200 numbers = re.compile(r'(\d+)') 9201 return sorted(iterable, key=sortkey) 9202 9203 9204def excel_datetime(timestamp, epoch=datetime.datetime.fromordinal(693594)): 9205 """Return datetime object from timestamp in Excel serial format. 9206 9207 Convert LSM time stamps. 9208 9209 >>> excel_datetime(40237.029999999795) 9210 datetime.datetime(2010, 2, 28, 0, 43, 11, 999982) 9211 9212 """ 9213 return epoch + datetime.timedelta(timestamp) 9214 9215 9216def julian_datetime(julianday, milisecond=0): 9217 """Return datetime from days since 1/1/4713 BC and ms since midnight. 9218 9219 Convert Julian dates according to MetaMorph. 9220 9221 >>> julian_datetime(2451576, 54362783) 9222 datetime.datetime(2000, 2, 2, 15, 6, 2, 783) 9223 9224 """ 9225 if julianday <= 1721423: 9226 # no datetime before year 1 9227 return None 9228 9229 a = julianday + 1 9230 if a > 2299160: 9231 alpha = math.trunc((a - 1867216.25) / 36524.25) 9232 a += 1 + alpha - alpha // 4 9233 b = a + (1524 if a > 1721423 else 1158) 9234 c = math.trunc((b - 122.1) / 365.25) 9235 d = math.trunc(365.25 * c) 9236 e = math.trunc((b - d) / 30.6001) 9237 9238 day = b - d - math.trunc(30.6001 * e) 9239 month = e - (1 if e < 13.5 else 13) 9240 year = c - (4716 if month > 2.5 else 4715) 9241 9242 hour, milisecond = divmod(milisecond, 1000 * 60 * 60) 9243 minute, milisecond = divmod(milisecond, 1000 * 60) 9244 second, milisecond = divmod(milisecond, 1000) 9245 9246 return datetime.datetime(year, month, day, 9247 hour, minute, second, milisecond) 9248 9249 9250def byteorder_isnative(byteorder): 9251 """Return if byteorder matches the system's byteorder. 9252 9253 >>> byteorder_isnative('=') 9254 True 9255 9256 """ 9257 if byteorder == '=' or byteorder == sys.byteorder: 9258 return True 9259 keys = {'big': '>', 'little': '<'} 9260 return keys.get(byteorder, byteorder) == keys[sys.byteorder] 9261 9262 9263def recarray2dict(recarray): 9264 """Return numpy.recarray as dict.""" 9265 # TODO: subarrays 9266 result = {} 9267 for descr, value in zip(recarray.dtype.descr, recarray): 9268 name, dtype = descr[:2] 9269 if dtype[1] == 'S': 9270 value = bytes2str(stripnull(value)) 9271 elif value.ndim < 2: 9272 value = value.tolist() 9273 result[name] = value 9274 return result 9275 9276 9277def xml2dict(xml, sanitize=True, prefix=None): 9278 """Return XML as dict. 9279 9280 >>> xml2dict('<?xml version="1.0" ?><root attr="name"><key>1</key></root>') 9281 {'root': {'key': 1, 'attr': 'name'}} 9282 9283 """ 9284 from xml.etree import cElementTree as etree # delayed import 9285 9286 at = tx = '' 9287 if prefix: 9288 at, tx = prefix 9289 9290 def astype(value): 9291 # return value as int, float, bool, or str 9292 for t in (int, float, asbool): 9293 try: 9294 return t(value) 9295 except Exception: 9296 pass 9297 return value 9298 9299 def etree2dict(t): 9300 # adapted from https://stackoverflow.com/a/10077069/453463 9301 key = t.tag 9302 if sanitize: 9303 key = key.rsplit('}', 1)[-1] 9304 d = {key: {} if t.attrib else None} 9305 children = list(t) 9306 if children: 9307 dd = collections.defaultdict(list) 9308 for dc in map(etree2dict, children): 9309 for k, v in dc.items(): 9310 dd[k].append(astype(v)) 9311 d = {key: {k: astype(v[0]) if len(v) == 1 else astype(v) 9312 for k, v in dd.items()}} 9313 if t.attrib: 9314 d[key].update((at + k, astype(v)) for k, v in t.attrib.items()) 9315 if t.text: 9316 text = t.text.strip() 9317 if children or t.attrib: 9318 if text: 9319 d[key][tx + 'value'] = astype(text) 9320 else: 9321 d[key] = astype(text) 9322 return d 9323 9324 return etree2dict(etree.fromstring(xml)) 9325 9326 9327def hexdump(bytestr, width=75, height=24, snipat=-2, modulo=2, ellipsis='...'): 9328 """Return hexdump representation of byte string. 9329 9330 >>> hexdump(binascii.unhexlify('49492a00080000000e00fe0004000100')) 9331 '49 49 2a 00 08 00 00 00 0e 00 fe 00 04 00 01 00 II*.............' 9332 9333 """ 9334 size = len(bytestr) 9335 if size < 1 or width < 2 or height < 1: 9336 return '' 9337 if height == 1: 9338 addr = b'' 9339 bytesperline = min(modulo * (((width - len(addr)) // 4) // modulo), 9340 size) 9341 if bytesperline < 1: 9342 return '' 9343 nlines = 1 9344 else: 9345 addr = b'%%0%ix: ' % len(b'%x' % size) 9346 bytesperline = min(modulo * (((width - len(addr % 1)) // 4) // modulo), 9347 size) 9348 if bytesperline < 1: 9349 return '' 9350 width = 3*bytesperline + len(addr % 1) 9351 nlines = (size - 1) // bytesperline + 1 9352 9353 if snipat is None or snipat == 1: 9354 snipat = height 9355 elif 0 < abs(snipat) < 1: 9356 snipat = int(math.floor(height * snipat)) 9357 if snipat < 0: 9358 snipat += height 9359 9360 if height == 1 or nlines == 1: 9361 blocks = [(0, bytestr[:bytesperline])] 9362 addr = b'' 9363 height = 1 9364 width = 3 * bytesperline 9365 elif height is None or nlines <= height: 9366 blocks = [(0, bytestr)] 9367 elif snipat <= 0: 9368 start = bytesperline * (nlines - height) 9369 blocks = [(start, bytestr[start:])] # (start, None) 9370 elif snipat >= height or height < 3: 9371 end = bytesperline * height 9372 blocks = [(0, bytestr[:end])] # (end, None) 9373 else: 9374 end1 = bytesperline * snipat 9375 end2 = bytesperline * (height - snipat - 1) 9376 blocks = [(0, bytestr[:end1]), 9377 (size-end1-end2, None), 9378 (size-end2, bytestr[size-end2:])] 9379 9380 ellipsis = str2bytes(ellipsis) 9381 result = [] 9382 for start, bytestr in blocks: 9383 if bytestr is None: 9384 result.append(ellipsis) # 'skip %i bytes' % start) 9385 continue 9386 hexstr = binascii.hexlify(bytestr) 9387 strstr = re.sub(br'[^\x20-\x7f]', b'.', bytestr) 9388 for i in range(0, len(bytestr), bytesperline): 9389 h = hexstr[2*i:2*i+bytesperline*2] 9390 r = (addr % (i + start)) if height > 1 else addr 9391 r += b' '.join(h[i:i+2] for i in range(0, 2*bytesperline, 2)) 9392 r += b' ' * (width - len(r)) 9393 r += strstr[i:i+bytesperline] 9394 result.append(r) 9395 result = b'\n'.join(result) 9396 if sys.version_info[0] == 3: 9397 result = result.decode('ascii') 9398 return result 9399 9400 9401def isprintable(string): 9402 """Return if all characters in string are printable. 9403 9404 >>> isprintable('abc') 9405 True 9406 >>> isprintable(b'\01') 9407 False 9408 9409 """ 9410 string = string.strip() 9411 if len(string) < 1: 9412 return True 9413 if sys.version_info[0] == 3: 9414 try: 9415 return string.isprintable() 9416 except Exception: 9417 pass 9418 try: 9419 return string.decode('utf-8').isprintable() 9420 except Exception: 9421 pass 9422 else: 9423 if string.isalnum(): 9424 return True 9425 printable = ('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRST' 9426 'UVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c') 9427 return all(c in printable for c in string) 9428 9429 9430def clean_whitespace(string, compact=False): 9431 """Return string with compressed whitespace.""" 9432 for a, b in (('\r\n', '\n'), ('\r', '\n'), ('\n\n', '\n'), 9433 ('\t', ' '), (' ', ' ')): 9434 string = string.replace(a, b) 9435 if compact: 9436 for a, b in (('\n', ' '), ('[ ', '['), 9437 (' ', ' '), (' ', ' '), (' ', ' ')): 9438 string = string.replace(a, b) 9439 return string.strip() 9440 9441 9442def pformat_xml(xml): 9443 """Return pretty formatted XML.""" 9444 try: 9445 import lxml.etree as etree # delayed import 9446 if not isinstance(xml, bytes): 9447 xml = xml.encode('utf-8') 9448 xml = etree.parse(io.BytesIO(xml)) 9449 xml = etree.tostring(xml, pretty_print=True, xml_declaration=True, 9450 encoding=xml.docinfo.encoding) 9451 xml = bytes2str(xml) 9452 except Exception: 9453 if isinstance(xml, bytes): 9454 xml = bytes2str(xml) 9455 xml = xml.replace('><', '>\n<') 9456 return xml.replace(' ', ' ').replace('\t', ' ') 9457 9458 9459def pformat(arg, width=79, height=24, compact=True): 9460 """Return pretty formatted representation of object as string. 9461 9462 Whitespace might be altered. 9463 9464 """ 9465 if height is None or height < 1: 9466 height = 1024 9467 if width is None or width < 1: 9468 width = 256 9469 9470 npopt = numpy.get_printoptions() 9471 numpy.set_printoptions(threshold=100, linewidth=width) 9472 9473 if isinstance(arg, basestring): 9474 if arg[:5].lower() in ('<?xml', b'<?xml'): 9475 if height == 1: 9476 arg = arg[:4*width] 9477 else: 9478 arg = pformat_xml(arg) 9479 elif isinstance(arg, bytes): 9480 if isprintable(arg): 9481 arg = bytes2str(arg) 9482 arg = clean_whitespace(arg) 9483 else: 9484 numpy.set_printoptions(**npopt) 9485 return hexdump(arg, width=width, height=height, modulo=1) 9486 arg = arg.rstrip() 9487 elif isinstance(arg, numpy.record): 9488 arg = arg.pprint() 9489 else: 9490 import pprint # delayed import 9491 compact = {} if sys.version_info[0] == 2 else dict(compact=compact) 9492 arg = pprint.pformat(arg, width=width, **compact) 9493 9494 numpy.set_printoptions(**npopt) 9495 9496 if height == 1: 9497 arg = clean_whitespace(arg, compact=True) 9498 return arg[:width] 9499 9500 argl = list(arg.splitlines()) 9501 if len(argl) > height: 9502 arg = '\n'.join(argl[:height//2] + ['...'] + argl[-height//2:]) 9503 return arg 9504 9505 9506def snipstr(string, width=79, snipat=0.5, ellipsis='...'): 9507 """Return string cut to specified length. 9508 9509 >>> snipstr('abcdefghijklmnop', 8) 9510 'abc...op' 9511 9512 """ 9513 if ellipsis is None: 9514 if isinstance(string, bytes): 9515 ellipsis = b'...' 9516 else: 9517 ellipsis = u'\u2026' # does not print on win-py3.5 9518 esize = len(ellipsis) 9519 9520 splitlines = string.splitlines() 9521 # TODO: finish and test multiline snip 9522 9523 result = [] 9524 for line in splitlines: 9525 if line is None: 9526 result.append(ellipsis) 9527 continue 9528 linelen = len(line) 9529 if linelen <= width: 9530 result.append(string) 9531 continue 9532 9533 split = snipat 9534 if split is None or split == 1: 9535 split = linelen 9536 elif 0 < abs(split) < 1: 9537 split = int(math.floor(linelen * split)) 9538 if split < 0: 9539 split += linelen 9540 if split < 0: 9541 split = 0 9542 9543 if esize == 0 or width < esize + 1: 9544 if split <= 0: 9545 result.append(string[-width:]) 9546 else: 9547 result.append(string[:width]) 9548 elif split <= 0: 9549 result.append(ellipsis + string[esize-width:]) 9550 elif split >= linelen or width < esize + 4: 9551 result.append(string[:width-esize] + ellipsis) 9552 else: 9553 splitlen = linelen - width + esize 9554 end1 = split - splitlen // 2 9555 end2 = end1 + splitlen 9556 result.append(string[:end1] + ellipsis + string[end2:]) 9557 9558 if isinstance(string, bytes): 9559 return b'\n'.join(result) 9560 else: 9561 return '\n'.join(result) 9562 9563 9564def enumarg(enum, arg): 9565 """Return enum member from its name or value. 9566 9567 >>> enumarg(TIFF.PHOTOMETRIC, 2) 9568 <PHOTOMETRIC.RGB: 2> 9569 >>> enumarg(TIFF.PHOTOMETRIC, 'RGB') 9570 <PHOTOMETRIC.RGB: 2> 9571 9572 """ 9573 try: 9574 return enum(arg) 9575 except Exception: 9576 try: 9577 return enum[arg.upper()] 9578 except Exception: 9579 raise ValueError('invalid argument %s' % arg) 9580 9581 9582def parse_kwargs(kwargs, *keys, **keyvalues): 9583 """Return dict with keys from keys|keyvals and values from kwargs|keyvals. 9584 9585 Existing keys are deleted from kwargs. 9586 9587 >>> kwargs = {'one': 1, 'two': 2, 'four': 4} 9588 >>> kwargs2 = parse_kwargs(kwargs, 'two', 'three', four=None, five=5) 9589 >>> kwargs == {'one': 1} 9590 True 9591 >>> kwargs2 == {'two': 2, 'four': 4, 'five': 5} 9592 True 9593 9594 """ 9595 result = {} 9596 for key in keys: 9597 if key in kwargs: 9598 result[key] = kwargs[key] 9599 del kwargs[key] 9600 for key, value in keyvalues.items(): 9601 if key in kwargs: 9602 result[key] = kwargs[key] 9603 del kwargs[key] 9604 else: 9605 result[key] = value 9606 return result 9607 9608 9609def update_kwargs(kwargs, **keyvalues): 9610 """Update dict with keys and values if keys do not already exist. 9611 9612 >>> kwargs = {'one': 1, } 9613 >>> update_kwargs(kwargs, one=None, two=2) 9614 >>> kwargs == {'one': 1, 'two': 2} 9615 True 9616 9617 """ 9618 for key, value in keyvalues.items(): 9619 if key not in kwargs: 9620 kwargs[key] = value 9621 9622 9623def validate_jhove(filename, jhove='jhove', ignore=('More than 50 IFDs',)): 9624 """Validate TIFF file using jhove -m TIFF-hul. 9625 9626 Raise ValueError if jhove outputs an error message unless the message 9627 contains one of the strings in 'ignore'. 9628 9629 JHOVE does not support bigtiff or more than 50 IFDs. 9630 9631 See `JHOVE TIFF-hul Module <http://jhove.sourceforge.net/tiff-hul.html>`_ 9632 9633 """ 9634 import subprocess # noqa: delayed import 9635 out = subprocess.check_output([jhove, filename, '-m', 'TIFF-hul']) 9636 if b'ErrorMessage: ' in out: 9637 for line in out.splitlines(): 9638 line = line.strip() 9639 if line.startswith(b'ErrorMessage: '): 9640 error = line[14:].decode('utf8') 9641 for i in ignore: 9642 if i in error: 9643 break 9644 else: 9645 raise ValueError(error) 9646 break 9647 9648 9649def lsm2bin(lsmfile, binfile=None, tile=(256, 256), verbose=True): 9650 """Convert [MP]TZCYX LSM file to series of BIN files. 9651 9652 One BIN file containing 'ZCYX' data are created for each position, time, 9653 and tile. The position, time, and tile indices are encoded at the end 9654 of the filenames. 9655 9656 """ 9657 verbose = print_ if verbose else nullfunc 9658 9659 if binfile is None: 9660 binfile = lsmfile 9661 elif binfile.lower() == 'none': 9662 binfile = None 9663 if binfile: 9664 binfile += '_(z%ic%iy%ix%i)_m%%ip%%it%%03iy%%ix%%i.bin' 9665 9666 verbose('\nOpening LSM file... ', end='', flush=True) 9667 start_time = time.time() 9668 9669 with TiffFile(lsmfile) as lsm: 9670 if not lsm.is_lsm: 9671 verbose('\n', lsm, flush=True) 9672 raise ValueError('not a LSM file') 9673 series = lsm.series[0] # first series contains the image data 9674 shape = series.shape 9675 axes = series.axes 9676 dtype = series.dtype 9677 size = product(shape) * dtype.itemsize 9678 9679 verbose('%.3f s' % (time.time() - start_time)) 9680 # verbose(lsm, flush=True) 9681 verbose('Image\n axes: %s\n shape: %s\n dtype: %s\n size: %s' 9682 % (axes, shape, dtype, format_size(size)), flush=True) 9683 if not series.axes.endswith('TZCYX'): 9684 raise ValueError('not a *TZCYX LSM file') 9685 9686 verbose('Copying image from LSM to BIN files', end='', flush=True) 9687 start_time = time.time() 9688 tiles = shape[-2] // tile[-2], shape[-1] // tile[-1] 9689 if binfile: 9690 binfile = binfile % (shape[-4], shape[-3], tile[0], tile[1]) 9691 shape = (1,) * (7-len(shape)) + shape 9692 # cache for ZCYX stacks and output files 9693 data = numpy.empty(shape[3:], dtype=dtype) 9694 out = numpy.empty((shape[-4], shape[-3], tile[0], tile[1]), 9695 dtype=dtype) 9696 # iterate over Tiff pages containing data 9697 pages = iter(series.pages) 9698 for m in range(shape[0]): # mosaic axis 9699 for p in range(shape[1]): # position axis 9700 for t in range(shape[2]): # time axis 9701 for z in range(shape[3]): # z slices 9702 data[z] = next(pages).asarray() 9703 for y in range(tiles[0]): # tile y 9704 for x in range(tiles[1]): # tile x 9705 out[:] = data[..., 9706 y*tile[0]:(y+1)*tile[0], 9707 x*tile[1]:(x+1)*tile[1]] 9708 if binfile: 9709 out.tofile(binfile % (m, p, t, y, x)) 9710 verbose('.', end='', flush=True) 9711 verbose(' %.3f s' % (time.time() - start_time)) 9712 9713 9714def imshow(data, title=None, vmin=0, vmax=None, cmap=None, bitspersample=None, 9715 photometric='RGB', interpolation=None, dpi=96, figure=None, 9716 subplot=111, maxdim=32768, **kwargs): 9717 """Plot n-dimensional images using matplotlib.pyplot. 9718 9719 Return figure, subplot and plot axis. 9720 Requires pyplot already imported C{from matplotlib import pyplot}. 9721 9722 Parameters 9723 ---------- 9724 bitspersample : int or None 9725 Number of bits per channel in integer RGB images. 9726 photometric : {'MINISWHITE', 'MINISBLACK', 'RGB', or 'PALETTE'} 9727 The color space of the image data. 9728 title : str 9729 Window and subplot title. 9730 figure : matplotlib.figure.Figure (optional). 9731 Matplotlib to use for plotting. 9732 subplot : int 9733 A matplotlib.pyplot.subplot axis. 9734 maxdim : int 9735 maximum image width and length. 9736 kwargs : optional 9737 Arguments for matplotlib.pyplot.imshow. 9738 9739 """ 9740 isrgb = photometric in ('RGB',) # 'PALETTE', 'YCBCR' 9741 if data.dtype.kind == 'b': 9742 isrgb = False 9743 if isrgb and not (data.shape[-1] in (3, 4) or ( 9744 data.ndim > 2 and data.shape[-3] in (3, 4))): 9745 isrgb = False 9746 photometric = 'MINISBLACK' 9747 9748 data = data.squeeze() 9749 if photometric in ('MINISWHITE', 'MINISBLACK', None): 9750 data = reshape_nd(data, 2) 9751 else: 9752 data = reshape_nd(data, 3) 9753 9754 dims = data.ndim 9755 if dims < 2: 9756 raise ValueError('not an image') 9757 elif dims == 2: 9758 dims = 0 9759 isrgb = False 9760 else: 9761 if isrgb and data.shape[-3] in (3, 4): 9762 data = numpy.swapaxes(data, -3, -2) 9763 data = numpy.swapaxes(data, -2, -1) 9764 elif not isrgb and (data.shape[-1] < data.shape[-2] // 8 and 9765 data.shape[-1] < data.shape[-3] // 8 and 9766 data.shape[-1] < 5): 9767 data = numpy.swapaxes(data, -3, -1) 9768 data = numpy.swapaxes(data, -2, -1) 9769 isrgb = isrgb and data.shape[-1] in (3, 4) 9770 dims -= 3 if isrgb else 2 9771 9772 if isrgb: 9773 data = data[..., :maxdim, :maxdim, :maxdim] 9774 else: 9775 data = data[..., :maxdim, :maxdim] 9776 9777 if photometric == 'PALETTE' and isrgb: 9778 datamax = data.max() 9779 if datamax > 255: 9780 data = data >> 8 # possible precision loss 9781 data = data.astype('B') 9782 elif data.dtype.kind in 'ui': 9783 if not (isrgb and data.dtype.itemsize <= 1) or bitspersample is None: 9784 try: 9785 bitspersample = int(math.ceil(math.log(data.max(), 2))) 9786 except Exception: 9787 bitspersample = data.dtype.itemsize * 8 9788 elif not isinstance(bitspersample, inttypes): 9789 # bitspersample can be tuple, e.g. (5, 6, 5) 9790 bitspersample = data.dtype.itemsize * 8 9791 datamax = 2**bitspersample 9792 if isrgb: 9793 if bitspersample < 8: 9794 data = data << (8 - bitspersample) 9795 elif bitspersample > 8: 9796 data = data >> (bitspersample - 8) # precision loss 9797 data = data.astype('B') 9798 elif data.dtype.kind == 'f': 9799 datamax = data.max() 9800 if isrgb and datamax > 1.0: 9801 if data.dtype.char == 'd': 9802 data = data.astype('f') 9803 data /= datamax 9804 else: 9805 data = data / datamax 9806 elif data.dtype.kind == 'b': 9807 datamax = 1 9808 elif data.dtype.kind == 'c': 9809 data = numpy.absolute(data) 9810 datamax = data.max() 9811 9812 if not isrgb: 9813 if vmax is None: 9814 vmax = datamax 9815 if vmin is None: 9816 if data.dtype.kind == 'i': 9817 dtmin = numpy.iinfo(data.dtype).min 9818 vmin = numpy.min(data) 9819 if vmin == dtmin: 9820 vmin = numpy.min(data > dtmin) 9821 if data.dtype.kind == 'f': 9822 dtmin = numpy.finfo(data.dtype).min 9823 vmin = numpy.min(data) 9824 if vmin == dtmin: 9825 vmin = numpy.min(data > dtmin) 9826 else: 9827 vmin = 0 9828 9829 pyplot = sys.modules['matplotlib.pyplot'] 9830 9831 if figure is None: 9832 pyplot.rc('font', family='sans-serif', weight='normal', size=8) 9833 figure = pyplot.figure(dpi=dpi, figsize=(10.3, 6.3), frameon=True, 9834 facecolor='1.0', edgecolor='w') 9835 try: 9836 figure.canvas.manager.window.title(title) 9837 except Exception: 9838 pass 9839 size = len(title.splitlines()) if title else 1 9840 pyplot.subplots_adjust(bottom=0.03*(dims+2), top=0.98-size*0.03, 9841 left=0.1, right=0.95, hspace=0.05, wspace=0.0) 9842 subplot = pyplot.subplot(subplot) 9843 9844 if title: 9845 try: 9846 title = unicode(title, 'Windows-1252') 9847 except TypeError: 9848 pass 9849 pyplot.title(title, size=11) 9850 9851 if cmap is None: 9852 if data.dtype.char == '?': 9853 cmap = 'gray' 9854 elif data.dtype.kind in 'buf' or vmin == 0: 9855 cmap = 'viridis' 9856 else: 9857 cmap = 'coolwarm' 9858 if photometric == 'MINISWHITE': 9859 cmap += '_r' 9860 9861 image = pyplot.imshow(numpy.atleast_2d(data[(0,) * dims].squeeze()), 9862 vmin=vmin, vmax=vmax, cmap=cmap, 9863 interpolation=interpolation, **kwargs) 9864 9865 if not isrgb: 9866 pyplot.colorbar() # panchor=(0.55, 0.5), fraction=0.05 9867 9868 def format_coord(x, y): 9869 # callback function to format coordinate display in toolbar 9870 x = int(x + 0.5) 9871 y = int(y + 0.5) 9872 try: 9873 if dims: 9874 return '%s @ %s [%4i, %4i]' % ( 9875 curaxdat[1][y, x], current, y, x) 9876 return '%s @ [%4i, %4i]' % (data[y, x], y, x) 9877 except IndexError: 9878 return '' 9879 9880 def none(event): 9881 return '' 9882 9883 subplot.format_coord = format_coord 9884 image.get_cursor_data = none 9885 image.format_cursor_data = none 9886 9887 if dims: 9888 current = list((0,) * dims) 9889 curaxdat = [0, data[tuple(current)].squeeze()] 9890 sliders = [pyplot.Slider( 9891 pyplot.axes([0.125, 0.03*(axis+1), 0.725, 0.025]), 9892 'Dimension %i' % axis, 0, data.shape[axis]-1, 0, facecolor='0.5', 9893 valfmt='%%.0f [%i]' % data.shape[axis]) for axis in range(dims)] 9894 for slider in sliders: 9895 slider.drawon = False 9896 9897 def set_image(current, sliders=sliders, data=data): 9898 # change image and redraw canvas 9899 curaxdat[1] = data[tuple(current)].squeeze() 9900 image.set_data(curaxdat[1]) 9901 for ctrl, index in zip(sliders, current): 9902 ctrl.eventson = False 9903 ctrl.set_val(index) 9904 ctrl.eventson = True 9905 figure.canvas.draw() 9906 9907 def on_changed(index, axis, data=data, current=current): 9908 # callback function for slider change event 9909 index = int(round(index)) 9910 curaxdat[0] = axis 9911 if index == current[axis]: 9912 return 9913 if index >= data.shape[axis]: 9914 index = 0 9915 elif index < 0: 9916 index = data.shape[axis] - 1 9917 current[axis] = index 9918 set_image(current) 9919 9920 def on_keypressed(event, data=data, current=current): 9921 # callback function for key press event 9922 key = event.key 9923 axis = curaxdat[0] 9924 if str(key) in '0123456789': 9925 on_changed(key, axis) 9926 elif key == 'right': 9927 on_changed(current[axis] + 1, axis) 9928 elif key == 'left': 9929 on_changed(current[axis] - 1, axis) 9930 elif key == 'up': 9931 curaxdat[0] = 0 if axis == len(data.shape)-1 else axis + 1 9932 elif key == 'down': 9933 curaxdat[0] = len(data.shape)-1 if axis == 0 else axis - 1 9934 elif key == 'end': 9935 on_changed(data.shape[axis] - 1, axis) 9936 elif key == 'home': 9937 on_changed(0, axis) 9938 9939 figure.canvas.mpl_connect('key_press_event', on_keypressed) 9940 for axis, ctrl in enumerate(sliders): 9941 ctrl.on_changed(lambda k, a=axis: on_changed(k, a)) 9942 9943 return figure, subplot, image 9944 9945 9946def _app_show(): 9947 """Block the GUI. For use as skimage plugin.""" 9948 pyplot = sys.modules['matplotlib.pyplot'] 9949 pyplot.show() 9950 9951 9952def askopenfilename(**kwargs): 9953 """Return file name(s) from Tkinter's file open dialog.""" 9954 try: 9955 from Tkinter import Tk 9956 import tkFileDialog as filedialog 9957 except ImportError: 9958 from tkinter import Tk, filedialog 9959 root = Tk() 9960 root.withdraw() 9961 root.update() 9962 filenames = filedialog.askopenfilename(**kwargs) 9963 root.destroy() 9964 return filenames 9965 9966 9967def main(argv=None): 9968 """Command line usage main function.""" 9969 if float(sys.version[0:3]) < 2.7: 9970 print('This script requires Python version 2.7 or better.') 9971 print('This is Python version %s' % sys.version) 9972 return 0 9973 if argv is None: 9974 argv = sys.argv 9975 9976 import optparse # TODO: use argparse 9977 9978 parser = optparse.OptionParser( 9979 usage='usage: %prog [options] path', 9980 description='Display image data in TIFF files.', 9981 version='%%prog %s' % __version__) 9982 opt = parser.add_option 9983 opt('-p', '--page', dest='page', type='int', default=-1, 9984 help='display single page') 9985 opt('-s', '--series', dest='series', type='int', default=-1, 9986 help='display series of pages of same shape') 9987 opt('--nomultifile', dest='nomultifile', action='store_true', 9988 default=False, help='do not read OME series from multiple files') 9989 opt('--noplots', dest='noplots', type='int', default=8, 9990 help='maximum number of plots') 9991 opt('--interpol', dest='interpol', metavar='INTERPOL', default='bilinear', 9992 help='image interpolation method') 9993 opt('--dpi', dest='dpi', type='int', default=96, 9994 help='plot resolution') 9995 opt('--vmin', dest='vmin', type='int', default=None, 9996 help='minimum value for colormapping') 9997 opt('--vmax', dest='vmax', type='int', default=None, 9998 help='maximum value for colormapping') 9999 opt('--debug', dest='debug', action='store_true', default=False, 10000 help='raise exception on failures') 10001 opt('--doctest', dest='doctest', action='store_true', default=False, 10002 help='runs the docstring examples') 10003 opt('-v', '--detail', dest='detail', type='int', default=2) 10004 opt('-q', '--quiet', dest='quiet', action='store_true') 10005 10006 settings, path = parser.parse_args() 10007 path = ' '.join(path) 10008 10009 if settings.doctest: 10010 import doctest 10011 doctest.testmod(optionflags=doctest.ELLIPSIS) 10012 return 0 10013 if not path: 10014 path = askopenfilename(title='Select a TIFF file', 10015 filetypes=TIFF.FILEOPEN_FILTER) 10016 if not path: 10017 parser.error('No file specified') 10018 10019 if any(i in path for i in '?*'): 10020 path = glob.glob(path) 10021 if not path: 10022 print('no files match the pattern') 10023 return 0 10024 # TODO: handle image sequences 10025 path = path[0] 10026 10027 if not settings.quiet: 10028 print('\nReading file structure...', end=' ') 10029 start = time.time() 10030 try: 10031 tif = TiffFile(path, multifile=not settings.nomultifile) 10032 except Exception as e: 10033 if settings.debug: 10034 raise 10035 else: 10036 print('\n', e) 10037 sys.exit(0) 10038 if not settings.quiet: 10039 print('%.3f ms' % ((time.time()-start) * 1e3)) 10040 10041 if tif.is_ome: 10042 settings.norgb = True 10043 10044 images = [] 10045 if settings.noplots > 0: 10046 if not settings.quiet: 10047 print('Reading image data... ', end=' ') 10048 10049 def notnone(x): 10050 return next(i for i in x if i is not None) 10051 10052 start = time.time() 10053 try: 10054 if settings.page >= 0: 10055 images = [(tif.asarray(key=settings.page), 10056 tif[settings.page], None)] 10057 elif settings.series >= 0: 10058 images = [(tif.asarray(series=settings.series), 10059 notnone(tif.series[settings.series]._pages), 10060 tif.series[settings.series])] 10061 else: 10062 images = [] 10063 for i, s in enumerate(tif.series[:settings.noplots]): 10064 try: 10065 images.append((tif.asarray(series=i), 10066 notnone(s._pages), 10067 tif.series[i])) 10068 except ValueError as e: 10069 images.append((None, notnone(s.pages), None)) 10070 if settings.debug: 10071 raise 10072 else: 10073 print('\nSeries %i failed: %s... ' % (i, e), 10074 end='') 10075 if not settings.quiet: 10076 print('%.3f ms' % ((time.time()-start) * 1e3)) 10077 except Exception as e: 10078 if settings.debug: 10079 raise 10080 else: 10081 print(e) 10082 10083 if not settings.quiet: 10084 print() 10085 print(TiffFile.__str__(tif, detail=int(settings.detail))) 10086 print() 10087 tif.close() 10088 10089 if images and settings.noplots > 0: 10090 try: 10091 import matplotlib 10092 matplotlib.use('TkAgg') 10093 from matplotlib import pyplot 10094 except ImportError as e: 10095 warnings.warn('failed to import matplotlib.\n%s' % e) 10096 else: 10097 for img, page, series in images: 10098 if img is None: 10099 continue 10100 vmin, vmax = settings.vmin, settings.vmax 10101 if 'GDAL_NODATA' in page.tags: 10102 try: 10103 vmin = numpy.min( 10104 img[img > float(page.tags['GDAL_NODATA'].value)]) 10105 except ValueError: 10106 pass 10107 if tif.is_stk: 10108 try: 10109 vmin = tif.stk_metadata['MinScale'] 10110 vmax = tif.stk_metadata['MaxScale'] 10111 except KeyError: 10112 pass 10113 else: 10114 if vmax <= vmin: 10115 vmin, vmax = settings.vmin, settings.vmax 10116 if series: 10117 title = '%s\n%s\n%s' % (str(tif), str(page), str(series)) 10118 else: 10119 title = '%s\n %s' % (str(tif), str(page)) 10120 photometric = 'MINISBLACK' 10121 if page.photometric not in (3,): 10122 photometric = TIFF.PHOTOMETRIC(page.photometric).name 10123 imshow(img, title=title, vmin=vmin, vmax=vmax, 10124 bitspersample=page.bitspersample, 10125 photometric=photometric, 10126 interpolation=settings.interpol, 10127 dpi=settings.dpi) 10128 pyplot.show() 10129 10130 10131if sys.version_info[0] == 2: 10132 inttypes = int, long # noqa 10133 10134 def print_(*args, **kwargs): 10135 """Print function with flush support.""" 10136 flush = kwargs.pop('flush', False) 10137 print(*args, **kwargs) 10138 if flush: 10139 sys.stdout.flush() 10140 10141 def bytes2str(b, encoding=None, errors=None): 10142 """Return string from bytes.""" 10143 return b 10144 10145 def str2bytes(s, encoding=None): 10146 """Return bytes from string.""" 10147 return s 10148 10149 def byte2int(b): 10150 """Return value of byte as int.""" 10151 return ord(b) 10152 10153 class FileNotFoundError(IOError): 10154 pass 10155 10156 TiffFrame = TiffPage # noqa 10157else: 10158 inttypes = int 10159 basestring = str, bytes 10160 unicode = str 10161 print_ = print 10162 10163 def bytes2str(b, encoding=None, errors='strict'): 10164 """Return unicode string from encoded bytes.""" 10165 if encoding is not None: 10166 return b.decode(encoding, errors) 10167 try: 10168 return b.decode('utf-8', errors) 10169 except UnicodeDecodeError: 10170 return b.decode('cp1252', errors) 10171 10172 def str2bytes(s, encoding='cp1252'): 10173 """Return bytes from unicode string.""" 10174 return s.encode(encoding) 10175 10176 def byte2int(b): 10177 """Return value of byte as int.""" 10178 return b 10179 10180if __name__ == '__main__': 10181 sys.exit(main()) 10182 10183