Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/imageio-2.35.1-py3.8.egg/imageio/plugins/_tifffile.py: 11%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

5186 statements  

1#! /usr/bin/env python3 

2# -*- coding: utf-8 -*- 

3# tifffile.py 

4 

5# Copyright (c) 2008-2018, Christoph Gohlke 

6# Copyright (c) 2008-2018, The Regents of the University of California 

7# Produced at the Laboratory for Fluorescence Dynamics 

8# All rights reserved. 

9# 

10# Redistribution and use in source and binary forms, with or without 

11# modification, are permitted provided that the following conditions are met: 

12# 

13# * Redistributions of source code must retain the above copyright 

14# notice, this list of conditions and the following disclaimer. 

15# * Redistributions in binary form must reproduce the above copyright 

16# notice, this list of conditions and the following disclaimer in the 

17# documentation and/or other materials provided with the distribution. 

18# * Neither the name of the copyright holders nor the names of any 

19# contributors may be used to endorse or promote products derived 

20# from this software without specific prior written permission. 

21# 

22# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 

23# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 

24# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 

25# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 

26# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 

27# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 

28# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 

29# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 

30# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 

31# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 

32# POSSIBILITY OF SUCH DAMAGE. 

33 

34"""Read image and meta data from (bio) TIFF(R) files. Save numpy arrays as TIFF. 

35 

36Image and metadata can be read from TIFF, BigTIFF, OME-TIFF, STK, LSM, NIH, 

37SGI, ImageJ, MicroManager, FluoView, ScanImage, SEQ, GEL, and GeoTIFF files. 

38 

39Tifffile is not a general-purpose TIFF library. 

40Only a subset of the TIFF specification is supported, mainly uncompressed and 

41losslessly compressed 1, 8, 16, 32 and 64 bit integer, 16, 32 and 64-bit float, 

42grayscale and RGB(A) images, which are commonly used in scientific imaging. 

43Specifically, reading slices of image data, image trees defined via SubIFDs, 

44CCITT and OJPEG compression, chroma subsampling without JPEG compression, 

45or IPTC and XMP metadata are not implemented. 

46 

47TIFF(R), the tagged Image File Format, is a trademark and under control of 

48Adobe Systems Incorporated. BigTIFF allows for files greater than 4 GB. 

49STK, LSM, FluoView, SGI, SEQ, GEL, and OME-TIFF, are custom extensions 

50defined by Molecular Devices (Universal Imaging Corporation), Carl Zeiss 

51MicroImaging, Olympus, Silicon Graphics International, Media Cybernetics, 

52Molecular Dynamics, and the Open Microscopy Environment consortium 

53respectively. 

54 

55For command line usage run C{python -m tifffile --help} 

56 

57:Author: 

58 `Christoph Gohlke <https://www.lfd.uci.edu/~gohlke/>`_ 

59 

60:Organization: 

61 Laboratory for Fluorescence Dynamics, University of California, Irvine 

62 

63:Version: 2018.06.15 

64 

65Requirements 

66------------ 

67* `CPython 3.6 64-bit <https://www.python.org>`_ 

68* `Numpy 1.14 <http://www.numpy.org>`_ 

69* `Matplotlib 2.2 <https://www.matplotlib.org>`_ (optional for plotting) 

70* `Tifffile.c 2018.02.10 <https://www.lfd.uci.edu/~gohlke/>`_ 

71 (recommended for faster decoding of PackBits and LZW encoded strings) 

72* `Tifffile_geodb.py 2018.02.10 <https://www.lfd.uci.edu/~gohlke/>`_ 

73 (optional enums for GeoTIFF metadata) 

74* Python 2 requires 'futures', 'enum34', 'pathlib'. 

75 

76Revisions 

77--------- 

782018.06.15 

79 Pass 2680 tests. 

80 Towards reading JPEG and other compressions via imagecodecs package (WIP). 

81 Add function to validate TIFF using 'jhove -m TIFF-hul'. 

82 Save bool arrays as bilevel TIFF. 

83 Accept pathlib.Path as filenames. 

84 Move 'software' argument from TiffWriter __init__ to save. 

85 Raise DOS limit to 16 TB. 

86 Lazy load lzma and zstd compressors and decompressors. 

87 Add option to save IJMetadata tags. 

88 Return correct number of pages for truncated series (bug fix). 

89 Move EXIF tags to TIFF.TAG as per TIFF/EP standard. 

902018.02.18 

91 Pass 2293 tests. 

92 Always save RowsPerStrip and Resolution tags as required by TIFF standard. 

93 Do not use badly typed ImageDescription. 

94 Coherce bad ASCII string tags to bytes. 

95 Tuning of __str__ functions. 

96 Fix reading 'undefined' tag values (bug fix). 

97 Read and write ZSTD compressed data. 

98 Use hexdump to print byte strings. 

99 Determine TIFF byte order from data dtype in imsave. 

100 Add option to specify RowsPerStrip for compressed strips. 

101 Allow memory map of arrays with non-native byte order. 

102 Attempt to handle ScanImage <= 5.1 files. 

103 Restore TiffPageSeries.pages sequence interface. 

104 Use numpy.frombuffer instead of fromstring to read from binary data. 

105 Parse GeoTIFF metadata. 

106 Add option to apply horizontal differencing before compression. 

107 Towards reading PerkinElmer QPTIFF (no test files). 

108 Do not index out of bounds data in tifffile.c unpackbits and decodelzw. 

1092017.09.29 (tentative) 

110 Many backwards incompatible changes improving speed and resource usage: 

111 Pass 2268 tests. 

112 Add detail argument to __str__ function. Remove info functions. 

113 Fix potential issue correcting offsets of large LSM files with positions. 

114 Remove TiffFile sequence interface; use TiffFile.pages instead. 

115 Do not make tag values available as TiffPage attributes. 

116 Use str (not bytes) type for tag and metadata strings (WIP). 

117 Use documented standard tag and value names (WIP). 

118 Use enums for some documented TIFF tag values. 

119 Remove 'memmap' and 'tmpfile' options; use out='memmap' instead. 

120 Add option to specify output in asarray functions. 

121 Add option to concurrently decode image strips or tiles using threads. 

122 Add TiffPage.asrgb function (WIP). 

123 Do not apply colormap in asarray. 

124 Remove 'colormapped', 'rgbonly', and 'scale_mdgel' options from asarray. 

125 Consolidate metadata in TiffFile _metadata functions. 

126 Remove non-tag metadata properties from TiffPage. 

127 Add function to convert LSM to tiled BIN files. 

128 Align image data in file. 

129 Make TiffPage.dtype a numpy.dtype. 

130 Add 'ndim' and 'size' properties to TiffPage and TiffPageSeries. 

131 Allow imsave to write non-BigTIFF files up to ~4 GB. 

132 Only read one page for shaped series if possible. 

133 Add memmap function to create memory-mapped array stored in TIFF file. 

134 Add option to save empty arrays to TIFF files. 

135 Add option to save truncated TIFF files. 

136 Allow single tile images to be saved contiguously. 

137 Add optional movie mode for files with uniform pages. 

138 Lazy load pages. 

139 Use lightweight TiffFrame for IFDs sharing properties with key TiffPage. 

140 Move module constants to 'TIFF' namespace (speed up module import). 

141 Remove 'fastij' option from TiffFile. 

142 Remove 'pages' parameter from TiffFile. 

143 Remove TIFFfile alias. 

144 Deprecate Python 2. 

145 Require enum34 and futures packages on Python 2.7. 

146 Remove Record class and return all metadata as dict instead. 

147 Add functions to parse STK, MetaSeries, ScanImage, SVS, Pilatus metadata. 

148 Read tags from EXIF and GPS IFDs. 

149 Use pformat for tag and metadata values. 

150 Fix reading some UIC tags (bug fix). 

151 Do not modify input array in imshow (bug fix). 

152 Fix Python implementation of unpack_ints. 

1532017.05.23 

154 Pass 1961 tests. 

155 Write correct number of SampleFormat values (bug fix). 

156 Use Adobe deflate code to write ZIP compressed files. 

157 Add option to pass tag values as packed binary data for writing. 

158 Defer tag validation to attribute access. 

159 Use property instead of lazyattr decorator for simple expressions. 

1602017.03.17 

161 Write IFDs and tag values on word boundaries. 

162 Read ScanImage metadata. 

163 Remove is_rgb and is_indexed attributes from TiffFile. 

164 Create files used by doctests. 

1652017.01.12 

166 Read Zeiss SEM metadata. 

167 Read OME-TIFF with invalid references to external files. 

168 Rewrite C LZW decoder (5x faster). 

169 Read corrupted LSM files missing EOI code in LZW stream. 

1702017.01.01 

171 Add option to append images to existing TIFF files. 

172 Read files without pages. 

173 Read S-FEG and Helios NanoLab tags created by FEI software. 

174 Allow saving Color Filter Array (CFA) images. 

175 Add info functions returning more information about TiffFile and TiffPage. 

176 Add option to read specific pages only. 

177 Remove maxpages argument (backwards incompatible). 

178 Remove test_tifffile function. 

1792016.10.28 

180 Pass 1944 tests. 

181 Improve detection of ImageJ hyperstacks. 

182 Read TVIPS metadata created by EM-MENU (by Marco Oster). 

183 Add option to disable using OME-XML metadata. 

184 Allow non-integer range attributes in modulo tags (by Stuart Berg). 

1852016.06.21 

186 Do not always memmap contiguous data in page series. 

1872016.05.13 

188 Add option to specify resolution unit. 

189 Write grayscale images with extra samples when planarconfig is specified. 

190 Do not write RGB color images with 2 samples. 

191 Reorder TiffWriter.save keyword arguments (backwards incompatible). 

1922016.04.18 

193 Pass 1932 tests. 

194 TiffWriter, imread, and imsave accept open binary file streams. 

1952016.04.13 

196 Correctly handle reversed fill order in 2 and 4 bps images (bug fix). 

197 Implement reverse_bitorder in C. 

1982016.03.18 

199 Fix saving additional ImageJ metadata. 

2002016.02.22 

201 Pass 1920 tests. 

202 Write 8 bytes double tag values using offset if necessary (bug fix). 

203 Add option to disable writing second image description tag. 

204 Detect tags with incorrect counts. 

205 Disable color mapping for LSM. 

2062015.11.13 

207 Read LSM 6 mosaics. 

208 Add option to specify directory of memory-mapped files. 

209 Add command line options to specify vmin and vmax values for colormapping. 

2102015.10.06 

211 New helper function to apply colormaps. 

212 Renamed is_palette attributes to is_indexed (backwards incompatible). 

213 Color-mapped samples are now contiguous (backwards incompatible). 

214 Do not color-map ImageJ hyperstacks (backwards incompatible). 

215 Towards reading Leica SCN. 

2162015.09.25 

217 Read images with reversed bit order (FillOrder is LSB2MSB). 

2182015.09.21 

219 Read RGB OME-TIFF. 

220 Warn about malformed OME-XML. 

2212015.09.16 

222 Detect some corrupted ImageJ metadata. 

223 Better axes labels for 'shaped' files. 

224 Do not create TiffTag for default values. 

225 Chroma subsampling is not supported. 

226 Memory-map data in TiffPageSeries if possible (optional). 

2272015.08.17 

228 Pass 1906 tests. 

229 Write ImageJ hyperstacks (optional). 

230 Read and write LZMA compressed data. 

231 Specify datetime when saving (optional). 

232 Save tiled and color-mapped images (optional). 

233 Ignore void bytecounts and offsets if possible. 

234 Ignore bogus image_depth tag created by ISS Vista software. 

235 Decode floating point horizontal differencing (not tiled). 

236 Save image data contiguously if possible. 

237 Only read first IFD from ImageJ files if possible. 

238 Read ImageJ 'raw' format (files larger than 4 GB). 

239 TiffPageSeries class for pages with compatible shape and data type. 

240 Try to read incomplete tiles. 

241 Open file dialog if no filename is passed on command line. 

242 Ignore errors when decoding OME-XML. 

243 Rename decoder functions (backwards incompatible). 

2442014.08.24 

245 TiffWriter class for incremental writing images. 

246 Simplify examples. 

2472014.08.19 

248 Add memmap function to FileHandle. 

249 Add function to determine if image data in TiffPage is memory-mappable. 

250 Do not close files if multifile_close parameter is False. 

2512014.08.10 

252 Pass 1730 tests. 

253 Return all extrasamples by default (backwards incompatible). 

254 Read data from series of pages into memory-mapped array (optional). 

255 Squeeze OME dimensions (backwards incompatible). 

256 Workaround missing EOI code in strips. 

257 Support image and tile depth tags (SGI extension). 

258 Better handling of STK/UIC tags (backwards incompatible). 

259 Disable color mapping for STK. 

260 Julian to datetime converter. 

261 TIFF ASCII type may be NULL separated. 

262 Unwrap strip offsets for LSM files greater than 4 GB. 

263 Correct strip byte counts in compressed LSM files. 

264 Skip missing files in OME series. 

265 Read embedded TIFF files. 

2662014.02.05 

267 Save rational numbers as type 5 (bug fix). 

2682013.12.20 

269 Keep other files in OME multi-file series closed. 

270 FileHandle class to abstract binary file handle. 

271 Disable color mapping for bad OME-TIFF produced by bio-formats. 

272 Read bad OME-XML produced by ImageJ when cropping. 

2732013.11.03 

274 Allow zlib compress data in imsave function (optional). 

275 Memory-map contiguous image data (optional). 

2762013.10.28 

277 Read MicroManager metadata and little-endian ImageJ tag. 

278 Save extra tags in imsave function. 

279 Save tags in ascending order by code (bug fix). 

2802012.10.18 

281 Accept file like objects (read from OIB files). 

2822012.08.21 

283 Rename TIFFfile to TiffFile and TIFFpage to TiffPage. 

284 TiffSequence class for reading sequence of TIFF files. 

285 Read UltraQuant tags. 

286 Allow float numbers as resolution in imsave function. 

2872012.08.03 

288 Read MD GEL tags and NIH Image header. 

2892012.07.25 

290 Read ImageJ tags. 

291 ... 

292 

293Notes 

294----- 

295The API is not stable yet and might change between revisions. 

296 

297Tested on little-endian platforms only. 

298 

299Other Python packages and modules for reading (bio) scientific TIFF files: 

300 

301* `python-bioformats <https://github.com/CellProfiler/python-bioformats>`_ 

302* `Imread <https://github.com/luispedro/imread>`_ 

303* `PyLibTiff <https://github.com/pearu/pylibtiff>`_ 

304* `ITK <https://www.itk.org>`_ 

305* `PyLSM <https://launchpad.net/pylsm>`_ 

306* `PyMca.TiffIO.py <https://github.com/vasole/pymca>`_ (same as fabio.TiffIO) 

307* `BioImageXD.Readers <http://www.bioimagexd.net/>`_ 

308* `Cellcognition.io <http://cellcognition.org/>`_ 

309* `pymimage <https://github.com/ardoi/pymimage>`_ 

310* `pytiff <https://github.com/FZJ-INM1-BDA/pytiff>`_ 

311 

312Acknowledgements 

313---------------- 

314* Egor Zindy, University of Manchester, for lsm_scan_info specifics. 

315* Wim Lewis for a bug fix and some LSM functions. 

316* Hadrien Mary for help on reading MicroManager files. 

317* Christian Kliche for help writing tiled and color-mapped files. 

318 

319References 

320---------- 

3211) TIFF 6.0 Specification and Supplements. Adobe Systems Incorporated. 

322 http://partners.adobe.com/public/developer/tiff/ 

3232) TIFF File Format FAQ. http://www.awaresystems.be/imaging/tiff/faq.html 

3243) MetaMorph Stack (STK) Image File Format. 

325 http://support.meta.moleculardevices.com/docs/t10243.pdf 

3264) Image File Format Description LSM 5/7 Release 6.0 (ZEN 2010). 

327 Carl Zeiss MicroImaging GmbH. BioSciences. May 10, 2011 

3285) The OME-TIFF format. 

329 http://www.openmicroscopy.org/site/support/file-formats/ome-tiff 

3306) UltraQuant(r) Version 6.0 for Windows Start-Up Guide. 

331 http://www.ultralum.com/images%20ultralum/pdf/UQStart%20Up%20Guide.pdf 

3327) Micro-Manager File Formats. 

333 http://www.micro-manager.org/wiki/Micro-Manager_File_Formats 

3348) Tags for TIFF and Related Specifications. Digital Preservation. 

335 http://www.digitalpreservation.gov/formats/content/tiff_tags.shtml 

3369) ScanImage BigTiff Specification - ScanImage 2016. 

337 http://scanimage.vidriotechnologies.com/display/SI2016/ 

338 ScanImage+BigTiff+Specification 

33910) CIPA DC-008-2016: Exchangeable image file format for digital still cameras: 

340 Exif Version 2.31. 

341 http://www.cipa.jp/std/documents/e/DC-008-Translation-2016-E.pdf 

342 

343Examples 

344-------- 

345>>> # write numpy array to TIFF file 

346>>> data = numpy.random.rand(4, 301, 219) 

347>>> imsave('temp.tif', data, photometric='minisblack') 

348 

349>>> # read numpy array from TIFF file 

350>>> image = imread('temp.tif') 

351>>> numpy.testing.assert_array_equal(image, data) 

352 

353>>> # iterate over pages and tags in TIFF file 

354>>> with TiffFile('temp.tif') as tif: 

355... images = tif.asarray() 

356... for page in tif.pages: 

357... for tag in page.tags.values(): 

358... _ = tag.name, tag.value 

359... image = page.asarray() 

360 

361""" 

362 

363from __future__ import division, print_function 

364 

365import sys 

366import os 

367import io 

368import re 

369import glob 

370import math 

371import zlib 

372import time 

373import json 

374import enum 

375import struct 

376import pathlib 

377import warnings 

378import binascii 

379import tempfile 

380import datetime 

381import threading 

382import collections 

383import multiprocessing 

384import concurrent.futures 

385 

386import numpy 

387 

388# delay imports: mmap, pprint, fractions, xml, tkinter, matplotlib, lzma, zstd, 

389# subprocess 

390 

391__version__ = "2018.06.15" 

392__docformat__ = "restructuredtext en" 

393__all__ = ( 

394 "imsave", 

395 "imread", 

396 "imshow", 

397 "memmap", 

398 "TiffFile", 

399 "TiffWriter", 

400 "TiffSequence", 

401 # utility functions used by oiffile or czifile 

402 "FileHandle", 

403 "lazyattr", 

404 "natural_sorted", 

405 "decode_lzw", 

406 "stripnull", 

407 "create_output", 

408 "repeat_nd", 

409 "format_size", 

410 "product", 

411 "xml2dict", 

412) 

413 

414 

415def imread(files, **kwargs): 

416 """Return image data from TIFF file(s) as numpy array. 

417 

418 Refer to the TiffFile class and member functions for documentation. 

419 

420 Parameters 

421 ---------- 

422 files : str, binary stream, or sequence 

423 File name, seekable binary stream, glob pattern, or sequence of 

424 file names. 

425 kwargs : dict 

426 Parameters 'multifile' and 'is_ome' are passed to the TiffFile class. 

427 The 'pattern' parameter is passed to the TiffSequence class. 

428 Other parameters are passed to the asarray functions. 

429 The first image series is returned if no arguments are provided. 

430 

431 Examples 

432 -------- 

433 >>> # get image from first page 

434 >>> imsave('temp.tif', numpy.random.rand(3, 4, 301, 219)) 

435 >>> im = imread('temp.tif', key=0) 

436 >>> im.shape 

437 (4, 301, 219) 

438 

439 >>> # get images from sequence of files 

440 >>> ims = imread(['temp.tif', 'temp.tif']) 

441 >>> ims.shape 

442 (2, 3, 4, 301, 219) 

443 

444 """ 

445 kwargs_file = parse_kwargs(kwargs, "multifile", "is_ome") 

446 kwargs_seq = parse_kwargs(kwargs, "pattern") 

447 

448 if isinstance(files, basestring) and any(i in files for i in "?*"): 

449 files = glob.glob(files) 

450 if not files: 

451 raise ValueError("no files found") 

452 if not hasattr(files, "seek") and len(files) == 1: 

453 files = files[0] 

454 

455 if isinstance(files, basestring) or hasattr(files, "seek"): 

456 with TiffFile(files, **kwargs_file) as tif: 

457 return tif.asarray(**kwargs) 

458 else: 

459 with TiffSequence(files, **kwargs_seq) as imseq: 

460 return imseq.asarray(**kwargs) 

461 

462 

463def imsave(file, data=None, shape=None, dtype=None, bigsize=2**32 - 2**25, **kwargs): 

464 """Write numpy array to TIFF file. 

465 

466 Refer to the TiffWriter class and member functions for documentation. 

467 

468 Parameters 

469 ---------- 

470 file : str or binary stream 

471 File name or writable binary stream, such as an open file or BytesIO. 

472 data : array_like 

473 Input image. The last dimensions are assumed to be image depth, 

474 height, width, and samples. 

475 If None, an empty array of the specified shape and dtype is 

476 saved to file. 

477 Unless 'byteorder' is specified in 'kwargs', the TIFF file byte order 

478 is determined from the data's dtype or the dtype argument. 

479 shape : tuple 

480 If 'data' is None, shape of an empty array to save to the file. 

481 dtype : numpy.dtype 

482 If 'data' is None, data-type of an empty array to save to the file. 

483 bigsize : int 

484 Create a BigTIFF file if the size of data in bytes is larger than 

485 this threshold and 'imagej' or 'truncate' are not enabled. 

486 By default, the threshold is 4 GB minus 32 MB reserved for metadata. 

487 Use the 'bigtiff' parameter to explicitly specify the type of 

488 file created. 

489 kwargs : dict 

490 Parameters 'append', 'byteorder', 'bigtiff', and 'imagej', are passed 

491 to TiffWriter(). Other parameters are passed to TiffWriter.save(). 

492 

493 Returns 

494 ------- 

495 If the image data are written contiguously, return offset and bytecount 

496 of image data in the file. 

497 

498 Examples 

499 -------- 

500 >>> # save a RGB image 

501 >>> data = numpy.random.randint(0, 255, (256, 256, 3), 'uint8') 

502 >>> imsave('temp.tif', data, photometric='rgb') 

503 

504 >>> # save a random array and metadata, using compression 

505 >>> data = numpy.random.rand(2, 5, 3, 301, 219) 

506 >>> imsave('temp.tif', data, compress=6, metadata={'axes': 'TZCYX'}) 

507 

508 """ 

509 tifargs = parse_kwargs(kwargs, "append", "bigtiff", "byteorder", "imagej") 

510 if data is None: 

511 size = product(shape) * numpy.dtype(dtype).itemsize 

512 byteorder = numpy.dtype(dtype).byteorder 

513 else: 

514 try: 

515 size = data.nbytes 

516 byteorder = data.dtype.byteorder 

517 except Exception: 

518 size = 0 

519 byteorder = None 

520 if ( 

521 size > bigsize 

522 and "bigtiff" not in tifargs 

523 and not (tifargs.get("imagej", False) or tifargs.get("truncate", False)) 

524 ): 

525 tifargs["bigtiff"] = True 

526 if "byteorder" not in tifargs: 

527 tifargs["byteorder"] = byteorder 

528 

529 with TiffWriter(file, **tifargs) as tif: 

530 return tif.save(data, shape, dtype, **kwargs) 

531 

532 

533def memmap(filename, shape=None, dtype=None, page=None, series=0, mode="r+", **kwargs): 

534 """Return memory-mapped numpy array stored in TIFF file. 

535 

536 Memory-mapping requires data stored in native byte order, without tiling, 

537 compression, predictors, etc. 

538 If 'shape' and 'dtype' are provided, existing files will be overwritten or 

539 appended to depending on the 'append' parameter. 

540 Otherwise the image data of a specified page or series in an existing 

541 file will be memory-mapped. By default, the image data of the first page 

542 series is memory-mapped. 

543 Call flush() to write any changes in the array to the file. 

544 Raise ValueError if the image data in the file is not memory-mappable. 

545 

546 Parameters 

547 ---------- 

548 filename : str 

549 Name of the TIFF file which stores the array. 

550 shape : tuple 

551 Shape of the empty array. 

552 dtype : numpy.dtype 

553 Data-type of the empty array. 

554 page : int 

555 Index of the page which image data to memory-map. 

556 series : int 

557 Index of the page series which image data to memory-map. 

558 mode : {'r+', 'r', 'c'}, optional 

559 The file open mode. Default is to open existing file for reading and 

560 writing ('r+'). 

561 kwargs : dict 

562 Additional parameters passed to imsave() or TiffFile(). 

563 

564 Examples 

565 -------- 

566 >>> # create an empty TIFF file and write to memory-mapped image 

567 >>> im = memmap('temp.tif', shape=(256, 256), dtype='float32') 

568 >>> im[255, 255] = 1.0 

569 >>> im.flush() 

570 >>> im.shape, im.dtype 

571 ((256, 256), dtype('float32')) 

572 >>> del im 

573 

574 >>> # memory-map image data in a TIFF file 

575 >>> im = memmap('temp.tif', page=0) 

576 >>> im[255, 255] 

577 1.0 

578 

579 """ 

580 if shape is not None and dtype is not None: 

581 # create a new, empty array 

582 kwargs.update( 

583 data=None, 

584 shape=shape, 

585 dtype=dtype, 

586 returnoffset=True, 

587 align=TIFF.ALLOCATIONGRANULARITY, 

588 ) 

589 result = imsave(filename, **kwargs) 

590 if result is None: 

591 # TODO: fail before creating file or writing data 

592 raise ValueError("image data are not memory-mappable") 

593 offset = result[0] 

594 else: 

595 # use existing file 

596 with TiffFile(filename, **kwargs) as tif: 

597 if page is not None: 

598 page = tif.pages[page] 

599 if not page.is_memmappable: 

600 raise ValueError("image data are not memory-mappable") 

601 offset, _ = page.is_contiguous 

602 shape = page.shape 

603 dtype = page.dtype 

604 else: 

605 series = tif.series[series] 

606 if series.offset is None: 

607 raise ValueError("image data are not memory-mappable") 

608 shape = series.shape 

609 dtype = series.dtype 

610 offset = series.offset 

611 dtype = tif.byteorder + dtype.char 

612 return numpy.memmap(filename, dtype, mode, offset, shape, "C") 

613 

614 

615class lazyattr(object): 

616 """Attribute whose value is computed on first access.""" 

617 

618 # TODO: help() doesn't work 

619 __slots__ = ("func",) 

620 

621 def __init__(self, func): 

622 self.func = func 

623 # self.__name__ = func.__name__ 

624 # self.__doc__ = func.__doc__ 

625 # self.lock = threading.RLock() 

626 

627 def __get__(self, instance, owner): 

628 # with self.lock: 

629 if instance is None: 

630 return self 

631 try: 

632 value = self.func(instance) 

633 except AttributeError as e: 

634 raise RuntimeError(e) 

635 if value is NotImplemented: 

636 return getattr(super(owner, instance), self.func.__name__) 

637 setattr(instance, self.func.__name__, value) 

638 return value 

639 

640 

641class TiffWriter(object): 

642 """Write numpy arrays to TIFF file. 

643 

644 TiffWriter instances must be closed using the 'close' method, which is 

645 automatically called when using the 'with' context manager. 

646 

647 TiffWriter's main purpose is saving nD numpy array's as TIFF, 

648 not to create any possible TIFF format. Specifically, JPEG compression, 

649 SubIFDs, ExifIFD, or GPSIFD tags are not supported. 

650 

651 Examples 

652 -------- 

653 >>> # successively append images to BigTIFF file 

654 >>> data = numpy.random.rand(2, 5, 3, 301, 219) 

655 >>> with TiffWriter('temp.tif', bigtiff=True) as tif: 

656 ... for i in range(data.shape[0]): 

657 ... tif.save(data[i], compress=6, photometric='minisblack') 

658 

659 """ 

660 

661 def __init__(self, file, bigtiff=False, byteorder=None, append=False, imagej=False): 

662 """Open a TIFF file for writing. 

663 

664 An empty TIFF file is created if the file does not exist, else the 

665 file is overwritten with an empty TIFF file unless 'append' 

666 is true. Use bigtiff=True when creating files larger than 4 GB. 

667 

668 Parameters 

669 ---------- 

670 file : str, binary stream, or FileHandle 

671 File name or writable binary stream, such as an open file 

672 or BytesIO. 

673 bigtiff : bool 

674 If True, the BigTIFF format is used. 

675 byteorder : {'<', '>', '=', '|'} 

676 The endianness of the data in the file. 

677 By default, this is the system's native byte order. 

678 append : bool 

679 If True and 'file' is an existing standard TIFF file, image data 

680 and tags are appended to the file. 

681 Appending data may corrupt specifically formatted TIFF files 

682 such as LSM, STK, ImageJ, NIH, or FluoView. 

683 imagej : bool 

684 If True, write an ImageJ hyperstack compatible file. 

685 This format can handle data types uint8, uint16, or float32 and 

686 data shapes up to 6 dimensions in TZCYXS order. 

687 RGB images (S=3 or S=4) must be uint8. 

688 ImageJ's default byte order is big-endian but this implementation 

689 uses the system's native byte order by default. 

690 ImageJ does not support BigTIFF format or LZMA compression. 

691 The ImageJ file format is undocumented. 

692 

693 """ 

694 if append: 

695 # determine if file is an existing TIFF file that can be extended 

696 try: 

697 with FileHandle(file, mode="rb", size=0) as fh: 

698 pos = fh.tell() 

699 try: 

700 with TiffFile(fh) as tif: 

701 if append != "force" and any( 

702 getattr(tif, "is_" + a) 

703 for a in ( 

704 "lsm", 

705 "stk", 

706 "imagej", 

707 "nih", 

708 "fluoview", 

709 "micromanager", 

710 ) 

711 ): 

712 raise ValueError("file contains metadata") 

713 byteorder = tif.byteorder 

714 bigtiff = tif.is_bigtiff 

715 self._ifdoffset = tif.pages.next_page_offset 

716 except Exception as e: 

717 raise ValueError("cannot append to file: %s" % str(e)) 

718 finally: 

719 fh.seek(pos) 

720 except (IOError, FileNotFoundError): 

721 append = False 

722 

723 if byteorder in (None, "=", "|"): 

724 byteorder = "<" if sys.byteorder == "little" else ">" 

725 elif byteorder not in ("<", ">"): 

726 raise ValueError("invalid byteorder %s" % byteorder) 

727 if imagej and bigtiff: 

728 warnings.warn("writing incompatible BigTIFF ImageJ") 

729 

730 self._byteorder = byteorder 

731 self._imagej = bool(imagej) 

732 self._truncate = False 

733 self._metadata = None 

734 self._colormap = None 

735 

736 self._descriptionoffset = 0 

737 self._descriptionlen = 0 

738 self._descriptionlenoffset = 0 

739 self._tags = None 

740 self._shape = None # normalized shape of data in consecutive pages 

741 self._datashape = None # shape of data in consecutive pages 

742 self._datadtype = None # data type 

743 self._dataoffset = None # offset to data 

744 self._databytecounts = None # byte counts per plane 

745 self._tagoffsets = None # strip or tile offset tag code 

746 

747 if bigtiff: 

748 self._bigtiff = True 

749 self._offsetsize = 8 

750 self._tagsize = 20 

751 self._tagnoformat = "Q" 

752 self._offsetformat = "Q" 

753 self._valueformat = "8s" 

754 else: 

755 self._bigtiff = False 

756 self._offsetsize = 4 

757 self._tagsize = 12 

758 self._tagnoformat = "H" 

759 self._offsetformat = "I" 

760 self._valueformat = "4s" 

761 

762 if append: 

763 self._fh = FileHandle(file, mode="r+b", size=0) 

764 self._fh.seek(0, 2) 

765 else: 

766 self._fh = FileHandle(file, mode="wb", size=0) 

767 self._fh.write({"<": b"II", ">": b"MM"}[byteorder]) 

768 if bigtiff: 

769 self._fh.write(struct.pack(byteorder + "HHH", 43, 8, 0)) 

770 else: 

771 self._fh.write(struct.pack(byteorder + "H", 42)) 

772 # first IFD 

773 self._ifdoffset = self._fh.tell() 

774 self._fh.write(struct.pack(byteorder + self._offsetformat, 0)) 

775 

776 def save( 

777 self, 

778 data=None, 

779 shape=None, 

780 dtype=None, 

781 returnoffset=False, 

782 photometric=None, 

783 planarconfig=None, 

784 tile=None, 

785 contiguous=True, 

786 align=16, 

787 truncate=False, 

788 compress=0, 

789 rowsperstrip=None, 

790 predictor=False, 

791 colormap=None, 

792 description=None, 

793 datetime=None, 

794 resolution=None, 

795 software="tifffile.py", 

796 metadata={}, 

797 ijmetadata=None, 

798 extratags=(), 

799 ): 

800 """Write numpy array and tags to TIFF file. 

801 

802 The data shape's last dimensions are assumed to be image depth, 

803 height (length), width, and samples. 

804 If a colormap is provided, the data's dtype must be uint8 or uint16 

805 and the data values are indices into the last dimension of the 

806 colormap. 

807 If 'shape' and 'dtype' are specified, an empty array is saved. 

808 This option cannot be used with compression or multiple tiles. 

809 Image data are written uncompressed in one strip per plane by default. 

810 Dimensions larger than 2 to 4 (depending on photometric mode, planar 

811 configuration, and SGI mode) are flattened and saved as separate pages. 

812 The SampleFormat and BitsPerSample tags are derived from the data type. 

813 

814 Parameters 

815 ---------- 

816 data : numpy.ndarray or None 

817 Input image array. 

818 shape : tuple or None 

819 Shape of the empty array to save. Used only if 'data' is None. 

820 dtype : numpy.dtype or None 

821 Data-type of the empty array to save. Used only if 'data' is None. 

822 returnoffset : bool 

823 If True and the image data in the file is memory-mappable, return 

824 the offset and number of bytes of the image data in the file. 

825 photometric : {'MINISBLACK', 'MINISWHITE', 'RGB', 'PALETTE', 'CFA'} 

826 The color space of the image data. 

827 By default, this setting is inferred from the data shape and the 

828 value of colormap. 

829 For CFA images, DNG tags must be specified in 'extratags'. 

830 planarconfig : {'CONTIG', 'SEPARATE'} 

831 Specifies if samples are stored contiguous or in separate planes. 

832 By default, this setting is inferred from the data shape. 

833 If this parameter is set, extra samples are used to store grayscale 

834 images. 

835 'CONTIG': last dimension contains samples. 

836 'SEPARATE': third last dimension contains samples. 

837 tile : tuple of int 

838 The shape (depth, length, width) of image tiles to write. 

839 If None (default), image data are written in strips. 

840 The tile length and width must be a multiple of 16. 

841 If the tile depth is provided, the SGI ImageDepth and TileDepth 

842 tags are used to save volume data. 

843 Unless a single tile is used, tiles cannot be used to write 

844 contiguous files. 

845 Few software can read the SGI format, e.g. MeVisLab. 

846 contiguous : bool 

847 If True (default) and the data and parameters are compatible with 

848 previous ones, if any, the image data are stored contiguously after 

849 the previous one. Parameters 'photometric' and 'planarconfig' 

850 are ignored. Parameters 'description', datetime', and 'extratags' 

851 are written to the first page of a contiguous series only. 

852 align : int 

853 Byte boundary on which to align the image data in the file. 

854 Default 16. Use mmap.ALLOCATIONGRANULARITY for memory-mapped data. 

855 Following contiguous writes are not aligned. 

856 truncate : bool 

857 If True, only write the first page including shape metadata if 

858 possible (uncompressed, contiguous, not tiled). 

859 Other TIFF readers will only be able to read part of the data. 

860 compress : int or 'LZMA', 'ZSTD' 

861 Values from 0 to 9 controlling the level of zlib compression. 

862 If 0 (default), data are written uncompressed. 

863 Compression cannot be used to write contiguous files. 

864 If 'LZMA' or 'ZSTD', LZMA or ZSTD compression is used, which is 

865 not available on all platforms. 

866 rowsperstrip : int 

867 The number of rows per strip used for compression. 

868 Uncompressed data are written in one strip per plane. 

869 predictor : bool 

870 If True, apply horizontal differencing to integer type images 

871 before compression. 

872 colormap : numpy.ndarray 

873 RGB color values for the corresponding data value. 

874 Must be of shape (3, 2**(data.itemsize*8)) and dtype uint16. 

875 description : str 

876 The subject of the image. Must be 7-bit ASCII. Cannot be used with 

877 the ImageJ format. Saved with the first page only. 

878 datetime : datetime 

879 Date and time of image creation in '%Y:%m:%d %H:%M:%S' format. 

880 If None (default), the current date and time is used. 

881 Saved with the first page only. 

882 resolution : (float, float[, str]) or ((int, int), (int, int)[, str]) 

883 X and Y resolutions in pixels per resolution unit as float or 

884 rational numbers. A third, optional parameter specifies the 

885 resolution unit, which must be None (default for ImageJ), 

886 'INCH' (default), or 'CENTIMETER'. 

887 software : str 

888 Name of the software used to create the file. Must be 7-bit ASCII. 

889 Saved with the first page only. 

890 metadata : dict 

891 Additional meta data to be saved along with shape information 

892 in JSON or ImageJ formats in an ImageDescription tag. 

893 If None, do not write a second ImageDescription tag. 

894 Strings must be 7-bit ASCII. Saved with the first page only. 

895 ijmetadata : dict 

896 Additional meta data to be saved in application specific 

897 IJMetadata and IJMetadataByteCounts tags. Refer to the 

898 imagej_metadata_tags function for valid keys and values. 

899 Saved with the first page only. 

900 extratags : sequence of tuples 

901 Additional tags as [(code, dtype, count, value, writeonce)]. 

902 

903 code : int 

904 The TIFF tag Id. 

905 dtype : str 

906 Data type of items in 'value' in Python struct format. 

907 One of B, s, H, I, 2I, b, h, i, 2i, f, d, Q, or q. 

908 count : int 

909 Number of data values. Not used for string or byte string 

910 values. 

911 value : sequence 

912 'Count' values compatible with 'dtype'. 

913 Byte strings must contain count values of dtype packed as 

914 binary data. 

915 writeonce : bool 

916 If True, the tag is written to the first page only. 

917 

918 """ 

919 # TODO: refactor this function 

920 fh = self._fh 

921 byteorder = self._byteorder 

922 

923 if data is None: 

924 if compress: 

925 raise ValueError("cannot save compressed empty file") 

926 datashape = shape 

927 datadtype = numpy.dtype(dtype).newbyteorder(byteorder) 

928 datadtypechar = datadtype.char 

929 else: 

930 data = numpy.asarray(data, byteorder + data.dtype.char, "C") 

931 if data.size == 0: 

932 raise ValueError("cannot save empty array") 

933 datashape = data.shape 

934 datadtype = data.dtype 

935 datadtypechar = data.dtype.char 

936 

937 returnoffset = returnoffset and datadtype.isnative 

938 bilevel = datadtypechar == "?" 

939 if bilevel: 

940 index = -1 if datashape[-1] > 1 else -2 

941 datasize = product(datashape[:index]) 

942 if datashape[index] % 8: 

943 datasize *= datashape[index] // 8 + 1 

944 else: 

945 datasize *= datashape[index] // 8 

946 else: 

947 datasize = product(datashape) * datadtype.itemsize 

948 

949 # just append contiguous data if possible 

950 self._truncate = bool(truncate) 

951 if self._datashape: 

952 if ( 

953 not contiguous 

954 or self._datashape[1:] != datashape 

955 or self._datadtype != datadtype 

956 or (compress and self._tags) 

957 or tile 

958 or not numpy.array_equal(colormap, self._colormap) 

959 ): 

960 # incompatible shape, dtype, compression mode, or colormap 

961 self._write_remaining_pages() 

962 self._write_image_description() 

963 self._truncate = False 

964 self._descriptionoffset = 0 

965 self._descriptionlenoffset = 0 

966 self._datashape = None 

967 self._colormap = None 

968 if self._imagej: 

969 raise ValueError("ImageJ does not support non-contiguous data") 

970 else: 

971 # consecutive mode 

972 self._datashape = (self._datashape[0] + 1,) + datashape 

973 if not compress: 

974 # write contiguous data, write IFDs/tags later 

975 offset = fh.tell() 

976 if data is None: 

977 fh.write_empty(datasize) 

978 else: 

979 fh.write_array(data) 

980 if returnoffset: 

981 return offset, datasize 

982 return 

983 

984 input_shape = datashape 

985 tagnoformat = self._tagnoformat 

986 valueformat = self._valueformat 

987 offsetformat = self._offsetformat 

988 offsetsize = self._offsetsize 

989 tagsize = self._tagsize 

990 

991 MINISBLACK = TIFF.PHOTOMETRIC.MINISBLACK 

992 RGB = TIFF.PHOTOMETRIC.RGB 

993 CFA = TIFF.PHOTOMETRIC.CFA 

994 PALETTE = TIFF.PHOTOMETRIC.PALETTE 

995 CONTIG = TIFF.PLANARCONFIG.CONTIG 

996 SEPARATE = TIFF.PLANARCONFIG.SEPARATE 

997 

998 # parse input 

999 if photometric is not None: 

1000 photometric = enumarg(TIFF.PHOTOMETRIC, photometric) 

1001 if planarconfig: 

1002 planarconfig = enumarg(TIFF.PLANARCONFIG, planarconfig) 

1003 if not compress: 

1004 compress = False 

1005 compresstag = 1 

1006 predictor = False 

1007 else: 

1008 if isinstance(compress, (tuple, list)): 

1009 compress, compresslevel = compress 

1010 elif isinstance(compress, int): 

1011 compress, compresslevel = "ADOBE_DEFLATE", int(compress) 

1012 if not 0 <= compresslevel <= 9: 

1013 raise ValueError("invalid compression level %s" % compress) 

1014 else: 

1015 compresslevel = None 

1016 compress = compress.upper() 

1017 compresstag = enumarg(TIFF.COMPRESSION, compress) 

1018 

1019 # prepare ImageJ format 

1020 if self._imagej: 

1021 if compress in ("LZMA", "ZSTD"): 

1022 raise ValueError("ImageJ cannot handle LZMA or ZSTD compression") 

1023 if description: 

1024 warnings.warn("not writing description to ImageJ file") 

1025 description = None 

1026 volume = False 

1027 if datadtypechar not in "BHhf": 

1028 raise ValueError("ImageJ does not support data type %s" % datadtypechar) 

1029 ijrgb = photometric == RGB if photometric else None 

1030 if datadtypechar not in "B": 

1031 ijrgb = False 

1032 ijshape = imagej_shape(datashape, ijrgb) 

1033 if ijshape[-1] in (3, 4): 

1034 photometric = RGB 

1035 if datadtypechar not in "B": 

1036 raise ValueError( 

1037 "ImageJ does not support data type %s " 

1038 "for RGB" % datadtypechar 

1039 ) 

1040 elif photometric is None: 

1041 photometric = MINISBLACK 

1042 planarconfig = None 

1043 if planarconfig == SEPARATE: 

1044 raise ValueError("ImageJ does not support planar images") 

1045 else: 

1046 planarconfig = CONTIG if ijrgb else None 

1047 

1048 # define compress function 

1049 if compress: 

1050 if compresslevel is None: 

1051 compressor, compresslevel = TIFF.COMPESSORS[compresstag] 

1052 else: 

1053 compressor, _ = TIFF.COMPESSORS[compresstag] 

1054 compresslevel = int(compresslevel) 

1055 if predictor: 

1056 if datadtype.kind not in "iu": 

1057 raise ValueError("prediction not implemented for %s" % datadtype) 

1058 

1059 def compress(data, level=compresslevel): 

1060 # horizontal differencing 

1061 diff = numpy.diff(data, axis=-2) 

1062 data = numpy.insert(diff, 0, data[..., 0, :], axis=-2) 

1063 return compressor(data, level) 

1064 

1065 else: 

1066 

1067 def compress(data, level=compresslevel): 

1068 return compressor(data, level) 

1069 

1070 # verify colormap and indices 

1071 if colormap is not None: 

1072 if datadtypechar not in "BH": 

1073 raise ValueError("invalid data dtype for palette mode") 

1074 colormap = numpy.asarray(colormap, dtype=byteorder + "H") 

1075 if colormap.shape != (3, 2 ** (datadtype.itemsize * 8)): 

1076 raise ValueError("invalid color map shape") 

1077 self._colormap = colormap 

1078 

1079 # verify tile shape 

1080 if tile: 

1081 tile = tuple(int(i) for i in tile[:3]) 

1082 volume = len(tile) == 3 

1083 if ( 

1084 len(tile) < 2 

1085 or tile[-1] % 16 

1086 or tile[-2] % 16 

1087 or any(i < 1 for i in tile) 

1088 ): 

1089 raise ValueError("invalid tile shape") 

1090 else: 

1091 tile = () 

1092 volume = False 

1093 

1094 # normalize data shape to 5D or 6D, depending on volume: 

1095 # (pages, planar_samples, [depth,] height, width, contig_samples) 

1096 datashape = reshape_nd(datashape, 3 if photometric == RGB else 2) 

1097 shape = datashape 

1098 ndim = len(datashape) 

1099 

1100 samplesperpixel = 1 

1101 extrasamples = 0 

1102 if volume and ndim < 3: 

1103 volume = False 

1104 if colormap is not None: 

1105 photometric = PALETTE 

1106 planarconfig = None 

1107 if photometric is None: 

1108 photometric = MINISBLACK 

1109 if bilevel: 

1110 photometric = TIFF.PHOTOMETRIC.MINISWHITE 

1111 elif planarconfig == CONTIG: 

1112 if ndim > 2 and shape[-1] in (3, 4): 

1113 photometric = RGB 

1114 elif planarconfig == SEPARATE: 

1115 if volume and ndim > 3 and shape[-4] in (3, 4): 

1116 photometric = RGB 

1117 elif ndim > 2 and shape[-3] in (3, 4): 

1118 photometric = RGB 

1119 elif ndim > 2 and shape[-1] in (3, 4): 

1120 photometric = RGB 

1121 elif self._imagej: 

1122 photometric = MINISBLACK 

1123 elif volume and ndim > 3 and shape[-4] in (3, 4): 

1124 photometric = RGB 

1125 elif ndim > 2 and shape[-3] in (3, 4): 

1126 photometric = RGB 

1127 if planarconfig and len(shape) <= (3 if volume else 2): 

1128 planarconfig = None 

1129 photometric = MINISBLACK 

1130 if photometric == RGB: 

1131 if len(shape) < 3: 

1132 raise ValueError("not a RGB(A) image") 

1133 if len(shape) < 4: 

1134 volume = False 

1135 if planarconfig is None: 

1136 if shape[-1] in (3, 4): 

1137 planarconfig = CONTIG 

1138 elif shape[-4 if volume else -3] in (3, 4): 

1139 planarconfig = SEPARATE 

1140 elif shape[-1] > shape[-4 if volume else -3]: 

1141 planarconfig = SEPARATE 

1142 else: 

1143 planarconfig = CONTIG 

1144 if planarconfig == CONTIG: 

1145 datashape = (-1, 1) + shape[(-4 if volume else -3) :] 

1146 samplesperpixel = datashape[-1] 

1147 else: 

1148 datashape = (-1,) + shape[(-4 if volume else -3) :] + (1,) 

1149 samplesperpixel = datashape[1] 

1150 if samplesperpixel > 3: 

1151 extrasamples = samplesperpixel - 3 

1152 elif photometric == CFA: 

1153 if len(shape) != 2: 

1154 raise ValueError("invalid CFA image") 

1155 volume = False 

1156 planarconfig = None 

1157 datashape = (-1, 1) + shape[-2:] + (1,) 

1158 if 50706 not in (et[0] for et in extratags): 

1159 raise ValueError("must specify DNG tags for CFA image") 

1160 elif planarconfig and len(shape) > (3 if volume else 2): 

1161 if planarconfig == CONTIG: 

1162 datashape = (-1, 1) + shape[(-4 if volume else -3) :] 

1163 samplesperpixel = datashape[-1] 

1164 else: 

1165 datashape = (-1,) + shape[(-4 if volume else -3) :] + (1,) 

1166 samplesperpixel = datashape[1] 

1167 extrasamples = samplesperpixel - 1 

1168 else: 

1169 planarconfig = None 

1170 # remove trailing 1s 

1171 while len(shape) > 2 and shape[-1] == 1: 

1172 shape = shape[:-1] 

1173 if len(shape) < 3: 

1174 volume = False 

1175 datashape = (-1, 1) + shape[(-3 if volume else -2) :] + (1,) 

1176 

1177 # normalize shape to 6D 

1178 assert len(datashape) in (5, 6) 

1179 if len(datashape) == 5: 

1180 datashape = datashape[:2] + (1,) + datashape[2:] 

1181 if datashape[0] == -1: 

1182 s0 = product(input_shape) // product(datashape[1:]) 

1183 datashape = (s0,) + datashape[1:] 

1184 shape = datashape 

1185 if data is not None: 

1186 data = data.reshape(shape) 

1187 

1188 if tile and not volume: 

1189 tile = (1, tile[-2], tile[-1]) 

1190 

1191 if photometric == PALETTE: 

1192 if samplesperpixel != 1 or extrasamples or shape[1] != 1 or shape[-1] != 1: 

1193 raise ValueError("invalid data shape for palette mode") 

1194 

1195 if photometric == RGB and samplesperpixel == 2: 

1196 raise ValueError("not a RGB image (samplesperpixel=2)") 

1197 

1198 if bilevel: 

1199 if compress: 

1200 raise ValueError("cannot save compressed bilevel image") 

1201 if tile: 

1202 raise ValueError("cannot save tiled bilevel image") 

1203 if photometric not in (0, 1): 

1204 raise ValueError("cannot save bilevel image as %s" % str(photometric)) 

1205 datashape = list(datashape) 

1206 if datashape[-2] % 8: 

1207 datashape[-2] = datashape[-2] // 8 + 1 

1208 else: 

1209 datashape[-2] = datashape[-2] // 8 

1210 datashape = tuple(datashape) 

1211 assert datasize == product(datashape) 

1212 if data is not None: 

1213 data = numpy.packbits(data, axis=-2) 

1214 assert datashape[-2] == data.shape[-2] 

1215 

1216 bytestr = ( 

1217 bytes 

1218 if sys.version[0] == "2" 

1219 else (lambda x: bytes(x, "ascii") if isinstance(x, str) else x) 

1220 ) 

1221 tags = [] # list of (code, ifdentry, ifdvalue, writeonce) 

1222 

1223 strip_or_tile = "Tile" if tile else "Strip" 

1224 tagbytecounts = TIFF.TAG_NAMES[strip_or_tile + "ByteCounts"] 

1225 tag_offsets = TIFF.TAG_NAMES[strip_or_tile + "Offsets"] 

1226 self._tagoffsets = tag_offsets 

1227 

1228 def pack(fmt, *val): 

1229 return struct.pack(byteorder + fmt, *val) 

1230 

1231 def addtag(code, dtype, count, value, writeonce=False): 

1232 # Compute ifdentry & ifdvalue bytes from code, dtype, count, value 

1233 # Append (code, ifdentry, ifdvalue, writeonce) to tags list 

1234 code = int(TIFF.TAG_NAMES.get(code, code)) 

1235 try: 

1236 tifftype = TIFF.DATA_DTYPES[dtype] 

1237 except KeyError: 

1238 raise ValueError("unknown dtype %s" % dtype) 

1239 rawcount = count 

1240 

1241 if dtype == "s": 

1242 # strings 

1243 value = bytestr(value) + b"\0" 

1244 count = rawcount = len(value) 

1245 rawcount = value.find(b"\0\0") 

1246 if rawcount < 0: 

1247 rawcount = count 

1248 else: 

1249 rawcount += 1 # length of string without buffer 

1250 value = (value,) 

1251 elif isinstance(value, bytes): 

1252 # packed binary data 

1253 dtsize = struct.calcsize(dtype) 

1254 if len(value) % dtsize: 

1255 raise ValueError("invalid packed binary data") 

1256 count = len(value) // dtsize 

1257 if len(dtype) > 1: 

1258 count *= int(dtype[:-1]) 

1259 dtype = dtype[-1] 

1260 ifdentry = [pack("HH", code, tifftype), pack(offsetformat, rawcount)] 

1261 ifdvalue = None 

1262 if struct.calcsize(dtype) * count <= offsetsize: 

1263 # value(s) can be written directly 

1264 if isinstance(value, bytes): 

1265 ifdentry.append(pack(valueformat, value)) 

1266 elif count == 1: 

1267 if isinstance(value, (tuple, list, numpy.ndarray)): 

1268 value = value[0] 

1269 ifdentry.append(pack(valueformat, pack(dtype, value))) 

1270 else: 

1271 ifdentry.append(pack(valueformat, pack(str(count) + dtype, *value))) 

1272 else: 

1273 # use offset to value(s) 

1274 ifdentry.append(pack(offsetformat, 0)) 

1275 if isinstance(value, bytes): 

1276 ifdvalue = value 

1277 elif isinstance(value, numpy.ndarray): 

1278 assert value.size == count 

1279 assert value.dtype.char == dtype 

1280 ifdvalue = value.tostring() 

1281 elif isinstance(value, (tuple, list)): 

1282 ifdvalue = pack(str(count) + dtype, *value) 

1283 else: 

1284 ifdvalue = pack(dtype, value) 

1285 tags.append((code, b"".join(ifdentry), ifdvalue, writeonce)) 

1286 

1287 def rational(arg, max_denominator=1000000): 

1288 """ "Return nominator and denominator from float or two integers.""" 

1289 from fractions import Fraction # delayed import 

1290 

1291 try: 

1292 f = Fraction.from_float(arg) 

1293 except TypeError: 

1294 f = Fraction(arg[0], arg[1]) 

1295 f = f.limit_denominator(max_denominator) 

1296 return f.numerator, f.denominator 

1297 

1298 if description: 

1299 # user provided description 

1300 addtag("ImageDescription", "s", 0, description, writeonce=True) 

1301 

1302 # write shape and metadata to ImageDescription 

1303 self._metadata = {} if not metadata else metadata.copy() 

1304 if self._imagej: 

1305 description = imagej_description( 

1306 input_shape, 

1307 shape[-1] in (3, 4), 

1308 self._colormap is not None, 

1309 **self._metadata 

1310 ) 

1311 elif metadata or metadata == {}: 

1312 if self._truncate: 

1313 self._metadata.update(truncated=True) 

1314 description = json_description(input_shape, **self._metadata) 

1315 else: 

1316 description = None 

1317 if description: 

1318 # add 64 bytes buffer 

1319 # the image description might be updated later with the final shape 

1320 description = str2bytes(description, "ascii") 

1321 description += b"\0" * 64 

1322 self._descriptionlen = len(description) 

1323 addtag("ImageDescription", "s", 0, description, writeonce=True) 

1324 

1325 if software: 

1326 addtag("Software", "s", 0, software, writeonce=True) 

1327 if datetime is None: 

1328 datetime = self._now() 

1329 addtag( 

1330 "DateTime", "s", 0, datetime.strftime("%Y:%m:%d %H:%M:%S"), writeonce=True 

1331 ) 

1332 addtag("Compression", "H", 1, compresstag) 

1333 if predictor: 

1334 addtag("Predictor", "H", 1, 2) 

1335 addtag("ImageWidth", "I", 1, shape[-2]) 

1336 addtag("ImageLength", "I", 1, shape[-3]) 

1337 if tile: 

1338 addtag("TileWidth", "I", 1, tile[-1]) 

1339 addtag("TileLength", "I", 1, tile[-2]) 

1340 if tile[0] > 1: 

1341 addtag("ImageDepth", "I", 1, shape[-4]) 

1342 addtag("TileDepth", "I", 1, tile[0]) 

1343 addtag("NewSubfileType", "I", 1, 0) 

1344 if not bilevel: 

1345 sampleformat = {"u": 1, "i": 2, "f": 3, "c": 6}[datadtype.kind] 

1346 addtag( 

1347 "SampleFormat", "H", samplesperpixel, (sampleformat,) * samplesperpixel 

1348 ) 

1349 addtag("PhotometricInterpretation", "H", 1, photometric.value) 

1350 if colormap is not None: 

1351 addtag("ColorMap", "H", colormap.size, colormap) 

1352 addtag("SamplesPerPixel", "H", 1, samplesperpixel) 

1353 if bilevel: 

1354 pass 

1355 elif planarconfig and samplesperpixel > 1: 

1356 addtag("PlanarConfiguration", "H", 1, planarconfig.value) 

1357 addtag( 

1358 "BitsPerSample", 

1359 "H", 

1360 samplesperpixel, 

1361 (datadtype.itemsize * 8,) * samplesperpixel, 

1362 ) 

1363 else: 

1364 addtag("BitsPerSample", "H", 1, datadtype.itemsize * 8) 

1365 if extrasamples: 

1366 if photometric == RGB and extrasamples == 1: 

1367 addtag("ExtraSamples", "H", 1, 1) # associated alpha channel 

1368 else: 

1369 addtag("ExtraSamples", "H", extrasamples, (0,) * extrasamples) 

1370 if resolution is not None: 

1371 addtag("XResolution", "2I", 1, rational(resolution[0])) 

1372 addtag("YResolution", "2I", 1, rational(resolution[1])) 

1373 if len(resolution) > 2: 

1374 unit = resolution[2] 

1375 unit = 1 if unit is None else enumarg(TIFF.RESUNIT, unit) 

1376 elif self._imagej: 

1377 unit = 1 

1378 else: 

1379 unit = 2 

1380 addtag("ResolutionUnit", "H", 1, unit) 

1381 elif not self._imagej: 

1382 addtag("XResolution", "2I", 1, (1, 1)) 

1383 addtag("YResolution", "2I", 1, (1, 1)) 

1384 addtag("ResolutionUnit", "H", 1, 1) 

1385 if ijmetadata: 

1386 for t in imagej_metadata_tags(ijmetadata, byteorder): 

1387 addtag(*t) 

1388 

1389 contiguous = not compress 

1390 if tile: 

1391 # one chunk per tile per plane 

1392 tiles = ( 

1393 (shape[2] + tile[0] - 1) // tile[0], 

1394 (shape[3] + tile[1] - 1) // tile[1], 

1395 (shape[4] + tile[2] - 1) // tile[2], 

1396 ) 

1397 numtiles = product(tiles) * shape[1] 

1398 stripbytecounts = [ 

1399 product(tile) * shape[-1] * datadtype.itemsize 

1400 ] * numtiles 

1401 addtag(tagbytecounts, offsetformat, numtiles, stripbytecounts) 

1402 addtag(tag_offsets, offsetformat, numtiles, [0] * numtiles) 

1403 contiguous = contiguous and product(tiles) == 1 

1404 if not contiguous: 

1405 # allocate tile buffer 

1406 chunk = numpy.empty(tile + (shape[-1],), dtype=datadtype) 

1407 elif contiguous: 

1408 # one strip per plane 

1409 if bilevel: 

1410 stripbytecounts = [product(datashape[2:])] * shape[1] 

1411 else: 

1412 stripbytecounts = [product(datashape[2:]) * datadtype.itemsize] * shape[ 

1413 1 

1414 ] 

1415 addtag(tagbytecounts, offsetformat, shape[1], stripbytecounts) 

1416 addtag(tag_offsets, offsetformat, shape[1], [0] * shape[1]) 

1417 addtag("RowsPerStrip", "I", 1, shape[-3]) 

1418 else: 

1419 # compress rowsperstrip or ~64 KB chunks 

1420 rowsize = product(shape[-2:]) * datadtype.itemsize 

1421 if rowsperstrip is None: 

1422 rowsperstrip = 65536 // rowsize 

1423 if rowsperstrip < 1: 

1424 rowsperstrip = 1 

1425 elif rowsperstrip > shape[-3]: 

1426 rowsperstrip = shape[-3] 

1427 addtag("RowsPerStrip", "I", 1, rowsperstrip) 

1428 

1429 numstrips = (shape[-3] + rowsperstrip - 1) // rowsperstrip 

1430 numstrips *= shape[1] 

1431 stripbytecounts = [0] * numstrips 

1432 addtag(tagbytecounts, offsetformat, numstrips, [0] * numstrips) 

1433 addtag(tag_offsets, offsetformat, numstrips, [0] * numstrips) 

1434 

1435 if data is None and not contiguous: 

1436 raise ValueError("cannot write non-contiguous empty file") 

1437 

1438 # add extra tags from user 

1439 for t in extratags: 

1440 addtag(*t) 

1441 

1442 # TODO: check TIFFReadDirectoryCheckOrder warning in files containing 

1443 # multiple tags of same code 

1444 # the entries in an IFD must be sorted in ascending order by tag code 

1445 tags = sorted(tags, key=lambda x: x[0]) 

1446 

1447 if not (self._bigtiff or self._imagej) and (fh.tell() + datasize > 2**31 - 1): 

1448 raise ValueError("data too large for standard TIFF file") 

1449 

1450 # if not compressed or multi-tiled, write the first IFD and then 

1451 # all data contiguously; else, write all IFDs and data interleaved 

1452 for pageindex in range(1 if contiguous else shape[0]): 

1453 # update pointer at ifd_offset 

1454 pos = fh.tell() 

1455 if pos % 2: 

1456 # location of IFD must begin on a word boundary 

1457 fh.write(b"\0") 

1458 pos += 1 

1459 fh.seek(self._ifdoffset) 

1460 fh.write(pack(offsetformat, pos)) 

1461 fh.seek(pos) 

1462 

1463 # write ifdentries 

1464 fh.write(pack(tagnoformat, len(tags))) 

1465 tag_offset = fh.tell() 

1466 fh.write(b"".join(t[1] for t in tags)) 

1467 self._ifdoffset = fh.tell() 

1468 fh.write(pack(offsetformat, 0)) # offset to next IFD 

1469 

1470 # write tag values and patch offsets in ifdentries, if necessary 

1471 for tagindex, tag in enumerate(tags): 

1472 if tag[2]: 

1473 pos = fh.tell() 

1474 if pos % 2: 

1475 # tag value is expected to begin on word boundary 

1476 fh.write(b"\0") 

1477 pos += 1 

1478 fh.seek(tag_offset + tagindex * tagsize + offsetsize + 4) 

1479 fh.write(pack(offsetformat, pos)) 

1480 fh.seek(pos) 

1481 if tag[0] == tag_offsets: 

1482 stripoffsetsoffset = pos 

1483 elif tag[0] == tagbytecounts: 

1484 strip_bytecounts_offset = pos 

1485 elif tag[0] == 270 and tag[2].endswith(b"\0\0\0\0"): 

1486 # image description buffer 

1487 self._descriptionoffset = pos 

1488 self._descriptionlenoffset = tag_offset + tagindex * tagsize + 4 

1489 fh.write(tag[2]) 

1490 

1491 # write image data 

1492 data_offset = fh.tell() 

1493 skip = align - data_offset % align 

1494 fh.seek(skip, 1) 

1495 data_offset += skip 

1496 if contiguous: 

1497 if data is None: 

1498 fh.write_empty(datasize) 

1499 else: 

1500 fh.write_array(data) 

1501 elif tile: 

1502 if data is None: 

1503 fh.write_empty(numtiles * stripbytecounts[0]) 

1504 else: 

1505 stripindex = 0 

1506 for plane in data[pageindex]: 

1507 for tz in range(tiles[0]): 

1508 for ty in range(tiles[1]): 

1509 for tx in range(tiles[2]): 

1510 c0 = min(tile[0], shape[2] - tz * tile[0]) 

1511 c1 = min(tile[1], shape[3] - ty * tile[1]) 

1512 c2 = min(tile[2], shape[4] - tx * tile[2]) 

1513 chunk[c0:, c1:, c2:] = 0 

1514 chunk[:c0, :c1, :c2] = plane[ 

1515 tz * tile[0] : tz * tile[0] + c0, 

1516 ty * tile[1] : ty * tile[1] + c1, 

1517 tx * tile[2] : tx * tile[2] + c2, 

1518 ] 

1519 if compress: 

1520 t = compress(chunk) 

1521 fh.write(t) 

1522 stripbytecounts[stripindex] = len(t) 

1523 stripindex += 1 

1524 else: 

1525 fh.write_array(chunk) 

1526 fh.flush() 

1527 elif compress: 

1528 # write one strip per rowsperstrip 

1529 assert data.shape[2] == 1 # not handling depth 

1530 numstrips = (shape[-3] + rowsperstrip - 1) // rowsperstrip 

1531 stripindex = 0 

1532 for plane in data[pageindex]: 

1533 for i in range(numstrips): 

1534 strip = plane[0, i * rowsperstrip : (i + 1) * rowsperstrip] 

1535 strip = compress(strip) 

1536 fh.write(strip) 

1537 stripbytecounts[stripindex] = len(strip) 

1538 stripindex += 1 

1539 

1540 # update strip/tile offsets and bytecounts if necessary 

1541 pos = fh.tell() 

1542 for tagindex, tag in enumerate(tags): 

1543 if tag[0] == tag_offsets: # strip/tile offsets 

1544 if tag[2]: 

1545 fh.seek(stripoffsetsoffset) 

1546 strip_offset = data_offset 

1547 for size in stripbytecounts: 

1548 fh.write(pack(offsetformat, strip_offset)) 

1549 strip_offset += size 

1550 else: 

1551 fh.seek(tag_offset + tagindex * tagsize + offsetsize + 4) 

1552 fh.write(pack(offsetformat, data_offset)) 

1553 elif tag[0] == tagbytecounts: # strip/tile bytecounts 

1554 if compress: 

1555 if tag[2]: 

1556 fh.seek(strip_bytecounts_offset) 

1557 for size in stripbytecounts: 

1558 fh.write(pack(offsetformat, size)) 

1559 else: 

1560 fh.seek(tag_offset + tagindex * tagsize + offsetsize + 4) 

1561 fh.write(pack(offsetformat, stripbytecounts[0])) 

1562 break 

1563 fh.seek(pos) 

1564 fh.flush() 

1565 

1566 # remove tags that should be written only once 

1567 if pageindex == 0: 

1568 tags = [tag for tag in tags if not tag[-1]] 

1569 

1570 self._shape = shape 

1571 self._datashape = (1,) + input_shape 

1572 self._datadtype = datadtype 

1573 self._dataoffset = data_offset 

1574 self._databytecounts = stripbytecounts 

1575 

1576 if contiguous: 

1577 # write remaining IFDs/tags later 

1578 self._tags = tags 

1579 # return offset and size of image data 

1580 if returnoffset: 

1581 return data_offset, sum(stripbytecounts) 

1582 

1583 def _write_remaining_pages(self): 

1584 """Write outstanding IFDs and tags to file.""" 

1585 if not self._tags or self._truncate: 

1586 return 

1587 

1588 fh = self._fh 

1589 fhpos = fh.tell() 

1590 if fhpos % 2: 

1591 fh.write(b"\0") 

1592 fhpos += 1 

1593 byteorder = self._byteorder 

1594 offsetformat = self._offsetformat 

1595 offsetsize = self._offsetsize 

1596 tagnoformat = self._tagnoformat 

1597 tagsize = self._tagsize 

1598 dataoffset = self._dataoffset 

1599 pagedatasize = sum(self._databytecounts) 

1600 pageno = self._shape[0] * self._datashape[0] - 1 

1601 

1602 def pack(fmt, *val): 

1603 return struct.pack(byteorder + fmt, *val) 

1604 

1605 # construct template IFD in memory 

1606 # need to patch offsets to next IFD and data before writing to disk 

1607 ifd = io.BytesIO() 

1608 ifd.write(pack(tagnoformat, len(self._tags))) 

1609 tagoffset = ifd.tell() 

1610 ifd.write(b"".join(t[1] for t in self._tags)) 

1611 ifdoffset = ifd.tell() 

1612 ifd.write(pack(offsetformat, 0)) # offset to next IFD 

1613 # tag values 

1614 for tagindex, tag in enumerate(self._tags): 

1615 offset2value = tagoffset + tagindex * tagsize + offsetsize + 4 

1616 if tag[2]: 

1617 pos = ifd.tell() 

1618 if pos % 2: # tag value is expected to begin on word boundary 

1619 ifd.write(b"\0") 

1620 pos += 1 

1621 ifd.seek(offset2value) 

1622 try: 

1623 ifd.write(pack(offsetformat, pos + fhpos)) 

1624 except Exception: # struct.error 

1625 if self._imagej: 

1626 warnings.warn("truncating ImageJ file") 

1627 self._truncate = True 

1628 return 

1629 raise ValueError("data too large for non-BigTIFF file") 

1630 ifd.seek(pos) 

1631 ifd.write(tag[2]) 

1632 if tag[0] == self._tagoffsets: 

1633 # save strip/tile offsets for later updates 

1634 stripoffset2offset = offset2value 

1635 stripoffset2value = pos 

1636 elif tag[0] == self._tagoffsets: 

1637 # save strip/tile offsets for later updates 

1638 stripoffset2offset = None 

1639 stripoffset2value = offset2value 

1640 # size to word boundary 

1641 if ifd.tell() % 2: 

1642 ifd.write(b"\0") 

1643 

1644 # check if all IFDs fit in file 

1645 pos = fh.tell() 

1646 if not self._bigtiff and pos + ifd.tell() * pageno > 2**32 - 256: 

1647 if self._imagej: 

1648 warnings.warn("truncating ImageJ file") 

1649 self._truncate = True 

1650 return 

1651 raise ValueError("data too large for non-BigTIFF file") 

1652 

1653 # TODO: assemble IFD chain in memory 

1654 for _ in range(pageno): 

1655 # update pointer at IFD offset 

1656 pos = fh.tell() 

1657 fh.seek(self._ifdoffset) 

1658 fh.write(pack(offsetformat, pos)) 

1659 fh.seek(pos) 

1660 self._ifdoffset = pos + ifdoffset 

1661 # update strip/tile offsets in IFD 

1662 dataoffset += pagedatasize # offset to image data 

1663 if stripoffset2offset is None: 

1664 ifd.seek(stripoffset2value) 

1665 ifd.write(pack(offsetformat, dataoffset)) 

1666 else: 

1667 ifd.seek(stripoffset2offset) 

1668 ifd.write(pack(offsetformat, pos + stripoffset2value)) 

1669 ifd.seek(stripoffset2value) 

1670 stripoffset = dataoffset 

1671 for size in self._databytecounts: 

1672 ifd.write(pack(offsetformat, stripoffset)) 

1673 stripoffset += size 

1674 # write IFD entry 

1675 fh.write(ifd.getvalue()) 

1676 

1677 self._tags = None 

1678 self._datadtype = None 

1679 self._dataoffset = None 

1680 self._databytecounts = None 

1681 # do not reset _shape or _data_shape 

1682 

1683 def _write_image_description(self): 

1684 """Write meta data to ImageDescription tag.""" 

1685 if ( 

1686 not self._datashape 

1687 or self._datashape[0] == 1 

1688 or self._descriptionoffset <= 0 

1689 ): 

1690 return 

1691 

1692 colormapped = self._colormap is not None 

1693 if self._imagej: 

1694 isrgb = self._shape[-1] in (3, 4) 

1695 description = imagej_description( 

1696 self._datashape, isrgb, colormapped, **self._metadata 

1697 ) 

1698 else: 

1699 description = json_description(self._datashape, **self._metadata) 

1700 

1701 # rewrite description and its length to file 

1702 description = description.encode("utf-8") 

1703 description = description[: self._descriptionlen - 1] 

1704 pos = self._fh.tell() 

1705 self._fh.seek(self._descriptionoffset) 

1706 self._fh.write(description) 

1707 self._fh.seek(self._descriptionlenoffset) 

1708 self._fh.write( 

1709 struct.pack(self._byteorder + self._offsetformat, len(description) + 1) 

1710 ) 

1711 self._fh.seek(pos) 

1712 

1713 self._descriptionoffset = 0 

1714 self._descriptionlenoffset = 0 

1715 self._descriptionlen = 0 

1716 

1717 def _now(self): 

1718 """Return current date and time.""" 

1719 return datetime.datetime.now() 

1720 

1721 def close(self): 

1722 """Write remaining pages and close file handle.""" 

1723 if not self._truncate: 

1724 self._write_remaining_pages() 

1725 self._write_image_description() 

1726 self._fh.close() 

1727 

1728 def __enter__(self): 

1729 return self 

1730 

1731 def __exit__(self, exc_type, exc_value, traceback): 

1732 self.close() 

1733 

1734 

1735class TiffFile(object): 

1736 """Read image and metadata from TIFF file. 

1737 

1738 TiffFile instances must be closed using the 'close' method, which is 

1739 automatically called when using the 'with' context manager. 

1740 

1741 Attributes 

1742 ---------- 

1743 pages : TiffPages 

1744 Sequence of TIFF pages in file. 

1745 series : list of TiffPageSeries 

1746 Sequences of closely related TIFF pages. These are computed 

1747 from OME, LSM, ImageJ, etc. metadata or based on similarity 

1748 of page properties such as shape, dtype, and compression. 

1749 byteorder : '>', '<' 

1750 The endianness of data in the file. 

1751 '>': big-endian (Motorola). 

1752 '>': little-endian (Intel). 

1753 is_flag : bool 

1754 If True, file is of a certain format. 

1755 Flags are: bigtiff, movie, shaped, ome, imagej, stk, lsm, fluoview, 

1756 nih, vista, 'micromanager, metaseries, mdgel, mediacy, tvips, fei, 

1757 sem, scn, svs, scanimage, andor, epics, pilatus, qptiff. 

1758 

1759 All attributes are read-only. 

1760 

1761 Examples 

1762 -------- 

1763 >>> # read image array from TIFF file 

1764 >>> imsave('temp.tif', numpy.random.rand(5, 301, 219)) 

1765 >>> with TiffFile('temp.tif') as tif: 

1766 ... data = tif.asarray() 

1767 >>> data.shape 

1768 (5, 301, 219) 

1769 

1770 """ 

1771 

1772 def __init__( 

1773 self, 

1774 arg, 

1775 name=None, 

1776 offset=None, 

1777 size=None, 

1778 multifile=True, 

1779 movie=None, 

1780 **kwargs 

1781 ): 

1782 """Initialize instance from file. 

1783 

1784 Parameters 

1785 ---------- 

1786 arg : str or open file 

1787 Name of file or open file object. 

1788 The file objects are closed in TiffFile.close(). 

1789 name : str 

1790 Optional name of file in case 'arg' is a file handle. 

1791 offset : int 

1792 Optional start position of embedded file. By default, this is 

1793 the current file position. 

1794 size : int 

1795 Optional size of embedded file. By default, this is the number 

1796 of bytes from the 'offset' to the end of the file. 

1797 multifile : bool 

1798 If True (default), series may include pages from multiple files. 

1799 Currently applies to OME-TIFF only. 

1800 movie : bool 

1801 If True, assume that later pages differ from first page only by 

1802 data offsets and byte counts. Significantly increases speed and 

1803 reduces memory usage when reading movies with thousands of pages. 

1804 Enabling this for non-movie files will result in data corruption 

1805 or crashes. Python 3 only. 

1806 kwargs : bool 

1807 'is_ome': If False, disable processing of OME-XML metadata. 

1808 

1809 """ 

1810 if "fastij" in kwargs: 

1811 del kwargs["fastij"] 

1812 raise DeprecationWarning("the fastij option will be removed") 

1813 for key, value in kwargs.items(): 

1814 if key[:3] == "is_" and key[3:] in TIFF.FILE_FLAGS: 

1815 if value is not None and not value: 

1816 setattr(self, key, bool(value)) 

1817 else: 

1818 raise TypeError("unexpected keyword argument: %s" % key) 

1819 

1820 fh = FileHandle(arg, mode="rb", name=name, offset=offset, size=size) 

1821 self._fh = fh 

1822 self._multifile = bool(multifile) 

1823 self._files = {fh.name: self} # cache of TiffFiles 

1824 try: 

1825 fh.seek(0) 

1826 try: 

1827 byteorder = {b"II": "<", b"MM": ">"}[fh.read(2)] 

1828 except KeyError: 

1829 raise ValueError("not a TIFF file") 

1830 sys_byteorder = {"big": ">", "little": "<"}[sys.byteorder] 

1831 self.isnative = byteorder == sys_byteorder 

1832 

1833 version = struct.unpack(byteorder + "H", fh.read(2))[0] 

1834 if version == 43: 

1835 # BigTiff 

1836 self.is_bigtiff = True 

1837 offsetsize, zero = struct.unpack(byteorder + "HH", fh.read(4)) 

1838 if zero or offsetsize != 8: 

1839 raise ValueError("invalid BigTIFF file") 

1840 self.byteorder = byteorder 

1841 self.offsetsize = 8 

1842 self.offsetformat = byteorder + "Q" 

1843 self.tagnosize = 8 

1844 self.tagnoformat = byteorder + "Q" 

1845 self.tagsize = 20 

1846 self.tagformat1 = byteorder + "HH" 

1847 self.tagformat2 = byteorder + "Q8s" 

1848 elif version == 42: 

1849 self.is_bigtiff = False 

1850 self.byteorder = byteorder 

1851 self.offsetsize = 4 

1852 self.offsetformat = byteorder + "I" 

1853 self.tagnosize = 2 

1854 self.tagnoformat = byteorder + "H" 

1855 self.tagsize = 12 

1856 self.tagformat1 = byteorder + "HH" 

1857 self.tagformat2 = byteorder + "I4s" 

1858 else: 

1859 raise ValueError("invalid TIFF file") 

1860 

1861 # file handle is at offset to offset to first page 

1862 self.pages = TiffPages(self) 

1863 

1864 if self.is_lsm and ( 

1865 self.filehandle.size >= 2**32 

1866 or self.pages[0].compression != 1 

1867 or self.pages[1].compression != 1 

1868 ): 

1869 self._lsm_load_pages() 

1870 self._lsm_fix_strip_offsets() 

1871 self._lsm_fix_strip_bytecounts() 

1872 elif movie: 

1873 self.pages.useframes = True 

1874 

1875 except Exception: 

1876 fh.close() 

1877 raise 

1878 

1879 @property 

1880 def filehandle(self): 

1881 """Return file handle.""" 

1882 return self._fh 

1883 

1884 @property 

1885 def filename(self): 

1886 """Return name of file handle.""" 

1887 return self._fh.name 

1888 

1889 @lazyattr 

1890 def fstat(self): 

1891 """Return status of file handle as stat_result object.""" 

1892 try: 

1893 return os.fstat(self._fh.fileno()) 

1894 except Exception: # io.UnsupportedOperation 

1895 return None 

1896 

1897 def close(self): 

1898 """Close open file handle(s).""" 

1899 for tif in self._files.values(): 

1900 tif.filehandle.close() 

1901 self._files = {} 

1902 

1903 def asarray(self, key=None, series=None, out=None, validate=True, maxworkers=1): 

1904 """Return image data from multiple TIFF pages as numpy array. 

1905 

1906 By default, the data from the first series is returned. 

1907 

1908 Parameters 

1909 ---------- 

1910 key : int, slice, or sequence of page indices 

1911 Defines which pages to return as array. 

1912 series : int or TiffPageSeries 

1913 Defines which series of pages to return as array. 

1914 out : numpy.ndarray, str, or file-like object; optional 

1915 Buffer where image data will be saved. 

1916 If None (default), a new array will be created. 

1917 If numpy.ndarray, a writable array of compatible dtype and shape. 

1918 If 'memmap', directly memory-map the image data in the TIFF file 

1919 if possible; else create a memory-mapped array in a temporary file. 

1920 If str or open file, the file name or file object used to 

1921 create a memory-map to an array stored in a binary file on disk. 

1922 validate : bool 

1923 If True (default), validate various tags. 

1924 Passed to TiffPage.asarray(). 

1925 maxworkers : int 

1926 Maximum number of threads to concurrently get data from pages. 

1927 Default is 1. If None, up to half the CPU cores are used. 

1928 Reading data from file is limited to a single thread. 

1929 Using multiple threads can significantly speed up this function 

1930 if the bottleneck is decoding compressed data, e.g. in case of 

1931 large LZW compressed LSM files. 

1932 If the bottleneck is I/O or pure Python code, using multiple 

1933 threads might be detrimental. 

1934 

1935 """ 

1936 if not self.pages: 

1937 return numpy.array([]) 

1938 if key is None and series is None: 

1939 series = 0 

1940 if series is not None: 

1941 try: 

1942 series = self.series[series] 

1943 except (KeyError, TypeError): 

1944 pass 

1945 pages = series._pages 

1946 else: 

1947 pages = self.pages 

1948 

1949 if key is None: 

1950 pass 

1951 elif isinstance(key, inttypes): 

1952 pages = [pages[key]] 

1953 elif isinstance(key, slice): 

1954 pages = pages[key] 

1955 elif isinstance(key, collections.Iterable): 

1956 pages = [pages[k] for k in key] 

1957 else: 

1958 raise TypeError("key must be an int, slice, or sequence") 

1959 

1960 if not pages: 

1961 raise ValueError("no pages selected") 

1962 

1963 if self.is_nih: 

1964 result = stack_pages(pages, out=out, maxworkers=maxworkers, squeeze=False) 

1965 elif key is None and series and series.offset: 

1966 typecode = self.byteorder + series.dtype.char 

1967 if out == "memmap" and pages[0].is_memmappable: 

1968 result = self.filehandle.memmap_array( 

1969 typecode, series.shape, series.offset 

1970 ) 

1971 else: 

1972 if out is not None: 

1973 out = create_output(out, series.shape, series.dtype) 

1974 self.filehandle.seek(series.offset) 

1975 result = self.filehandle.read_array( 

1976 typecode, product(series.shape), out=out, native=True 

1977 ) 

1978 elif len(pages) == 1: 

1979 result = pages[0].asarray(out=out, validate=validate) 

1980 else: 

1981 result = stack_pages(pages, out=out, maxworkers=maxworkers) 

1982 

1983 if result is None: 

1984 return 

1985 

1986 if key is None: 

1987 try: 

1988 result.shape = series.shape 

1989 except ValueError: 

1990 try: 

1991 warnings.warn( 

1992 "failed to reshape %s to %s" % (result.shape, series.shape) 

1993 ) 

1994 # try series of expected shapes 

1995 result.shape = (-1,) + series.shape 

1996 except ValueError: 

1997 # revert to generic shape 

1998 result.shape = (-1,) + pages[0].shape 

1999 elif len(pages) == 1: 

2000 result.shape = pages[0].shape 

2001 else: 

2002 result.shape = (-1,) + pages[0].shape 

2003 return result 

2004 

2005 @lazyattr 

2006 def series(self): 

2007 """Return related pages as TiffPageSeries. 

2008 

2009 Side effect: after calling this function, TiffFile.pages might contain 

2010 TiffPage and TiffFrame instances. 

2011 

2012 """ 

2013 if not self.pages: 

2014 return [] 

2015 

2016 useframes = self.pages.useframes 

2017 keyframe = self.pages.keyframe 

2018 series = [] 

2019 for name in "ome imagej lsm fluoview nih mdgel shaped".split(): 

2020 if getattr(self, "is_" + name, False): 

2021 series = getattr(self, "_%s_series" % name)() 

2022 break 

2023 self.pages.useframes = useframes 

2024 self.pages.keyframe = keyframe 

2025 if not series: 

2026 series = self._generic_series() 

2027 

2028 # remove empty series, e.g. in MD Gel files 

2029 series = [s for s in series if sum(s.shape) > 0] 

2030 

2031 for i, s in enumerate(series): 

2032 s.index = i 

2033 return series 

2034 

2035 def _generic_series(self): 

2036 """Return image series in file.""" 

2037 if self.pages.useframes: 

2038 # movie mode 

2039 page = self.pages[0] 

2040 shape = page.shape 

2041 axes = page.axes 

2042 if len(self.pages) > 1: 

2043 shape = (len(self.pages),) + shape 

2044 axes = "I" + axes 

2045 return [ 

2046 TiffPageSeries(self.pages[:], shape, page.dtype, axes, stype="movie") 

2047 ] 

2048 

2049 self.pages.clear(False) 

2050 self.pages.load() 

2051 result = [] 

2052 keys = [] 

2053 series = {} 

2054 compressions = TIFF.DECOMPESSORS 

2055 for page in self.pages: 

2056 if not page.shape: 

2057 continue 

2058 key = page.shape + (page.axes, page.compression in compressions) 

2059 if key in series: 

2060 series[key].append(page) 

2061 else: 

2062 keys.append(key) 

2063 series[key] = [page] 

2064 for key in keys: 

2065 pages = series[key] 

2066 page = pages[0] 

2067 shape = page.shape 

2068 axes = page.axes 

2069 if len(pages) > 1: 

2070 shape = (len(pages),) + shape 

2071 axes = "I" + axes 

2072 result.append( 

2073 TiffPageSeries(pages, shape, page.dtype, axes, stype="Generic") 

2074 ) 

2075 

2076 return result 

2077 

2078 def _shaped_series(self): 

2079 """Return image series in "shaped" file.""" 

2080 pages = self.pages 

2081 pages.useframes = True 

2082 lenpages = len(pages) 

2083 

2084 def append_series(series, pages, axes, shape, reshape, name, truncated): 

2085 page = pages[0] 

2086 if not axes: 

2087 shape = page.shape 

2088 axes = page.axes 

2089 if len(pages) > 1: 

2090 shape = (len(pages),) + shape 

2091 axes = "Q" + axes 

2092 size = product(shape) 

2093 resize = product(reshape) 

2094 if page.is_contiguous and resize > size and resize % size == 0: 

2095 if truncated is None: 

2096 truncated = True 

2097 axes = "Q" + axes 

2098 shape = (resize // size,) + shape 

2099 try: 

2100 axes = reshape_axes(axes, shape, reshape) 

2101 shape = reshape 

2102 except ValueError as e: 

2103 warnings.warn(str(e)) 

2104 series.append( 

2105 TiffPageSeries( 

2106 pages, 

2107 shape, 

2108 page.dtype, 

2109 axes, 

2110 name=name, 

2111 stype="Shaped", 

2112 truncated=truncated, 

2113 ) 

2114 ) 

2115 

2116 keyframe = axes = shape = reshape = name = None 

2117 series = [] 

2118 index = 0 

2119 while True: 

2120 if index >= lenpages: 

2121 break 

2122 # new keyframe; start of new series 

2123 pages.keyframe = index 

2124 keyframe = pages[index] 

2125 if not keyframe.is_shaped: 

2126 warnings.warn("invalid shape metadata or corrupted file") 

2127 return 

2128 # read metadata 

2129 axes = None 

2130 shape = None 

2131 metadata = json_description_metadata(keyframe.is_shaped) 

2132 name = metadata.get("name", "") 

2133 reshape = metadata["shape"] 

2134 truncated = metadata.get("truncated", None) 

2135 if "axes" in metadata: 

2136 axes = metadata["axes"] 

2137 if len(axes) == len(reshape): 

2138 shape = reshape 

2139 else: 

2140 axes = "" 

2141 warnings.warn("axes do not match shape") 

2142 # skip pages if possible 

2143 spages = [keyframe] 

2144 size = product(reshape) 

2145 npages, mod = divmod(size, product(keyframe.shape)) 

2146 if mod: 

2147 warnings.warn("series shape does not match page shape") 

2148 return 

2149 if 1 < npages <= lenpages - index: 

2150 size *= keyframe._dtype.itemsize 

2151 if truncated: 

2152 npages = 1 

2153 elif ( 

2154 keyframe.is_final 

2155 and keyframe.offset + size < pages[index + 1].offset 

2156 ): 

2157 truncated = False 

2158 else: 

2159 # need to read all pages for series 

2160 truncated = False 

2161 for j in range(index + 1, index + npages): 

2162 page = pages[j] 

2163 page.keyframe = keyframe 

2164 spages.append(page) 

2165 append_series(series, spages, axes, shape, reshape, name, truncated) 

2166 index += npages 

2167 

2168 return series 

2169 

2170 def _imagej_series(self): 

2171 """Return image series in ImageJ file.""" 

2172 # ImageJ's dimension order is always TZCYXS 

2173 # TODO: fix loading of color, composite, or palette images 

2174 self.pages.useframes = True 

2175 self.pages.keyframe = 0 

2176 

2177 ij = self.imagej_metadata 

2178 pages = self.pages 

2179 page = pages[0] 

2180 

2181 def is_hyperstack(): 

2182 # ImageJ hyperstack store all image metadata in the first page and 

2183 # image data are stored contiguously before the second page, if any 

2184 if not page.is_final: 

2185 return False 

2186 images = ij.get("images", 0) 

2187 if images <= 1: 

2188 return False 

2189 offset, count = page.is_contiguous 

2190 if ( 

2191 count != product(page.shape) * page.bitspersample // 8 

2192 or offset + count * images > self.filehandle.size 

2193 ): 

2194 raise ValueError() 

2195 # check that next page is stored after data 

2196 if len(pages) > 1 and offset + count * images > pages[1].offset: 

2197 return False 

2198 return True 

2199 

2200 try: 

2201 hyperstack = is_hyperstack() 

2202 except ValueError: 

2203 warnings.warn("invalid ImageJ metadata or corrupted file") 

2204 return 

2205 if hyperstack: 

2206 # no need to read other pages 

2207 pages = [page] 

2208 else: 

2209 self.pages.load() 

2210 

2211 shape = [] 

2212 axes = [] 

2213 if "frames" in ij: 

2214 shape.append(ij["frames"]) 

2215 axes.append("T") 

2216 if "slices" in ij: 

2217 shape.append(ij["slices"]) 

2218 axes.append("Z") 

2219 if "channels" in ij and not ( 

2220 page.photometric == 2 and not ij.get("hyperstack", False) 

2221 ): 

2222 shape.append(ij["channels"]) 

2223 axes.append("C") 

2224 remain = ij.get("images", len(pages)) // (product(shape) if shape else 1) 

2225 if remain > 1: 

2226 shape.append(remain) 

2227 axes.append("I") 

2228 if page.axes[0] == "I": 

2229 # contiguous multiple images 

2230 shape.extend(page.shape[1:]) 

2231 axes.extend(page.axes[1:]) 

2232 elif page.axes[:2] == "SI": 

2233 # color-mapped contiguous multiple images 

2234 shape = page.shape[0:1] + tuple(shape) + page.shape[2:] 

2235 axes = list(page.axes[0]) + axes + list(page.axes[2:]) 

2236 else: 

2237 shape.extend(page.shape) 

2238 axes.extend(page.axes) 

2239 

2240 truncated = ( 

2241 hyperstack 

2242 and len(self.pages) == 1 

2243 and page.is_contiguous[1] != product(shape) * page.bitspersample // 8 

2244 ) 

2245 

2246 return [ 

2247 TiffPageSeries( 

2248 pages, shape, page.dtype, axes, stype="ImageJ", truncated=truncated 

2249 ) 

2250 ] 

2251 

2252 def _fluoview_series(self): 

2253 """Return image series in FluoView file.""" 

2254 self.pages.useframes = True 

2255 self.pages.keyframe = 0 

2256 self.pages.load() 

2257 mm = self.fluoview_metadata 

2258 mmhd = list(reversed(mm["Dimensions"])) 

2259 axes = "".join( 

2260 TIFF.MM_DIMENSIONS.get(i[0].upper(), "Q") for i in mmhd if i[1] > 1 

2261 ) 

2262 shape = tuple(int(i[1]) for i in mmhd if i[1] > 1) 

2263 return [ 

2264 TiffPageSeries( 

2265 self.pages, 

2266 shape, 

2267 self.pages[0].dtype, 

2268 axes, 

2269 name=mm["ImageName"], 

2270 stype="FluoView", 

2271 ) 

2272 ] 

2273 

2274 def _mdgel_series(self): 

2275 """Return image series in MD Gel file.""" 

2276 # only a single page, scaled according to metadata in second page 

2277 self.pages.useframes = False 

2278 self.pages.keyframe = 0 

2279 self.pages.load() 

2280 md = self.mdgel_metadata 

2281 if md["FileTag"] in (2, 128): 

2282 dtype = numpy.dtype("float32") 

2283 scale = md["ScalePixel"] 

2284 scale = scale[0] / scale[1] # rational 

2285 if md["FileTag"] == 2: 

2286 # squary root data format 

2287 def transform(a): 

2288 return a.astype("float32") ** 2 * scale 

2289 

2290 else: 

2291 

2292 def transform(a): 

2293 return a.astype("float32") * scale 

2294 

2295 else: 

2296 transform = None 

2297 page = self.pages[0] 

2298 return [ 

2299 TiffPageSeries( 

2300 [page], page.shape, dtype, page.axes, transform=transform, stype="MDGel" 

2301 ) 

2302 ] 

2303 

2304 def _nih_series(self): 

2305 """Return image series in NIH file.""" 

2306 self.pages.useframes = True 

2307 self.pages.keyframe = 0 

2308 self.pages.load() 

2309 page0 = self.pages[0] 

2310 if len(self.pages) == 1: 

2311 shape = page0.shape 

2312 axes = page0.axes 

2313 else: 

2314 shape = (len(self.pages),) + page0.shape 

2315 axes = "I" + page0.axes 

2316 return [TiffPageSeries(self.pages, shape, page0.dtype, axes, stype="NIH")] 

2317 

2318 def _ome_series(self): 

2319 """Return image series in OME-TIFF file(s).""" 

2320 from xml.etree import cElementTree as etree # delayed import 

2321 

2322 omexml = self.pages[0].description 

2323 try: 

2324 root = etree.fromstring(omexml) 

2325 except etree.ParseError as e: 

2326 # TODO: test badly encoded OME-XML 

2327 warnings.warn("ome-xml: %s" % e) 

2328 try: 

2329 # might work on Python 2 

2330 omexml = omexml.decode("utf-8", "ignore").encode("utf-8") 

2331 root = etree.fromstring(omexml) 

2332 except Exception: 

2333 return 

2334 

2335 self.pages.useframes = True 

2336 self.pages.keyframe = 0 

2337 self.pages.load() 

2338 

2339 uuid = root.attrib.get("UUID", None) 

2340 self._files = {uuid: self} 

2341 dirname = self._fh.dirname 

2342 modulo = {} 

2343 series = [] 

2344 for element in root: 

2345 if element.tag.endswith("BinaryOnly"): 

2346 # TODO: load OME-XML from master or companion file 

2347 warnings.warn("ome-xml: not an ome-tiff master file") 

2348 break 

2349 if element.tag.endswith("StructuredAnnotations"): 

2350 for annot in element: 

2351 if not annot.attrib.get("Namespace", "").endswith("modulo"): 

2352 continue 

2353 for value in annot: 

2354 for modul in value: 

2355 for along in modul: 

2356 if not along.tag[:-1].endswith("Along"): 

2357 continue 

2358 axis = along.tag[-1] 

2359 newaxis = along.attrib.get("Type", "other") 

2360 newaxis = TIFF.AXES_LABELS[newaxis] 

2361 if "Start" in along.attrib: 

2362 step = float(along.attrib.get("Step", 1)) 

2363 start = float(along.attrib["Start"]) 

2364 stop = float(along.attrib["End"]) + step 

2365 labels = numpy.arange(start, stop, step) 

2366 else: 

2367 labels = [ 

2368 label.text 

2369 for label in along 

2370 if label.tag.endswith("Label") 

2371 ] 

2372 modulo[axis] = (newaxis, labels) 

2373 

2374 if not element.tag.endswith("Image"): 

2375 continue 

2376 

2377 attr = element.attrib 

2378 name = attr.get("Name", None) 

2379 

2380 for pixels in element: 

2381 if not pixels.tag.endswith("Pixels"): 

2382 continue 

2383 attr = pixels.attrib 

2384 dtype = attr.get("PixelType", None) 

2385 axes = "".join(reversed(attr["DimensionOrder"])) 

2386 shape = list(int(attr["Size" + ax]) for ax in axes) 

2387 size = product(shape[:-2]) 

2388 ifds = None 

2389 spp = 1 # samples per pixel 

2390 # FIXME: this implementation assumes the last two 

2391 # dimensions are stored in tiff pages (shape[:-2]). 

2392 # Apparently that is not always the case. 

2393 for data in pixels: 

2394 if data.tag.endswith("Channel"): 

2395 attr = data.attrib 

2396 if ifds is None: 

2397 spp = int(attr.get("SamplesPerPixel", spp)) 

2398 ifds = [None] * (size // spp) 

2399 elif int(attr.get("SamplesPerPixel", 1)) != spp: 

2400 raise ValueError("cannot handle differing SamplesPerPixel") 

2401 continue 

2402 if ifds is None: 

2403 ifds = [None] * (size // spp) 

2404 if not data.tag.endswith("TiffData"): 

2405 continue 

2406 attr = data.attrib 

2407 ifd = int(attr.get("IFD", 0)) 

2408 num = int(attr.get("NumPlanes", 1 if "IFD" in attr else 0)) 

2409 num = int(attr.get("PlaneCount", num)) 

2410 idx = [int(attr.get("First" + ax, 0)) for ax in axes[:-2]] 

2411 try: 

2412 idx = numpy.ravel_multi_index(idx, shape[:-2]) 

2413 except ValueError: 

2414 # ImageJ produces invalid ome-xml when cropping 

2415 warnings.warn("ome-xml: invalid TiffData index") 

2416 continue 

2417 for uuid in data: 

2418 if not uuid.tag.endswith("UUID"): 

2419 continue 

2420 if uuid.text not in self._files: 

2421 if not self._multifile: 

2422 # abort reading multifile OME series 

2423 # and fall back to generic series 

2424 return [] 

2425 fname = uuid.attrib["FileName"] 

2426 try: 

2427 tif = TiffFile(os.path.join(dirname, fname)) 

2428 tif.pages.useframes = True 

2429 tif.pages.keyframe = 0 

2430 tif.pages.load() 

2431 except (IOError, FileNotFoundError, ValueError): 

2432 warnings.warn("ome-xml: failed to read '%s'" % fname) 

2433 break 

2434 self._files[uuid.text] = tif 

2435 tif.close() 

2436 pages = self._files[uuid.text].pages 

2437 try: 

2438 for i in range(num if num else len(pages)): 

2439 ifds[idx + i] = pages[ifd + i] 

2440 except IndexError: 

2441 warnings.warn("ome-xml: index out of range") 

2442 # only process first UUID 

2443 break 

2444 else: 

2445 pages = self.pages 

2446 try: 

2447 for i in range(num if num else len(pages)): 

2448 ifds[idx + i] = pages[ifd + i] 

2449 except IndexError: 

2450 warnings.warn("ome-xml: index out of range") 

2451 

2452 if all(i is None for i in ifds): 

2453 # skip images without data 

2454 continue 

2455 

2456 # set a keyframe on all IFDs 

2457 keyframe = None 

2458 for i in ifds: 

2459 # try find a TiffPage 

2460 if i and i == i.keyframe: 

2461 keyframe = i 

2462 break 

2463 if not keyframe: 

2464 # reload a TiffPage from file 

2465 for i, keyframe in enumerate(ifds): 

2466 if keyframe: 

2467 keyframe.parent.pages.keyframe = keyframe.index 

2468 keyframe = keyframe.parent.pages[keyframe.index] 

2469 ifds[i] = keyframe 

2470 break 

2471 for i in ifds: 

2472 if i is not None: 

2473 i.keyframe = keyframe 

2474 

2475 dtype = keyframe.dtype 

2476 series.append( 

2477 TiffPageSeries( 

2478 ifds, shape, dtype, axes, parent=self, name=name, stype="OME" 

2479 ) 

2480 ) 

2481 for serie in series: 

2482 shape = list(serie.shape) 

2483 for axis, (newaxis, labels) in modulo.items(): 

2484 i = serie.axes.index(axis) 

2485 size = len(labels) 

2486 if shape[i] == size: 

2487 serie.axes = serie.axes.replace(axis, newaxis, 1) 

2488 else: 

2489 shape[i] //= size 

2490 shape.insert(i + 1, size) 

2491 serie.axes = serie.axes.replace(axis, axis + newaxis, 1) 

2492 serie.shape = tuple(shape) 

2493 # squeeze dimensions 

2494 for serie in series: 

2495 serie.shape, serie.axes = squeeze_axes(serie.shape, serie.axes) 

2496 return series 

2497 

2498 def _lsm_series(self): 

2499 """Return main image series in LSM file. Skip thumbnails.""" 

2500 lsmi = self.lsm_metadata 

2501 axes = TIFF.CZ_LSMINFO_SCANTYPE[lsmi["ScanType"]] 

2502 if self.pages[0].photometric == 2: # RGB; more than one channel 

2503 axes = axes.replace("C", "").replace("XY", "XYC") 

2504 if lsmi.get("DimensionP", 0) > 1: 

2505 axes += "P" 

2506 if lsmi.get("DimensionM", 0) > 1: 

2507 axes += "M" 

2508 axes = axes[::-1] 

2509 shape = tuple(int(lsmi[TIFF.CZ_LSMINFO_DIMENSIONS[i]]) for i in axes) 

2510 name = lsmi.get("Name", "") 

2511 self.pages.keyframe = 0 

2512 pages = self.pages[::2] 

2513 dtype = pages[0].dtype 

2514 series = [TiffPageSeries(pages, shape, dtype, axes, name=name, stype="LSM")] 

2515 

2516 if self.pages[1].is_reduced: 

2517 self.pages.keyframe = 1 

2518 pages = self.pages[1::2] 

2519 dtype = pages[0].dtype 

2520 cp, i = 1, 0 

2521 while cp < len(pages) and i < len(shape) - 2: 

2522 cp *= shape[i] 

2523 i += 1 

2524 shape = shape[:i] + pages[0].shape 

2525 axes = axes[:i] + "CYX" 

2526 series.append( 

2527 TiffPageSeries(pages, shape, dtype, axes, name=name, stype="LSMreduced") 

2528 ) 

2529 

2530 return series 

2531 

2532 def _lsm_load_pages(self): 

2533 """Load all pages from LSM file.""" 

2534 self.pages.cache = True 

2535 self.pages.useframes = True 

2536 # second series: thumbnails 

2537 self.pages.keyframe = 1 

2538 keyframe = self.pages[1] 

2539 for page in self.pages[1::2]: 

2540 page.keyframe = keyframe 

2541 # first series: data 

2542 self.pages.keyframe = 0 

2543 keyframe = self.pages[0] 

2544 for page in self.pages[::2]: 

2545 page.keyframe = keyframe 

2546 

2547 def _lsm_fix_strip_offsets(self): 

2548 """Unwrap strip offsets for LSM files greater than 4 GB. 

2549 

2550 Each series and position require separate unwrapping (undocumented). 

2551 

2552 """ 

2553 if self.filehandle.size < 2**32: 

2554 return 

2555 

2556 pages = self.pages 

2557 npages = len(pages) 

2558 series = self.series[0] 

2559 axes = series.axes 

2560 

2561 # find positions 

2562 positions = 1 

2563 for i in 0, 1: 

2564 if series.axes[i] in "PM": 

2565 positions *= series.shape[i] 

2566 

2567 # make time axis first 

2568 if positions > 1: 

2569 ntimes = 0 

2570 for i in 1, 2: 

2571 if axes[i] == "T": 

2572 ntimes = series.shape[i] 

2573 break 

2574 if ntimes: 

2575 div, mod = divmod(npages, 2 * positions * ntimes) 

2576 assert mod == 0 

2577 shape = (positions, ntimes, div, 2) 

2578 indices = numpy.arange(product(shape)).reshape(shape) 

2579 indices = numpy.moveaxis(indices, 1, 0) 

2580 else: 

2581 indices = numpy.arange(npages).reshape(-1, 2) 

2582 

2583 # images of reduced page might be stored first 

2584 if pages[0].dataoffsets[0] > pages[1].dataoffsets[0]: 

2585 indices = indices[..., ::-1] 

2586 

2587 # unwrap offsets 

2588 wrap = 0 

2589 previousoffset = 0 

2590 for i in indices.flat: 

2591 page = pages[i] 

2592 dataoffsets = [] 

2593 for currentoffset in page.dataoffsets: 

2594 if currentoffset < previousoffset: 

2595 wrap += 2**32 

2596 dataoffsets.append(currentoffset + wrap) 

2597 previousoffset = currentoffset 

2598 page.dataoffsets = tuple(dataoffsets) 

2599 

2600 def _lsm_fix_strip_bytecounts(self): 

2601 """Set databytecounts to size of compressed data. 

2602 

2603 The StripByteCounts tag in LSM files contains the number of bytes 

2604 for the uncompressed data. 

2605 

2606 """ 

2607 pages = self.pages 

2608 if pages[0].compression == 1: 

2609 return 

2610 # sort pages by first strip offset 

2611 pages = sorted(pages, key=lambda p: p.dataoffsets[0]) 

2612 npages = len(pages) - 1 

2613 for i, page in enumerate(pages): 

2614 if page.index % 2: 

2615 continue 

2616 offsets = page.dataoffsets 

2617 bytecounts = page.databytecounts 

2618 if i < npages: 

2619 lastoffset = pages[i + 1].dataoffsets[0] 

2620 else: 

2621 # LZW compressed strips might be longer than uncompressed 

2622 lastoffset = min(offsets[-1] + 2 * bytecounts[-1], self._fh.size) 

2623 offsets = offsets + (lastoffset,) 

2624 page.databytecounts = tuple( 

2625 offsets[j + 1] - offsets[j] for j in range(len(bytecounts)) 

2626 ) 

2627 

2628 def __getattr__(self, name): 

2629 """Return 'is_flag' attributes from first page.""" 

2630 if name[3:] in TIFF.FILE_FLAGS: 

2631 if not self.pages: 

2632 return False 

2633 value = bool(getattr(self.pages[0], name)) 

2634 setattr(self, name, value) 

2635 return value 

2636 raise AttributeError( 

2637 "'%s' object has no attribute '%s'" % (self.__class__.__name__, name) 

2638 ) 

2639 

2640 def __enter__(self): 

2641 return self 

2642 

2643 def __exit__(self, exc_type, exc_value, traceback): 

2644 self.close() 

2645 

2646 def __str__(self, detail=0, width=79): 

2647 """Return string containing information about file. 

2648 

2649 The detail parameter specifies the level of detail returned: 

2650 

2651 0: file only. 

2652 1: all series, first page of series and its tags. 

2653 2: large tag values and file metadata. 

2654 3: all pages. 

2655 

2656 """ 

2657 info = [ 

2658 "TiffFile '%s'", 

2659 format_size(self._fh.size), 

2660 {"<": "LittleEndian", ">": "BigEndian"}[self.byteorder], 

2661 ] 

2662 if self.is_bigtiff: 

2663 info.append("BigTiff") 

2664 info.append("|".join(f.upper() for f in self.flags)) 

2665 if len(self.pages) > 1: 

2666 info.append("%i Pages" % len(self.pages)) 

2667 if len(self.series) > 1: 

2668 info.append("%i Series" % len(self.series)) 

2669 if len(self._files) > 1: 

2670 info.append("%i Files" % (len(self._files))) 

2671 info = " ".join(info) 

2672 info = info.replace(" ", " ").replace(" ", " ") 

2673 info = info % snipstr(self._fh.name, max(12, width + 2 - len(info))) 

2674 if detail <= 0: 

2675 return info 

2676 info = [info] 

2677 info.append("\n".join(str(s) for s in self.series)) 

2678 if detail >= 3: 

2679 info.extend( 

2680 ( 

2681 TiffPage.__str__(p, detail=detail, width=width) 

2682 for p in self.pages 

2683 if p is not None 

2684 ) 

2685 ) 

2686 else: 

2687 info.extend( 

2688 ( 

2689 TiffPage.__str__(s.pages[0], detail=detail, width=width) 

2690 for s in self.series 

2691 if s.pages[0] is not None 

2692 ) 

2693 ) 

2694 if detail >= 2: 

2695 for name in sorted(self.flags): 

2696 if hasattr(self, name + "_metadata"): 

2697 m = getattr(self, name + "_metadata") 

2698 if m: 

2699 info.append( 

2700 "%s_METADATA\n%s" 

2701 % ( 

2702 name.upper(), 

2703 pformat(m, width=width, height=detail * 12), 

2704 ) 

2705 ) 

2706 return "\n\n".join(info).replace("\n\n\n", "\n\n") 

2707 

2708 @lazyattr 

2709 def flags(self): 

2710 """Return set of file flags.""" 

2711 return set( 

2712 name.lower() 

2713 for name in sorted(TIFF.FILE_FLAGS) 

2714 if getattr(self, "is_" + name) 

2715 ) 

2716 

2717 @lazyattr 

2718 def is_mdgel(self): 

2719 """File has MD Gel format.""" 

2720 try: 

2721 return self.pages[0].is_mdgel or self.pages[1].is_mdgel 

2722 except IndexError: 

2723 return False 

2724 

2725 @property 

2726 def is_movie(self): 

2727 """Return if file is a movie.""" 

2728 return self.pages.useframes 

2729 

2730 @lazyattr 

2731 def shaped_metadata(self): 

2732 """Return Tifffile metadata from JSON descriptions as dicts.""" 

2733 if not self.is_shaped: 

2734 return 

2735 return tuple( 

2736 json_description_metadata(s.pages[0].is_shaped) 

2737 for s in self.series 

2738 if s.stype.lower() == "shaped" 

2739 ) 

2740 

2741 @lazyattr 

2742 def ome_metadata(self): 

2743 """Return OME XML as dict.""" 

2744 # TODO: remove this or return XML? 

2745 if not self.is_ome: 

2746 return 

2747 return xml2dict(self.pages[0].description)["OME"] 

2748 

2749 @lazyattr 

2750 def qptiff_metadata(self): 

2751 """Return PerkinElmer-QPI-ImageDescription XML element as dict.""" 

2752 if not self.is_qptiff: 

2753 return 

2754 root = "PerkinElmer-QPI-ImageDescription" 

2755 xml = self.pages[0].description.replace(" " + root + " ", root) 

2756 return xml2dict(xml)[root] 

2757 

2758 @lazyattr 

2759 def lsm_metadata(self): 

2760 """Return LSM metadata from CZ_LSMINFO tag as dict.""" 

2761 if not self.is_lsm: 

2762 return 

2763 return self.pages[0].tags["CZ_LSMINFO"].value 

2764 

2765 @lazyattr 

2766 def stk_metadata(self): 

2767 """Return STK metadata from UIC tags as dict.""" 

2768 if not self.is_stk: 

2769 return 

2770 page = self.pages[0] 

2771 tags = page.tags 

2772 result = {} 

2773 result["NumberPlanes"] = tags["UIC2tag"].count 

2774 if page.description: 

2775 result["PlaneDescriptions"] = page.description.split("\0") 

2776 # result['plane_descriptions'] = stk_description_metadata( 

2777 # page.image_description) 

2778 if "UIC1tag" in tags: 

2779 result.update(tags["UIC1tag"].value) 

2780 if "UIC3tag" in tags: 

2781 result.update(tags["UIC3tag"].value) # wavelengths 

2782 if "UIC4tag" in tags: 

2783 result.update(tags["UIC4tag"].value) # override uic1 tags 

2784 uic2tag = tags["UIC2tag"].value 

2785 result["ZDistance"] = uic2tag["ZDistance"] 

2786 result["TimeCreated"] = uic2tag["TimeCreated"] 

2787 result["TimeModified"] = uic2tag["TimeModified"] 

2788 try: 

2789 result["DatetimeCreated"] = numpy.array( 

2790 [ 

2791 julian_datetime(*dt) 

2792 for dt in zip(uic2tag["DateCreated"], uic2tag["TimeCreated"]) 

2793 ], 

2794 dtype="datetime64[ns]", 

2795 ) 

2796 result["DatetimeModified"] = numpy.array( 

2797 [ 

2798 julian_datetime(*dt) 

2799 for dt in zip(uic2tag["DateModified"], uic2tag["TimeModified"]) 

2800 ], 

2801 dtype="datetime64[ns]", 

2802 ) 

2803 except ValueError as e: 

2804 warnings.warn("stk_metadata: %s" % e) 

2805 return result 

2806 

2807 @lazyattr 

2808 def imagej_metadata(self): 

2809 """Return consolidated ImageJ metadata as dict.""" 

2810 if not self.is_imagej: 

2811 return 

2812 page = self.pages[0] 

2813 result = imagej_description_metadata(page.is_imagej) 

2814 if "IJMetadata" in page.tags: 

2815 try: 

2816 result.update(page.tags["IJMetadata"].value) 

2817 except Exception: 

2818 pass 

2819 return result 

2820 

2821 @lazyattr 

2822 def fluoview_metadata(self): 

2823 """Return consolidated FluoView metadata as dict.""" 

2824 if not self.is_fluoview: 

2825 return 

2826 result = {} 

2827 page = self.pages[0] 

2828 result.update(page.tags["MM_Header"].value) 

2829 # TODO: read stamps from all pages 

2830 result["Stamp"] = page.tags["MM_Stamp"].value 

2831 # skip parsing image description; not reliable 

2832 # try: 

2833 # t = fluoview_description_metadata(page.image_description) 

2834 # if t is not None: 

2835 # result['ImageDescription'] = t 

2836 # except Exception as e: 

2837 # warnings.warn( 

2838 # "failed to read FluoView image description: %s" % e) 

2839 return result 

2840 

2841 @lazyattr 

2842 def nih_metadata(self): 

2843 """Return NIH Image metadata from NIHImageHeader tag as dict.""" 

2844 if not self.is_nih: 

2845 return 

2846 return self.pages[0].tags["NIHImageHeader"].value 

2847 

2848 @lazyattr 

2849 def fei_metadata(self): 

2850 """Return FEI metadata from SFEG or HELIOS tags as dict.""" 

2851 if not self.is_fei: 

2852 return 

2853 tags = self.pages[0].tags 

2854 if "FEI_SFEG" in tags: 

2855 return tags["FEI_SFEG"].value 

2856 if "FEI_HELIOS" in tags: 

2857 return tags["FEI_HELIOS"].value 

2858 

2859 @lazyattr 

2860 def sem_metadata(self): 

2861 """Return SEM metadata from CZ_SEM tag as dict.""" 

2862 if not self.is_sem: 

2863 return 

2864 return self.pages[0].tags["CZ_SEM"].value 

2865 

2866 @lazyattr 

2867 def mdgel_metadata(self): 

2868 """Return consolidated metadata from MD GEL tags as dict.""" 

2869 for page in self.pages[:2]: 

2870 if "MDFileTag" in page.tags: 

2871 tags = page.tags 

2872 break 

2873 else: 

2874 return 

2875 result = {} 

2876 for code in range(33445, 33453): 

2877 name = TIFF.TAGS[code] 

2878 if name not in tags: 

2879 continue 

2880 result[name[2:]] = tags[name].value 

2881 return result 

2882 

2883 @lazyattr 

2884 def andor_metadata(self): 

2885 """Return Andor tags as dict.""" 

2886 return self.pages[0].andor_tags 

2887 

2888 @lazyattr 

2889 def epics_metadata(self): 

2890 """Return EPICS areaDetector tags as dict.""" 

2891 return self.pages[0].epics_tags 

2892 

2893 @lazyattr 

2894 def tvips_metadata(self): 

2895 """Return TVIPS tag as dict.""" 

2896 if not self.is_tvips: 

2897 return 

2898 return self.pages[0].tags["TVIPS"].value 

2899 

2900 @lazyattr 

2901 def metaseries_metadata(self): 

2902 """Return MetaSeries metadata from image description as dict.""" 

2903 if not self.is_metaseries: 

2904 return 

2905 return metaseries_description_metadata(self.pages[0].description) 

2906 

2907 @lazyattr 

2908 def pilatus_metadata(self): 

2909 """Return Pilatus metadata from image description as dict.""" 

2910 if not self.is_pilatus: 

2911 return 

2912 return pilatus_description_metadata(self.pages[0].description) 

2913 

2914 @lazyattr 

2915 def micromanager_metadata(self): 

2916 """Return consolidated MicroManager metadata as dict.""" 

2917 if not self.is_micromanager: 

2918 return 

2919 # from file header 

2920 result = read_micromanager_metadata(self._fh) 

2921 # from tag 

2922 result.update(self.pages[0].tags["MicroManagerMetadata"].value) 

2923 return result 

2924 

2925 @lazyattr 

2926 def scanimage_metadata(self): 

2927 """Return ScanImage non-varying frame and ROI metadata as dict.""" 

2928 if not self.is_scanimage: 

2929 return 

2930 result = {} 

2931 try: 

2932 framedata, roidata = read_scanimage_metadata(self._fh) 

2933 result["FrameData"] = framedata 

2934 result.update(roidata) 

2935 except ValueError: 

2936 pass 

2937 # TODO: scanimage_artist_metadata 

2938 try: 

2939 result["Description"] = scanimage_description_metadata( 

2940 self.pages[0].description 

2941 ) 

2942 except Exception as e: 

2943 warnings.warn("scanimage_description_metadata failed: %s" % e) 

2944 return result 

2945 

2946 @property 

2947 def geotiff_metadata(self): 

2948 """Return GeoTIFF metadata from first page as dict.""" 

2949 if not self.is_geotiff: 

2950 return 

2951 return self.pages[0].geotiff_tags 

2952 

2953 

2954class TiffPages(object): 

2955 """Sequence of TIFF image file directories.""" 

2956 

2957 def __init__(self, parent): 

2958 """Initialize instance from file. Read first TiffPage from file. 

2959 

2960 The file position must be at an offset to an offset to a TiffPage. 

2961 

2962 """ 

2963 self.parent = parent 

2964 self.pages = [] # cache of TiffPages, TiffFrames, or their offsets 

2965 self.complete = False # True if offsets to all pages were read 

2966 self._tiffpage = TiffPage # class for reading tiff pages 

2967 self._keyframe = None 

2968 self._cache = True 

2969 

2970 # read offset to first page 

2971 fh = parent.filehandle 

2972 self._nextpageoffset = fh.tell() 

2973 offset = struct.unpack(parent.offsetformat, fh.read(parent.offsetsize))[0] 

2974 

2975 if offset == 0: 

2976 # warnings.warn('file contains no pages') 

2977 self.complete = True 

2978 return 

2979 if offset >= fh.size: 

2980 warnings.warn("invalid page offset (%i)" % offset) 

2981 self.complete = True 

2982 return 

2983 

2984 # always read and cache first page 

2985 fh.seek(offset) 

2986 page = TiffPage(parent, index=0) 

2987 self.pages.append(page) 

2988 self._keyframe = page 

2989 

2990 @property 

2991 def cache(self): 

2992 """Return if pages/frames are currently being cached.""" 

2993 return self._cache 

2994 

2995 @cache.setter 

2996 def cache(self, value): 

2997 """Enable or disable caching of pages/frames. Clear cache if False.""" 

2998 value = bool(value) 

2999 if self._cache and not value: 

3000 self.clear() 

3001 self._cache = value 

3002 

3003 @property 

3004 def useframes(self): 

3005 """Return if currently using TiffFrame (True) or TiffPage (False).""" 

3006 return self._tiffpage == TiffFrame and TiffFrame is not TiffPage 

3007 

3008 @useframes.setter 

3009 def useframes(self, value): 

3010 """Set to use TiffFrame (True) or TiffPage (False).""" 

3011 self._tiffpage = TiffFrame if value else TiffPage 

3012 

3013 @property 

3014 def keyframe(self): 

3015 """Return index of current keyframe.""" 

3016 return self._keyframe.index 

3017 

3018 @keyframe.setter 

3019 def keyframe(self, index): 

3020 """Set current keyframe. Load TiffPage from file if necessary.""" 

3021 if self._keyframe.index == index: 

3022 return 

3023 if self.complete or 0 <= index < len(self.pages): 

3024 page = self.pages[index] 

3025 if isinstance(page, TiffPage): 

3026 self._keyframe = page 

3027 return 

3028 elif isinstance(page, TiffFrame): 

3029 # remove existing frame 

3030 self.pages[index] = page.offset 

3031 # load TiffPage from file 

3032 useframes = self.useframes 

3033 self._tiffpage = TiffPage 

3034 self._keyframe = self[index] 

3035 self.useframes = useframes 

3036 

3037 @property 

3038 def next_page_offset(self): 

3039 """Return offset where offset to a new page can be stored.""" 

3040 if not self.complete: 

3041 self._seek(-1) 

3042 return self._nextpageoffset 

3043 

3044 def load(self): 

3045 """Read all remaining pages from file.""" 

3046 fh = self.parent.filehandle 

3047 keyframe = self._keyframe 

3048 pages = self.pages 

3049 if not self.complete: 

3050 self._seek(-1) 

3051 for i, page in enumerate(pages): 

3052 if isinstance(page, inttypes): 

3053 fh.seek(page) 

3054 page = self._tiffpage(self.parent, index=i, keyframe=keyframe) 

3055 pages[i] = page 

3056 

3057 def clear(self, fully=True): 

3058 """Delete all but first page from cache. Set keyframe to first page.""" 

3059 pages = self.pages 

3060 if not self._cache or len(pages) < 1: 

3061 return 

3062 self._keyframe = pages[0] 

3063 if fully: 

3064 # delete all but first TiffPage/TiffFrame 

3065 for i, page in enumerate(pages[1:]): 

3066 if not isinstance(page, inttypes): 

3067 pages[i + 1] = page.offset 

3068 elif TiffFrame is not TiffPage: 

3069 # delete only TiffFrames 

3070 for i, page in enumerate(pages): 

3071 if isinstance(page, TiffFrame): 

3072 pages[i] = page.offset 

3073 

3074 def _seek(self, index, maxpages=2**22): 

3075 """Seek file to offset of specified page.""" 

3076 pages = self.pages 

3077 if not pages: 

3078 return 

3079 

3080 fh = self.parent.filehandle 

3081 if fh.closed: 

3082 raise RuntimeError("FileHandle is closed") 

3083 

3084 if self.complete or 0 <= index < len(pages): 

3085 page = pages[index] 

3086 offset = page if isinstance(page, inttypes) else page.offset 

3087 fh.seek(offset) 

3088 return 

3089 

3090 offsetformat = self.parent.offsetformat 

3091 offsetsize = self.parent.offsetsize 

3092 tagnoformat = self.parent.tagnoformat 

3093 tagnosize = self.parent.tagnosize 

3094 tagsize = self.parent.tagsize 

3095 unpack = struct.unpack 

3096 

3097 page = pages[-1] 

3098 offset = page if isinstance(page, inttypes) else page.offset 

3099 

3100 while len(pages) < maxpages: 

3101 # read offsets to pages from file until index is reached 

3102 fh.seek(offset) 

3103 # skip tags 

3104 try: 

3105 tagno = unpack(tagnoformat, fh.read(tagnosize))[0] 

3106 if tagno > 4096: 

3107 raise ValueError("suspicious number of tags") 

3108 except Exception: 

3109 warnings.warn("corrupted tag list at offset %i" % offset) 

3110 del pages[-1] 

3111 self.complete = True 

3112 break 

3113 self._nextpageoffset = offset + tagnosize + tagno * tagsize 

3114 fh.seek(self._nextpageoffset) 

3115 

3116 # read offset to next page 

3117 offset = unpack(offsetformat, fh.read(offsetsize))[0] 

3118 if offset == 0: 

3119 self.complete = True 

3120 break 

3121 if offset >= fh.size: 

3122 warnings.warn("invalid page offset (%i)" % offset) 

3123 self.complete = True 

3124 break 

3125 

3126 pages.append(offset) 

3127 if 0 <= index < len(pages): 

3128 break 

3129 

3130 if index >= len(pages): 

3131 raise IndexError("list index out of range") 

3132 

3133 page = pages[index] 

3134 fh.seek(page if isinstance(page, inttypes) else page.offset) 

3135 

3136 def __bool__(self): 

3137 """Return True if file contains any pages.""" 

3138 return len(self.pages) > 0 

3139 

3140 def __len__(self): 

3141 """Return number of pages in file.""" 

3142 if not self.complete: 

3143 self._seek(-1) 

3144 return len(self.pages) 

3145 

3146 def __getitem__(self, key): 

3147 """Return specified page(s) from cache or file.""" 

3148 pages = self.pages 

3149 if not pages: 

3150 raise IndexError("list index out of range") 

3151 if key == 0: 

3152 return pages[key] 

3153 

3154 if isinstance(key, slice): 

3155 start, stop, _ = key.indices(2**31 - 1) 

3156 if not self.complete and max(stop, start) > len(pages): 

3157 self._seek(-1) 

3158 return [self[i] for i in range(*key.indices(len(pages)))] 

3159 

3160 if self.complete and key >= len(pages): 

3161 raise IndexError("list index out of range") 

3162 

3163 try: 

3164 page = pages[key] 

3165 except IndexError: 

3166 page = 0 

3167 if not isinstance(page, inttypes): 

3168 return page 

3169 

3170 self._seek(key) 

3171 page = self._tiffpage(self.parent, index=key, keyframe=self._keyframe) 

3172 if self._cache: 

3173 pages[key] = page 

3174 return page 

3175 

3176 def __iter__(self): 

3177 """Return iterator over all pages.""" 

3178 i = 0 

3179 while True: 

3180 try: 

3181 yield self[i] 

3182 i += 1 

3183 except IndexError: 

3184 break 

3185 

3186 

3187class TiffPage(object): 

3188 """TIFF image file directory (IFD). 

3189 

3190 Attributes 

3191 ---------- 

3192 index : int 

3193 Index of page in file. 

3194 dtype : numpy.dtype or None 

3195 Data type (native byte order) of the image in IFD. 

3196 shape : tuple 

3197 Dimensions of the image in IFD. 

3198 axes : str 

3199 Axes label codes: 

3200 'X' width, 'Y' height, 'S' sample, 'I' image series|page|plane, 

3201 'Z' depth, 'C' color|em-wavelength|channel, 'E' ex-wavelength|lambda, 

3202 'T' time, 'R' region|tile, 'A' angle, 'P' phase, 'H' lifetime, 

3203 'L' exposure, 'V' event, 'Q' unknown, '_' missing 

3204 tags : dict 

3205 Dictionary of tags in IFD. {tag.name: TiffTag} 

3206 colormap : numpy.ndarray 

3207 Color look up table, if exists. 

3208 

3209 All attributes are read-only. 

3210 

3211 Notes 

3212 ----- 

3213 The internal, normalized '_shape' attribute is 6 dimensional: 

3214 

3215 0 : number planes/images (stk, ij). 

3216 1 : planar samplesperpixel. 

3217 2 : imagedepth Z (sgi). 

3218 3 : imagelength Y. 

3219 4 : imagewidth X. 

3220 5 : contig samplesperpixel. 

3221 

3222 """ 

3223 

3224 # default properties; will be updated from tags 

3225 imagewidth = 0 

3226 imagelength = 0 

3227 imagedepth = 1 

3228 tilewidth = 0 

3229 tilelength = 0 

3230 tiledepth = 1 

3231 bitspersample = 1 

3232 samplesperpixel = 1 

3233 sampleformat = 1 

3234 rowsperstrip = 2**32 - 1 

3235 compression = 1 

3236 planarconfig = 1 

3237 fillorder = 1 

3238 photometric = 0 

3239 predictor = 1 

3240 extrasamples = 1 

3241 colormap = None 

3242 software = "" 

3243 description = "" 

3244 description1 = "" 

3245 

3246 def __init__(self, parent, index, keyframe=None): 

3247 """Initialize instance from file. 

3248 

3249 The file handle position must be at offset to a valid IFD. 

3250 

3251 """ 

3252 self.parent = parent 

3253 self.index = index 

3254 self.shape = () 

3255 self._shape = () 

3256 self.dtype = None 

3257 self._dtype = None 

3258 self.axes = "" 

3259 self.tags = {} 

3260 

3261 self.dataoffsets = () 

3262 self.databytecounts = () 

3263 

3264 # read TIFF IFD structure and its tags from file 

3265 fh = parent.filehandle 

3266 self.offset = fh.tell() # offset to this IFD 

3267 try: 

3268 tagno = struct.unpack(parent.tagnoformat, fh.read(parent.tagnosize))[0] 

3269 if tagno > 4096: 

3270 raise ValueError("suspicious number of tags") 

3271 except Exception: 

3272 raise ValueError("corrupted tag list at offset %i" % self.offset) 

3273 

3274 tagsize = parent.tagsize 

3275 data = fh.read(tagsize * tagno) 

3276 tags = self.tags 

3277 index = -tagsize 

3278 for _ in range(tagno): 

3279 index += tagsize 

3280 try: 

3281 tag = TiffTag(self.parent, data[index : index + tagsize]) 

3282 except TiffTag.Error as e: 

3283 warnings.warn(str(e)) 

3284 continue 

3285 tagname = tag.name 

3286 if tagname not in tags: 

3287 name = tagname 

3288 tags[name] = tag 

3289 else: 

3290 # some files contain multiple tags with same code 

3291 # e.g. MicroManager files contain two ImageDescription tags 

3292 i = 1 

3293 while True: 

3294 name = "%s%i" % (tagname, i) 

3295 if name not in tags: 

3296 tags[name] = tag 

3297 break 

3298 name = TIFF.TAG_ATTRIBUTES.get(name, "") 

3299 if name: 

3300 if name[:3] in "sof des" and not isinstance(tag.value, str): 

3301 pass # wrong string type for software, description 

3302 else: 

3303 setattr(self, name, tag.value) 

3304 

3305 if not tags: 

3306 return # found in FIBICS 

3307 

3308 # consolidate private tags; remove them from self.tags 

3309 if self.is_andor: 

3310 self.andor_tags 

3311 elif self.is_epics: 

3312 self.epics_tags 

3313 

3314 if self.is_lsm or (self.index and self.parent.is_lsm): 

3315 # correct non standard LSM bitspersample tags 

3316 self.tags["BitsPerSample"]._fix_lsm_bitspersample(self) 

3317 

3318 if self.is_vista or (self.index and self.parent.is_vista): 

3319 # ISS Vista writes wrong ImageDepth tag 

3320 self.imagedepth = 1 

3321 

3322 if self.is_stk and "UIC1tag" in tags and not tags["UIC1tag"].value: 

3323 # read UIC1tag now that plane count is known 

3324 uic1tag = tags["UIC1tag"] 

3325 fh.seek(uic1tag.valueoffset) 

3326 tags["UIC1tag"].value = read_uic1tag( 

3327 fh, 

3328 self.parent.byteorder, 

3329 uic1tag.dtype, 

3330 uic1tag.count, 

3331 None, 

3332 tags["UIC2tag"].count, 

3333 ) 

3334 

3335 if "IJMetadata" in tags: 

3336 # decode IJMetadata tag 

3337 try: 

3338 tags["IJMetadata"].value = imagej_metadata( 

3339 tags["IJMetadata"].value, 

3340 tags["IJMetadataByteCounts"].value, 

3341 self.parent.byteorder, 

3342 ) 

3343 except Exception as e: 

3344 warnings.warn(str(e)) 

3345 

3346 if "BitsPerSample" in tags: 

3347 tag = tags["BitsPerSample"] 

3348 if tag.count == 1: 

3349 self.bitspersample = tag.value 

3350 else: 

3351 # LSM might list more items than samplesperpixel 

3352 value = tag.value[: self.samplesperpixel] 

3353 if any((v - value[0] for v in value)): 

3354 self.bitspersample = value 

3355 else: 

3356 self.bitspersample = value[0] 

3357 

3358 if "SampleFormat" in tags: 

3359 tag = tags["SampleFormat"] 

3360 if tag.count == 1: 

3361 self.sampleformat = tag.value 

3362 else: 

3363 value = tag.value[: self.samplesperpixel] 

3364 if any((v - value[0] for v in value)): 

3365 self.sampleformat = value 

3366 else: 

3367 self.sampleformat = value[0] 

3368 

3369 if "ImageLength" in tags: 

3370 if "RowsPerStrip" not in tags or tags["RowsPerStrip"].count > 1: 

3371 self.rowsperstrip = self.imagelength 

3372 # self.stripsperimage = int(math.floor( 

3373 # float(self.imagelength + self.rowsperstrip - 1) / 

3374 # self.rowsperstrip)) 

3375 

3376 # determine dtype 

3377 dtype = self.sampleformat, self.bitspersample 

3378 dtype = TIFF.SAMPLE_DTYPES.get(dtype, None) 

3379 if dtype is not None: 

3380 dtype = numpy.dtype(dtype) 

3381 self.dtype = self._dtype = dtype 

3382 

3383 # determine shape of data 

3384 imagelength = self.imagelength 

3385 imagewidth = self.imagewidth 

3386 imagedepth = self.imagedepth 

3387 samplesperpixel = self.samplesperpixel 

3388 

3389 if self.is_stk: 

3390 assert self.imagedepth == 1 

3391 uictag = tags["UIC2tag"].value 

3392 planes = tags["UIC2tag"].count 

3393 if self.planarconfig == 1: 

3394 self._shape = (planes, 1, 1, imagelength, imagewidth, samplesperpixel) 

3395 if samplesperpixel == 1: 

3396 self.shape = (planes, imagelength, imagewidth) 

3397 self.axes = "YX" 

3398 else: 

3399 self.shape = (planes, imagelength, imagewidth, samplesperpixel) 

3400 self.axes = "YXS" 

3401 else: 

3402 self._shape = (planes, samplesperpixel, 1, imagelength, imagewidth, 1) 

3403 if samplesperpixel == 1: 

3404 self.shape = (planes, imagelength, imagewidth) 

3405 self.axes = "YX" 

3406 else: 

3407 self.shape = (planes, samplesperpixel, imagelength, imagewidth) 

3408 self.axes = "SYX" 

3409 # detect type of series 

3410 if planes == 1: 

3411 self.shape = self.shape[1:] 

3412 elif numpy.all(uictag["ZDistance"] != 0): 

3413 self.axes = "Z" + self.axes 

3414 elif numpy.all(numpy.diff(uictag["TimeCreated"]) != 0): 

3415 self.axes = "T" + self.axes 

3416 else: 

3417 self.axes = "I" + self.axes 

3418 elif self.photometric == 2 or samplesperpixel > 1: # PHOTOMETRIC.RGB 

3419 if self.planarconfig == 1: 

3420 self._shape = ( 

3421 1, 

3422 1, 

3423 imagedepth, 

3424 imagelength, 

3425 imagewidth, 

3426 samplesperpixel, 

3427 ) 

3428 if imagedepth == 1: 

3429 self.shape = (imagelength, imagewidth, samplesperpixel) 

3430 self.axes = "YXS" 

3431 else: 

3432 self.shape = (imagedepth, imagelength, imagewidth, samplesperpixel) 

3433 self.axes = "ZYXS" 

3434 else: 

3435 self._shape = ( 

3436 1, 

3437 samplesperpixel, 

3438 imagedepth, 

3439 imagelength, 

3440 imagewidth, 

3441 1, 

3442 ) 

3443 if imagedepth == 1: 

3444 self.shape = (samplesperpixel, imagelength, imagewidth) 

3445 self.axes = "SYX" 

3446 else: 

3447 self.shape = (samplesperpixel, imagedepth, imagelength, imagewidth) 

3448 self.axes = "SZYX" 

3449 else: 

3450 self._shape = (1, 1, imagedepth, imagelength, imagewidth, 1) 

3451 if imagedepth == 1: 

3452 self.shape = (imagelength, imagewidth) 

3453 self.axes = "YX" 

3454 else: 

3455 self.shape = (imagedepth, imagelength, imagewidth) 

3456 self.axes = "ZYX" 

3457 

3458 # dataoffsets and databytecounts 

3459 if "TileOffsets" in tags: 

3460 self.dataoffsets = tags["TileOffsets"].value 

3461 elif "StripOffsets" in tags: 

3462 self.dataoffsets = tags["StripOffsets"].value 

3463 else: 

3464 self.dataoffsets = (0,) 

3465 

3466 if "TileByteCounts" in tags: 

3467 self.databytecounts = tags["TileByteCounts"].value 

3468 elif "StripByteCounts" in tags: 

3469 self.databytecounts = tags["StripByteCounts"].value 

3470 else: 

3471 self.databytecounts = (product(self.shape) * (self.bitspersample // 8),) 

3472 if self.compression != 1: 

3473 warnings.warn("required ByteCounts tag is missing") 

3474 

3475 assert len(self.shape) == len(self.axes) 

3476 

3477 def asarray( 

3478 self, 

3479 out=None, 

3480 squeeze=True, 

3481 lock=None, 

3482 reopen=True, 

3483 maxsize=2**44, 

3484 validate=True, 

3485 ): 

3486 """Read image data from file and return as numpy array. 

3487 

3488 Raise ValueError if format is unsupported. 

3489 

3490 Parameters 

3491 ---------- 

3492 out : numpy.ndarray, str, or file-like object; optional 

3493 Buffer where image data will be saved. 

3494 If None (default), a new array will be created. 

3495 If numpy.ndarray, a writable array of compatible dtype and shape. 

3496 If 'memmap', directly memory-map the image data in the TIFF file 

3497 if possible; else create a memory-mapped array in a temporary file. 

3498 If str or open file, the file name or file object used to 

3499 create a memory-map to an array stored in a binary file on disk. 

3500 squeeze : bool 

3501 If True, all length-1 dimensions (except X and Y) are 

3502 squeezed out from the array. 

3503 If False, the shape of the returned array might be different from 

3504 the page.shape. 

3505 lock : {RLock, NullContext} 

3506 A reentrant lock used to synchronize reads from file. 

3507 If None (default), the lock of the parent's filehandle is used. 

3508 reopen : bool 

3509 If True (default) and the parent file handle is closed, the file 

3510 is temporarily re-opened and closed if no exception occurs. 

3511 maxsize: int or None 

3512 Maximum size of data before a ValueError is raised. 

3513 Can be used to catch DOS. Default: 16 TB. 

3514 validate : bool 

3515 If True (default), validate various parameters. 

3516 If None, only validate parameters and return None. 

3517 

3518 """ 

3519 self_ = self 

3520 self = self.keyframe # self or keyframe 

3521 

3522 if not self._shape or product(self._shape) == 0: 

3523 return 

3524 

3525 tags = self.tags 

3526 

3527 if validate or validate is None: 

3528 if maxsize and product(self._shape) > maxsize: 

3529 raise ValueError("data are too large %s" % str(self._shape)) 

3530 if self.dtype is None: 

3531 raise ValueError( 

3532 "data type not supported: %s%i" 

3533 % (self.sampleformat, self.bitspersample) 

3534 ) 

3535 if self.compression not in TIFF.DECOMPESSORS: 

3536 raise ValueError("cannot decompress %s" % self.compression.name) 

3537 if "SampleFormat" in tags: 

3538 tag = tags["SampleFormat"] 

3539 if tag.count != 1 and any((i - tag.value[0] for i in tag.value)): 

3540 raise ValueError("sample formats do not match %s" % tag.value) 

3541 if self.is_chroma_subsampled and ( 

3542 self.compression != 7 or self.planarconfig == 2 

3543 ): 

3544 raise NotImplementedError("chroma subsampling not supported") 

3545 if validate is None: 

3546 return 

3547 

3548 fh = self_.parent.filehandle 

3549 lock = fh.lock if lock is None else lock 

3550 with lock: 

3551 closed = fh.closed 

3552 if closed: 

3553 if reopen: 

3554 fh.open() 

3555 else: 

3556 raise IOError("file handle is closed") 

3557 

3558 dtype = self._dtype 

3559 shape = self._shape 

3560 imagewidth = self.imagewidth 

3561 imagelength = self.imagelength 

3562 imagedepth = self.imagedepth 

3563 bitspersample = self.bitspersample 

3564 typecode = self.parent.byteorder + dtype.char 

3565 lsb2msb = self.fillorder == 2 

3566 offsets, bytecounts = self_.offsets_bytecounts 

3567 istiled = self.is_tiled 

3568 

3569 if istiled: 

3570 tilewidth = self.tilewidth 

3571 tilelength = self.tilelength 

3572 tiledepth = self.tiledepth 

3573 tw = (imagewidth + tilewidth - 1) // tilewidth 

3574 tl = (imagelength + tilelength - 1) // tilelength 

3575 td = (imagedepth + tiledepth - 1) // tiledepth 

3576 shape = ( 

3577 shape[0], 

3578 shape[1], 

3579 td * tiledepth, 

3580 tl * tilelength, 

3581 tw * tilewidth, 

3582 shape[-1], 

3583 ) 

3584 tileshape = (tiledepth, tilelength, tilewidth, shape[-1]) 

3585 runlen = tilewidth 

3586 else: 

3587 runlen = imagewidth 

3588 

3589 if self.planarconfig == 1: 

3590 runlen *= self.samplesperpixel 

3591 

3592 if out == "memmap" and self.is_memmappable: 

3593 with lock: 

3594 result = fh.memmap_array(typecode, shape, offset=offsets[0]) 

3595 elif self.is_contiguous: 

3596 if out is not None: 

3597 out = create_output(out, shape, dtype) 

3598 with lock: 

3599 fh.seek(offsets[0]) 

3600 result = fh.read_array(typecode, product(shape), out=out) 

3601 if out is None and not result.dtype.isnative: 

3602 # swap byte order and dtype without copy 

3603 result.byteswap(True) 

3604 result = result.newbyteorder() 

3605 if lsb2msb: 

3606 reverse_bitorder(result) 

3607 else: 

3608 result = create_output(out, shape, dtype) 

3609 

3610 decompress = TIFF.DECOMPESSORS[self.compression] 

3611 

3612 if self.compression == 7: # COMPRESSION.JPEG 

3613 if bitspersample not in (8, 12): 

3614 raise ValueError("unsupported JPEG precision %i" % bitspersample) 

3615 if "JPEGTables" in tags: 

3616 table = tags["JPEGTables"].value 

3617 else: 

3618 table = b"" 

3619 unpack = identityfunc 

3620 colorspace = TIFF.PHOTOMETRIC(self.photometric).name 

3621 

3622 def decompress( 

3623 x, 

3624 func=decompress, 

3625 table=table, 

3626 bitspersample=bitspersample, 

3627 colorspace=colorspace, 

3628 ): 

3629 return func(x, table, bitspersample, colorspace).reshape(-1) 

3630 

3631 elif bitspersample in (8, 16, 32, 64, 128): 

3632 if (bitspersample * runlen) % 8: 

3633 raise ValueError("data and sample size mismatch") 

3634 

3635 def unpack(x, typecode=typecode): 

3636 if self.predictor == 3: # PREDICTOR.FLOATINGPOINT 

3637 # the floating point horizontal differencing decoder 

3638 # needs the raw byte order 

3639 typecode = dtype.char 

3640 try: 

3641 # read only numpy array 

3642 return numpy.frombuffer(x, typecode) 

3643 except ValueError: 

3644 # strips may be missing EOI 

3645 # warnings.warn('unpack: %s' % e) 

3646 xlen = (len(x) // (bitspersample // 8)) * (bitspersample // 8) 

3647 return numpy.frombuffer(x[:xlen], typecode) 

3648 

3649 elif isinstance(bitspersample, tuple): 

3650 

3651 def unpack(x, typecode=typecode, bitspersample=bitspersample): 

3652 return unpack_rgb(x, typecode, bitspersample) 

3653 

3654 else: 

3655 

3656 def unpack( 

3657 x, typecode=typecode, bitspersample=bitspersample, runlen=runlen 

3658 ): 

3659 return unpack_ints(x, typecode, bitspersample, runlen) 

3660 

3661 if istiled: 

3662 writable = None 

3663 tw, tl, td, pl = 0, 0, 0, 0 

3664 for tile in buffered_read(fh, lock, offsets, bytecounts): 

3665 if lsb2msb: 

3666 tile = reverse_bitorder(tile) 

3667 tile = decompress(tile) 

3668 tile = unpack(tile) 

3669 try: 

3670 tile.shape = tileshape 

3671 except ValueError: 

3672 # incomplete tiles; see gdal issue #1179 

3673 warnings.warn("invalid tile data") 

3674 t = numpy.zeros(tileshape, dtype).reshape(-1) 

3675 s = min(tile.size, t.size) 

3676 t[:s] = tile[:s] 

3677 tile = t.reshape(tileshape) 

3678 if self.predictor == 2: # PREDICTOR.HORIZONTAL 

3679 if writable is None: 

3680 writable = tile.flags["WRITEABLE"] 

3681 if writable: 

3682 numpy.cumsum(tile, axis=-2, dtype=dtype, out=tile) 

3683 else: 

3684 tile = numpy.cumsum(tile, axis=-2, dtype=dtype) 

3685 elif self.predictor == 3: # PREDICTOR.FLOATINGPOINT 

3686 raise NotImplementedError() 

3687 result[ 

3688 0, 

3689 pl, 

3690 td : td + tiledepth, 

3691 tl : tl + tilelength, 

3692 tw : tw + tilewidth, 

3693 :, 

3694 ] = tile 

3695 del tile 

3696 tw += tilewidth 

3697 if tw >= shape[4]: 

3698 tw, tl = 0, tl + tilelength 

3699 if tl >= shape[3]: 

3700 tl, td = 0, td + tiledepth 

3701 if td >= shape[2]: 

3702 td, pl = 0, pl + 1 

3703 result = result[..., :imagedepth, :imagelength, :imagewidth, :] 

3704 else: 

3705 strip_size = self.rowsperstrip * self.imagewidth 

3706 if self.planarconfig == 1: 

3707 strip_size *= self.samplesperpixel 

3708 result = result.reshape(-1) 

3709 index = 0 

3710 for strip in buffered_read(fh, lock, offsets, bytecounts): 

3711 if lsb2msb: 

3712 strip = reverse_bitorder(strip) 

3713 strip = decompress(strip) 

3714 strip = unpack(strip) 

3715 size = min(result.size, strip.size, strip_size, result.size - index) 

3716 result[index : index + size] = strip[:size] 

3717 del strip 

3718 index += size 

3719 

3720 result.shape = self._shape 

3721 

3722 if self.predictor != 1 and not (istiled and not self.is_contiguous): 

3723 if self.parent.is_lsm and self.compression == 1: 

3724 pass # work around bug in LSM510 software 

3725 elif self.predictor == 2: # PREDICTOR.HORIZONTAL 

3726 numpy.cumsum(result, axis=-2, dtype=dtype, out=result) 

3727 elif self.predictor == 3: # PREDICTOR.FLOATINGPOINT 

3728 result = decode_floats(result) 

3729 

3730 if squeeze: 

3731 try: 

3732 result.shape = self.shape 

3733 except ValueError: 

3734 warnings.warn( 

3735 "failed to reshape from %s to %s" 

3736 % (str(result.shape), str(self.shape)) 

3737 ) 

3738 

3739 if closed: 

3740 # TODO: file should remain open if an exception occurred above 

3741 fh.close() 

3742 return result 

3743 

3744 def asrgb( 

3745 self, 

3746 uint8=False, 

3747 alpha=None, 

3748 colormap=None, 

3749 dmin=None, 

3750 dmax=None, 

3751 *args, 

3752 **kwargs 

3753 ): 

3754 """Return image data as RGB(A). 

3755 

3756 Work in progress. 

3757 

3758 """ 

3759 data = self.asarray(*args, **kwargs) 

3760 self = self.keyframe # self or keyframe 

3761 photometric = self.photometric 

3762 PHOTOMETRIC = TIFF.PHOTOMETRIC 

3763 

3764 if photometric == PHOTOMETRIC.PALETTE: 

3765 colormap = self.colormap 

3766 if colormap.shape[1] < 2**self.bitspersample or self.dtype.char not in "BH": 

3767 raise ValueError("cannot apply colormap") 

3768 if uint8: 

3769 if colormap.max() > 255: 

3770 colormap >>= 8 

3771 colormap = colormap.astype("uint8") 

3772 if "S" in self.axes: 

3773 data = data[..., 0] if self.planarconfig == 1 else data[0] 

3774 data = apply_colormap(data, colormap) 

3775 

3776 elif photometric == PHOTOMETRIC.RGB: 

3777 if "ExtraSamples" in self.tags: 

3778 if alpha is None: 

3779 alpha = TIFF.EXTRASAMPLE 

3780 extrasamples = self.extrasamples 

3781 if self.tags["ExtraSamples"].count == 1: 

3782 extrasamples = (extrasamples,) 

3783 for i, exs in enumerate(extrasamples): 

3784 if exs in alpha: 

3785 if self.planarconfig == 1: 

3786 data = data[..., [0, 1, 2, 3 + i]] 

3787 else: 

3788 data = data[:, [0, 1, 2, 3 + i]] 

3789 break 

3790 else: 

3791 if self.planarconfig == 1: 

3792 data = data[..., :3] 

3793 else: 

3794 data = data[:, :3] 

3795 # TODO: convert to uint8? 

3796 

3797 elif photometric == PHOTOMETRIC.MINISBLACK: 

3798 raise NotImplementedError() 

3799 elif photometric == PHOTOMETRIC.MINISWHITE: 

3800 raise NotImplementedError() 

3801 elif photometric == PHOTOMETRIC.SEPARATED: 

3802 raise NotImplementedError() 

3803 else: 

3804 raise NotImplementedError() 

3805 return data 

3806 

3807 def aspage(self): 

3808 return self 

3809 

3810 @property 

3811 def keyframe(self): 

3812 return self 

3813 

3814 @keyframe.setter 

3815 def keyframe(self, index): 

3816 return 

3817 

3818 @lazyattr 

3819 def offsets_bytecounts(self): 

3820 """Return simplified offsets and bytecounts.""" 

3821 if self.is_contiguous: 

3822 offset, byte_count = self.is_contiguous 

3823 return [offset], [byte_count] 

3824 return clean_offsets_counts(self.dataoffsets, self.databytecounts) 

3825 

3826 @lazyattr 

3827 def is_contiguous(self): 

3828 """Return offset and size of contiguous data, else None. 

3829 

3830 Excludes prediction and fill_order. 

3831 

3832 """ 

3833 if self.compression != 1 or self.bitspersample not in (8, 16, 32, 64): 

3834 return 

3835 if "TileWidth" in self.tags: 

3836 if ( 

3837 self.imagewidth != self.tilewidth 

3838 or self.imagelength % self.tilelength 

3839 or self.tilewidth % 16 

3840 or self.tilelength % 16 

3841 ): 

3842 return 

3843 if ( 

3844 "ImageDepth" in self.tags 

3845 and "TileDepth" in self.tags 

3846 and ( 

3847 self.imagelength != self.tilelength 

3848 or self.imagedepth % self.tiledepth 

3849 ) 

3850 ): 

3851 return 

3852 

3853 offsets = self.dataoffsets 

3854 bytecounts = self.databytecounts 

3855 if len(offsets) == 1: 

3856 return offsets[0], bytecounts[0] 

3857 if self.is_stk or all( 

3858 ( 

3859 offsets[i] + bytecounts[i] == offsets[i + 1] or bytecounts[i + 1] == 0 

3860 ) # no data/ignore offset 

3861 for i in range(len(offsets) - 1) 

3862 ): 

3863 return offsets[0], sum(bytecounts) 

3864 

3865 @lazyattr 

3866 def is_final(self): 

3867 """Return if page's image data are stored in final form. 

3868 

3869 Excludes byte-swapping. 

3870 

3871 """ 

3872 return ( 

3873 self.is_contiguous 

3874 and self.fillorder == 1 

3875 and self.predictor == 1 

3876 and not self.is_chroma_subsampled 

3877 ) 

3878 

3879 @lazyattr 

3880 def is_memmappable(self): 

3881 """Return if page's image data in file can be memory-mapped.""" 

3882 return ( 

3883 self.parent.filehandle.is_file 

3884 and self.is_final 

3885 and 

3886 # (self.bitspersample == 8 or self.parent.isnative) and 

3887 self.is_contiguous[0] % self.dtype.itemsize == 0 

3888 ) # aligned? 

3889 

3890 def __str__(self, detail=0, width=79): 

3891 """Return string containing information about page.""" 

3892 if self.keyframe != self: 

3893 return TiffFrame.__str__(self, detail) 

3894 attr = "" 

3895 for name in ("memmappable", "final", "contiguous"): 

3896 attr = getattr(self, "is_" + name) 

3897 if attr: 

3898 attr = name.upper() 

3899 break 

3900 info = " ".join( 

3901 s 

3902 for s in ( 

3903 "x".join(str(i) for i in self.shape), 

3904 "%s%s" 

3905 % (TIFF.SAMPLEFORMAT(self.sampleformat).name, self.bitspersample), 

3906 "|".join( 

3907 i 

3908 for i in ( 

3909 TIFF.PHOTOMETRIC(self.photometric).name, 

3910 "TILED" if self.is_tiled else "", 

3911 self.compression.name if self.compression != 1 else "", 

3912 self.planarconfig.name if self.planarconfig != 1 else "", 

3913 self.predictor.name if self.predictor != 1 else "", 

3914 self.fillorder.name if self.fillorder != 1 else "", 

3915 ) 

3916 if i 

3917 ), 

3918 attr, 

3919 "|".join((f.upper() for f in self.flags)), 

3920 ) 

3921 if s 

3922 ) 

3923 info = "TiffPage %i @%i %s" % (self.index, self.offset, info) 

3924 if detail <= 0: 

3925 return info 

3926 info = [info] 

3927 tags = self.tags 

3928 tlines = [] 

3929 vlines = [] 

3930 for tag in sorted(tags.values(), key=lambda x: x.code): 

3931 value = tag.__str__(width=width + 1) 

3932 tlines.append(value[:width].strip()) 

3933 if detail > 1 and len(value) > width: 

3934 name = tag.name.upper() 

3935 if detail <= 2 and ("COUNTS" in name or "OFFSETS" in name): 

3936 value = pformat(tag.value, width=width, height=detail * 4) 

3937 else: 

3938 value = pformat(tag.value, width=width, height=detail * 12) 

3939 vlines.append("%s\n%s" % (tag.name, value)) 

3940 info.append("\n".join(tlines)) 

3941 if detail > 1: 

3942 info.append("\n\n".join(vlines)) 

3943 if detail > 3: 

3944 try: 

3945 info.append( 

3946 "DATA\n%s" % pformat(self.asarray(), width=width, height=detail * 8) 

3947 ) 

3948 except Exception: 

3949 pass 

3950 return "\n\n".join(info) 

3951 

3952 @lazyattr 

3953 def flags(self): 

3954 """Return set of flags.""" 

3955 return set( 

3956 ( 

3957 name.lower() 

3958 for name in sorted(TIFF.FILE_FLAGS) 

3959 if getattr(self, "is_" + name) 

3960 ) 

3961 ) 

3962 

3963 @property 

3964 def ndim(self): 

3965 """Return number of array dimensions.""" 

3966 return len(self.shape) 

3967 

3968 @property 

3969 def size(self): 

3970 """Return number of elements in array.""" 

3971 return product(self.shape) 

3972 

3973 @lazyattr 

3974 def andor_tags(self): 

3975 """Return consolidated metadata from Andor tags as dict. 

3976 

3977 Remove Andor tags from self.tags. 

3978 

3979 """ 

3980 if not self.is_andor: 

3981 return 

3982 tags = self.tags 

3983 result = {"Id": tags["AndorId"].value} 

3984 for tag in list(self.tags.values()): 

3985 code = tag.code 

3986 if not 4864 < code < 5031: 

3987 continue 

3988 value = tag.value 

3989 name = tag.name[5:] if len(tag.name) > 5 else tag.name 

3990 result[name] = value 

3991 del tags[tag.name] 

3992 return result 

3993 

3994 @lazyattr 

3995 def epics_tags(self): 

3996 """Return consolidated metadata from EPICS areaDetector tags as dict. 

3997 

3998 Remove areaDetector tags from self.tags. 

3999 

4000 """ 

4001 if not self.is_epics: 

4002 return 

4003 result = {} 

4004 tags = self.tags 

4005 for tag in list(self.tags.values()): 

4006 code = tag.code 

4007 if not 65000 <= code < 65500: 

4008 continue 

4009 value = tag.value 

4010 if code == 65000: 

4011 result["timeStamp"] = datetime.datetime.fromtimestamp(float(value)) 

4012 elif code == 65001: 

4013 result["uniqueID"] = int(value) 

4014 elif code == 65002: 

4015 result["epicsTSSec"] = int(value) 

4016 elif code == 65003: 

4017 result["epicsTSNsec"] = int(value) 

4018 else: 

4019 key, value = value.split(":", 1) 

4020 result[key] = astype(value) 

4021 del tags[tag.name] 

4022 return result 

4023 

4024 @lazyattr 

4025 def geotiff_tags(self): 

4026 """Return consolidated metadata from GeoTIFF tags as dict.""" 

4027 if not self.is_geotiff: 

4028 return 

4029 tags = self.tags 

4030 

4031 gkd = tags["GeoKeyDirectoryTag"].value 

4032 if gkd[0] != 1: 

4033 warnings.warn("invalid GeoKeyDirectoryTag") 

4034 return {} 

4035 

4036 result = { 

4037 "KeyDirectoryVersion": gkd[0], 

4038 "KeyRevision": gkd[1], 

4039 "KeyRevisionMinor": gkd[2], 

4040 # 'NumberOfKeys': gkd[3], 

4041 } 

4042 # deltags = ['GeoKeyDirectoryTag'] 

4043 geokeys = TIFF.GEO_KEYS 

4044 geocodes = TIFF.GEO_CODES 

4045 for index in range(gkd[3]): 

4046 keyid, tagid, count, offset = gkd[4 + index * 4 : index * 4 + 8] 

4047 keyid = geokeys.get(keyid, keyid) 

4048 if tagid == 0: 

4049 value = offset 

4050 else: 

4051 tagname = TIFF.TAGS[tagid] 

4052 # deltags.append(tagname) 

4053 value = tags[tagname].value[offset : offset + count] 

4054 if tagid == 34737 and count > 1 and value[-1] == "|": 

4055 value = value[:-1] 

4056 value = value if count > 1 else value[0] 

4057 if keyid in geocodes: 

4058 try: 

4059 value = geocodes[keyid](value) 

4060 except Exception: 

4061 pass 

4062 result[keyid] = value 

4063 

4064 if "IntergraphMatrixTag" in tags: 

4065 value = tags["IntergraphMatrixTag"].value 

4066 value = numpy.array(value) 

4067 if len(value) == 16: 

4068 value = value.reshape((4, 4)).tolist() 

4069 result["IntergraphMatrix"] = value 

4070 if "ModelPixelScaleTag" in tags: 

4071 value = numpy.array(tags["ModelPixelScaleTag"].value).tolist() 

4072 result["ModelPixelScale"] = value 

4073 if "ModelTiepointTag" in tags: 

4074 value = tags["ModelTiepointTag"].value 

4075 value = numpy.array(value).reshape((-1, 6)).squeeze().tolist() 

4076 result["ModelTiepoint"] = value 

4077 if "ModelTransformationTag" in tags: 

4078 value = tags["ModelTransformationTag"].value 

4079 value = numpy.array(value).reshape((4, 4)).tolist() 

4080 result["ModelTransformation"] = value 

4081 elif False: 

4082 # if 'ModelPixelScaleTag' in tags and 'ModelTiepointTag' in tags: 

4083 sx, sy, sz = tags["ModelPixelScaleTag"].value 

4084 tiepoints = tags["ModelTiepointTag"].value 

4085 transforms = [] 

4086 for tp in range(0, len(tiepoints), 6): 

4087 i, j, k, x, y, z = tiepoints[tp : tp + 6] 

4088 transforms.append( 

4089 [ 

4090 [sx, 0.0, 0.0, x - i * sx], 

4091 [0.0, -sy, 0.0, y + j * sy], 

4092 [0.0, 0.0, sz, z - k * sz], 

4093 [0.0, 0.0, 0.0, 1.0], 

4094 ] 

4095 ) 

4096 if len(tiepoints) == 6: 

4097 transforms = transforms[0] 

4098 result["ModelTransformation"] = transforms 

4099 

4100 if "RPCCoefficientTag" in tags: 

4101 rpcc = tags["RPCCoefficientTag"].value 

4102 result["RPCCoefficient"] = { 

4103 "ERR_BIAS": rpcc[0], 

4104 "ERR_RAND": rpcc[1], 

4105 "LINE_OFF": rpcc[2], 

4106 "SAMP_OFF": rpcc[3], 

4107 "LAT_OFF": rpcc[4], 

4108 "LONG_OFF": rpcc[5], 

4109 "HEIGHT_OFF": rpcc[6], 

4110 "LINE_SCALE": rpcc[7], 

4111 "SAMP_SCALE": rpcc[8], 

4112 "LAT_SCALE": rpcc[9], 

4113 "LONG_SCALE": rpcc[10], 

4114 "HEIGHT_SCALE": rpcc[11], 

4115 "LINE_NUM_COEFF": rpcc[12:33], 

4116 "LINE_DEN_COEFF ": rpcc[33:53], 

4117 "SAMP_NUM_COEFF": rpcc[53:73], 

4118 "SAMP_DEN_COEFF": rpcc[73:], 

4119 } 

4120 

4121 return result 

4122 

4123 @property 

4124 def is_tiled(self): 

4125 """Page contains tiled image.""" 

4126 return "TileWidth" in self.tags 

4127 

4128 @property 

4129 def is_reduced(self): 

4130 """Page is reduced image of another image.""" 

4131 return "NewSubfileType" in self.tags and self.tags["NewSubfileType"].value & 1 

4132 

4133 @property 

4134 def is_chroma_subsampled(self): 

4135 """Page contains chroma subsampled image.""" 

4136 return "YCbCrSubSampling" in self.tags and self.tags[ 

4137 "YCbCrSubSampling" 

4138 ].value != (1, 1) 

4139 

4140 @lazyattr 

4141 def is_imagej(self): 

4142 """Return ImageJ description if exists, else None.""" 

4143 for description in (self.description, self.description1): 

4144 if not description: 

4145 return 

4146 if description[:7] == "ImageJ=": 

4147 return description 

4148 

4149 @lazyattr 

4150 def is_shaped(self): 

4151 """Return description containing array shape if exists, else None.""" 

4152 for description in (self.description, self.description1): 

4153 if not description: 

4154 return 

4155 if description[:1] == "{" and '"shape":' in description: 

4156 return description 

4157 if description[:6] == "shape=": 

4158 return description 

4159 

4160 @property 

4161 def is_mdgel(self): 

4162 """Page contains MDFileTag tag.""" 

4163 return "MDFileTag" in self.tags 

4164 

4165 @property 

4166 def is_mediacy(self): 

4167 """Page contains Media Cybernetics Id tag.""" 

4168 return "MC_Id" in self.tags and self.tags["MC_Id"].value[:7] == b"MC TIFF" 

4169 

4170 @property 

4171 def is_stk(self): 

4172 """Page contains UIC2Tag tag.""" 

4173 return "UIC2tag" in self.tags 

4174 

4175 @property 

4176 def is_lsm(self): 

4177 """Page contains CZ_LSMINFO tag.""" 

4178 return "CZ_LSMINFO" in self.tags 

4179 

4180 @property 

4181 def is_fluoview(self): 

4182 """Page contains FluoView MM_STAMP tag.""" 

4183 return "MM_Stamp" in self.tags 

4184 

4185 @property 

4186 def is_nih(self): 

4187 """Page contains NIH image header.""" 

4188 return "NIHImageHeader" in self.tags 

4189 

4190 @property 

4191 def is_sgi(self): 

4192 """Page contains SGI image and tile depth tags.""" 

4193 return "ImageDepth" in self.tags and "TileDepth" in self.tags 

4194 

4195 @property 

4196 def is_vista(self): 

4197 """Software tag is 'ISS Vista'.""" 

4198 return self.software == "ISS Vista" 

4199 

4200 @property 

4201 def is_metaseries(self): 

4202 """Page contains MDS MetaSeries metadata in ImageDescription tag.""" 

4203 if self.index > 1 or self.software != "MetaSeries": 

4204 return False 

4205 d = self.description 

4206 return d.startswith("<MetaData>") and d.endswith("</MetaData>") 

4207 

4208 @property 

4209 def is_ome(self): 

4210 """Page contains OME-XML in ImageDescription tag.""" 

4211 if self.index > 1 or not self.description: 

4212 return False 

4213 d = self.description 

4214 return d[:14] == "<?xml version=" and d[-6:] == "</OME>" 

4215 

4216 @property 

4217 def is_scn(self): 

4218 """Page contains Leica SCN XML in ImageDescription tag.""" 

4219 if self.index > 1 or not self.description: 

4220 return False 

4221 d = self.description 

4222 return d[:14] == "<?xml version=" and d[-6:] == "</scn>" 

4223 

4224 @property 

4225 def is_micromanager(self): 

4226 """Page contains Micro-Manager metadata.""" 

4227 return "MicroManagerMetadata" in self.tags 

4228 

4229 @property 

4230 def is_andor(self): 

4231 """Page contains Andor Technology tags.""" 

4232 return "AndorId" in self.tags 

4233 

4234 @property 

4235 def is_pilatus(self): 

4236 """Page contains Pilatus tags.""" 

4237 return self.software[:8] == "TVX TIFF" and self.description[:2] == "# " 

4238 

4239 @property 

4240 def is_epics(self): 

4241 """Page contains EPICS areaDetector tags.""" 

4242 return ( 

4243 self.description == "EPICS areaDetector" 

4244 or self.software == "EPICS areaDetector" 

4245 ) 

4246 

4247 @property 

4248 def is_tvips(self): 

4249 """Page contains TVIPS metadata.""" 

4250 return "TVIPS" in self.tags 

4251 

4252 @property 

4253 def is_fei(self): 

4254 """Page contains SFEG or HELIOS metadata.""" 

4255 return "FEI_SFEG" in self.tags or "FEI_HELIOS" in self.tags 

4256 

4257 @property 

4258 def is_sem(self): 

4259 """Page contains Zeiss SEM metadata.""" 

4260 return "CZ_SEM" in self.tags 

4261 

4262 @property 

4263 def is_svs(self): 

4264 """Page contains Aperio metadata.""" 

4265 return self.description[:20] == "Aperio Image Library" 

4266 

4267 @property 

4268 def is_scanimage(self): 

4269 """Page contains ScanImage metadata.""" 

4270 return ( 

4271 self.description[:12] == "state.config" 

4272 or self.software[:22] == "SI.LINE_FORMAT_VERSION" 

4273 or "scanimage.SI." in self.description[-256:] 

4274 ) 

4275 

4276 @property 

4277 def is_qptiff(self): 

4278 """Page contains PerkinElmer tissue images metadata.""" 

4279 # The ImageDescription tag contains XML with a top-level 

4280 # <PerkinElmer-QPI-ImageDescription> element 

4281 return self.software[:15] == "PerkinElmer-QPI" 

4282 

4283 @property 

4284 def is_geotiff(self): 

4285 """Page contains GeoTIFF metadata.""" 

4286 return "GeoKeyDirectoryTag" in self.tags 

4287 

4288 

4289class TiffFrame(object): 

4290 """Lightweight TIFF image file directory (IFD). 

4291 

4292 Only a limited number of tag values are read from file, e.g. StripOffsets, 

4293 and StripByteCounts. Other tag values are assumed to be identical with a 

4294 specified TiffPage instance, the keyframe. 

4295 

4296 TiffFrame is intended to reduce resource usage and speed up reading data 

4297 from file, not for introspection of metadata. 

4298 

4299 Not compatible with Python 2. 

4300 

4301 """ 

4302 

4303 __slots__ = ( 

4304 "keyframe", 

4305 "parent", 

4306 "index", 

4307 "offset", 

4308 "dataoffsets", 

4309 "databytecounts", 

4310 ) 

4311 

4312 is_mdgel = False 

4313 tags = {} 

4314 

4315 def __init__(self, parent, index, keyframe): 

4316 """Read specified tags from file. 

4317 

4318 The file handle position must be at the offset to a valid IFD. 

4319 

4320 """ 

4321 self.keyframe = keyframe 

4322 self.parent = parent 

4323 self.index = index 

4324 self.dataoffsets = None 

4325 self.databytecounts = None 

4326 

4327 unpack = struct.unpack 

4328 fh = parent.filehandle 

4329 self.offset = fh.tell() 

4330 try: 

4331 tagno = unpack(parent.tagnoformat, fh.read(parent.tagnosize))[0] 

4332 if tagno > 4096: 

4333 raise ValueError("suspicious number of tags") 

4334 except Exception: 

4335 raise ValueError("corrupted page list at offset %i" % self.offset) 

4336 

4337 # tags = {} 

4338 tagcodes = {273, 279, 324, 325} # TIFF.FRAME_TAGS 

4339 tagsize = parent.tagsize 

4340 codeformat = parent.tagformat1[:2] 

4341 

4342 data = fh.read(tagsize * tagno) 

4343 index = -tagsize 

4344 for _ in range(tagno): 

4345 index += tagsize 

4346 code = unpack(codeformat, data[index : index + 2])[0] 

4347 if code not in tagcodes: 

4348 continue 

4349 try: 

4350 tag = TiffTag(parent, data[index : index + tagsize]) 

4351 except TiffTag.Error as e: 

4352 warnings.warn(str(e)) 

4353 continue 

4354 if code == 273 or code == 324: 

4355 setattr(self, "dataoffsets", tag.value) 

4356 elif code == 279 or code == 325: 

4357 setattr(self, "databytecounts", tag.value) 

4358 # elif code == 270: 

4359 # tagname = tag.name 

4360 # if tagname not in tags: 

4361 # tags[tagname] = bytes2str(tag.value) 

4362 # elif 'ImageDescription1' not in tags: 

4363 # tags['ImageDescription1'] = bytes2str(tag.value) 

4364 # else: 

4365 # tags[tag.name] = tag.value 

4366 

4367 def aspage(self): 

4368 """Return TiffPage from file.""" 

4369 self.parent.filehandle.seek(self.offset) 

4370 return TiffPage(self.parent, index=self.index, keyframe=None) 

4371 

4372 def asarray(self, *args, **kwargs): 

4373 """Read image data from file and return as numpy array.""" 

4374 # TODO: fix TypeError on Python 2 

4375 # "TypeError: unbound method asarray() must be called with TiffPage 

4376 # instance as first argument (got TiffFrame instance instead)" 

4377 kwargs["validate"] = False 

4378 return TiffPage.asarray(self, *args, **kwargs) 

4379 

4380 def asrgb(self, *args, **kwargs): 

4381 """Read image data from file and return RGB image as numpy array.""" 

4382 kwargs["validate"] = False 

4383 return TiffPage.asrgb(self, *args, **kwargs) 

4384 

4385 @property 

4386 def offsets_bytecounts(self): 

4387 """Return simplified offsets and bytecounts.""" 

4388 if self.keyframe.is_contiguous: 

4389 return self.dataoffsets[:1], self.keyframe.is_contiguous[1:] 

4390 return clean_offsets_counts(self.dataoffsets, self.databytecounts) 

4391 

4392 @property 

4393 def is_contiguous(self): 

4394 """Return offset and size of contiguous data, else None.""" 

4395 if self.keyframe.is_contiguous: 

4396 return self.dataoffsets[0], self.keyframe.is_contiguous[1] 

4397 

4398 @property 

4399 def is_memmappable(self): 

4400 """Return if page's image data in file can be memory-mapped.""" 

4401 return self.keyframe.is_memmappable 

4402 

4403 def __getattr__(self, name): 

4404 """Return attribute from keyframe.""" 

4405 if name in TIFF.FRAME_ATTRS: 

4406 return getattr(self.keyframe, name) 

4407 # this error could be raised because an AttributeError was 

4408 # raised inside a @property function 

4409 raise AttributeError( 

4410 "'%s' object has no attribute '%s'" % (self.__class__.__name__, name) 

4411 ) 

4412 

4413 def __str__(self, detail=0): 

4414 """Return string containing information about frame.""" 

4415 info = " ".join( 

4416 s for s in ("x".join(str(i) for i in self.shape), str(self.dtype)) 

4417 ) 

4418 return "TiffFrame %i @%i %s" % (self.index, self.offset, info) 

4419 

4420 

4421class TiffTag(object): 

4422 """TIFF tag structure. 

4423 

4424 Attributes 

4425 ---------- 

4426 name : string 

4427 Name of tag. 

4428 code : int 

4429 Decimal code of tag. 

4430 dtype : str 

4431 Datatype of tag data. One of TIFF DATA_FORMATS. 

4432 count : int 

4433 Number of values. 

4434 value : various types 

4435 Tag data as Python object. 

4436 ImageSourceData : int 

4437 Location of value in file. 

4438 

4439 All attributes are read-only. 

4440 

4441 """ 

4442 

4443 __slots__ = ("code", "count", "dtype", "value", "valueoffset") 

4444 

4445 class Error(Exception): 

4446 pass 

4447 

4448 def __init__(self, parent, tagheader, **kwargs): 

4449 """Initialize instance from tag header.""" 

4450 fh = parent.filehandle 

4451 byteorder = parent.byteorder 

4452 unpack = struct.unpack 

4453 offsetsize = parent.offsetsize 

4454 

4455 self.valueoffset = fh.tell() + offsetsize + 4 

4456 code, type_ = unpack(parent.tagformat1, tagheader[:4]) 

4457 count, value = unpack(parent.tagformat2, tagheader[4:]) 

4458 

4459 try: 

4460 dtype = TIFF.DATA_FORMATS[type_] 

4461 except KeyError: 

4462 raise TiffTag.Error("unknown tag data type %i" % type_) 

4463 

4464 fmt = "%s%i%s" % (byteorder, count * int(dtype[0]), dtype[1]) 

4465 size = struct.calcsize(fmt) 

4466 if size > offsetsize or code in TIFF.TAG_READERS: 

4467 self.valueoffset = offset = unpack(parent.offsetformat, value)[0] 

4468 if offset < 8 or offset > fh.size - size: 

4469 raise TiffTag.Error("invalid tag value offset") 

4470 # if offset % 2: 

4471 # warnings.warn('tag value does not begin on word boundary') 

4472 fh.seek(offset) 

4473 if code in TIFF.TAG_READERS: 

4474 readfunc = TIFF.TAG_READERS[code] 

4475 value = readfunc(fh, byteorder, dtype, count, offsetsize) 

4476 elif type_ == 7 or (count > 1 and dtype[-1] == "B"): 

4477 value = read_bytes(fh, byteorder, dtype, count, offsetsize) 

4478 elif code in TIFF.TAGS or dtype[-1] == "s": 

4479 value = unpack(fmt, fh.read(size)) 

4480 else: 

4481 value = read_numpy(fh, byteorder, dtype, count, offsetsize) 

4482 elif dtype[-1] == "B" or type_ == 7: 

4483 value = value[:size] 

4484 else: 

4485 value = unpack(fmt, value[:size]) 

4486 

4487 process = ( 

4488 code not in TIFF.TAG_READERS and code not in TIFF.TAG_TUPLE and type_ != 7 

4489 ) 

4490 if process and dtype[-1] == "s" and isinstance(value[0], bytes): 

4491 # TIFF ASCII fields can contain multiple strings, 

4492 # each terminated with a NUL 

4493 value = value[0] 

4494 try: 

4495 value = bytes2str(stripascii(value).strip()) 

4496 except UnicodeDecodeError: 

4497 warnings.warn("tag %i: coercing invalid ASCII to bytes" % code) 

4498 dtype = "1B" 

4499 else: 

4500 if code in TIFF.TAG_ENUM: 

4501 t = TIFF.TAG_ENUM[code] 

4502 try: 

4503 value = tuple(t(v) for v in value) 

4504 except ValueError as e: 

4505 warnings.warn(str(e)) 

4506 if process: 

4507 if len(value) == 1: 

4508 value = value[0] 

4509 

4510 self.code = code 

4511 self.dtype = dtype 

4512 self.count = count 

4513 self.value = value 

4514 

4515 @property 

4516 def name(self): 

4517 return TIFF.TAGS.get(self.code, str(self.code)) 

4518 

4519 def _fix_lsm_bitspersample(self, parent): 

4520 """Correct LSM bitspersample tag. 

4521 

4522 Old LSM writers may use a separate region for two 16-bit values, 

4523 although they fit into the tag value element of the tag. 

4524 

4525 """ 

4526 if self.code == 258 and self.count == 2: 

4527 # TODO: test this case; need example file 

4528 warnings.warn("correcting LSM bitspersample tag") 

4529 tof = parent.offsetformat[parent.offsetsize] 

4530 self.valueoffset = struct.unpack(tof, self._value)[0] 

4531 parent.filehandle.seek(self.valueoffset) 

4532 self.value = struct.unpack("<HH", parent.filehandle.read(4)) 

4533 

4534 def __str__(self, detail=0, width=79): 

4535 """Return string containing information about tag.""" 

4536 height = 1 if detail <= 0 else 8 * detail 

4537 tcode = "%i%s" % (self.count * int(self.dtype[0]), self.dtype[1]) 

4538 line = ( 

4539 "TiffTag %i %s %s @%i " 

4540 % (self.code, self.name, tcode, self.valueoffset)[:width] 

4541 ) 

4542 

4543 if self.code in TIFF.TAG_ENUM: 

4544 if self.count == 1: 

4545 value = TIFF.TAG_ENUM[self.code](self.value).name 

4546 else: 

4547 value = pformat(tuple(v.name for v in self.value)) 

4548 else: 

4549 value = pformat(self.value, width=width, height=height) 

4550 

4551 if detail <= 0: 

4552 line += value 

4553 line = line[:width] 

4554 else: 

4555 line += "\n" + value 

4556 return line 

4557 

4558 

4559# Added to produce cleaner exceptions if tifffile unexpectedly fails to open the 

4560# file. See this comment (and the following) for details: 

4561# https://github.com/imageio/imageio/commit/bdbe699bbcda4223b0b6bd4d7474f84bbe34af09#r64068747 

4562class TiffFileError(ValueError): 

4563 pass 

4564 

4565 

4566class TiffPageSeries(object): 

4567 """Series of TIFF pages with compatible shape and data type. 

4568 

4569 Attributes 

4570 ---------- 

4571 pages : list of TiffPage 

4572 Sequence of TiffPages in series. 

4573 dtype : numpy.dtype 

4574 Data type (native byte order) of the image array in series. 

4575 shape : tuple 

4576 Dimensions of the image array in series. 

4577 axes : str 

4578 Labels of axes in shape. See TiffPage.axes. 

4579 offset : int or None 

4580 Position of image data in file if memory-mappable, else None. 

4581 

4582 """ 

4583 

4584 def __init__( 

4585 self, 

4586 pages, 

4587 shape, 

4588 dtype, 

4589 axes, 

4590 parent=None, 

4591 name=None, 

4592 transform=None, 

4593 stype=None, 

4594 truncated=False, 

4595 ): 

4596 """Initialize instance.""" 

4597 self.index = 0 

4598 self._pages = pages # might contain only first of contiguous pages 

4599 self.shape = tuple(shape) 

4600 self.axes = "".join(axes) 

4601 self.dtype = numpy.dtype(dtype) 

4602 self.stype = stype if stype else "" 

4603 self.name = name if name else "" 

4604 self.transform = transform 

4605 if parent: 

4606 self.parent = parent 

4607 elif pages: 

4608 self.parent = pages[0].parent 

4609 else: 

4610 self.parent = None 

4611 if len(pages) == 1 and not truncated: 

4612 self._len = int(product(self.shape) // product(pages[0].shape)) 

4613 else: 

4614 self._len = len(pages) 

4615 

4616 def asarray(self, out=None): 

4617 """Return image data from series of TIFF pages as numpy array.""" 

4618 if self.parent: 

4619 result = self.parent.asarray(series=self, out=out) 

4620 if self.transform is not None: 

4621 result = self.transform(result) 

4622 return result 

4623 

4624 @lazyattr 

4625 def offset(self): 

4626 """Return offset to series data in file, if any.""" 

4627 if not self._pages: 

4628 return 

4629 

4630 pos = 0 

4631 for page in self._pages: 

4632 if page is None: 

4633 return 

4634 if not page.is_final: 

4635 return 

4636 if not pos: 

4637 pos = page.is_contiguous[0] + page.is_contiguous[1] 

4638 continue 

4639 if pos != page.is_contiguous[0]: 

4640 return 

4641 pos += page.is_contiguous[1] 

4642 

4643 page = self._pages[0] 

4644 offset = page.is_contiguous[0] 

4645 if (page.is_imagej or page.is_shaped) and len(self._pages) == 1: 

4646 # truncated files 

4647 return offset 

4648 if pos == offset + product(self.shape) * self.dtype.itemsize: 

4649 return offset 

4650 

4651 @property 

4652 def ndim(self): 

4653 """Return number of array dimensions.""" 

4654 return len(self.shape) 

4655 

4656 @property 

4657 def size(self): 

4658 """Return number of elements in array.""" 

4659 return int(product(self.shape)) 

4660 

4661 @property 

4662 def pages(self): 

4663 """Return sequence of all pages in series.""" 

4664 # a workaround to keep the old interface working 

4665 return self 

4666 

4667 def __len__(self): 

4668 """Return number of TiffPages in series.""" 

4669 return self._len 

4670 

4671 def __getitem__(self, key): 

4672 """Return specified TiffPage.""" 

4673 if len(self._pages) == 1 and 0 < key < self._len: 

4674 index = self._pages[0].index 

4675 return self.parent.pages[index + key] 

4676 return self._pages[key] 

4677 

4678 def __iter__(self): 

4679 """Return iterator over TiffPages in series.""" 

4680 if len(self._pages) == self._len: 

4681 for page in self._pages: 

4682 yield page 

4683 else: 

4684 pages = self.parent.pages 

4685 index = self._pages[0].index 

4686 for i in range(self._len): 

4687 yield pages[index + i] 

4688 

4689 def __str__(self): 

4690 """Return string with information about series.""" 

4691 s = " ".join( 

4692 s 

4693 for s in ( 

4694 snipstr("'%s'" % self.name, 20) if self.name else "", 

4695 "x".join(str(i) for i in self.shape), 

4696 str(self.dtype), 

4697 self.axes, 

4698 self.stype, 

4699 "%i Pages" % len(self.pages), 

4700 ("Offset=%i" % self.offset) if self.offset else "", 

4701 ) 

4702 if s 

4703 ) 

4704 return "TiffPageSeries %i %s" % (self.index, s) 

4705 

4706 

4707class TiffSequence(object): 

4708 """Sequence of TIFF files. 

4709 

4710 The image data in all files must match shape, dtype, etc. 

4711 

4712 Attributes 

4713 ---------- 

4714 files : list 

4715 List of file names. 

4716 shape : tuple 

4717 Shape of image sequence. Excludes shape of image array. 

4718 axes : str 

4719 Labels of axes in shape. 

4720 

4721 Examples 

4722 -------- 

4723 >>> # read image stack from sequence of TIFF files 

4724 >>> imsave('temp_C001T001.tif', numpy.random.rand(64, 64)) 

4725 >>> imsave('temp_C001T002.tif', numpy.random.rand(64, 64)) 

4726 >>> tifs = TiffSequence('temp_C001*.tif') 

4727 >>> tifs.shape 

4728 (1, 2) 

4729 >>> tifs.axes 

4730 'CT' 

4731 >>> data = tifs.asarray() 

4732 >>> data.shape 

4733 (1, 2, 64, 64) 

4734 

4735 """ 

4736 

4737 _patterns = { 

4738 "axes": r""" 

4739 # matches Olympus OIF and Leica TIFF series 

4740 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4})) 

4741 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))? 

4742 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))? 

4743 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))? 

4744 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))? 

4745 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))? 

4746 _?(?:(q|l|p|a|c|t|x|y|z|ch|tp)(\d{1,4}))? 

4747 """ 

4748 } 

4749 

4750 class ParseError(Exception): 

4751 pass 

4752 

4753 def __init__(self, files, imread=TiffFile, pattern="axes", *args, **kwargs): 

4754 """Initialize instance from multiple files. 

4755 

4756 Parameters 

4757 ---------- 

4758 files : str, pathlib.Path, or sequence thereof 

4759 Glob pattern or sequence of file names. 

4760 Binary streams are not supported. 

4761 imread : function or class 

4762 Image read function or class with asarray function returning numpy 

4763 array from single file. 

4764 pattern : str 

4765 Regular expression pattern that matches axes names and sequence 

4766 indices in file names. 

4767 By default, the pattern matches Olympus OIF and Leica TIFF series. 

4768 

4769 """ 

4770 if isinstance(files, pathlib.Path): 

4771 files = str(files) 

4772 if isinstance(files, basestring): 

4773 files = natural_sorted(glob.glob(files)) 

4774 files = list(files) 

4775 if not files: 

4776 raise ValueError("no files found") 

4777 if isinstance(files[0], pathlib.Path): 

4778 files = [str(pathlib.Path(f)) for f in files] 

4779 elif not isinstance(files[0], basestring): 

4780 raise ValueError("not a file name") 

4781 self.files = files 

4782 

4783 if hasattr(imread, "asarray"): 

4784 # redefine imread 

4785 _imread = imread 

4786 

4787 def imread(fname, *args, **kwargs): 

4788 with _imread(fname) as im: 

4789 return im.asarray(*args, **kwargs) 

4790 

4791 self.imread = imread 

4792 

4793 self.pattern = self._patterns.get(pattern, pattern) 

4794 try: 

4795 self._parse() 

4796 if not self.axes: 

4797 self.axes = "I" 

4798 except self.ParseError: 

4799 self.axes = "I" 

4800 self.shape = (len(files),) 

4801 self._startindex = (0,) 

4802 self._indices = tuple((i,) for i in range(len(files))) 

4803 

4804 def __str__(self): 

4805 """Return string with information about image sequence.""" 

4806 return "\n".join( 

4807 [ 

4808 self.files[0], 

4809 " size: %i" % len(self.files), 

4810 " axes: %s" % self.axes, 

4811 " shape: %s" % str(self.shape), 

4812 ] 

4813 ) 

4814 

4815 def __len__(self): 

4816 return len(self.files) 

4817 

4818 def __enter__(self): 

4819 return self 

4820 

4821 def __exit__(self, exc_type, exc_value, traceback): 

4822 self.close() 

4823 

4824 def close(self): 

4825 pass 

4826 

4827 def asarray(self, out=None, *args, **kwargs): 

4828 """Read image data from all files and return as numpy array. 

4829 

4830 The args and kwargs parameters are passed to the imread function. 

4831 

4832 Raise IndexError or ValueError if image shapes do not match. 

4833 

4834 """ 

4835 im = self.imread(self.files[0], *args, **kwargs) 

4836 shape = self.shape + im.shape 

4837 result = create_output(out, shape, dtype=im.dtype) 

4838 result = result.reshape(-1, *im.shape) 

4839 for index, fname in zip(self._indices, self.files): 

4840 index = [i - j for i, j in zip(index, self._startindex)] 

4841 index = numpy.ravel_multi_index(index, self.shape) 

4842 im = self.imread(fname, *args, **kwargs) 

4843 result[index] = im 

4844 result.shape = shape 

4845 return result 

4846 

4847 def _parse(self): 

4848 """Get axes and shape from file names.""" 

4849 if not self.pattern: 

4850 raise self.ParseError("invalid pattern") 

4851 pattern = re.compile(self.pattern, re.IGNORECASE | re.VERBOSE) 

4852 matches = pattern.findall(self.files[0]) 

4853 if not matches: 

4854 raise self.ParseError("pattern does not match file names") 

4855 matches = matches[-1] 

4856 if len(matches) % 2: 

4857 raise self.ParseError("pattern does not match axis name and index") 

4858 axes = "".join(m for m in matches[::2] if m) 

4859 if not axes: 

4860 raise self.ParseError("pattern does not match file names") 

4861 

4862 indices = [] 

4863 for fname in self.files: 

4864 matches = pattern.findall(fname)[-1] 

4865 if axes != "".join(m for m in matches[::2] if m): 

4866 raise ValueError("axes do not match within the image sequence") 

4867 indices.append([int(m) for m in matches[1::2] if m]) 

4868 shape = tuple(numpy.max(indices, axis=0)) 

4869 startindex = tuple(numpy.min(indices, axis=0)) 

4870 shape = tuple(i - j + 1 for i, j in zip(shape, startindex)) 

4871 if product(shape) != len(self.files): 

4872 warnings.warn("files are missing. Missing data are zeroed") 

4873 

4874 self.axes = axes.upper() 

4875 self.shape = shape 

4876 self._indices = indices 

4877 self._startindex = startindex 

4878 

4879 

4880class FileHandle(object): 

4881 """Binary file handle. 

4882 

4883 A limited, special purpose file handler that can: 

4884 

4885 * handle embedded files (for CZI within CZI files) 

4886 * re-open closed files (for multi-file formats, such as OME-TIFF) 

4887 * read and write numpy arrays and records from file like objects 

4888 

4889 Only 'rb' and 'wb' modes are supported. Concurrently reading and writing 

4890 of the same stream is untested. 

4891 

4892 When initialized from another file handle, do not use it unless this 

4893 FileHandle is closed. 

4894 

4895 Attributes 

4896 ---------- 

4897 name : str 

4898 Name of the file. 

4899 path : str 

4900 Absolute path to file. 

4901 size : int 

4902 Size of file in bytes. 

4903 is_file : bool 

4904 If True, file has a filno and can be memory-mapped. 

4905 

4906 All attributes are read-only. 

4907 

4908 """ 

4909 

4910 __slots__ = ( 

4911 "_fh", 

4912 "_file", 

4913 "_mode", 

4914 "_name", 

4915 "_dir", 

4916 "_lock", 

4917 "_offset", 

4918 "_size", 

4919 "_close", 

4920 "is_file", 

4921 ) 

4922 

4923 def __init__(self, file, mode="rb", name=None, offset=None, size=None): 

4924 """Initialize file handle from file name or another file handle. 

4925 

4926 Parameters 

4927 ---------- 

4928 file : str, pathlib.Path, binary stream, or FileHandle 

4929 File name or seekable binary stream, such as an open file 

4930 or BytesIO. 

4931 mode : str 

4932 File open mode in case 'file' is a file name. Must be 'rb' or 'wb'. 

4933 name : str 

4934 Optional name of file in case 'file' is a binary stream. 

4935 offset : int 

4936 Optional start position of embedded file. By default, this is 

4937 the current file position. 

4938 size : int 

4939 Optional size of embedded file. By default, this is the number 

4940 of bytes from the 'offset' to the end of the file. 

4941 

4942 """ 

4943 self._file = file 

4944 self._fh = None 

4945 self._mode = mode 

4946 self._name = name 

4947 self._dir = "" 

4948 self._offset = offset 

4949 self._size = size 

4950 self._close = True 

4951 self.is_file = False 

4952 self._lock = NullContext() 

4953 self.open() 

4954 

4955 def open(self): 

4956 """Open or re-open file.""" 

4957 if self._fh: 

4958 return # file is open 

4959 

4960 if isinstance(self._file, pathlib.Path): 

4961 self._file = str(self._file) 

4962 if isinstance(self._file, basestring): 

4963 # file name 

4964 self._file = os.path.realpath(self._file) 

4965 self._dir, self._name = os.path.split(self._file) 

4966 self._fh = open(self._file, self._mode) 

4967 self._close = True 

4968 if self._offset is None: 

4969 self._offset = 0 

4970 elif isinstance(self._file, FileHandle): 

4971 # FileHandle 

4972 self._fh = self._file._fh 

4973 if self._offset is None: 

4974 self._offset = 0 

4975 self._offset += self._file._offset 

4976 self._close = False 

4977 if not self._name: 

4978 if self._offset: 

4979 name, ext = os.path.splitext(self._file._name) 

4980 self._name = "%s@%i%s" % (name, self._offset, ext) 

4981 else: 

4982 self._name = self._file._name 

4983 if self._mode and self._mode != self._file._mode: 

4984 raise ValueError("FileHandle has wrong mode") 

4985 self._mode = self._file._mode 

4986 self._dir = self._file._dir 

4987 elif hasattr(self._file, "seek"): 

4988 # binary stream: open file, BytesIO 

4989 try: 

4990 self._file.tell() 

4991 except Exception: 

4992 raise ValueError("binary stream is not seekable") 

4993 self._fh = self._file 

4994 if self._offset is None: 

4995 self._offset = self._file.tell() 

4996 self._close = False 

4997 if not self._name: 

4998 try: 

4999 self._dir, self._name = os.path.split(self._fh.name) 

5000 except AttributeError: 

5001 self._name = "Unnamed binary stream" 

5002 try: 

5003 self._mode = self._fh.mode 

5004 except AttributeError: 

5005 pass 

5006 else: 

5007 raise ValueError( 

5008 "The first parameter must be a file name, " 

5009 "seekable binary stream, or FileHandle" 

5010 ) 

5011 

5012 if self._offset: 

5013 self._fh.seek(self._offset) 

5014 

5015 if self._size is None: 

5016 pos = self._fh.tell() 

5017 self._fh.seek(self._offset, 2) 

5018 self._size = self._fh.tell() 

5019 self._fh.seek(pos) 

5020 

5021 try: 

5022 self._fh.fileno() 

5023 self.is_file = True 

5024 except Exception: 

5025 self.is_file = False 

5026 

5027 def read(self, size=-1): 

5028 """Read 'size' bytes from file, or until EOF is reached.""" 

5029 if size < 0 and self._offset: 

5030 size = self._size 

5031 return self._fh.read(size) 

5032 

5033 def write(self, bytestring): 

5034 """Write bytestring to file.""" 

5035 return self._fh.write(bytestring) 

5036 

5037 def flush(self): 

5038 """Flush write buffers if applicable.""" 

5039 return self._fh.flush() 

5040 

5041 def memmap_array(self, dtype, shape, offset=0, mode="r", order="C"): 

5042 """Return numpy.memmap of data stored in file.""" 

5043 if not self.is_file: 

5044 raise ValueError("Cannot memory-map file without fileno") 

5045 return numpy.memmap( 

5046 self._fh, 

5047 dtype=dtype, 

5048 mode=mode, 

5049 offset=self._offset + offset, 

5050 shape=shape, 

5051 order=order, 

5052 ) 

5053 

5054 def read_array( 

5055 self, dtype, count=-1, sep="", chunksize=2**25, out=None, native=False 

5056 ): 

5057 """Return numpy array from file. 

5058 

5059 Work around numpy issue #2230, "numpy.fromfile does not accept 

5060 StringIO object" https://github.com/numpy/numpy/issues/2230. 

5061 

5062 """ 

5063 fh = self._fh 

5064 dtype = numpy.dtype(dtype) 

5065 size = self._size if count < 0 else count * dtype.itemsize 

5066 

5067 if out is None: 

5068 try: 

5069 result = numpy.fromfile(fh, dtype, count, sep) 

5070 except IOError: 

5071 # ByteIO 

5072 data = fh.read(size) 

5073 result = numpy.frombuffer(data, dtype, count).copy() 

5074 if native and not result.dtype.isnative: 

5075 # swap byte order and dtype without copy 

5076 result.byteswap(True) 

5077 result = result.newbyteorder() 

5078 return result 

5079 

5080 # Read data from file in chunks and copy to output array 

5081 shape = out.shape 

5082 size = min(out.nbytes, size) 

5083 out = out.reshape(-1) 

5084 index = 0 

5085 while size > 0: 

5086 data = fh.read(min(chunksize, size)) 

5087 datasize = len(data) 

5088 if datasize == 0: 

5089 break 

5090 size -= datasize 

5091 data = numpy.frombuffer(data, dtype) 

5092 out[index : index + data.size] = data 

5093 index += data.size 

5094 

5095 if hasattr(out, "flush"): 

5096 out.flush() 

5097 return out.reshape(shape) 

5098 

5099 def read_record(self, dtype, shape=1, byteorder=None): 

5100 """Return numpy record from file.""" 

5101 rec = numpy.rec 

5102 try: 

5103 record = rec.fromfile(self._fh, dtype, shape, byteorder=byteorder) 

5104 except Exception: 

5105 dtype = numpy.dtype(dtype) 

5106 if shape is None: 

5107 shape = self._size // dtype.itemsize 

5108 size = product(sequence(shape)) * dtype.itemsize 

5109 data = self._fh.read(size) 

5110 record = rec.fromstring(data, dtype, shape, byteorder=byteorder) 

5111 return record[0] if shape == 1 else record 

5112 

5113 def write_empty(self, size): 

5114 """Append size bytes to file. Position must be at end of file.""" 

5115 if size < 1: 

5116 return 

5117 self._fh.seek(size - 1, 1) 

5118 self._fh.write(b"\x00") 

5119 

5120 def write_array(self, data): 

5121 """Write numpy array to binary file.""" 

5122 try: 

5123 data.tofile(self._fh) 

5124 except Exception: 

5125 # BytesIO 

5126 self._fh.write(data.tostring()) 

5127 

5128 def tell(self): 

5129 """Return file's current position.""" 

5130 return self._fh.tell() - self._offset 

5131 

5132 def seek(self, offset, whence=0): 

5133 """Set file's current position.""" 

5134 if self._offset: 

5135 if whence == 0: 

5136 self._fh.seek(self._offset + offset, whence) 

5137 return 

5138 elif whence == 2 and self._size > 0: 

5139 self._fh.seek(self._offset + self._size + offset, 0) 

5140 return 

5141 self._fh.seek(offset, whence) 

5142 

5143 def close(self): 

5144 """Close file.""" 

5145 if self._close and self._fh: 

5146 self._fh.close() 

5147 self._fh = None 

5148 

5149 def __enter__(self): 

5150 return self 

5151 

5152 def __exit__(self, exc_type, exc_value, traceback): 

5153 self.close() 

5154 

5155 def __getattr__(self, name): 

5156 """Return attribute from underlying file object.""" 

5157 if self._offset: 

5158 warnings.warn("FileHandle: '%s' not implemented for embedded files" % name) 

5159 return getattr(self._fh, name) 

5160 

5161 @property 

5162 def name(self): 

5163 return self._name 

5164 

5165 @property 

5166 def dirname(self): 

5167 return self._dir 

5168 

5169 @property 

5170 def path(self): 

5171 return os.path.join(self._dir, self._name) 

5172 

5173 @property 

5174 def size(self): 

5175 return self._size 

5176 

5177 @property 

5178 def closed(self): 

5179 return self._fh is None 

5180 

5181 @property 

5182 def lock(self): 

5183 return self._lock 

5184 

5185 @lock.setter 

5186 def lock(self, value): 

5187 self._lock = threading.RLock() if value else NullContext() 

5188 

5189 

5190class NullContext(object): 

5191 """Null context manager. 

5192 

5193 >>> with NullContext(): 

5194 ... pass 

5195 

5196 """ 

5197 

5198 def __enter__(self): 

5199 return self 

5200 

5201 def __exit__(self, exc_type, exc_value, traceback): 

5202 pass 

5203 

5204 

5205class OpenFileCache(object): 

5206 """Keep files open.""" 

5207 

5208 __slots__ = ("files", "past", "lock", "size") 

5209 

5210 def __init__(self, size, lock=None): 

5211 """Initialize open file cache.""" 

5212 self.past = [] # FIFO of opened files 

5213 self.files = {} # refcounts of opened files 

5214 self.lock = NullContext() if lock is None else lock 

5215 self.size = int(size) 

5216 

5217 def open(self, filehandle): 

5218 """Re-open file if necessary.""" 

5219 with self.lock: 

5220 if filehandle in self.files: 

5221 self.files[filehandle] += 1 

5222 elif filehandle.closed: 

5223 filehandle.open() 

5224 self.files[filehandle] = 1 

5225 self.past.append(filehandle) 

5226 

5227 def close(self, filehandle): 

5228 """Close opened file if no longer used.""" 

5229 with self.lock: 

5230 if filehandle in self.files: 

5231 self.files[filehandle] -= 1 

5232 # trim the file cache 

5233 index = 0 

5234 size = len(self.past) 

5235 while size > self.size and index < size: 

5236 filehandle = self.past[index] 

5237 if self.files[filehandle] == 0: 

5238 filehandle.close() 

5239 del self.files[filehandle] 

5240 del self.past[index] 

5241 size -= 1 

5242 else: 

5243 index += 1 

5244 

5245 def clear(self): 

5246 """Close all opened files if not in use.""" 

5247 with self.lock: 

5248 for filehandle, refcount in list(self.files.items()): 

5249 if refcount == 0: 

5250 filehandle.close() 

5251 del self.files[filehandle] 

5252 del self.past[self.past.index(filehandle)] 

5253 

5254 

5255class LazyConst(object): 

5256 """Class whose attributes are computed on first access from its methods.""" 

5257 

5258 def __init__(self, cls): 

5259 self._cls = cls 

5260 self.__doc__ = getattr(cls, "__doc__") 

5261 

5262 def __getattr__(self, name): 

5263 func = getattr(self._cls, name) 

5264 if not callable(func): 

5265 return func 

5266 try: 

5267 value = func() 

5268 except TypeError: 

5269 # Python 2 unbound method 

5270 value = func.__func__() 

5271 setattr(self, name, value) 

5272 return value 

5273 

5274 

5275@LazyConst 

5276class TIFF(object): 

5277 """Namespace for module constants.""" 

5278 

5279 def TAGS(): 

5280 # TIFF tag codes and names from TIFF6, TIFF/EP, EXIF, and other specs 

5281 return { 

5282 11: "ProcessingSoftware", 

5283 254: "NewSubfileType", 

5284 255: "SubfileType", 

5285 256: "ImageWidth", 

5286 257: "ImageLength", 

5287 258: "BitsPerSample", 

5288 259: "Compression", 

5289 262: "PhotometricInterpretation", 

5290 263: "Thresholding", 

5291 264: "CellWidth", 

5292 265: "CellLength", 

5293 266: "FillOrder", 

5294 269: "DocumentName", 

5295 270: "ImageDescription", 

5296 271: "Make", 

5297 272: "Model", 

5298 273: "StripOffsets", 

5299 274: "Orientation", 

5300 277: "SamplesPerPixel", 

5301 278: "RowsPerStrip", 

5302 279: "StripByteCounts", 

5303 280: "MinSampleValue", 

5304 281: "MaxSampleValue", 

5305 282: "XResolution", 

5306 283: "YResolution", 

5307 284: "PlanarConfiguration", 

5308 285: "PageName", 

5309 286: "XPosition", 

5310 287: "YPosition", 

5311 288: "FreeOffsets", 

5312 289: "FreeByteCounts", 

5313 290: "GrayResponseUnit", 

5314 291: "GrayResponseCurve", 

5315 292: "T4Options", 

5316 293: "T6Options", 

5317 296: "ResolutionUnit", 

5318 297: "PageNumber", 

5319 300: "ColorResponseUnit", 

5320 301: "TransferFunction", 

5321 305: "Software", 

5322 306: "DateTime", 

5323 315: "Artist", 

5324 316: "HostComputer", 

5325 317: "Predictor", 

5326 318: "WhitePoint", 

5327 319: "PrimaryChromaticities", 

5328 320: "ColorMap", 

5329 321: "HalftoneHints", 

5330 322: "TileWidth", 

5331 323: "TileLength", 

5332 324: "TileOffsets", 

5333 325: "TileByteCounts", 

5334 326: "BadFaxLines", 

5335 327: "CleanFaxData", 

5336 328: "ConsecutiveBadFaxLines", 

5337 330: "SubIFDs", 

5338 332: "InkSet", 

5339 333: "InkNames", 

5340 334: "NumberOfInks", 

5341 336: "DotRange", 

5342 337: "TargetPrinter", 

5343 338: "ExtraSamples", 

5344 339: "SampleFormat", 

5345 340: "SMinSampleValue", 

5346 341: "SMaxSampleValue", 

5347 342: "TransferRange", 

5348 343: "ClipPath", 

5349 344: "XClipPathUnits", 

5350 345: "YClipPathUnits", 

5351 346: "Indexed", 

5352 347: "JPEGTables", 

5353 351: "OPIProxy", 

5354 400: "GlobalParametersIFD", 

5355 401: "ProfileType", 

5356 402: "FaxProfile", 

5357 403: "CodingMethods", 

5358 404: "VersionYear", 

5359 405: "ModeNumber", 

5360 433: "Decode", 

5361 434: "DefaultImageColor", 

5362 435: "T82Options", 

5363 437: "JPEGTables_", # 347 

5364 512: "JPEGProc", 

5365 513: "JPEGInterchangeFormat", 

5366 514: "JPEGInterchangeFormatLength", 

5367 515: "JPEGRestartInterval", 

5368 517: "JPEGLosslessPredictors", 

5369 518: "JPEGPointTransforms", 

5370 519: "JPEGQTables", 

5371 520: "JPEGDCTables", 

5372 521: "JPEGACTables", 

5373 529: "YCbCrCoefficients", 

5374 530: "YCbCrSubSampling", 

5375 531: "YCbCrPositioning", 

5376 532: "ReferenceBlackWhite", 

5377 559: "StripRowCounts", 

5378 700: "XMP", # XMLPacket 

5379 769: "GDIGamma", # GDI+ 

5380 770: "ICCProfileDescriptor", # GDI+ 

5381 771: "SRGBRenderingIntent", # GDI+ 

5382 800: "ImageTitle", # GDI+ 

5383 999: "USPTO_Miscellaneous", 

5384 4864: "AndorId", # TODO: Andor Technology 4864 - 5030 

5385 4869: "AndorTemperature", 

5386 4876: "AndorExposureTime", 

5387 4878: "AndorKineticCycleTime", 

5388 4879: "AndorAccumulations", 

5389 4881: "AndorAcquisitionCycleTime", 

5390 4882: "AndorReadoutTime", 

5391 4884: "AndorPhotonCounting", 

5392 4885: "AndorEmDacLevel", 

5393 4890: "AndorFrames", 

5394 4896: "AndorHorizontalFlip", 

5395 4897: "AndorVerticalFlip", 

5396 4898: "AndorClockwise", 

5397 4899: "AndorCounterClockwise", 

5398 4904: "AndorVerticalClockVoltage", 

5399 4905: "AndorVerticalShiftSpeed", 

5400 4907: "AndorPreAmpSetting", 

5401 4908: "AndorCameraSerial", 

5402 4911: "AndorActualTemperature", 

5403 4912: "AndorBaselineClamp", 

5404 4913: "AndorPrescans", 

5405 4914: "AndorModel", 

5406 4915: "AndorChipSizeX", 

5407 4916: "AndorChipSizeY", 

5408 4944: "AndorBaselineOffset", 

5409 4966: "AndorSoftwareVersion", 

5410 18246: "Rating", 

5411 18247: "XP_DIP_XML", 

5412 18248: "StitchInfo", 

5413 18249: "RatingPercent", 

5414 20481: "ResolutionXUnit", # GDI+ 

5415 20482: "ResolutionYUnit", # GDI+ 

5416 20483: "ResolutionXLengthUnit", # GDI+ 

5417 20484: "ResolutionYLengthUnit", # GDI+ 

5418 20485: "PrintFlags", # GDI+ 

5419 20486: "PrintFlagsVersion", # GDI+ 

5420 20487: "PrintFlagsCrop", # GDI+ 

5421 20488: "PrintFlagsBleedWidth", # GDI+ 

5422 20489: "PrintFlagsBleedWidthScale", # GDI+ 

5423 20490: "HalftoneLPI", # GDI+ 

5424 20491: "HalftoneLPIUnit", # GDI+ 

5425 20492: "HalftoneDegree", # GDI+ 

5426 20493: "HalftoneShape", # GDI+ 

5427 20494: "HalftoneMisc", # GDI+ 

5428 20495: "HalftoneScreen", # GDI+ 

5429 20496: "JPEGQuality", # GDI+ 

5430 20497: "GridSize", # GDI+ 

5431 20498: "ThumbnailFormat", # GDI+ 

5432 20499: "ThumbnailWidth", # GDI+ 

5433 20500: "ThumbnailHeight", # GDI+ 

5434 20501: "ThumbnailColorDepth", # GDI+ 

5435 20502: "ThumbnailPlanes", # GDI+ 

5436 20503: "ThumbnailRawBytes", # GDI+ 

5437 20504: "ThumbnailSize", # GDI+ 

5438 20505: "ThumbnailCompressedSize", # GDI+ 

5439 20506: "ColorTransferFunction", # GDI+ 

5440 20507: "ThumbnailData", 

5441 20512: "ThumbnailImageWidth", # GDI+ 

5442 20513: "ThumbnailImageHeight", # GDI+ 

5443 20514: "ThumbnailBitsPerSample", # GDI+ 

5444 20515: "ThumbnailCompression", 

5445 20516: "ThumbnailPhotometricInterp", # GDI+ 

5446 20517: "ThumbnailImageDescription", # GDI+ 

5447 20518: "ThumbnailEquipMake", # GDI+ 

5448 20519: "ThumbnailEquipModel", # GDI+ 

5449 20520: "ThumbnailStripOffsets", # GDI+ 

5450 20521: "ThumbnailOrientation", # GDI+ 

5451 20522: "ThumbnailSamplesPerPixel", # GDI+ 

5452 20523: "ThumbnailRowsPerStrip", # GDI+ 

5453 20524: "ThumbnailStripBytesCount", # GDI+ 

5454 20525: "ThumbnailResolutionX", 

5455 20526: "ThumbnailResolutionY", 

5456 20527: "ThumbnailPlanarConfig", # GDI+ 

5457 20528: "ThumbnailResolutionUnit", 

5458 20529: "ThumbnailTransferFunction", 

5459 20530: "ThumbnailSoftwareUsed", # GDI+ 

5460 20531: "ThumbnailDateTime", # GDI+ 

5461 20532: "ThumbnailArtist", # GDI+ 

5462 20533: "ThumbnailWhitePoint", # GDI+ 

5463 20534: "ThumbnailPrimaryChromaticities", # GDI+ 

5464 20535: "ThumbnailYCbCrCoefficients", # GDI+ 

5465 20536: "ThumbnailYCbCrSubsampling", # GDI+ 

5466 20537: "ThumbnailYCbCrPositioning", 

5467 20538: "ThumbnailRefBlackWhite", # GDI+ 

5468 20539: "ThumbnailCopyRight", # GDI+ 

5469 20545: "InteroperabilityIndex", 

5470 20546: "InteroperabilityVersion", 

5471 20624: "LuminanceTable", 

5472 20625: "ChrominanceTable", 

5473 20736: "FrameDelay", # GDI+ 

5474 20737: "LoopCount", # GDI+ 

5475 20738: "GlobalPalette", # GDI+ 

5476 20739: "IndexBackground", # GDI+ 

5477 20740: "IndexTransparent", # GDI+ 

5478 20752: "PixelUnit", # GDI+ 

5479 20753: "PixelPerUnitX", # GDI+ 

5480 20754: "PixelPerUnitY", # GDI+ 

5481 20755: "PaletteHistogram", # GDI+ 

5482 28672: "SonyRawFileType", # Sony ARW 

5483 28722: "VignettingCorrParams", # Sony ARW 

5484 28725: "ChromaticAberrationCorrParams", # Sony ARW 

5485 28727: "DistortionCorrParams", # Sony ARW 

5486 # Private tags >= 32768 

5487 32781: "ImageID", 

5488 32931: "WangTag1", 

5489 32932: "WangAnnotation", 

5490 32933: "WangTag3", 

5491 32934: "WangTag4", 

5492 32953: "ImageReferencePoints", 

5493 32954: "RegionXformTackPoint", 

5494 32955: "WarpQuadrilateral", 

5495 32956: "AffineTransformMat", 

5496 32995: "Matteing", 

5497 32996: "DataType", 

5498 32997: "ImageDepth", 

5499 32998: "TileDepth", 

5500 33300: "ImageFullWidth", 

5501 33301: "ImageFullLength", 

5502 33302: "TextureFormat", 

5503 33303: "TextureWrapModes", 

5504 33304: "FieldOfViewCotangent", 

5505 33305: "MatrixWorldToScreen", 

5506 33306: "MatrixWorldToCamera", 

5507 33405: "Model2", 

5508 33421: "CFARepeatPatternDim", 

5509 33422: "CFAPattern", 

5510 33423: "BatteryLevel", 

5511 33424: "KodakIFD", 

5512 33434: "ExposureTime", 

5513 33437: "FNumber", 

5514 33432: "Copyright", 

5515 33445: "MDFileTag", 

5516 33446: "MDScalePixel", 

5517 33447: "MDColorTable", 

5518 33448: "MDLabName", 

5519 33449: "MDSampleInfo", 

5520 33450: "MDPrepDate", 

5521 33451: "MDPrepTime", 

5522 33452: "MDFileUnits", 

5523 33550: "ModelPixelScaleTag", 

5524 33589: "AdventScale", 

5525 33590: "AdventRevision", 

5526 33628: "UIC1tag", # Metamorph Universal Imaging Corp STK 

5527 33629: "UIC2tag", 

5528 33630: "UIC3tag", 

5529 33631: "UIC4tag", 

5530 33723: "IPTCNAA", 

5531 33858: "ExtendedTagsOffset", # DEFF points IFD with private tags 

5532 33918: "IntergraphPacketData", # INGRPacketDataTag 

5533 33919: "IntergraphFlagRegisters", # INGRFlagRegisters 

5534 33920: "IntergraphMatrixTag", # IrasBTransformationMatrix 

5535 33921: "INGRReserved", 

5536 33922: "ModelTiepointTag", 

5537 33923: "LeicaMagic", 

5538 34016: "Site", 

5539 34017: "ColorSequence", 

5540 34018: "IT8Header", 

5541 34019: "RasterPadding", 

5542 34020: "BitsPerRunLength", 

5543 34021: "BitsPerExtendedRunLength", 

5544 34022: "ColorTable", 

5545 34023: "ImageColorIndicator", 

5546 34024: "BackgroundColorIndicator", 

5547 34025: "ImageColorValue", 

5548 34026: "BackgroundColorValue", 

5549 34027: "PixelIntensityRange", 

5550 34028: "TransparencyIndicator", 

5551 34029: "ColorCharacterization", 

5552 34030: "HCUsage", 

5553 34031: "TrapIndicator", 

5554 34032: "CMYKEquivalent", 

5555 34118: "CZ_SEM", # Zeiss SEM 

5556 34152: "AFCP_IPTC", 

5557 34232: "PixelMagicJBIGOptions", 

5558 34263: "JPLCartoIFD", 

5559 34122: "IPLAB", # number of images 

5560 34264: "ModelTransformationTag", 

5561 34306: "WB_GRGBLevels", # Leaf MOS 

5562 34310: "LeafData", 

5563 34361: "MM_Header", 

5564 34362: "MM_Stamp", 

5565 34363: "MM_Unknown", 

5566 34377: "ImageResources", # Photoshop 

5567 34386: "MM_UserBlock", 

5568 34412: "CZ_LSMINFO", 

5569 34665: "ExifTag", 

5570 34675: "InterColorProfile", # ICCProfile 

5571 34680: "FEI_SFEG", # 

5572 34682: "FEI_HELIOS", # 

5573 34683: "FEI_TITAN", # 

5574 34687: "FXExtensions", 

5575 34688: "MultiProfiles", 

5576 34689: "SharedData", 

5577 34690: "T88Options", 

5578 34710: "MarCCD", # offset to MarCCD header 

5579 34732: "ImageLayer", 

5580 34735: "GeoKeyDirectoryTag", 

5581 34736: "GeoDoubleParamsTag", 

5582 34737: "GeoAsciiParamsTag", 

5583 34750: "JBIGOptions", 

5584 34821: "PIXTIFF", # ? Pixel Translations Inc 

5585 34850: "ExposureProgram", 

5586 34852: "SpectralSensitivity", 

5587 34853: "GPSTag", # GPSIFD 

5588 34855: "ISOSpeedRatings", 

5589 34856: "OECF", 

5590 34857: "Interlace", 

5591 34858: "TimeZoneOffset", 

5592 34859: "SelfTimerMode", 

5593 34864: "SensitivityType", 

5594 34865: "StandardOutputSensitivity", 

5595 34866: "RecommendedExposureIndex", 

5596 34867: "ISOSpeed", 

5597 34868: "ISOSpeedLatitudeyyy", 

5598 34869: "ISOSpeedLatitudezzz", 

5599 34908: "HylaFAXFaxRecvParams", 

5600 34909: "HylaFAXFaxSubAddress", 

5601 34910: "HylaFAXFaxRecvTime", 

5602 34911: "FaxDcs", 

5603 34929: "FedexEDR", 

5604 34954: "LeafSubIFD", 

5605 34959: "Aphelion1", 

5606 34960: "Aphelion2", 

5607 34961: "AphelionInternal", # ADCIS 

5608 36864: "ExifVersion", 

5609 36867: "DateTimeOriginal", 

5610 36868: "DateTimeDigitized", 

5611 36873: "GooglePlusUploadCode", 

5612 36880: "OffsetTime", 

5613 36881: "OffsetTimeOriginal", 

5614 36882: "OffsetTimeDigitized", 

5615 # TODO: Pilatus/CHESS/TV6 36864..37120 conflicting with Exif tags 

5616 # 36864: 'TVX ?', 

5617 # 36865: 'TVX_NumExposure', 

5618 # 36866: 'TVX_NumBackground', 

5619 # 36867: 'TVX_ExposureTime', 

5620 # 36868: 'TVX_BackgroundTime', 

5621 # 36870: 'TVX ?', 

5622 # 36873: 'TVX_SubBpp', 

5623 # 36874: 'TVX_SubWide', 

5624 # 36875: 'TVX_SubHigh', 

5625 # 36876: 'TVX_BlackLevel', 

5626 # 36877: 'TVX_DarkCurrent', 

5627 # 36878: 'TVX_ReadNoise', 

5628 # 36879: 'TVX_DarkCurrentNoise', 

5629 # 36880: 'TVX_BeamMonitor', 

5630 # 37120: 'TVX_UserVariables', # A/D values 

5631 37121: "ComponentsConfiguration", 

5632 37122: "CompressedBitsPerPixel", 

5633 37377: "ShutterSpeedValue", 

5634 37378: "ApertureValue", 

5635 37379: "BrightnessValue", 

5636 37380: "ExposureBiasValue", 

5637 37381: "MaxApertureValue", 

5638 37382: "SubjectDistance", 

5639 37383: "MeteringMode", 

5640 37384: "LightSource", 

5641 37385: "Flash", 

5642 37386: "FocalLength", 

5643 37387: "FlashEnergy_", # 37387 

5644 37388: "SpatialFrequencyResponse_", # 37388 

5645 37389: "Noise", 

5646 37390: "FocalPlaneXResolution", 

5647 37391: "FocalPlaneYResolution", 

5648 37392: "FocalPlaneResolutionUnit", 

5649 37393: "ImageNumber", 

5650 37394: "SecurityClassification", 

5651 37395: "ImageHistory", 

5652 37396: "SubjectLocation", 

5653 37397: "ExposureIndex", 

5654 37398: "TIFFEPStandardID", 

5655 37399: "SensingMethod", 

5656 37434: "CIP3DataFile", 

5657 37435: "CIP3Sheet", 

5658 37436: "CIP3Side", 

5659 37439: "StoNits", 

5660 37500: "MakerNote", 

5661 37510: "UserComment", 

5662 37520: "SubsecTime", 

5663 37521: "SubsecTimeOriginal", 

5664 37522: "SubsecTimeDigitized", 

5665 37679: "MODIText", # Microsoft Office Document Imaging 

5666 37680: "MODIOLEPropertySetStorage", 

5667 37681: "MODIPositioning", 

5668 37706: "TVIPS", # offset to TemData structure 

5669 37707: "TVIPS1", 

5670 37708: "TVIPS2", # same TemData structure as undefined 

5671 37724: "ImageSourceData", # Photoshop 

5672 37888: "Temperature", 

5673 37889: "Humidity", 

5674 37890: "Pressure", 

5675 37891: "WaterDepth", 

5676 37892: "Acceleration", 

5677 37893: "CameraElevationAngle", 

5678 40001: "MC_IpWinScal", # Media Cybernetics 

5679 40100: "MC_IdOld", 

5680 40965: "InteroperabilityTag", # InteropOffset 

5681 40091: "XPTitle", 

5682 40092: "XPComment", 

5683 40093: "XPAuthor", 

5684 40094: "XPKeywords", 

5685 40095: "XPSubject", 

5686 40960: "FlashpixVersion", 

5687 40961: "ColorSpace", 

5688 40962: "PixelXDimension", 

5689 40963: "PixelYDimension", 

5690 40964: "RelatedSoundFile", 

5691 40976: "SamsungRawPointersOffset", 

5692 40977: "SamsungRawPointersLength", 

5693 41217: "SamsungRawByteOrder", 

5694 41218: "SamsungRawUnknown", 

5695 41483: "FlashEnergy", 

5696 41484: "SpatialFrequencyResponse", 

5697 41485: "Noise_", # 37389 

5698 41486: "FocalPlaneXResolution_", # 37390 

5699 41487: "FocalPlaneYResolution_", # 37391 

5700 41488: "FocalPlaneResolutionUnit_", # 37392 

5701 41489: "ImageNumber_", # 37393 

5702 41490: "SecurityClassification_", # 37394 

5703 41491: "ImageHistory_", # 37395 

5704 41492: "SubjectLocation_", # 37395 

5705 41493: "ExposureIndex_ ", # 37397 

5706 41494: "TIFF-EPStandardID", 

5707 41495: "SensingMethod_", # 37399 

5708 41728: "FileSource", 

5709 41729: "SceneType", 

5710 41730: "CFAPattern_", # 33422 

5711 41985: "CustomRendered", 

5712 41986: "ExposureMode", 

5713 41987: "WhiteBalance", 

5714 41988: "DigitalZoomRatio", 

5715 41989: "FocalLengthIn35mmFilm", 

5716 41990: "SceneCaptureType", 

5717 41991: "GainControl", 

5718 41992: "Contrast", 

5719 41993: "Saturation", 

5720 41994: "Sharpness", 

5721 41995: "DeviceSettingDescription", 

5722 41996: "SubjectDistanceRange", 

5723 42016: "ImageUniqueID", 

5724 42032: "CameraOwnerName", 

5725 42033: "BodySerialNumber", 

5726 42034: "LensSpecification", 

5727 42035: "LensMake", 

5728 42036: "LensModel", 

5729 42037: "LensSerialNumber", 

5730 42112: "GDAL_METADATA", 

5731 42113: "GDAL_NODATA", 

5732 42240: "Gamma", 

5733 43314: "NIHImageHeader", 

5734 44992: "ExpandSoftware", 

5735 44993: "ExpandLens", 

5736 44994: "ExpandFilm", 

5737 44995: "ExpandFilterLens", 

5738 44996: "ExpandScanner", 

5739 44997: "ExpandFlashLamp", 

5740 48129: "PixelFormat", # HDP and WDP 

5741 48130: "Transformation", 

5742 48131: "Uncompressed", 

5743 48132: "ImageType", 

5744 48256: "ImageWidth_", # 256 

5745 48257: "ImageHeight_", 

5746 48258: "WidthResolution", 

5747 48259: "HeightResolution", 

5748 48320: "ImageOffset", 

5749 48321: "ImageByteCount", 

5750 48322: "AlphaOffset", 

5751 48323: "AlphaByteCount", 

5752 48324: "ImageDataDiscard", 

5753 48325: "AlphaDataDiscard", 

5754 50215: "OceScanjobDescription", 

5755 50216: "OceApplicationSelector", 

5756 50217: "OceIdentificationNumber", 

5757 50218: "OceImageLogicCharacteristics", 

5758 50255: "Annotations", 

5759 50288: "MC_Id", # Media Cybernetics 

5760 50289: "MC_XYPosition", 

5761 50290: "MC_ZPosition", 

5762 50291: "MC_XYCalibration", 

5763 50292: "MC_LensCharacteristics", 

5764 50293: "MC_ChannelName", 

5765 50294: "MC_ExcitationWavelength", 

5766 50295: "MC_TimeStamp", 

5767 50296: "MC_FrameProperties", 

5768 50341: "PrintImageMatching", 

5769 50495: "PCO_RAW", # TODO: PCO CamWare 

5770 50547: "OriginalFileName", 

5771 50560: "USPTO_OriginalContentType", # US Patent Office 

5772 50561: "USPTO_RotationCode", 

5773 50656: "CR2CFAPattern", 

5774 50706: "DNGVersion", # DNG 50706 .. 51112 

5775 50707: "DNGBackwardVersion", 

5776 50708: "UniqueCameraModel", 

5777 50709: "LocalizedCameraModel", 

5778 50710: "CFAPlaneColor", 

5779 50711: "CFALayout", 

5780 50712: "LinearizationTable", 

5781 50713: "BlackLevelRepeatDim", 

5782 50714: "BlackLevel", 

5783 50715: "BlackLevelDeltaH", 

5784 50716: "BlackLevelDeltaV", 

5785 50717: "WhiteLevel", 

5786 50718: "DefaultScale", 

5787 50719: "DefaultCropOrigin", 

5788 50720: "DefaultCropSize", 

5789 50721: "ColorMatrix1", 

5790 50722: "ColorMatrix2", 

5791 50723: "CameraCalibration1", 

5792 50724: "CameraCalibration2", 

5793 50725: "ReductionMatrix1", 

5794 50726: "ReductionMatrix2", 

5795 50727: "AnalogBalance", 

5796 50728: "AsShotNeutral", 

5797 50729: "AsShotWhiteXY", 

5798 50730: "BaselineExposure", 

5799 50731: "BaselineNoise", 

5800 50732: "BaselineSharpness", 

5801 50733: "BayerGreenSplit", 

5802 50734: "LinearResponseLimit", 

5803 50735: "CameraSerialNumber", 

5804 50736: "LensInfo", 

5805 50737: "ChromaBlurRadius", 

5806 50738: "AntiAliasStrength", 

5807 50739: "ShadowScale", 

5808 50740: "DNGPrivateData", 

5809 50741: "MakerNoteSafety", 

5810 50752: "RawImageSegmentation", 

5811 50778: "CalibrationIlluminant1", 

5812 50779: "CalibrationIlluminant2", 

5813 50780: "BestQualityScale", 

5814 50781: "RawDataUniqueID", 

5815 50784: "AliasLayerMetadata", 

5816 50827: "OriginalRawFileName", 

5817 50828: "OriginalRawFileData", 

5818 50829: "ActiveArea", 

5819 50830: "MaskedAreas", 

5820 50831: "AsShotICCProfile", 

5821 50832: "AsShotPreProfileMatrix", 

5822 50833: "CurrentICCProfile", 

5823 50834: "CurrentPreProfileMatrix", 

5824 50838: "IJMetadataByteCounts", 

5825 50839: "IJMetadata", 

5826 50844: "RPCCoefficientTag", 

5827 50879: "ColorimetricReference", 

5828 50885: "SRawType", 

5829 50898: "PanasonicTitle", 

5830 50899: "PanasonicTitle2", 

5831 50931: "CameraCalibrationSignature", 

5832 50932: "ProfileCalibrationSignature", 

5833 50933: "ProfileIFD", 

5834 50934: "AsShotProfileName", 

5835 50935: "NoiseReductionApplied", 

5836 50936: "ProfileName", 

5837 50937: "ProfileHueSatMapDims", 

5838 50938: "ProfileHueSatMapData1", 

5839 50939: "ProfileHueSatMapData2", 

5840 50940: "ProfileToneCurve", 

5841 50941: "ProfileEmbedPolicy", 

5842 50942: "ProfileCopyright", 

5843 50964: "ForwardMatrix1", 

5844 50965: "ForwardMatrix2", 

5845 50966: "PreviewApplicationName", 

5846 50967: "PreviewApplicationVersion", 

5847 50968: "PreviewSettingsName", 

5848 50969: "PreviewSettingsDigest", 

5849 50970: "PreviewColorSpace", 

5850 50971: "PreviewDateTime", 

5851 50972: "RawImageDigest", 

5852 50973: "OriginalRawFileDigest", 

5853 50974: "SubTileBlockSize", 

5854 50975: "RowInterleaveFactor", 

5855 50981: "ProfileLookTableDims", 

5856 50982: "ProfileLookTableData", 

5857 51008: "OpcodeList1", 

5858 51009: "OpcodeList2", 

5859 51022: "OpcodeList3", 

5860 51023: "FibicsXML", # 

5861 51041: "NoiseProfile", 

5862 51043: "TimeCodes", 

5863 51044: "FrameRate", 

5864 51058: "TStop", 

5865 51081: "ReelName", 

5866 51089: "OriginalDefaultFinalSize", 

5867 51090: "OriginalBestQualitySize", 

5868 51091: "OriginalDefaultCropSize", 

5869 51105: "CameraLabel", 

5870 51107: "ProfileHueSatMapEncoding", 

5871 51108: "ProfileLookTableEncoding", 

5872 51109: "BaselineExposureOffset", 

5873 51110: "DefaultBlackRender", 

5874 51111: "NewRawImageDigest", 

5875 51112: "RawToPreviewGain", 

5876 51125: "DefaultUserCrop", 

5877 51123: "MicroManagerMetadata", 

5878 59932: "Padding", 

5879 59933: "OffsetSchema", 

5880 # Reusable Tags 65000-65535 

5881 # 65000: Dimap_Document XML 

5882 # 65000-65112: Photoshop Camera RAW EXIF tags 

5883 # 65000: 'OwnerName', 

5884 # 65001: 'SerialNumber', 

5885 # 65002: 'Lens', 

5886 # 65024: 'KDC_IFD', 

5887 # 65100: 'RawFile', 

5888 # 65101: 'Converter', 

5889 # 65102: 'WhiteBalance', 

5890 # 65105: 'Exposure', 

5891 # 65106: 'Shadows', 

5892 # 65107: 'Brightness', 

5893 # 65108: 'Contrast', 

5894 # 65109: 'Saturation', 

5895 # 65110: 'Sharpness', 

5896 # 65111: 'Smoothness', 

5897 # 65112: 'MoireFilter', 

5898 65200: "FlexXML", # 

5899 65563: "PerSample", 

5900 } 

5901 

5902 def TAG_NAMES(): 

5903 return {v: c for c, v in TIFF.TAGS.items()} 

5904 

5905 def TAG_READERS(): 

5906 # Map TIFF tag codes to import functions 

5907 return { 

5908 320: read_colormap, 

5909 # 700: read_bytes, # read_utf8, 

5910 # 34377: read_bytes, 

5911 33723: read_bytes, 

5912 # 34675: read_bytes, 

5913 33628: read_uic1tag, # Universal Imaging Corp STK 

5914 33629: read_uic2tag, 

5915 33630: read_uic3tag, 

5916 33631: read_uic4tag, 

5917 34118: read_cz_sem, # Carl Zeiss SEM 

5918 34361: read_mm_header, # Olympus FluoView 

5919 34362: read_mm_stamp, 

5920 34363: read_numpy, # MM_Unknown 

5921 34386: read_numpy, # MM_UserBlock 

5922 34412: read_cz_lsminfo, # Carl Zeiss LSM 

5923 34680: read_fei_metadata, # S-FEG 

5924 34682: read_fei_metadata, # Helios NanoLab 

5925 37706: read_tvips_header, # TVIPS EMMENU 

5926 37724: read_bytes, # ImageSourceData 

5927 33923: read_bytes, # read_leica_magic 

5928 43314: read_nih_image_header, 

5929 # 40001: read_bytes, 

5930 40100: read_bytes, 

5931 50288: read_bytes, 

5932 50296: read_bytes, 

5933 50839: read_bytes, 

5934 51123: read_json, 

5935 34665: read_exif_ifd, 

5936 34853: read_gps_ifd, 

5937 40965: read_interoperability_ifd, 

5938 } 

5939 

5940 def TAG_TUPLE(): 

5941 # Tags whose values must be stored as tuples 

5942 return frozenset((273, 279, 324, 325, 530, 531, 34736)) 

5943 

5944 def TAG_ATTRIBUTES(): 

5945 # Map tag codes to TiffPage attribute names 

5946 return { 

5947 "ImageWidth": "imagewidth", 

5948 "ImageLength": "imagelength", 

5949 "BitsPerSample": "bitspersample", 

5950 "Compression": "compression", 

5951 "PlanarConfiguration": "planarconfig", 

5952 "FillOrder": "fillorder", 

5953 "PhotometricInterpretation": "photometric", 

5954 "ColorMap": "colormap", 

5955 "ImageDescription": "description", 

5956 "ImageDescription1": "description1", 

5957 "SamplesPerPixel": "samplesperpixel", 

5958 "RowsPerStrip": "rowsperstrip", 

5959 "Software": "software", 

5960 "Predictor": "predictor", 

5961 "TileWidth": "tilewidth", 

5962 "TileLength": "tilelength", 

5963 "ExtraSamples": "extrasamples", 

5964 "SampleFormat": "sampleformat", 

5965 "ImageDepth": "imagedepth", 

5966 "TileDepth": "tiledepth", 

5967 } 

5968 

5969 def TAG_ENUM(): 

5970 return { 

5971 # 254: TIFF.FILETYPE, 

5972 255: TIFF.OFILETYPE, 

5973 259: TIFF.COMPRESSION, 

5974 262: TIFF.PHOTOMETRIC, 

5975 263: TIFF.THRESHHOLD, 

5976 266: TIFF.FILLORDER, 

5977 274: TIFF.ORIENTATION, 

5978 284: TIFF.PLANARCONFIG, 

5979 290: TIFF.GRAYRESPONSEUNIT, 

5980 # 292: TIFF.GROUP3OPT, 

5981 # 293: TIFF.GROUP4OPT, 

5982 296: TIFF.RESUNIT, 

5983 300: TIFF.COLORRESPONSEUNIT, 

5984 317: TIFF.PREDICTOR, 

5985 338: TIFF.EXTRASAMPLE, 

5986 339: TIFF.SAMPLEFORMAT, 

5987 # 512: TIFF.JPEGPROC, 

5988 # 531: TIFF.YCBCRPOSITION, 

5989 } 

5990 

5991 def FILETYPE(): 

5992 class FILETYPE(enum.IntFlag): 

5993 # Python 3.6 only 

5994 UNDEFINED = 0 

5995 REDUCEDIMAGE = 1 

5996 PAGE = 2 

5997 MASK = 4 

5998 

5999 return FILETYPE 

6000 

6001 def OFILETYPE(): 

6002 class OFILETYPE(enum.IntEnum): 

6003 UNDEFINED = 0 

6004 IMAGE = 1 

6005 REDUCEDIMAGE = 2 

6006 PAGE = 3 

6007 

6008 return OFILETYPE 

6009 

6010 def COMPRESSION(): 

6011 class COMPRESSION(enum.IntEnum): 

6012 NONE = 1 # Uncompressed 

6013 CCITTRLE = 2 # CCITT 1D 

6014 CCITT_T4 = 3 # 'T4/Group 3 Fax', 

6015 CCITT_T6 = 4 # 'T6/Group 4 Fax', 

6016 LZW = 5 

6017 OJPEG = 6 # old-style JPEG 

6018 JPEG = 7 

6019 ADOBE_DEFLATE = 8 

6020 JBIG_BW = 9 

6021 JBIG_COLOR = 10 

6022 JPEG_99 = 99 

6023 KODAK_262 = 262 

6024 NEXT = 32766 

6025 SONY_ARW = 32767 

6026 PACKED_RAW = 32769 

6027 SAMSUNG_SRW = 32770 

6028 CCIRLEW = 32771 

6029 SAMSUNG_SRW2 = 32772 

6030 PACKBITS = 32773 

6031 THUNDERSCAN = 32809 

6032 IT8CTPAD = 32895 

6033 IT8LW = 32896 

6034 IT8MP = 32897 

6035 IT8BL = 32898 

6036 PIXARFILM = 32908 

6037 PIXARLOG = 32909 

6038 DEFLATE = 32946 

6039 DCS = 32947 

6040 APERIO_JP2000_YCBC = 33003 # Leica Aperio 

6041 APERIO_JP2000_RGB = 33005 # Leica Aperio 

6042 JBIG = 34661 

6043 SGILOG = 34676 

6044 SGILOG24 = 34677 

6045 JPEG2000 = 34712 

6046 NIKON_NEF = 34713 

6047 JBIG2 = 34715 

6048 MDI_BINARY = 34718 # 'Microsoft Document Imaging 

6049 MDI_PROGRESSIVE = 34719 # 'Microsoft Document Imaging 

6050 MDI_VECTOR = 34720 # 'Microsoft Document Imaging 

6051 JPEG_LOSSY = 34892 

6052 LZMA = 34925 

6053 ZSTD = 34926 

6054 OPS_PNG = 34933 # Objective Pathology Services 

6055 OPS_JPEGXR = 34934 # Objective Pathology Services 

6056 PIXTIFF = 50013 

6057 KODAK_DCR = 65000 

6058 PENTAX_PEF = 65535 

6059 # def __bool__(self): return self != 1 # Python 3.6 only 

6060 

6061 return COMPRESSION 

6062 

6063 def PHOTOMETRIC(): 

6064 class PHOTOMETRIC(enum.IntEnum): 

6065 MINISWHITE = 0 

6066 MINISBLACK = 1 

6067 RGB = 2 

6068 PALETTE = 3 

6069 MASK = 4 

6070 SEPARATED = 5 # CMYK 

6071 YCBCR = 6 

6072 CIELAB = 8 

6073 ICCLAB = 9 

6074 ITULAB = 10 

6075 CFA = 32803 # Color Filter Array 

6076 LOGL = 32844 

6077 LOGLUV = 32845 

6078 LINEAR_RAW = 34892 

6079 

6080 return PHOTOMETRIC 

6081 

6082 def THRESHHOLD(): 

6083 class THRESHHOLD(enum.IntEnum): 

6084 BILEVEL = 1 

6085 HALFTONE = 2 

6086 ERRORDIFFUSE = 3 

6087 

6088 return THRESHHOLD 

6089 

6090 def FILLORDER(): 

6091 class FILLORDER(enum.IntEnum): 

6092 MSB2LSB = 1 

6093 LSB2MSB = 2 

6094 

6095 return FILLORDER 

6096 

6097 def ORIENTATION(): 

6098 class ORIENTATION(enum.IntEnum): 

6099 TOPLEFT = 1 

6100 TOPRIGHT = 2 

6101 BOTRIGHT = 3 

6102 BOTLEFT = 4 

6103 LEFTTOP = 5 

6104 RIGHTTOP = 6 

6105 RIGHTBOT = 7 

6106 LEFTBOT = 8 

6107 

6108 return ORIENTATION 

6109 

6110 def PLANARCONFIG(): 

6111 class PLANARCONFIG(enum.IntEnum): 

6112 CONTIG = 1 

6113 SEPARATE = 2 

6114 

6115 return PLANARCONFIG 

6116 

6117 def GRAYRESPONSEUNIT(): 

6118 class GRAYRESPONSEUNIT(enum.IntEnum): 

6119 _10S = 1 

6120 _100S = 2 

6121 _1000S = 3 

6122 _10000S = 4 

6123 _100000S = 5 

6124 

6125 return GRAYRESPONSEUNIT 

6126 

6127 def GROUP4OPT(): 

6128 class GROUP4OPT(enum.IntEnum): 

6129 UNCOMPRESSED = 2 

6130 

6131 return GROUP4OPT 

6132 

6133 def RESUNIT(): 

6134 class RESUNIT(enum.IntEnum): 

6135 NONE = 1 

6136 INCH = 2 

6137 CENTIMETER = 3 

6138 # def __bool__(self): return self != 1 # Python 3.6 only 

6139 

6140 return RESUNIT 

6141 

6142 def COLORRESPONSEUNIT(): 

6143 class COLORRESPONSEUNIT(enum.IntEnum): 

6144 _10S = 1 

6145 _100S = 2 

6146 _1000S = 3 

6147 _10000S = 4 

6148 _100000S = 5 

6149 

6150 return COLORRESPONSEUNIT 

6151 

6152 def PREDICTOR(): 

6153 class PREDICTOR(enum.IntEnum): 

6154 NONE = 1 

6155 HORIZONTAL = 2 

6156 FLOATINGPOINT = 3 

6157 # def __bool__(self): return self != 1 # Python 3.6 only 

6158 

6159 return PREDICTOR 

6160 

6161 def EXTRASAMPLE(): 

6162 class EXTRASAMPLE(enum.IntEnum): 

6163 UNSPECIFIED = 0 

6164 ASSOCALPHA = 1 

6165 UNASSALPHA = 2 

6166 

6167 return EXTRASAMPLE 

6168 

6169 def SAMPLEFORMAT(): 

6170 class SAMPLEFORMAT(enum.IntEnum): 

6171 UINT = 1 

6172 INT = 2 

6173 IEEEFP = 3 

6174 VOID = 4 

6175 COMPLEXINT = 5 

6176 COMPLEXIEEEFP = 6 

6177 

6178 return SAMPLEFORMAT 

6179 

6180 def DATATYPES(): 

6181 class DATATYPES(enum.IntEnum): 

6182 NOTYPE = 0 

6183 BYTE = 1 

6184 ASCII = 2 

6185 SHORT = 3 

6186 LONG = 4 

6187 RATIONAL = 5 

6188 SBYTE = 6 

6189 UNDEFINED = 7 

6190 SSHORT = 8 

6191 SLONG = 9 

6192 SRATIONAL = 10 

6193 FLOAT = 11 

6194 DOUBLE = 12 

6195 IFD = 13 

6196 UNICODE = 14 

6197 COMPLEX = 15 

6198 LONG8 = 16 

6199 SLONG8 = 17 

6200 IFD8 = 18 

6201 

6202 return DATATYPES 

6203 

6204 def DATA_FORMATS(): 

6205 # Map TIFF DATATYPES to Python struct formats 

6206 return { 

6207 1: "1B", # BYTE 8-bit unsigned integer. 

6208 2: "1s", # ASCII 8-bit byte that contains a 7-bit ASCII code; 

6209 # the last byte must be NULL (binary zero). 

6210 3: "1H", # SHORT 16-bit (2-byte) unsigned integer 

6211 4: "1I", # LONG 32-bit (4-byte) unsigned integer. 

6212 5: "2I", # RATIONAL Two LONGs: the first represents the numerator 

6213 # of a fraction; the second, the denominator. 

6214 6: "1b", # SBYTE An 8-bit signed (twos-complement) integer. 

6215 7: "1B", # UNDEFINED An 8-bit byte that may contain anything, 

6216 # depending on the definition of the field. 

6217 8: "1h", # SSHORT A 16-bit (2-byte) signed (twos-complement) 

6218 # integer. 

6219 9: "1i", # SLONG A 32-bit (4-byte) signed (twos-complement) 

6220 # integer. 

6221 10: "2i", # SRATIONAL Two SLONGs: the first represents the 

6222 # numerator of a fraction, the second the denominator. 

6223 11: "1f", # FLOAT Single precision (4-byte) IEEE format. 

6224 12: "1d", # DOUBLE Double precision (8-byte) IEEE format. 

6225 13: "1I", # IFD unsigned 4 byte IFD offset. 

6226 # 14: '', # UNICODE 

6227 # 15: '', # COMPLEX 

6228 16: "1Q", # LONG8 unsigned 8 byte integer (BigTiff) 

6229 17: "1q", # SLONG8 signed 8 byte integer (BigTiff) 

6230 18: "1Q", # IFD8 unsigned 8 byte IFD offset (BigTiff) 

6231 } 

6232 

6233 def DATA_DTYPES(): 

6234 # Map numpy dtypes to TIFF DATATYPES 

6235 return { 

6236 "B": 1, 

6237 "s": 2, 

6238 "H": 3, 

6239 "I": 4, 

6240 "2I": 5, 

6241 "b": 6, 

6242 "h": 8, 

6243 "i": 9, 

6244 "2i": 10, 

6245 "f": 11, 

6246 "d": 12, 

6247 "Q": 16, 

6248 "q": 17, 

6249 } 

6250 

6251 def SAMPLE_DTYPES(): 

6252 # Map TIFF SampleFormats and BitsPerSample to numpy dtype 

6253 return { 

6254 (1, 1): "?", # bitmap 

6255 (1, 2): "B", 

6256 (1, 3): "B", 

6257 (1, 4): "B", 

6258 (1, 5): "B", 

6259 (1, 6): "B", 

6260 (1, 7): "B", 

6261 (1, 8): "B", 

6262 (1, 9): "H", 

6263 (1, 10): "H", 

6264 (1, 11): "H", 

6265 (1, 12): "H", 

6266 (1, 13): "H", 

6267 (1, 14): "H", 

6268 (1, 15): "H", 

6269 (1, 16): "H", 

6270 (1, 17): "I", 

6271 (1, 18): "I", 

6272 (1, 19): "I", 

6273 (1, 20): "I", 

6274 (1, 21): "I", 

6275 (1, 22): "I", 

6276 (1, 23): "I", 

6277 (1, 24): "I", 

6278 (1, 25): "I", 

6279 (1, 26): "I", 

6280 (1, 27): "I", 

6281 (1, 28): "I", 

6282 (1, 29): "I", 

6283 (1, 30): "I", 

6284 (1, 31): "I", 

6285 (1, 32): "I", 

6286 (1, 64): "Q", 

6287 (2, 8): "b", 

6288 (2, 16): "h", 

6289 (2, 32): "i", 

6290 (2, 64): "q", 

6291 (3, 16): "e", 

6292 (3, 32): "f", 

6293 (3, 64): "d", 

6294 (6, 64): "F", 

6295 (6, 128): "D", 

6296 (1, (5, 6, 5)): "B", 

6297 } 

6298 

6299 def COMPESSORS(): 

6300 # Map COMPRESSION to compress functions and default compression levels 

6301 

6302 class Compressors(object): 

6303 """Delay import compressor functions.""" 

6304 

6305 def __init__(self): 

6306 self._compressors = {8: (zlib.compress, 6), 32946: (zlib.compress, 6)} 

6307 

6308 def __getitem__(self, key): 

6309 if key in self._compressors: 

6310 return self._compressors[key] 

6311 

6312 if key == 34925: 

6313 try: 

6314 import lzma # delayed import 

6315 except ImportError: 

6316 try: 

6317 import backports.lzma as lzma # delayed import 

6318 except ImportError: 

6319 raise KeyError 

6320 

6321 def lzma_compress(x, level): 

6322 return lzma.compress(x) 

6323 

6324 self._compressors[key] = lzma_compress, 0 

6325 return lzma_compress, 0 

6326 

6327 if key == 34926: 

6328 try: 

6329 import zstd # delayed import 

6330 except ImportError: 

6331 raise KeyError 

6332 self._compressors[key] = zstd.compress, 9 

6333 return zstd.compress, 9 

6334 

6335 raise KeyError 

6336 

6337 def __contains__(self, key): 

6338 try: 

6339 self[key] 

6340 return True 

6341 except KeyError: 

6342 return False 

6343 

6344 return Compressors() 

6345 

6346 def DECOMPESSORS(): 

6347 # Map COMPRESSION to decompress functions 

6348 

6349 class Decompressors(object): 

6350 """Delay import decompressor functions.""" 

6351 

6352 def __init__(self): 

6353 self._decompressors = { 

6354 None: identityfunc, 

6355 1: identityfunc, 

6356 5: decode_lzw, 

6357 8: zlib.decompress, 

6358 32773: decode_packbits, 

6359 32946: zlib.decompress, 

6360 } 

6361 

6362 def __getitem__(self, key): 

6363 if key in self._decompressors: 

6364 return self._decompressors[key] 

6365 

6366 if key == 7: 

6367 try: 

6368 from imagecodecs import jpeg, jpeg_12 

6369 except ImportError: 

6370 raise KeyError 

6371 

6372 def decode_jpeg(x, table, bps, colorspace=None): 

6373 if bps == 8: 

6374 return jpeg.decode_jpeg(x, table, colorspace) 

6375 elif bps == 12: 

6376 return jpeg_12.decode_jpeg_12(x, table, colorspace) 

6377 else: 

6378 raise ValueError("bitspersample not supported") 

6379 

6380 self._decompressors[key] = decode_jpeg 

6381 return decode_jpeg 

6382 

6383 if key == 34925: 

6384 try: 

6385 import lzma # delayed import 

6386 except ImportError: 

6387 try: 

6388 import backports.lzma as lzma # delayed import 

6389 except ImportError: 

6390 raise KeyError 

6391 self._decompressors[key] = lzma.decompress 

6392 return lzma.decompress 

6393 

6394 if key == 34926: 

6395 try: 

6396 import zstd # delayed import 

6397 except ImportError: 

6398 raise KeyError 

6399 self._decompressors[key] = zstd.decompress 

6400 return zstd.decompress 

6401 raise KeyError 

6402 

6403 def __contains__(self, item): 

6404 try: 

6405 self[item] 

6406 return True 

6407 except KeyError: 

6408 return False 

6409 

6410 return Decompressors() 

6411 

6412 def FRAME_ATTRS(): 

6413 # Attributes that a TiffFrame shares with its keyframe 

6414 return set("shape ndim size dtype axes is_final".split()) 

6415 

6416 def FILE_FLAGS(): 

6417 # TiffFile and TiffPage 'is_\*' attributes 

6418 exclude = set( 

6419 "reduced final memmappable contiguous tiled " "chroma_subsampled".split() 

6420 ) 

6421 return set( 

6422 a[3:] for a in dir(TiffPage) if a[:3] == "is_" and a[3:] not in exclude 

6423 ) 

6424 

6425 def FILE_EXTENSIONS(): 

6426 # TIFF file extensions 

6427 return tuple( 

6428 "tif tiff ome.tif lsm stk qptiff pcoraw " 

6429 "gel seq svs bif tf8 tf2 btf".split() 

6430 ) 

6431 

6432 def FILEOPEN_FILTER(): 

6433 # String for use in Windows File Open box 

6434 return [ 

6435 ("%s files" % ext.upper(), "*.%s" % ext) for ext in TIFF.FILE_EXTENSIONS 

6436 ] + [("allfiles", "*")] 

6437 

6438 def AXES_LABELS(): 

6439 # TODO: is there a standard for character axes labels? 

6440 axes = { 

6441 "X": "width", 

6442 "Y": "height", 

6443 "Z": "depth", 

6444 "S": "sample", # rgb(a) 

6445 "I": "series", # general sequence, plane, page, IFD 

6446 "T": "time", 

6447 "C": "channel", # color, emission wavelength 

6448 "A": "angle", 

6449 "P": "phase", # formerly F # P is Position in LSM! 

6450 "R": "tile", # region, point, mosaic 

6451 "H": "lifetime", # histogram 

6452 "E": "lambda", # excitation wavelength 

6453 "L": "exposure", # lux 

6454 "V": "event", 

6455 "Q": "other", 

6456 "M": "mosaic", # LSM 6 

6457 } 

6458 axes.update(dict((v, k) for k, v in axes.items())) 

6459 return axes 

6460 

6461 def ANDOR_TAGS(): 

6462 # Andor Technology tags #4864 - 5030 

6463 return set(range(4864, 5030)) 

6464 

6465 def EXIF_TAGS(): 

6466 tags = { 

6467 # 65000 - 65112 Photoshop Camera RAW EXIF tags 

6468 65000: "OwnerName", 

6469 65001: "SerialNumber", 

6470 65002: "Lens", 

6471 65100: "RawFile", 

6472 65101: "Converter", 

6473 65102: "WhiteBalance", 

6474 65105: "Exposure", 

6475 65106: "Shadows", 

6476 65107: "Brightness", 

6477 65108: "Contrast", 

6478 65109: "Saturation", 

6479 65110: "Sharpness", 

6480 65111: "Smoothness", 

6481 65112: "MoireFilter", 

6482 } 

6483 tags.update(TIFF.TAGS) 

6484 return tags 

6485 

6486 def GPS_TAGS(): 

6487 return { 

6488 0: "GPSVersionID", 

6489 1: "GPSLatitudeRef", 

6490 2: "GPSLatitude", 

6491 3: "GPSLongitudeRef", 

6492 4: "GPSLongitude", 

6493 5: "GPSAltitudeRef", 

6494 6: "GPSAltitude", 

6495 7: "GPSTimeStamp", 

6496 8: "GPSSatellites", 

6497 9: "GPSStatus", 

6498 10: "GPSMeasureMode", 

6499 11: "GPSDOP", 

6500 12: "GPSSpeedRef", 

6501 13: "GPSSpeed", 

6502 14: "GPSTrackRef", 

6503 15: "GPSTrack", 

6504 16: "GPSImgDirectionRef", 

6505 17: "GPSImgDirection", 

6506 18: "GPSMapDatum", 

6507 19: "GPSDestLatitudeRef", 

6508 20: "GPSDestLatitude", 

6509 21: "GPSDestLongitudeRef", 

6510 22: "GPSDestLongitude", 

6511 23: "GPSDestBearingRef", 

6512 24: "GPSDestBearing", 

6513 25: "GPSDestDistanceRef", 

6514 26: "GPSDestDistance", 

6515 27: "GPSProcessingMethod", 

6516 28: "GPSAreaInformation", 

6517 29: "GPSDateStamp", 

6518 30: "GPSDifferential", 

6519 31: "GPSHPositioningError", 

6520 } 

6521 

6522 def IOP_TAGS(): 

6523 return { 

6524 1: "InteroperabilityIndex", 

6525 2: "InteroperabilityVersion", 

6526 4096: "RelatedImageFileFormat", 

6527 4097: "RelatedImageWidth", 

6528 4098: "RelatedImageLength", 

6529 } 

6530 

6531 def GEO_KEYS(): 

6532 return { 

6533 1024: "GTModelTypeGeoKey", 

6534 1025: "GTRasterTypeGeoKey", 

6535 1026: "GTCitationGeoKey", 

6536 2048: "GeographicTypeGeoKey", 

6537 2049: "GeogCitationGeoKey", 

6538 2050: "GeogGeodeticDatumGeoKey", 

6539 2051: "GeogPrimeMeridianGeoKey", 

6540 2052: "GeogLinearUnitsGeoKey", 

6541 2053: "GeogLinearUnitSizeGeoKey", 

6542 2054: "GeogAngularUnitsGeoKey", 

6543 2055: "GeogAngularUnitsSizeGeoKey", 

6544 2056: "GeogEllipsoidGeoKey", 

6545 2057: "GeogSemiMajorAxisGeoKey", 

6546 2058: "GeogSemiMinorAxisGeoKey", 

6547 2059: "GeogInvFlatteningGeoKey", 

6548 2060: "GeogAzimuthUnitsGeoKey", 

6549 2061: "GeogPrimeMeridianLongGeoKey", 

6550 2062: "GeogTOWGS84GeoKey", 

6551 3059: "ProjLinearUnitsInterpCorrectGeoKey", # GDAL 

6552 3072: "ProjectedCSTypeGeoKey", 

6553 3073: "PCSCitationGeoKey", 

6554 3074: "ProjectionGeoKey", 

6555 3075: "ProjCoordTransGeoKey", 

6556 3076: "ProjLinearUnitsGeoKey", 

6557 3077: "ProjLinearUnitSizeGeoKey", 

6558 3078: "ProjStdParallel1GeoKey", 

6559 3079: "ProjStdParallel2GeoKey", 

6560 3080: "ProjNatOriginLongGeoKey", 

6561 3081: "ProjNatOriginLatGeoKey", 

6562 3082: "ProjFalseEastingGeoKey", 

6563 3083: "ProjFalseNorthingGeoKey", 

6564 3084: "ProjFalseOriginLongGeoKey", 

6565 3085: "ProjFalseOriginLatGeoKey", 

6566 3086: "ProjFalseOriginEastingGeoKey", 

6567 3087: "ProjFalseOriginNorthingGeoKey", 

6568 3088: "ProjCenterLongGeoKey", 

6569 3089: "ProjCenterLatGeoKey", 

6570 3090: "ProjCenterEastingGeoKey", 

6571 3091: "ProjFalseOriginNorthingGeoKey", 

6572 3092: "ProjScaleAtNatOriginGeoKey", 

6573 3093: "ProjScaleAtCenterGeoKey", 

6574 3094: "ProjAzimuthAngleGeoKey", 

6575 3095: "ProjStraightVertPoleLongGeoKey", 

6576 3096: "ProjRectifiedGridAngleGeoKey", 

6577 4096: "VerticalCSTypeGeoKey", 

6578 4097: "VerticalCitationGeoKey", 

6579 4098: "VerticalDatumGeoKey", 

6580 4099: "VerticalUnitsGeoKey", 

6581 } 

6582 

6583 def GEO_CODES(): 

6584 try: 

6585 from .tifffile_geodb import GEO_CODES # delayed import 

6586 except (ImportError, ValueError): 

6587 try: 

6588 from tifffile_geodb import GEO_CODES # delayed import 

6589 except (ImportError, ValueError): 

6590 GEO_CODES = {} 

6591 return GEO_CODES 

6592 

6593 def CZ_LSMINFO(): 

6594 return [ 

6595 ("MagicNumber", "u4"), 

6596 ("StructureSize", "i4"), 

6597 ("DimensionX", "i4"), 

6598 ("DimensionY", "i4"), 

6599 ("DimensionZ", "i4"), 

6600 ("DimensionChannels", "i4"), 

6601 ("DimensionTime", "i4"), 

6602 ("DataType", "i4"), # DATATYPES 

6603 ("ThumbnailX", "i4"), 

6604 ("ThumbnailY", "i4"), 

6605 ("VoxelSizeX", "f8"), 

6606 ("VoxelSizeY", "f8"), 

6607 ("VoxelSizeZ", "f8"), 

6608 ("OriginX", "f8"), 

6609 ("OriginY", "f8"), 

6610 ("OriginZ", "f8"), 

6611 ("ScanType", "u2"), 

6612 ("SpectralScan", "u2"), 

6613 ("TypeOfData", "u4"), # TYPEOFDATA 

6614 ("OffsetVectorOverlay", "u4"), 

6615 ("OffsetInputLut", "u4"), 

6616 ("OffsetOutputLut", "u4"), 

6617 ("OffsetChannelColors", "u4"), 

6618 ("TimeIntervall", "f8"), 

6619 ("OffsetChannelDataTypes", "u4"), 

6620 ("OffsetScanInformation", "u4"), # SCANINFO 

6621 ("OffsetKsData", "u4"), 

6622 ("OffsetTimeStamps", "u4"), 

6623 ("OffsetEventList", "u4"), 

6624 ("OffsetRoi", "u4"), 

6625 ("OffsetBleachRoi", "u4"), 

6626 ("OffsetNextRecording", "u4"), 

6627 # LSM 2.0 ends here 

6628 ("DisplayAspectX", "f8"), 

6629 ("DisplayAspectY", "f8"), 

6630 ("DisplayAspectZ", "f8"), 

6631 ("DisplayAspectTime", "f8"), 

6632 ("OffsetMeanOfRoisOverlay", "u4"), 

6633 ("OffsetTopoIsolineOverlay", "u4"), 

6634 ("OffsetTopoProfileOverlay", "u4"), 

6635 ("OffsetLinescanOverlay", "u4"), 

6636 ("ToolbarFlags", "u4"), 

6637 ("OffsetChannelWavelength", "u4"), 

6638 ("OffsetChannelFactors", "u4"), 

6639 ("ObjectiveSphereCorrection", "f8"), 

6640 ("OffsetUnmixParameters", "u4"), 

6641 # LSM 3.2, 4.0 end here 

6642 ("OffsetAcquisitionParameters", "u4"), 

6643 ("OffsetCharacteristics", "u4"), 

6644 ("OffsetPalette", "u4"), 

6645 ("TimeDifferenceX", "f8"), 

6646 ("TimeDifferenceY", "f8"), 

6647 ("TimeDifferenceZ", "f8"), 

6648 ("InternalUse1", "u4"), 

6649 ("DimensionP", "i4"), 

6650 ("DimensionM", "i4"), 

6651 ("DimensionsReserved", "16i4"), 

6652 ("OffsetTilePositions", "u4"), 

6653 ("", "9u4"), # Reserved 

6654 ("OffsetPositions", "u4"), 

6655 # ('', '21u4'), # must be 0 

6656 ] 

6657 

6658 def CZ_LSMINFO_READERS(): 

6659 # Import functions for CZ_LSMINFO sub-records 

6660 # TODO: read more CZ_LSMINFO sub-records 

6661 return { 

6662 "ScanInformation": read_lsm_scaninfo, 

6663 "TimeStamps": read_lsm_timestamps, 

6664 "EventList": read_lsm_eventlist, 

6665 "ChannelColors": read_lsm_channelcolors, 

6666 "Positions": read_lsm_floatpairs, 

6667 "TilePositions": read_lsm_floatpairs, 

6668 "VectorOverlay": None, 

6669 "InputLut": None, 

6670 "OutputLut": None, 

6671 "TimeIntervall": None, 

6672 "ChannelDataTypes": None, 

6673 "KsData": None, 

6674 "Roi": None, 

6675 "BleachRoi": None, 

6676 "NextRecording": None, 

6677 "MeanOfRoisOverlay": None, 

6678 "TopoIsolineOverlay": None, 

6679 "TopoProfileOverlay": None, 

6680 "ChannelWavelength": None, 

6681 "SphereCorrection": None, 

6682 "ChannelFactors": None, 

6683 "UnmixParameters": None, 

6684 "AcquisitionParameters": None, 

6685 "Characteristics": None, 

6686 } 

6687 

6688 def CZ_LSMINFO_SCANTYPE(): 

6689 # Map CZ_LSMINFO.ScanType to dimension order 

6690 return { 

6691 0: "XYZCT", # 'Stack' normal x-y-z-scan 

6692 1: "XYZCT", # 'Z-Scan' x-z-plane Y=1 

6693 2: "XYZCT", # 'Line' 

6694 3: "XYTCZ", # 'Time Series Plane' time series x-y XYCTZ ? Z=1 

6695 4: "XYZTC", # 'Time Series z-Scan' time series x-z 

6696 5: "XYTCZ", # 'Time Series Mean-of-ROIs' 

6697 6: "XYZTC", # 'Time Series Stack' time series x-y-z 

6698 7: "XYCTZ", # Spline Scan 

6699 8: "XYCZT", # Spline Plane x-z 

6700 9: "XYTCZ", # Time Series Spline Plane x-z 

6701 10: "XYZCT", # 'Time Series Point' point mode 

6702 } 

6703 

6704 def CZ_LSMINFO_DIMENSIONS(): 

6705 # Map dimension codes to CZ_LSMINFO attribute 

6706 return { 

6707 "X": "DimensionX", 

6708 "Y": "DimensionY", 

6709 "Z": "DimensionZ", 

6710 "C": "DimensionChannels", 

6711 "T": "DimensionTime", 

6712 "P": "DimensionP", 

6713 "M": "DimensionM", 

6714 } 

6715 

6716 def CZ_LSMINFO_DATATYPES(): 

6717 # Description of CZ_LSMINFO.DataType 

6718 return { 

6719 0: "varying data types", 

6720 1: "8 bit unsigned integer", 

6721 2: "12 bit unsigned integer", 

6722 5: "32 bit float", 

6723 } 

6724 

6725 def CZ_LSMINFO_TYPEOFDATA(): 

6726 # Description of CZ_LSMINFO.TypeOfData 

6727 return { 

6728 0: "Original scan data", 

6729 1: "Calculated data", 

6730 2: "3D reconstruction", 

6731 3: "Topography height map", 

6732 } 

6733 

6734 def CZ_LSMINFO_SCANINFO_ARRAYS(): 

6735 return { 

6736 0x20000000: "Tracks", 

6737 0x30000000: "Lasers", 

6738 0x60000000: "DetectionChannels", 

6739 0x80000000: "IlluminationChannels", 

6740 0xA0000000: "BeamSplitters", 

6741 0xC0000000: "DataChannels", 

6742 0x11000000: "Timers", 

6743 0x13000000: "Markers", 

6744 } 

6745 

6746 def CZ_LSMINFO_SCANINFO_STRUCTS(): 

6747 return { 

6748 # 0x10000000: 'Recording', 

6749 0x40000000: "Track", 

6750 0x50000000: "Laser", 

6751 0x70000000: "DetectionChannel", 

6752 0x90000000: "IlluminationChannel", 

6753 0xB0000000: "BeamSplitter", 

6754 0xD0000000: "DataChannel", 

6755 0x12000000: "Timer", 

6756 0x14000000: "Marker", 

6757 } 

6758 

6759 def CZ_LSMINFO_SCANINFO_ATTRIBUTES(): 

6760 return { 

6761 # Recording 

6762 0x10000001: "Name", 

6763 0x10000002: "Description", 

6764 0x10000003: "Notes", 

6765 0x10000004: "Objective", 

6766 0x10000005: "ProcessingSummary", 

6767 0x10000006: "SpecialScanMode", 

6768 0x10000007: "ScanType", 

6769 0x10000008: "ScanMode", 

6770 0x10000009: "NumberOfStacks", 

6771 0x1000000A: "LinesPerPlane", 

6772 0x1000000B: "SamplesPerLine", 

6773 0x1000000C: "PlanesPerVolume", 

6774 0x1000000D: "ImagesWidth", 

6775 0x1000000E: "ImagesHeight", 

6776 0x1000000F: "ImagesNumberPlanes", 

6777 0x10000010: "ImagesNumberStacks", 

6778 0x10000011: "ImagesNumberChannels", 

6779 0x10000012: "LinscanXySize", 

6780 0x10000013: "ScanDirection", 

6781 0x10000014: "TimeSeries", 

6782 0x10000015: "OriginalScanData", 

6783 0x10000016: "ZoomX", 

6784 0x10000017: "ZoomY", 

6785 0x10000018: "ZoomZ", 

6786 0x10000019: "Sample0X", 

6787 0x1000001A: "Sample0Y", 

6788 0x1000001B: "Sample0Z", 

6789 0x1000001C: "SampleSpacing", 

6790 0x1000001D: "LineSpacing", 

6791 0x1000001E: "PlaneSpacing", 

6792 0x1000001F: "PlaneWidth", 

6793 0x10000020: "PlaneHeight", 

6794 0x10000021: "VolumeDepth", 

6795 0x10000023: "Nutation", 

6796 0x10000034: "Rotation", 

6797 0x10000035: "Precession", 

6798 0x10000036: "Sample0time", 

6799 0x10000037: "StartScanTriggerIn", 

6800 0x10000038: "StartScanTriggerOut", 

6801 0x10000039: "StartScanEvent", 

6802 0x10000040: "StartScanTime", 

6803 0x10000041: "StopScanTriggerIn", 

6804 0x10000042: "StopScanTriggerOut", 

6805 0x10000043: "StopScanEvent", 

6806 0x10000044: "StopScanTime", 

6807 0x10000045: "UseRois", 

6808 0x10000046: "UseReducedMemoryRois", 

6809 0x10000047: "User", 

6810 0x10000048: "UseBcCorrection", 

6811 0x10000049: "PositionBcCorrection1", 

6812 0x10000050: "PositionBcCorrection2", 

6813 0x10000051: "InterpolationY", 

6814 0x10000052: "CameraBinning", 

6815 0x10000053: "CameraSupersampling", 

6816 0x10000054: "CameraFrameWidth", 

6817 0x10000055: "CameraFrameHeight", 

6818 0x10000056: "CameraOffsetX", 

6819 0x10000057: "CameraOffsetY", 

6820 0x10000059: "RtBinning", 

6821 0x1000005A: "RtFrameWidth", 

6822 0x1000005B: "RtFrameHeight", 

6823 0x1000005C: "RtRegionWidth", 

6824 0x1000005D: "RtRegionHeight", 

6825 0x1000005E: "RtOffsetX", 

6826 0x1000005F: "RtOffsetY", 

6827 0x10000060: "RtZoom", 

6828 0x10000061: "RtLinePeriod", 

6829 0x10000062: "Prescan", 

6830 0x10000063: "ScanDirectionZ", 

6831 # Track 

6832 0x40000001: "MultiplexType", # 0 After Line; 1 After Frame 

6833 0x40000002: "MultiplexOrder", 

6834 0x40000003: "SamplingMode", # 0 Sample; 1 Line Avg; 2 Frame Avg 

6835 0x40000004: "SamplingMethod", # 1 Mean; 2 Sum 

6836 0x40000005: "SamplingNumber", 

6837 0x40000006: "Acquire", 

6838 0x40000007: "SampleObservationTime", 

6839 0x4000000B: "TimeBetweenStacks", 

6840 0x4000000C: "Name", 

6841 0x4000000D: "Collimator1Name", 

6842 0x4000000E: "Collimator1Position", 

6843 0x4000000F: "Collimator2Name", 

6844 0x40000010: "Collimator2Position", 

6845 0x40000011: "IsBleachTrack", 

6846 0x40000012: "IsBleachAfterScanNumber", 

6847 0x40000013: "BleachScanNumber", 

6848 0x40000014: "TriggerIn", 

6849 0x40000015: "TriggerOut", 

6850 0x40000016: "IsRatioTrack", 

6851 0x40000017: "BleachCount", 

6852 0x40000018: "SpiCenterWavelength", 

6853 0x40000019: "PixelTime", 

6854 0x40000021: "CondensorFrontlens", 

6855 0x40000023: "FieldStopValue", 

6856 0x40000024: "IdCondensorAperture", 

6857 0x40000025: "CondensorAperture", 

6858 0x40000026: "IdCondensorRevolver", 

6859 0x40000027: "CondensorFilter", 

6860 0x40000028: "IdTransmissionFilter1", 

6861 0x40000029: "IdTransmission1", 

6862 0x40000030: "IdTransmissionFilter2", 

6863 0x40000031: "IdTransmission2", 

6864 0x40000032: "RepeatBleach", 

6865 0x40000033: "EnableSpotBleachPos", 

6866 0x40000034: "SpotBleachPosx", 

6867 0x40000035: "SpotBleachPosy", 

6868 0x40000036: "SpotBleachPosz", 

6869 0x40000037: "IdTubelens", 

6870 0x40000038: "IdTubelensPosition", 

6871 0x40000039: "TransmittedLight", 

6872 0x4000003A: "ReflectedLight", 

6873 0x4000003B: "SimultanGrabAndBleach", 

6874 0x4000003C: "BleachPixelTime", 

6875 # Laser 

6876 0x50000001: "Name", 

6877 0x50000002: "Acquire", 

6878 0x50000003: "Power", 

6879 # DetectionChannel 

6880 0x70000001: "IntegrationMode", 

6881 0x70000002: "SpecialMode", 

6882 0x70000003: "DetectorGainFirst", 

6883 0x70000004: "DetectorGainLast", 

6884 0x70000005: "AmplifierGainFirst", 

6885 0x70000006: "AmplifierGainLast", 

6886 0x70000007: "AmplifierOffsFirst", 

6887 0x70000008: "AmplifierOffsLast", 

6888 0x70000009: "PinholeDiameter", 

6889 0x7000000A: "CountingTrigger", 

6890 0x7000000B: "Acquire", 

6891 0x7000000C: "PointDetectorName", 

6892 0x7000000D: "AmplifierName", 

6893 0x7000000E: "PinholeName", 

6894 0x7000000F: "FilterSetName", 

6895 0x70000010: "FilterName", 

6896 0x70000013: "IntegratorName", 

6897 0x70000014: "ChannelName", 

6898 0x70000015: "DetectorGainBc1", 

6899 0x70000016: "DetectorGainBc2", 

6900 0x70000017: "AmplifierGainBc1", 

6901 0x70000018: "AmplifierGainBc2", 

6902 0x70000019: "AmplifierOffsetBc1", 

6903 0x70000020: "AmplifierOffsetBc2", 

6904 0x70000021: "SpectralScanChannels", 

6905 0x70000022: "SpiWavelengthStart", 

6906 0x70000023: "SpiWavelengthStop", 

6907 0x70000026: "DyeName", 

6908 0x70000027: "DyeFolder", 

6909 # IlluminationChannel 

6910 0x90000001: "Name", 

6911 0x90000002: "Power", 

6912 0x90000003: "Wavelength", 

6913 0x90000004: "Aquire", 

6914 0x90000005: "DetchannelName", 

6915 0x90000006: "PowerBc1", 

6916 0x90000007: "PowerBc2", 

6917 # BeamSplitter 

6918 0xB0000001: "FilterSet", 

6919 0xB0000002: "Filter", 

6920 0xB0000003: "Name", 

6921 # DataChannel 

6922 0xD0000001: "Name", 

6923 0xD0000003: "Acquire", 

6924 0xD0000004: "Color", 

6925 0xD0000005: "SampleType", 

6926 0xD0000006: "BitsPerSample", 

6927 0xD0000007: "RatioType", 

6928 0xD0000008: "RatioTrack1", 

6929 0xD0000009: "RatioTrack2", 

6930 0xD000000A: "RatioChannel1", 

6931 0xD000000B: "RatioChannel2", 

6932 0xD000000C: "RatioConst1", 

6933 0xD000000D: "RatioConst2", 

6934 0xD000000E: "RatioConst3", 

6935 0xD000000F: "RatioConst4", 

6936 0xD0000010: "RatioConst5", 

6937 0xD0000011: "RatioConst6", 

6938 0xD0000012: "RatioFirstImages1", 

6939 0xD0000013: "RatioFirstImages2", 

6940 0xD0000014: "DyeName", 

6941 0xD0000015: "DyeFolder", 

6942 0xD0000016: "Spectrum", 

6943 0xD0000017: "Acquire", 

6944 # Timer 

6945 0x12000001: "Name", 

6946 0x12000002: "Description", 

6947 0x12000003: "Interval", 

6948 0x12000004: "TriggerIn", 

6949 0x12000005: "TriggerOut", 

6950 0x12000006: "ActivationTime", 

6951 0x12000007: "ActivationNumber", 

6952 # Marker 

6953 0x14000001: "Name", 

6954 0x14000002: "Description", 

6955 0x14000003: "TriggerIn", 

6956 0x14000004: "TriggerOut", 

6957 } 

6958 

6959 def NIH_IMAGE_HEADER(): 

6960 return [ 

6961 ("FileID", "a8"), 

6962 ("nLines", "i2"), 

6963 ("PixelsPerLine", "i2"), 

6964 ("Version", "i2"), 

6965 ("OldLutMode", "i2"), 

6966 ("OldnColors", "i2"), 

6967 ("Colors", "u1", (3, 32)), 

6968 ("OldColorStart", "i2"), 

6969 ("ColorWidth", "i2"), 

6970 ("ExtraColors", "u2", (6, 3)), 

6971 ("nExtraColors", "i2"), 

6972 ("ForegroundIndex", "i2"), 

6973 ("BackgroundIndex", "i2"), 

6974 ("XScale", "f8"), 

6975 ("Unused2", "i2"), 

6976 ("Unused3", "i2"), 

6977 ("UnitsID", "i2"), # NIH_UNITS_TYPE 

6978 ("p1", [("x", "i2"), ("y", "i2")]), 

6979 ("p2", [("x", "i2"), ("y", "i2")]), 

6980 ("CurveFitType", "i2"), # NIH_CURVEFIT_TYPE 

6981 ("nCoefficients", "i2"), 

6982 ("Coeff", "f8", 6), 

6983 ("UMsize", "u1"), 

6984 ("UM", "a15"), 

6985 ("UnusedBoolean", "u1"), 

6986 ("BinaryPic", "b1"), 

6987 ("SliceStart", "i2"), 

6988 ("SliceEnd", "i2"), 

6989 ("ScaleMagnification", "f4"), 

6990 ("nSlices", "i2"), 

6991 ("SliceSpacing", "f4"), 

6992 ("CurrentSlice", "i2"), 

6993 ("FrameInterval", "f4"), 

6994 ("PixelAspectRatio", "f4"), 

6995 ("ColorStart", "i2"), 

6996 ("ColorEnd", "i2"), 

6997 ("nColors", "i2"), 

6998 ("Fill1", "3u2"), 

6999 ("Fill2", "3u2"), 

7000 ("Table", "u1"), # NIH_COLORTABLE_TYPE 

7001 ("LutMode", "u1"), # NIH_LUTMODE_TYPE 

7002 ("InvertedTable", "b1"), 

7003 ("ZeroClip", "b1"), 

7004 ("XUnitSize", "u1"), 

7005 ("XUnit", "a11"), 

7006 ("StackType", "i2"), # NIH_STACKTYPE_TYPE 

7007 # ('UnusedBytes', 'u1', 200) 

7008 ] 

7009 

7010 def NIH_COLORTABLE_TYPE(): 

7011 return ( 

7012 "CustomTable", 

7013 "AppleDefault", 

7014 "Pseudo20", 

7015 "Pseudo32", 

7016 "Rainbow", 

7017 "Fire1", 

7018 "Fire2", 

7019 "Ice", 

7020 "Grays", 

7021 "Spectrum", 

7022 ) 

7023 

7024 def NIH_LUTMODE_TYPE(): 

7025 return ( 

7026 "PseudoColor", 

7027 "OldAppleDefault", 

7028 "OldSpectrum", 

7029 "GrayScale", 

7030 "ColorLut", 

7031 "CustomGrayscale", 

7032 ) 

7033 

7034 def NIH_CURVEFIT_TYPE(): 

7035 return ( 

7036 "StraightLine", 

7037 "Poly2", 

7038 "Poly3", 

7039 "Poly4", 

7040 "Poly5", 

7041 "ExpoFit", 

7042 "PowerFit", 

7043 "LogFit", 

7044 "RodbardFit", 

7045 "SpareFit1", 

7046 "Uncalibrated", 

7047 "UncalibratedOD", 

7048 ) 

7049 

7050 def NIH_UNITS_TYPE(): 

7051 return ( 

7052 "Nanometers", 

7053 "Micrometers", 

7054 "Millimeters", 

7055 "Centimeters", 

7056 "Meters", 

7057 "Kilometers", 

7058 "Inches", 

7059 "Feet", 

7060 "Miles", 

7061 "Pixels", 

7062 "OtherUnits", 

7063 ) 

7064 

7065 def NIH_STACKTYPE_TYPE(): 

7066 return ("VolumeStack", "RGBStack", "MovieStack", "HSVStack") 

7067 

7068 def TVIPS_HEADER_V1(): 

7069 # TVIPS TemData structure from EMMENU Help file 

7070 return [ 

7071 ("Version", "i4"), 

7072 ("CommentV1", "a80"), 

7073 ("HighTension", "i4"), 

7074 ("SphericalAberration", "i4"), 

7075 ("IlluminationAperture", "i4"), 

7076 ("Magnification", "i4"), 

7077 ("PostMagnification", "i4"), 

7078 ("FocalLength", "i4"), 

7079 ("Defocus", "i4"), 

7080 ("Astigmatism", "i4"), 

7081 ("AstigmatismDirection", "i4"), 

7082 ("BiprismVoltage", "i4"), 

7083 ("SpecimenTiltAngle", "i4"), 

7084 ("SpecimenTiltDirection", "i4"), 

7085 ("IlluminationTiltDirection", "i4"), 

7086 ("IlluminationTiltAngle", "i4"), 

7087 ("ImageMode", "i4"), 

7088 ("EnergySpread", "i4"), 

7089 ("ChromaticAberration", "i4"), 

7090 ("ShutterType", "i4"), 

7091 ("DefocusSpread", "i4"), 

7092 ("CcdNumber", "i4"), 

7093 ("CcdSize", "i4"), 

7094 ("OffsetXV1", "i4"), 

7095 ("OffsetYV1", "i4"), 

7096 ("PhysicalPixelSize", "i4"), 

7097 ("Binning", "i4"), 

7098 ("ReadoutSpeed", "i4"), 

7099 ("GainV1", "i4"), 

7100 ("SensitivityV1", "i4"), 

7101 ("ExposureTimeV1", "i4"), 

7102 ("FlatCorrected", "i4"), 

7103 ("DeadPxCorrected", "i4"), 

7104 ("ImageMean", "i4"), 

7105 ("ImageStd", "i4"), 

7106 ("DisplacementX", "i4"), 

7107 ("DisplacementY", "i4"), 

7108 ("DateV1", "i4"), 

7109 ("TimeV1", "i4"), 

7110 ("ImageMin", "i4"), 

7111 ("ImageMax", "i4"), 

7112 ("ImageStatisticsQuality", "i4"), 

7113 ] 

7114 

7115 def TVIPS_HEADER_V2(): 

7116 return [ 

7117 ("ImageName", "V160"), # utf16 

7118 ("ImageFolder", "V160"), 

7119 ("ImageSizeX", "i4"), 

7120 ("ImageSizeY", "i4"), 

7121 ("ImageSizeZ", "i4"), 

7122 ("ImageSizeE", "i4"), 

7123 ("ImageDataType", "i4"), 

7124 ("Date", "i4"), 

7125 ("Time", "i4"), 

7126 ("Comment", "V1024"), 

7127 ("ImageHistory", "V1024"), 

7128 ("Scaling", "16f4"), 

7129 ("ImageStatistics", "16c16"), 

7130 ("ImageType", "i4"), 

7131 ("ImageDisplaType", "i4"), 

7132 ("PixelSizeX", "f4"), # distance between two px in x, [nm] 

7133 ("PixelSizeY", "f4"), # distance between two px in y, [nm] 

7134 ("ImageDistanceZ", "f4"), 

7135 ("ImageDistanceE", "f4"), 

7136 ("ImageMisc", "32f4"), 

7137 ("TemType", "V160"), 

7138 ("TemHighTension", "f4"), 

7139 ("TemAberrations", "32f4"), 

7140 ("TemEnergy", "32f4"), 

7141 ("TemMode", "i4"), 

7142 ("TemMagnification", "f4"), 

7143 ("TemMagnificationCorrection", "f4"), 

7144 ("PostMagnification", "f4"), 

7145 ("TemStageType", "i4"), 

7146 ("TemStagePosition", "5f4"), # x, y, z, a, b 

7147 ("TemImageShift", "2f4"), 

7148 ("TemBeamShift", "2f4"), 

7149 ("TemBeamTilt", "2f4"), 

7150 ("TilingParameters", "7f4"), # 0: tiling? 1:x 2:y 3: max x 

7151 # 4: max y 5: overlap x 6: overlap y 

7152 ("TemIllumination", "3f4"), # 0: spotsize 1: intensity 

7153 ("TemShutter", "i4"), 

7154 ("TemMisc", "32f4"), 

7155 ("CameraType", "V160"), 

7156 ("PhysicalPixelSizeX", "f4"), 

7157 ("PhysicalPixelSizeY", "f4"), 

7158 ("OffsetX", "i4"), 

7159 ("OffsetY", "i4"), 

7160 ("BinningX", "i4"), 

7161 ("BinningY", "i4"), 

7162 ("ExposureTime", "f4"), 

7163 ("Gain", "f4"), 

7164 ("ReadoutRate", "f4"), 

7165 ("FlatfieldDescription", "V160"), 

7166 ("Sensitivity", "f4"), 

7167 ("Dose", "f4"), 

7168 ("CamMisc", "32f4"), 

7169 ("FeiMicroscopeInformation", "V1024"), 

7170 ("FeiSpecimenInformation", "V1024"), 

7171 ("Magic", "u4"), 

7172 ] 

7173 

7174 def MM_HEADER(): 

7175 # Olympus FluoView MM_Header 

7176 MM_DIMENSION = [ 

7177 ("Name", "a16"), 

7178 ("Size", "i4"), 

7179 ("Origin", "f8"), 

7180 ("Resolution", "f8"), 

7181 ("Unit", "a64"), 

7182 ] 

7183 return [ 

7184 ("HeaderFlag", "i2"), 

7185 ("ImageType", "u1"), 

7186 ("ImageName", "a257"), 

7187 ("OffsetData", "u4"), 

7188 ("PaletteSize", "i4"), 

7189 ("OffsetPalette0", "u4"), 

7190 ("OffsetPalette1", "u4"), 

7191 ("CommentSize", "i4"), 

7192 ("OffsetComment", "u4"), 

7193 ("Dimensions", MM_DIMENSION, 10), 

7194 ("OffsetPosition", "u4"), 

7195 ("MapType", "i2"), 

7196 ("MapMin", "f8"), 

7197 ("MapMax", "f8"), 

7198 ("MinValue", "f8"), 

7199 ("MaxValue", "f8"), 

7200 ("OffsetMap", "u4"), 

7201 ("Gamma", "f8"), 

7202 ("Offset", "f8"), 

7203 ("GrayChannel", MM_DIMENSION), 

7204 ("OffsetThumbnail", "u4"), 

7205 ("VoiceField", "i4"), 

7206 ("OffsetVoiceField", "u4"), 

7207 ] 

7208 

7209 def MM_DIMENSIONS(): 

7210 # Map FluoView MM_Header.Dimensions to axes characters 

7211 return { 

7212 "X": "X", 

7213 "Y": "Y", 

7214 "Z": "Z", 

7215 "T": "T", 

7216 "CH": "C", 

7217 "WAVELENGTH": "C", 

7218 "TIME": "T", 

7219 "XY": "R", 

7220 "EVENT": "V", 

7221 "EXPOSURE": "L", 

7222 } 

7223 

7224 def UIC_TAGS(): 

7225 # Map Universal Imaging Corporation MetaMorph internal tag ids to 

7226 # name and type 

7227 from fractions import Fraction # delayed import 

7228 

7229 return [ 

7230 ("AutoScale", int), 

7231 ("MinScale", int), 

7232 ("MaxScale", int), 

7233 ("SpatialCalibration", int), 

7234 ("XCalibration", Fraction), 

7235 ("YCalibration", Fraction), 

7236 ("CalibrationUnits", str), 

7237 ("Name", str), 

7238 ("ThreshState", int), 

7239 ("ThreshStateRed", int), 

7240 ("tagid_10", None), # undefined 

7241 ("ThreshStateGreen", int), 

7242 ("ThreshStateBlue", int), 

7243 ("ThreshStateLo", int), 

7244 ("ThreshStateHi", int), 

7245 ("Zoom", int), 

7246 ("CreateTime", julian_datetime), 

7247 ("LastSavedTime", julian_datetime), 

7248 ("currentBuffer", int), 

7249 ("grayFit", None), 

7250 ("grayPointCount", None), 

7251 ("grayX", Fraction), 

7252 ("grayY", Fraction), 

7253 ("grayMin", Fraction), 

7254 ("grayMax", Fraction), 

7255 ("grayUnitName", str), 

7256 ("StandardLUT", int), 

7257 ("wavelength", int), 

7258 ("StagePosition", "(%i,2,2)u4"), # N xy positions as fract 

7259 ("CameraChipOffset", "(%i,2,2)u4"), # N xy offsets as fract 

7260 ("OverlayMask", None), 

7261 ("OverlayCompress", None), 

7262 ("Overlay", None), 

7263 ("SpecialOverlayMask", None), 

7264 ("SpecialOverlayCompress", None), 

7265 ("SpecialOverlay", None), 

7266 ("ImageProperty", read_uic_image_property), 

7267 ("StageLabel", "%ip"), # N str 

7268 ("AutoScaleLoInfo", Fraction), 

7269 ("AutoScaleHiInfo", Fraction), 

7270 ("AbsoluteZ", "(%i,2)u4"), # N fractions 

7271 ("AbsoluteZValid", "(%i,)u4"), # N long 

7272 ("Gamma", "I"), # 'I' uses offset 

7273 ("GammaRed", "I"), 

7274 ("GammaGreen", "I"), 

7275 ("GammaBlue", "I"), 

7276 ("CameraBin", "2I"), 

7277 ("NewLUT", int), 

7278 ("ImagePropertyEx", None), 

7279 ("PlaneProperty", int), 

7280 ("UserLutTable", "(256,3)u1"), 

7281 ("RedAutoScaleInfo", int), 

7282 ("RedAutoScaleLoInfo", Fraction), 

7283 ("RedAutoScaleHiInfo", Fraction), 

7284 ("RedMinScaleInfo", int), 

7285 ("RedMaxScaleInfo", int), 

7286 ("GreenAutoScaleInfo", int), 

7287 ("GreenAutoScaleLoInfo", Fraction), 

7288 ("GreenAutoScaleHiInfo", Fraction), 

7289 ("GreenMinScaleInfo", int), 

7290 ("GreenMaxScaleInfo", int), 

7291 ("BlueAutoScaleInfo", int), 

7292 ("BlueAutoScaleLoInfo", Fraction), 

7293 ("BlueAutoScaleHiInfo", Fraction), 

7294 ("BlueMinScaleInfo", int), 

7295 ("BlueMaxScaleInfo", int), 

7296 # ('OverlayPlaneColor', read_uic_overlay_plane_color), 

7297 ] 

7298 

7299 def PILATUS_HEADER(): 

7300 # PILATUS CBF Header Specification, Version 1.4 

7301 # Map key to [value_indices], type 

7302 return { 

7303 "Detector": ([slice(1, None)], str), 

7304 "Pixel_size": ([1, 4], float), 

7305 "Silicon": ([3], float), 

7306 "Exposure_time": ([1], float), 

7307 "Exposure_period": ([1], float), 

7308 "Tau": ([1], float), 

7309 "Count_cutoff": ([1], int), 

7310 "Threshold_setting": ([1], float), 

7311 "Gain_setting": ([1, 2], str), 

7312 "N_excluded_pixels": ([1], int), 

7313 "Excluded_pixels": ([1], str), 

7314 "Flat_field": ([1], str), 

7315 "Trim_file": ([1], str), 

7316 "Image_path": ([1], str), 

7317 # optional 

7318 "Wavelength": ([1], float), 

7319 "Energy_range": ([1, 2], float), 

7320 "Detector_distance": ([1], float), 

7321 "Detector_Voffset": ([1], float), 

7322 "Beam_xy": ([1, 2], float), 

7323 "Flux": ([1], str), 

7324 "Filter_transmission": ([1], float), 

7325 "Start_angle": ([1], float), 

7326 "Angle_increment": ([1], float), 

7327 "Detector_2theta": ([1], float), 

7328 "Polarization": ([1], float), 

7329 "Alpha": ([1], float), 

7330 "Kappa": ([1], float), 

7331 "Phi": ([1], float), 

7332 "Phi_increment": ([1], float), 

7333 "Chi": ([1], float), 

7334 "Chi_increment": ([1], float), 

7335 "Oscillation_axis": ([slice(1, None)], str), 

7336 "N_oscillations": ([1], int), 

7337 "Start_position": ([1], float), 

7338 "Position_increment": ([1], float), 

7339 "Shutter_time": ([1], float), 

7340 "Omega": ([1], float), 

7341 "Omega_increment": ([1], float), 

7342 } 

7343 

7344 def REVERSE_BITORDER_BYTES(): 

7345 # Bytes with reversed bitorder 

7346 return ( 

7347 b"\x00\x80@\xc0 \xa0`\xe0\x10\x90P\xd00\xb0p\xf0\x08\x88H\xc8(" 

7348 b"\xa8h\xe8\x18\x98X\xd88\xb8x\xf8\x04\x84D\xc4$\xa4d\xe4\x14" 

7349 b"\x94T\xd44\xb4t\xf4\x0c\x8cL\xcc,\xacl\xec\x1c\x9c\\\xdc<\xbc|" 

7350 b'\xfc\x02\x82B\xc2"\xa2b\xe2\x12\x92R\xd22\xb2r\xf2\n\x8aJ\xca*' 

7351 b"\xaaj\xea\x1a\x9aZ\xda:\xbaz\xfa\x06\x86F\xc6&\xa6f\xe6\x16" 

7352 b"\x96V\xd66\xb6v\xf6\x0e\x8eN\xce.\xaen\xee\x1e\x9e^\xde>\xbe~" 

7353 b"\xfe\x01\x81A\xc1!\xa1a\xe1\x11\x91Q\xd11\xb1q\xf1\t\x89I\xc9)" 

7354 b"\xa9i\xe9\x19\x99Y\xd99\xb9y\xf9\x05\x85E\xc5%\xa5e\xe5\x15" 

7355 b"\x95U\xd55\xb5u\xf5\r\x8dM\xcd-\xadm\xed\x1d\x9d]\xdd=\xbd}" 

7356 b"\xfd\x03\x83C\xc3#\xa3c\xe3\x13\x93S\xd33\xb3s\xf3\x0b\x8bK" 

7357 b"\xcb+\xabk\xeb\x1b\x9b[\xdb;\xbb{\xfb\x07\x87G\xc7'\xa7g\xe7" 

7358 b"\x17\x97W\xd77\xb7w\xf7\x0f\x8fO\xcf/\xafo\xef\x1f\x9f_" 

7359 b"\xdf?\xbf\x7f\xff" 

7360 ) 

7361 

7362 def REVERSE_BITORDER_ARRAY(): 

7363 # Numpy array of bytes with reversed bitorder 

7364 return numpy.frombuffer(TIFF.REVERSE_BITORDER_BYTES, dtype="uint8") 

7365 

7366 def ALLOCATIONGRANULARITY(): 

7367 # alignment for writing contiguous data to TIFF 

7368 import mmap # delayed import 

7369 

7370 return mmap.ALLOCATIONGRANULARITY 

7371 

7372 

7373def read_tags(fh, byteorder, offsetsize, tagnames, customtags=None, maxifds=None): 

7374 """Read tags from chain of IFDs and return as list of dicts. 

7375 

7376 The file handle position must be at a valid IFD header. 

7377 

7378 """ 

7379 if offsetsize == 4: 

7380 offsetformat = byteorder + "I" 

7381 tagnosize = 2 

7382 tagnoformat = byteorder + "H" 

7383 tagsize = 12 

7384 tagformat1 = byteorder + "HH" 

7385 tagformat2 = byteorder + "I4s" 

7386 elif offsetsize == 8: 

7387 offsetformat = byteorder + "Q" 

7388 tagnosize = 8 

7389 tagnoformat = byteorder + "Q" 

7390 tagsize = 20 

7391 tagformat1 = byteorder + "HH" 

7392 tagformat2 = byteorder + "Q8s" 

7393 else: 

7394 raise ValueError("invalid offset size") 

7395 

7396 if customtags is None: 

7397 customtags = {} 

7398 if maxifds is None: 

7399 maxifds = 2**32 

7400 

7401 result = [] 

7402 unpack = struct.unpack 

7403 offset = fh.tell() 

7404 while len(result) < maxifds: 

7405 # loop over IFDs 

7406 try: 

7407 tagno = unpack(tagnoformat, fh.read(tagnosize))[0] 

7408 if tagno > 4096: 

7409 raise ValueError("suspicious number of tags") 

7410 except Exception: 

7411 warnings.warn("corrupted tag list at offset %i" % offset) 

7412 break 

7413 

7414 tags = {} 

7415 data = fh.read(tagsize * tagno) 

7416 pos = fh.tell() 

7417 index = 0 

7418 for _ in range(tagno): 

7419 code, type_ = unpack(tagformat1, data[index : index + 4]) 

7420 count, value = unpack(tagformat2, data[index + 4 : index + tagsize]) 

7421 index += tagsize 

7422 name = tagnames.get(code, str(code)) 

7423 try: 

7424 dtype = TIFF.DATA_FORMATS[type_] 

7425 except KeyError: 

7426 raise TiffTag.Error("unknown tag data type %i" % type_) 

7427 

7428 fmt = "%s%i%s" % (byteorder, count * int(dtype[0]), dtype[1]) 

7429 size = struct.calcsize(fmt) 

7430 if size > offsetsize or code in customtags: 

7431 offset = unpack(offsetformat, value)[0] 

7432 if offset < 8 or offset > fh.size - size: 

7433 raise TiffTag.Error("invalid tag value offset %i" % offset) 

7434 fh.seek(offset) 

7435 if code in customtags: 

7436 readfunc = customtags[code][1] 

7437 value = readfunc(fh, byteorder, dtype, count, offsetsize) 

7438 elif type_ == 7 or (count > 1 and dtype[-1] == "B"): 

7439 value = read_bytes(fh, byteorder, dtype, count, offsetsize) 

7440 elif code in tagnames or dtype[-1] == "s": 

7441 value = unpack(fmt, fh.read(size)) 

7442 else: 

7443 value = read_numpy(fh, byteorder, dtype, count, offsetsize) 

7444 elif dtype[-1] == "B" or type_ == 7: 

7445 value = value[:size] 

7446 else: 

7447 value = unpack(fmt, value[:size]) 

7448 

7449 if code not in customtags and code not in TIFF.TAG_TUPLE: 

7450 if len(value) == 1: 

7451 value = value[0] 

7452 if type_ != 7 and dtype[-1] == "s" and isinstance(value, bytes): 

7453 # TIFF ASCII fields can contain multiple strings, 

7454 # each terminated with a NUL 

7455 try: 

7456 value = bytes2str(stripascii(value).strip()) 

7457 except UnicodeDecodeError: 

7458 warnings.warn("tag %i: coercing invalid ASCII to bytes" % code) 

7459 

7460 tags[name] = value 

7461 

7462 result.append(tags) 

7463 # read offset to next page 

7464 fh.seek(pos) 

7465 offset = unpack(offsetformat, fh.read(offsetsize))[0] 

7466 if offset == 0: 

7467 break 

7468 if offset >= fh.size: 

7469 warnings.warn("invalid page offset %i" % offset) 

7470 break 

7471 fh.seek(offset) 

7472 

7473 if result and maxifds == 1: 

7474 result = result[0] 

7475 return result 

7476 

7477 

7478def read_exif_ifd(fh, byteorder, dtype, count, offsetsize): 

7479 """Read EXIF tags from file and return as dict.""" 

7480 exif = read_tags(fh, byteorder, offsetsize, TIFF.EXIF_TAGS, maxifds=1) 

7481 for name in ("ExifVersion", "FlashpixVersion"): 

7482 try: 

7483 exif[name] = bytes2str(exif[name]) 

7484 except Exception: 

7485 pass 

7486 if "UserComment" in exif: 

7487 idcode = exif["UserComment"][:8] 

7488 try: 

7489 if idcode == b"ASCII\x00\x00\x00": 

7490 exif["UserComment"] = bytes2str(exif["UserComment"][8:]) 

7491 elif idcode == b"UNICODE\x00": 

7492 exif["UserComment"] = exif["UserComment"][8:].decode("utf-16") 

7493 except Exception: 

7494 pass 

7495 return exif 

7496 

7497 

7498def read_gps_ifd(fh, byteorder, dtype, count, offsetsize): 

7499 """Read GPS tags from file and return as dict.""" 

7500 return read_tags(fh, byteorder, offsetsize, TIFF.GPS_TAGS, maxifds=1) 

7501 

7502 

7503def read_interoperability_ifd(fh, byteorder, dtype, count, offsetsize): 

7504 """Read Interoperability tags from file and return as dict.""" 

7505 tag_names = {1: "InteroperabilityIndex"} 

7506 return read_tags(fh, byteorder, offsetsize, tag_names, maxifds=1) 

7507 

7508 

7509def read_bytes(fh, byteorder, dtype, count, offsetsize): 

7510 """Read tag data from file and return as byte string.""" 

7511 dtype = "B" if dtype[-1] == "s" else byteorder + dtype[-1] 

7512 count *= numpy.dtype(dtype).itemsize 

7513 data = fh.read(count) 

7514 if len(data) != count: 

7515 warnings.warn("failed to read all bytes: %i, %i" % (len(data), count)) 

7516 return data 

7517 

7518 

7519def read_utf8(fh, byteorder, dtype, count, offsetsize): 

7520 """Read tag data from file and return as unicode string.""" 

7521 return fh.read(count).decode("utf-8") 

7522 

7523 

7524def read_numpy(fh, byteorder, dtype, count, offsetsize): 

7525 """Read tag data from file and return as numpy array.""" 

7526 dtype = "b" if dtype[-1] == "s" else byteorder + dtype[-1] 

7527 return fh.read_array(dtype, count) 

7528 

7529 

7530def read_colormap(fh, byteorder, dtype, count, offsetsize): 

7531 """Read ColorMap data from file and return as numpy array.""" 

7532 cmap = fh.read_array(byteorder + dtype[-1], count) 

7533 cmap.shape = (3, -1) 

7534 return cmap 

7535 

7536 

7537def read_json(fh, byteorder, dtype, count, offsetsize): 

7538 """Read JSON tag data from file and return as object.""" 

7539 data = fh.read(count) 

7540 try: 

7541 return json.loads(unicode(stripnull(data), "utf-8")) 

7542 except ValueError: 

7543 warnings.warn("invalid JSON '%s'" % data) 

7544 

7545 

7546def read_mm_header(fh, byteorder, dtype, count, offsetsize): 

7547 """Read FluoView mm_header tag from file and return as dict.""" 

7548 mmh = fh.read_record(TIFF.MM_HEADER, byteorder=byteorder) 

7549 mmh = recarray2dict(mmh) 

7550 mmh["Dimensions"] = [ 

7551 (bytes2str(d[0]).strip(), d[1], d[2], d[3], bytes2str(d[4]).strip()) 

7552 for d in mmh["Dimensions"] 

7553 ] 

7554 d = mmh["GrayChannel"] 

7555 mmh["GrayChannel"] = ( 

7556 bytes2str(d[0]).strip(), 

7557 d[1], 

7558 d[2], 

7559 d[3], 

7560 bytes2str(d[4]).strip(), 

7561 ) 

7562 return mmh 

7563 

7564 

7565def read_mm_stamp(fh, byteorder, dtype, count, offsetsize): 

7566 """Read FluoView mm_stamp tag from file and return as numpy.ndarray.""" 

7567 return fh.read_array(byteorder + "f8", 8) 

7568 

7569 

7570def read_uic1tag(fh, byteorder, dtype, count, offsetsize, planecount=None): 

7571 """Read MetaMorph STK UIC1Tag from file and return as dict. 

7572 

7573 Return empty dictionary if planecount is unknown. 

7574 

7575 """ 

7576 assert dtype in ("2I", "1I") and byteorder == "<" 

7577 result = {} 

7578 if dtype == "2I": 

7579 # pre MetaMorph 2.5 (not tested) 

7580 values = fh.read_array("<u4", 2 * count).reshape(count, 2) 

7581 result = {"ZDistance": values[:, 0] / values[:, 1]} 

7582 elif planecount: 

7583 for _ in range(count): 

7584 tagid = struct.unpack("<I", fh.read(4))[0] 

7585 if tagid in (28, 29, 37, 40, 41): 

7586 # silently skip unexpected tags 

7587 fh.read(4) 

7588 continue 

7589 name, value = read_uic_tag(fh, tagid, planecount, offset=True) 

7590 result[name] = value 

7591 return result 

7592 

7593 

7594def read_uic2tag(fh, byteorder, dtype, planecount, offsetsize): 

7595 """Read MetaMorph STK UIC2Tag from file and return as dict.""" 

7596 assert dtype == "2I" and byteorder == "<" 

7597 values = fh.read_array("<u4", 6 * planecount).reshape(planecount, 6) 

7598 return { 

7599 "ZDistance": values[:, 0] / values[:, 1], 

7600 "DateCreated": values[:, 2], # julian days 

7601 "TimeCreated": values[:, 3], # milliseconds 

7602 "DateModified": values[:, 4], # julian days 

7603 "TimeModified": values[:, 5], 

7604 } # milliseconds 

7605 

7606 

7607def read_uic3tag(fh, byteorder, dtype, planecount, offsetsize): 

7608 """Read MetaMorph STK UIC3Tag from file and return as dict.""" 

7609 assert dtype == "2I" and byteorder == "<" 

7610 values = fh.read_array("<u4", 2 * planecount).reshape(planecount, 2) 

7611 return {"Wavelengths": values[:, 0] / values[:, 1]} 

7612 

7613 

7614def read_uic4tag(fh, byteorder, dtype, planecount, offsetsize): 

7615 """Read MetaMorph STK UIC4Tag from file and return as dict.""" 

7616 assert dtype == "1I" and byteorder == "<" 

7617 result = {} 

7618 while True: 

7619 tagid = struct.unpack("<H", fh.read(2))[0] 

7620 if tagid == 0: 

7621 break 

7622 name, value = read_uic_tag(fh, tagid, planecount, offset=False) 

7623 result[name] = value 

7624 return result 

7625 

7626 

7627def read_uic_tag(fh, tagid, planecount, offset): 

7628 """Read a single UIC tag value from file and return tag name and value. 

7629 

7630 UIC1Tags use an offset. 

7631 

7632 """ 

7633 

7634 def read_int(count=1): 

7635 value = struct.unpack("<%iI" % count, fh.read(4 * count)) 

7636 return value[0] if count == 1 else value 

7637 

7638 try: 

7639 name, dtype = TIFF.UIC_TAGS[tagid] 

7640 except IndexError: 

7641 # unknown tag 

7642 return "_TagId%i" % tagid, read_int() 

7643 

7644 Fraction = TIFF.UIC_TAGS[4][1] 

7645 

7646 if offset: 

7647 pos = fh.tell() 

7648 if dtype not in (int, None): 

7649 off = read_int() 

7650 if off < 8: 

7651 if dtype is str: 

7652 return name, "" 

7653 warnings.warn("invalid offset for uic tag '%s': %i" % (name, off)) 

7654 return name, off 

7655 fh.seek(off) 

7656 

7657 if dtype is None: 

7658 # skip 

7659 name = "_" + name 

7660 value = read_int() 

7661 elif dtype is int: 

7662 # int 

7663 value = read_int() 

7664 elif dtype is Fraction: 

7665 # fraction 

7666 value = read_int(2) 

7667 value = value[0] / value[1] 

7668 elif dtype is julian_datetime: 

7669 # datetime 

7670 value = julian_datetime(*read_int(2)) 

7671 elif dtype is read_uic_image_property: 

7672 # ImagePropertyEx 

7673 value = read_uic_image_property(fh) 

7674 elif dtype is str: 

7675 # pascal string 

7676 size = read_int() 

7677 if 0 <= size < 2**10: 

7678 value = struct.unpack("%is" % size, fh.read(size))[0][:-1] 

7679 value = bytes2str(stripnull(value)) 

7680 elif offset: 

7681 value = "" 

7682 warnings.warn("corrupt string in uic tag '%s'" % name) 

7683 else: 

7684 raise ValueError("invalid string size: %i" % size) 

7685 elif dtype == "%ip": 

7686 # sequence of pascal strings 

7687 value = [] 

7688 for _ in range(planecount): 

7689 size = read_int() 

7690 if 0 <= size < 2**10: 

7691 string = struct.unpack("%is" % size, fh.read(size))[0][:-1] 

7692 string = bytes2str(stripnull(string)) 

7693 value.append(string) 

7694 elif offset: 

7695 warnings.warn("corrupt string in uic tag '%s'" % name) 

7696 else: 

7697 raise ValueError("invalid string size: %i" % size) 

7698 else: 

7699 # struct or numpy type 

7700 dtype = "<" + dtype 

7701 if "%i" in dtype: 

7702 dtype = dtype % planecount 

7703 if "(" in dtype: 

7704 # numpy type 

7705 value = fh.read_array(dtype, 1)[0] 

7706 if value.shape[-1] == 2: 

7707 # assume fractions 

7708 value = value[..., 0] / value[..., 1] 

7709 else: 

7710 # struct format 

7711 value = struct.unpack(dtype, fh.read(struct.calcsize(dtype))) 

7712 if len(value) == 1: 

7713 value = value[0] 

7714 

7715 if offset: 

7716 fh.seek(pos + 4) 

7717 

7718 return name, value 

7719 

7720 

7721def read_uic_image_property(fh): 

7722 """Read UIC ImagePropertyEx tag from file and return as dict.""" 

7723 # TODO: test this 

7724 size = struct.unpack("B", fh.read(1))[0] 

7725 name = struct.unpack("%is" % size, fh.read(size))[0][:-1] 

7726 flags, prop = struct.unpack("<IB", fh.read(5)) 

7727 if prop == 1: 

7728 value = struct.unpack("II", fh.read(8)) 

7729 value = value[0] / value[1] 

7730 else: 

7731 size = struct.unpack("B", fh.read(1))[0] 

7732 value = struct.unpack("%is" % size, fh.read(size))[0] 

7733 return dict(name=name, flags=flags, value=value) 

7734 

7735 

7736def read_cz_lsminfo(fh, byteorder, dtype, count, offsetsize): 

7737 """Read CZ_LSMINFO tag from file and return as dict.""" 

7738 assert byteorder == "<" 

7739 magic_number, structure_size = struct.unpack("<II", fh.read(8)) 

7740 if magic_number not in (50350412, 67127628): 

7741 raise ValueError("invalid CZ_LSMINFO structure") 

7742 fh.seek(-8, 1) 

7743 

7744 if structure_size < numpy.dtype(TIFF.CZ_LSMINFO).itemsize: 

7745 # adjust structure according to structure_size 

7746 lsminfo = [] 

7747 size = 0 

7748 for name, dtype in TIFF.CZ_LSMINFO: 

7749 size += numpy.dtype(dtype).itemsize 

7750 if size > structure_size: 

7751 break 

7752 lsminfo.append((name, dtype)) 

7753 else: 

7754 lsminfo = TIFF.CZ_LSMINFO 

7755 

7756 lsminfo = fh.read_record(lsminfo, byteorder=byteorder) 

7757 lsminfo = recarray2dict(lsminfo) 

7758 

7759 # read LSM info subrecords at offsets 

7760 for name, reader in TIFF.CZ_LSMINFO_READERS.items(): 

7761 if reader is None: 

7762 continue 

7763 offset = lsminfo.get("Offset" + name, 0) 

7764 if offset < 8: 

7765 continue 

7766 fh.seek(offset) 

7767 try: 

7768 lsminfo[name] = reader(fh) 

7769 except ValueError: 

7770 pass 

7771 return lsminfo 

7772 

7773 

7774def read_lsm_floatpairs(fh): 

7775 """Read LSM sequence of float pairs from file and return as list.""" 

7776 size = struct.unpack("<i", fh.read(4))[0] 

7777 return fh.read_array("<2f8", count=size) 

7778 

7779 

7780def read_lsm_positions(fh): 

7781 """Read LSM positions from file and return as list.""" 

7782 size = struct.unpack("<I", fh.read(4))[0] 

7783 return fh.read_array("<2f8", count=size) 

7784 

7785 

7786def read_lsm_timestamps(fh): 

7787 """Read LSM time stamps from file and return as list.""" 

7788 size, count = struct.unpack("<ii", fh.read(8)) 

7789 if size != (8 + 8 * count): 

7790 warnings.warn("invalid LSM TimeStamps block") 

7791 return [] 

7792 # return struct.unpack('<%dd' % count, fh.read(8*count)) 

7793 return fh.read_array("<f8", count=count) 

7794 

7795 

7796def read_lsm_eventlist(fh): 

7797 """Read LSM events from file and return as list of (time, type, text).""" 

7798 count = struct.unpack("<II", fh.read(8))[1] 

7799 events = [] 

7800 while count > 0: 

7801 esize, etime, etype = struct.unpack("<IdI", fh.read(16)) 

7802 etext = bytes2str(stripnull(fh.read(esize - 16))) 

7803 events.append((etime, etype, etext)) 

7804 count -= 1 

7805 return events 

7806 

7807 

7808def read_lsm_channelcolors(fh): 

7809 """Read LSM ChannelColors structure from file and return as dict.""" 

7810 result = {"Mono": False, "Colors": [], "ColorNames": []} 

7811 pos = fh.tell() 

7812 (size, ncolors, nnames, coffset, noffset, mono) = struct.unpack( 

7813 "<IIIIII", fh.read(24) 

7814 ) 

7815 if ncolors != nnames: 

7816 warnings.warn("invalid LSM ChannelColors structure") 

7817 return result 

7818 result["Mono"] = bool(mono) 

7819 # Colors 

7820 fh.seek(pos + coffset) 

7821 colors = fh.read_array("uint8", count=ncolors * 4).reshape((ncolors, 4)) 

7822 result["Colors"] = colors.tolist() 

7823 # ColorNames 

7824 fh.seek(pos + noffset) 

7825 buffer = fh.read(size - noffset) 

7826 names = [] 

7827 while len(buffer) > 4: 

7828 size = struct.unpack("<I", buffer[:4])[0] 

7829 names.append(bytes2str(buffer[4 : 3 + size])) 

7830 buffer = buffer[4 + size :] 

7831 result["ColorNames"] = names 

7832 return result 

7833 

7834 

7835def read_lsm_scaninfo(fh): 

7836 """Read LSM ScanInfo structure from file and return as dict.""" 

7837 block = {} 

7838 blocks = [block] 

7839 unpack = struct.unpack 

7840 if struct.unpack("<I", fh.read(4))[0] != 0x10000000: 

7841 # not a Recording sub block 

7842 warnings.warn("invalid LSM ScanInfo structure") 

7843 return block 

7844 fh.read(8) 

7845 while True: 

7846 entry, dtype, size = unpack("<III", fh.read(12)) 

7847 if dtype == 2: 

7848 # ascii 

7849 value = bytes2str(stripnull(fh.read(size))) 

7850 elif dtype == 4: 

7851 # long 

7852 value = unpack("<i", fh.read(4))[0] 

7853 elif dtype == 5: 

7854 # rational 

7855 value = unpack("<d", fh.read(8))[0] 

7856 else: 

7857 value = 0 

7858 if entry in TIFF.CZ_LSMINFO_SCANINFO_ARRAYS: 

7859 blocks.append(block) 

7860 name = TIFF.CZ_LSMINFO_SCANINFO_ARRAYS[entry] 

7861 newobj = [] 

7862 block[name] = newobj 

7863 block = newobj 

7864 elif entry in TIFF.CZ_LSMINFO_SCANINFO_STRUCTS: 

7865 blocks.append(block) 

7866 newobj = {} 

7867 block.append(newobj) 

7868 block = newobj 

7869 elif entry in TIFF.CZ_LSMINFO_SCANINFO_ATTRIBUTES: 

7870 name = TIFF.CZ_LSMINFO_SCANINFO_ATTRIBUTES[entry] 

7871 block[name] = value 

7872 elif entry == 0xFFFFFFFF: 

7873 # end sub block 

7874 block = blocks.pop() 

7875 else: 

7876 # unknown entry 

7877 block["Entry0x%x" % entry] = value 

7878 if not blocks: 

7879 break 

7880 return block 

7881 

7882 

7883def read_tvips_header(fh, byteorder, dtype, count, offsetsize): 

7884 """Read TVIPS EM-MENU headers and return as dict.""" 

7885 result = {} 

7886 header = fh.read_record(TIFF.TVIPS_HEADER_V1, byteorder=byteorder) 

7887 for name, typestr in TIFF.TVIPS_HEADER_V1: 

7888 result[name] = header[name].tolist() 

7889 if header["Version"] == 2: 

7890 header = fh.read_record(TIFF.TVIPS_HEADER_V2, byteorder=byteorder) 

7891 if header["Magic"] != int(0xAAAAAAAA): 

7892 warnings.warn("invalid TVIPS v2 magic number") 

7893 return {} 

7894 # decode utf16 strings 

7895 for name, typestr in TIFF.TVIPS_HEADER_V2: 

7896 if typestr.startswith("V"): 

7897 s = header[name].tostring().decode("utf16", errors="ignore") 

7898 result[name] = stripnull(s, null="\0") 

7899 else: 

7900 result[name] = header[name].tolist() 

7901 # convert nm to m 

7902 for axis in "XY": 

7903 header["PhysicalPixelSize" + axis] /= 1e9 

7904 header["PixelSize" + axis] /= 1e9 

7905 elif header.version != 1: 

7906 warnings.warn("unknown TVIPS header version") 

7907 return {} 

7908 return result 

7909 

7910 

7911def read_fei_metadata(fh, byteorder, dtype, count, offsetsize): 

7912 """Read FEI SFEG/HELIOS headers and return as dict.""" 

7913 result = {} 

7914 section = {} 

7915 data = bytes2str(fh.read(count)) 

7916 for line in data.splitlines(): 

7917 line = line.strip() 

7918 if line.startswith("["): 

7919 section = {} 

7920 result[line[1:-1]] = section 

7921 continue 

7922 try: 

7923 key, value = line.split("=") 

7924 except ValueError: 

7925 continue 

7926 section[key] = astype(value) 

7927 return result 

7928 

7929 

7930def read_cz_sem(fh, byteorder, dtype, count, offsetsize): 

7931 """Read Zeiss SEM tag and return as dict.""" 

7932 result = {"": ()} 

7933 key = None 

7934 data = bytes2str(fh.read(count)) 

7935 for line in data.splitlines(): 

7936 if line.isupper(): 

7937 key = line.lower() 

7938 elif key: 

7939 try: 

7940 name, value = line.split("=") 

7941 except ValueError: 

7942 continue 

7943 value = value.strip() 

7944 unit = "" 

7945 try: 

7946 v, u = value.split() 

7947 number = astype(v, (int, float)) 

7948 if number != v: 

7949 value = number 

7950 unit = u 

7951 except Exception: 

7952 number = astype(value, (int, float)) 

7953 if number != value: 

7954 value = number 

7955 if value in ("No", "Off"): 

7956 value = False 

7957 elif value in ("Yes", "On"): 

7958 value = True 

7959 result[key] = (name.strip(), value) 

7960 if unit: 

7961 result[key] += (unit,) 

7962 key = None 

7963 else: 

7964 result[""] += (astype(line, (int, float)),) 

7965 return result 

7966 

7967 

7968def read_nih_image_header(fh, byteorder, dtype, count, offsetsize): 

7969 """Read NIH_IMAGE_HEADER tag from file and return as dict.""" 

7970 a = fh.read_record(TIFF.NIH_IMAGE_HEADER, byteorder=byteorder) 

7971 a = a.newbyteorder(byteorder) 

7972 a = recarray2dict(a) 

7973 a["XUnit"] = a["XUnit"][: a["XUnitSize"]] 

7974 a["UM"] = a["UM"][: a["UMsize"]] 

7975 return a 

7976 

7977 

7978def read_scanimage_metadata(fh): 

7979 """Read ScanImage BigTIFF v3 static and ROI metadata from open file. 

7980 

7981 Return non-varying frame data as dict and ROI group data as JSON. 

7982 

7983 The settings can be used to read image data and metadata without parsing 

7984 the TIFF file. 

7985 

7986 Raise ValueError if file does not contain valid ScanImage v3 metadata. 

7987 

7988 """ 

7989 fh.seek(0) 

7990 try: 

7991 byteorder, version = struct.unpack("<2sH", fh.read(4)) 

7992 if byteorder != b"II" or version != 43: 

7993 raise Exception 

7994 fh.seek(16) 

7995 magic, version, size0, size1 = struct.unpack("<IIII", fh.read(16)) 

7996 if magic != 117637889 or version != 3: 

7997 raise Exception 

7998 except Exception: 

7999 raise ValueError("not a ScanImage BigTIFF v3 file") 

8000 

8001 frame_data = matlabstr2py(bytes2str(fh.read(size0)[:-1])) 

8002 roi_data = read_json(fh, "<", None, size1, None) if size1 > 1 else {} 

8003 return frame_data, roi_data 

8004 

8005 

8006def read_micromanager_metadata(fh): 

8007 """Read MicroManager non-TIFF settings from open file and return as dict. 

8008 

8009 The settings can be used to read image data without parsing the TIFF file. 

8010 

8011 Raise ValueError if the file does not contain valid MicroManager metadata. 

8012 

8013 """ 

8014 fh.seek(0) 

8015 try: 

8016 byteorder = {b"II": "<", b"MM": ">"}[fh.read(2)] 

8017 except IndexError: 

8018 raise ValueError("not a MicroManager TIFF file") 

8019 

8020 result = {} 

8021 fh.seek(8) 

8022 ( 

8023 index_header, 

8024 index_offset, 

8025 display_header, 

8026 display_offset, 

8027 comments_header, 

8028 comments_offset, 

8029 summary_header, 

8030 summary_length, 

8031 ) = struct.unpack(byteorder + "IIIIIIII", fh.read(32)) 

8032 

8033 if summary_header != 2355492: 

8034 raise ValueError("invalid MicroManager summary header") 

8035 result["Summary"] = read_json(fh, byteorder, None, summary_length, None) 

8036 

8037 if index_header != 54773648: 

8038 raise ValueError("invalid MicroManager index header") 

8039 fh.seek(index_offset) 

8040 header, count = struct.unpack(byteorder + "II", fh.read(8)) 

8041 if header != 3453623: 

8042 raise ValueError("invalid MicroManager index header") 

8043 data = struct.unpack(byteorder + "IIIII" * count, fh.read(20 * count)) 

8044 result["IndexMap"] = { 

8045 "Channel": data[::5], 

8046 "Slice": data[1::5], 

8047 "Frame": data[2::5], 

8048 "Position": data[3::5], 

8049 "Offset": data[4::5], 

8050 } 

8051 

8052 if display_header != 483765892: 

8053 raise ValueError("invalid MicroManager display header") 

8054 fh.seek(display_offset) 

8055 header, count = struct.unpack(byteorder + "II", fh.read(8)) 

8056 if header != 347834724: 

8057 raise ValueError("invalid MicroManager display header") 

8058 result["DisplaySettings"] = read_json(fh, byteorder, None, count, None) 

8059 

8060 if comments_header != 99384722: 

8061 raise ValueError("invalid MicroManager comments header") 

8062 fh.seek(comments_offset) 

8063 header, count = struct.unpack(byteorder + "II", fh.read(8)) 

8064 if header != 84720485: 

8065 raise ValueError("invalid MicroManager comments header") 

8066 result["Comments"] = read_json(fh, byteorder, None, count, None) 

8067 

8068 return result 

8069 

8070 

8071def read_metaseries_catalog(fh): 

8072 """Read MetaSeries non-TIFF hint catalog from file. 

8073 

8074 Raise ValueError if the file does not contain a valid hint catalog. 

8075 

8076 """ 

8077 # TODO: implement read_metaseries_catalog 

8078 raise NotImplementedError() 

8079 

8080 

8081def imagej_metadata_tags(metadata, byteorder): 

8082 """Return IJMetadata and IJMetadataByteCounts tags from metadata dict. 

8083 

8084 The tags can be passed to the TiffWriter.save function as extratags. 

8085 

8086 The metadata dict may contain the following keys and values: 

8087 

8088 Info : str 

8089 Human-readable information as string. 

8090 Labels : sequence of str 

8091 Human-readable labels for each channel. 

8092 Ranges : sequence of doubles 

8093 Lower and upper values for each channel. 

8094 LUTs : sequence of (3, 256) uint8 ndarrays 

8095 Color palettes for each channel. 

8096 Plot : bytes 

8097 Undocumented ImageJ internal format. 

8098 ROI: bytes 

8099 Undocumented ImageJ internal region of interest format. 

8100 Overlays : bytes 

8101 Undocumented ImageJ internal format. 

8102 

8103 """ 

8104 header = [{">": b"IJIJ", "<": b"JIJI"}[byteorder]] 

8105 bytecounts = [0] 

8106 body = [] 

8107 

8108 def _string(data, byteorder): 

8109 return data.encode("utf-16" + {">": "be", "<": "le"}[byteorder]) 

8110 

8111 def _doubles(data, byteorder): 

8112 return struct.pack(byteorder + ("d" * len(data)), *data) 

8113 

8114 def _ndarray(data, byteorder): 

8115 return data.tobytes() 

8116 

8117 def _bytes(data, byteorder): 

8118 return data 

8119 

8120 metadata_types = ( 

8121 ("Info", b"info", 1, _string), 

8122 ("Labels", b"labl", None, _string), 

8123 ("Ranges", b"rang", 1, _doubles), 

8124 ("LUTs", b"luts", None, _ndarray), 

8125 ("Plot", b"plot", 1, _bytes), 

8126 ("ROI", b"roi ", 1, _bytes), 

8127 ("Overlays", b"over", None, _bytes), 

8128 ) 

8129 

8130 for key, mtype, count, func in metadata_types: 

8131 if key.lower() in metadata: 

8132 key = key.lower() 

8133 elif key not in metadata: 

8134 continue 

8135 if byteorder == "<": 

8136 mtype = mtype[::-1] 

8137 values = metadata[key] 

8138 if count is None: 

8139 count = len(values) 

8140 else: 

8141 values = [values] 

8142 header.append(mtype + struct.pack(byteorder + "I", count)) 

8143 for value in values: 

8144 data = func(value, byteorder) 

8145 body.append(data) 

8146 bytecounts.append(len(data)) 

8147 

8148 if not body: 

8149 return () 

8150 body = b"".join(body) 

8151 header = b"".join(header) 

8152 data = header + body 

8153 bytecounts[0] = len(header) 

8154 bytecounts = struct.pack(byteorder + ("I" * len(bytecounts)), *bytecounts) 

8155 return ( 

8156 (50839, "B", len(data), data, True), 

8157 (50838, "I", len(bytecounts) // 4, bytecounts, True), 

8158 ) 

8159 

8160 

8161def imagej_metadata(data, bytecounts, byteorder): 

8162 """Return IJMetadata tag value as dict. 

8163 

8164 The 'Info' string can have multiple formats, e.g. OIF or ScanImage, 

8165 that might be parsed into dicts using the matlabstr2py or 

8166 oiffile.SettingsFile functions. 

8167 

8168 """ 

8169 

8170 def _string(data, byteorder): 

8171 return data.decode("utf-16" + {">": "be", "<": "le"}[byteorder]) 

8172 

8173 def _doubles(data, byteorder): 

8174 return struct.unpack(byteorder + ("d" * (len(data) // 8)), data) 

8175 

8176 def _lut(data, byteorder): 

8177 return numpy.frombuffer(data, "uint8").reshape(-1, 256) 

8178 

8179 def _bytes(data, byteorder): 

8180 return data 

8181 

8182 metadata_types = { # big-endian 

8183 b"info": ("Info", _string), 

8184 b"labl": ("Labels", _string), 

8185 b"rang": ("Ranges", _doubles), 

8186 b"luts": ("LUTs", _lut), 

8187 b"plot": ("Plots", _bytes), 

8188 b"roi ": ("ROI", _bytes), 

8189 b"over": ("Overlays", _bytes), 

8190 } 

8191 metadata_types.update( # little-endian 

8192 dict((k[::-1], v) for k, v in metadata_types.items()) 

8193 ) 

8194 

8195 if not bytecounts: 

8196 raise ValueError("no ImageJ metadata") 

8197 

8198 if data[:4] not in (b"IJIJ", b"JIJI"): 

8199 raise ValueError("invalid ImageJ metadata") 

8200 

8201 header_size = bytecounts[0] 

8202 if header_size < 12 or header_size > 804: 

8203 raise ValueError("invalid ImageJ metadata header size") 

8204 

8205 ntypes = (header_size - 4) // 8 

8206 header = struct.unpack(byteorder + "4sI" * ntypes, data[4 : 4 + ntypes * 8]) 

8207 pos = 4 + ntypes * 8 

8208 counter = 0 

8209 result = {} 

8210 for mtype, count in zip(header[::2], header[1::2]): 

8211 values = [] 

8212 name, func = metadata_types.get(mtype, (bytes2str(mtype), read_bytes)) 

8213 for _ in range(count): 

8214 counter += 1 

8215 pos1 = pos + bytecounts[counter] 

8216 values.append(func(data[pos:pos1], byteorder)) 

8217 pos = pos1 

8218 result[name.strip()] = values[0] if count == 1 else values 

8219 return result 

8220 

8221 

8222def imagej_description_metadata(description): 

8223 """Return metatata from ImageJ image description as dict. 

8224 

8225 Raise ValueError if not a valid ImageJ description. 

8226 

8227 >>> description = 'ImageJ=1.11a\\nimages=510\\nhyperstack=true\\n' 

8228 >>> imagej_description_metadata(description) # doctest: +SKIP 

8229 {'ImageJ': '1.11a', 'images': 510, 'hyperstack': True} 

8230 

8231 """ 

8232 

8233 def _bool(val): 

8234 return {"true": True, "false": False}[val.lower()] 

8235 

8236 result = {} 

8237 for line in description.splitlines(): 

8238 try: 

8239 key, val = line.split("=") 

8240 except Exception: 

8241 continue 

8242 key = key.strip() 

8243 val = val.strip() 

8244 for dtype in (int, float, _bool): 

8245 try: 

8246 val = dtype(val) 

8247 break 

8248 except Exception: 

8249 pass 

8250 result[key] = val 

8251 

8252 if "ImageJ" not in result: 

8253 raise ValueError("not a ImageJ image description") 

8254 return result 

8255 

8256 

8257def imagej_description( 

8258 shape, 

8259 rgb=None, 

8260 colormaped=False, 

8261 version="1.11a", 

8262 hyperstack=None, 

8263 mode=None, 

8264 loop=None, 

8265 **kwargs 

8266): 

8267 """Return ImageJ image description from data shape. 

8268 

8269 ImageJ can handle up to 6 dimensions in order TZCYXS. 

8270 

8271 >>> imagej_description((51, 5, 2, 196, 171)) # doctest: +SKIP 

8272 ImageJ=1.11a 

8273 images=510 

8274 channels=2 

8275 slices=5 

8276 frames=51 

8277 hyperstack=true 

8278 mode=grayscale 

8279 loop=false 

8280 

8281 """ 

8282 if colormaped: 

8283 raise NotImplementedError("ImageJ colormapping not supported") 

8284 shape = imagej_shape(shape, rgb=rgb) 

8285 rgb = shape[-1] in (3, 4) 

8286 

8287 result = ["ImageJ=%s" % version] 

8288 append = [] 

8289 result.append("images=%i" % product(shape[:-3])) 

8290 if hyperstack is None: 

8291 hyperstack = True 

8292 append.append("hyperstack=true") 

8293 else: 

8294 append.append("hyperstack=%s" % bool(hyperstack)) 

8295 if shape[2] > 1: 

8296 result.append("channels=%i" % shape[2]) 

8297 if mode is None and not rgb: 

8298 mode = "grayscale" 

8299 if hyperstack and mode: 

8300 append.append("mode=%s" % mode) 

8301 if shape[1] > 1: 

8302 result.append("slices=%i" % shape[1]) 

8303 if shape[0] > 1: 

8304 result.append("frames=%i" % shape[0]) 

8305 if loop is None: 

8306 append.append("loop=false") 

8307 if loop is not None: 

8308 append.append("loop=%s" % bool(loop)) 

8309 for key, value in kwargs.items(): 

8310 append.append("%s=%s" % (key.lower(), value)) 

8311 

8312 return "\n".join(result + append + [""]) 

8313 

8314 

8315def imagej_shape(shape, rgb=None): 

8316 """Return shape normalized to 6D ImageJ hyperstack TZCYXS. 

8317 

8318 Raise ValueError if not a valid ImageJ hyperstack shape. 

8319 

8320 >>> imagej_shape((2, 3, 4, 5, 3), False) 

8321 (2, 3, 4, 5, 3, 1) 

8322 

8323 """ 

8324 shape = tuple(int(i) for i in shape) 

8325 ndim = len(shape) 

8326 if 1 > ndim > 6: 

8327 raise ValueError("invalid ImageJ hyperstack: not 2 to 6 dimensional") 

8328 if rgb is None: 

8329 rgb = shape[-1] in (3, 4) and ndim > 2 

8330 if rgb and shape[-1] not in (3, 4): 

8331 raise ValueError("invalid ImageJ hyperstack: not a RGB image") 

8332 if not rgb and ndim == 6 and shape[-1] != 1: 

8333 raise ValueError("invalid ImageJ hyperstack: not a non-RGB image") 

8334 if rgb or shape[-1] == 1: 

8335 return (1,) * (6 - ndim) + shape 

8336 return (1,) * (5 - ndim) + shape + (1,) 

8337 

8338 

8339def json_description(shape, **metadata): 

8340 """Return JSON image description from data shape and other meta data. 

8341 

8342 Return UTF-8 encoded JSON. 

8343 

8344 >>> json_description((256, 256, 3), axes='YXS') # doctest: +SKIP 

8345 b'{"shape": [256, 256, 3], "axes": "YXS"}' 

8346 

8347 """ 

8348 metadata.update(shape=shape) 

8349 return json.dumps(metadata) # .encode('utf-8') 

8350 

8351 

8352def json_description_metadata(description): 

8353 """Return metatata from JSON formatted image description as dict. 

8354 

8355 Raise ValuError if description is of unknown format. 

8356 

8357 >>> description = '{"shape": [256, 256, 3], "axes": "YXS"}' 

8358 >>> json_description_metadata(description) # doctest: +SKIP 

8359 {'shape': [256, 256, 3], 'axes': 'YXS'} 

8360 >>> json_description_metadata('shape=(256, 256, 3)') 

8361 {'shape': (256, 256, 3)} 

8362 

8363 """ 

8364 if description[:6] == "shape=": 

8365 # old style 'shaped' description; not JSON 

8366 shape = tuple(int(i) for i in description[7:-1].split(",")) 

8367 return dict(shape=shape) 

8368 if description[:1] == "{" and description[-1:] == "}": 

8369 # JSON description 

8370 return json.loads(description) 

8371 raise ValueError("invalid JSON image description", description) 

8372 

8373 

8374def fluoview_description_metadata(description, ignoresections=None): 

8375 """Return metatata from FluoView image description as dict. 

8376 

8377 The FluoView image description format is unspecified. Expect failures. 

8378 

8379 >>> descr = ('[Intensity Mapping]\\nMap Ch0: Range=00000 to 02047\\n' 

8380 ... '[Intensity Mapping End]') 

8381 >>> fluoview_description_metadata(descr) 

8382 {'Intensity Mapping': {'Map Ch0: Range': '00000 to 02047'}} 

8383 

8384 """ 

8385 if not description.startswith("["): 

8386 raise ValueError("invalid FluoView image description") 

8387 if ignoresections is None: 

8388 ignoresections = {"Region Info (Fields)", "Protocol Description"} 

8389 

8390 result = {} 

8391 sections = [result] 

8392 comment = False 

8393 for line in description.splitlines(): 

8394 if not comment: 

8395 line = line.strip() 

8396 if not line: 

8397 continue 

8398 if line[0] == "[": 

8399 if line[-5:] == " End]": 

8400 # close section 

8401 del sections[-1] 

8402 section = sections[-1] 

8403 name = line[1:-5] 

8404 if comment: 

8405 section[name] = "\n".join(section[name]) 

8406 if name[:4] == "LUT ": 

8407 a = numpy.array(section[name], dtype="uint8") 

8408 a.shape = -1, 3 

8409 section[name] = a 

8410 continue 

8411 # new section 

8412 comment = False 

8413 name = line[1:-1] 

8414 if name[:4] == "LUT ": 

8415 section = [] 

8416 elif name in ignoresections: 

8417 section = [] 

8418 comment = True 

8419 else: 

8420 section = {} 

8421 sections.append(section) 

8422 result[name] = section 

8423 continue 

8424 # add entry 

8425 if comment: 

8426 section.append(line) 

8427 continue 

8428 line = line.split("=", 1) 

8429 if len(line) == 1: 

8430 section[line[0].strip()] = None 

8431 continue 

8432 key, value = line 

8433 if key[:4] == "RGB ": 

8434 section.extend(int(rgb) for rgb in value.split()) 

8435 else: 

8436 section[key.strip()] = astype(value.strip()) 

8437 return result 

8438 

8439 

8440def pilatus_description_metadata(description): 

8441 """Return metatata from Pilatus image description as dict. 

8442 

8443 Return metadata from Pilatus pixel array detectors by Dectris, created 

8444 by camserver or TVX software. 

8445 

8446 >>> pilatus_description_metadata('# Pixel_size 172e-6 m x 172e-6 m') 

8447 {'Pixel_size': (0.000172, 0.000172)} 

8448 

8449 """ 

8450 result = {} 

8451 if not description.startswith("# "): 

8452 return result 

8453 for c in "#:=,()": 

8454 description = description.replace(c, " ") 

8455 for line in description.split("\n"): 

8456 if line[:2] != " ": 

8457 continue 

8458 line = line.split() 

8459 name = line[0] 

8460 if line[0] not in TIFF.PILATUS_HEADER: 

8461 try: 

8462 result["DateTime"] = datetime.datetime.strptime( 

8463 " ".join(line), "%Y-%m-%dT%H %M %S.%f" 

8464 ) 

8465 except Exception: 

8466 result[name] = " ".join(line[1:]) 

8467 continue 

8468 indices, dtype = TIFF.PILATUS_HEADER[line[0]] 

8469 if isinstance(indices[0], slice): 

8470 # assumes one slice 

8471 values = line[indices[0]] 

8472 else: 

8473 values = [line[i] for i in indices] 

8474 if dtype is float and values[0] == "not": 

8475 values = ["NaN"] 

8476 values = tuple(dtype(v) for v in values) 

8477 if dtype == str: 

8478 values = " ".join(values) 

8479 elif len(values) == 1: 

8480 values = values[0] 

8481 result[name] = values 

8482 return result 

8483 

8484 

8485def svs_description_metadata(description): 

8486 """Return metatata from Aperio image description as dict. 

8487 

8488 The Aperio image description format is unspecified. Expect failures. 

8489 

8490 >>> svs_description_metadata('Aperio Image Library v1.0') 

8491 {'Aperio Image Library': 'v1.0'} 

8492 

8493 """ 

8494 if not description.startswith("Aperio Image Library "): 

8495 raise ValueError("invalid Aperio image description") 

8496 result = {} 

8497 lines = description.split("\n") 

8498 key, value = lines[0].strip().rsplit(None, 1) # 'Aperio Image Library' 

8499 result[key.strip()] = value.strip() 

8500 if len(lines) == 1: 

8501 return result 

8502 items = lines[1].split("|") 

8503 result[""] = items[0].strip() # TODO: parse this? 

8504 for item in items[1:]: 

8505 key, value = item.split(" = ") 

8506 result[key.strip()] = astype(value.strip()) 

8507 return result 

8508 

8509 

8510def stk_description_metadata(description): 

8511 """Return metadata from MetaMorph image description as list of dict. 

8512 

8513 The MetaMorph image description format is unspecified. Expect failures. 

8514 

8515 """ 

8516 description = description.strip() 

8517 if not description: 

8518 return [] 

8519 try: 

8520 description = bytes2str(description) 

8521 except UnicodeDecodeError: 

8522 warnings.warn("failed to parse MetaMorph image description") 

8523 return [] 

8524 result = [] 

8525 for plane in description.split("\x00"): 

8526 d = {} 

8527 for line in plane.split("\r\n"): 

8528 line = line.split(":", 1) 

8529 if len(line) > 1: 

8530 name, value = line 

8531 d[name.strip()] = astype(value.strip()) 

8532 else: 

8533 value = line[0].strip() 

8534 if value: 

8535 if "" in d: 

8536 d[""].append(value) 

8537 else: 

8538 d[""] = [value] 

8539 result.append(d) 

8540 return result 

8541 

8542 

8543def metaseries_description_metadata(description): 

8544 """Return metatata from MetaSeries image description as dict.""" 

8545 if not description.startswith("<MetaData>"): 

8546 raise ValueError("invalid MetaSeries image description") 

8547 

8548 from xml.etree import cElementTree as etree # delayed import 

8549 

8550 root = etree.fromstring(description) 

8551 types = {"float": float, "int": int, "bool": lambda x: asbool(x, "on", "off")} 

8552 

8553 def parse(root, result): 

8554 # recursive 

8555 for child in root: 

8556 attrib = child.attrib 

8557 if not attrib: 

8558 result[child.tag] = parse(child, {}) 

8559 continue 

8560 if "id" in attrib: 

8561 i = attrib["id"] 

8562 t = attrib["type"] 

8563 v = attrib["value"] 

8564 if t in types: 

8565 result[i] = types[t](v) 

8566 else: 

8567 result[i] = v 

8568 return result 

8569 

8570 adict = parse(root, {}) 

8571 if "Description" in adict: 

8572 adict["Description"] = adict["Description"].replace("&#13;&#10;", "\n") 

8573 return adict 

8574 

8575 

8576def scanimage_description_metadata(description): 

8577 """Return metatata from ScanImage image description as dict.""" 

8578 return matlabstr2py(description) 

8579 

8580 

8581def scanimage_artist_metadata(artist): 

8582 """Return metatata from ScanImage artist tag as dict.""" 

8583 try: 

8584 return json.loads(artist) 

8585 except ValueError: 

8586 warnings.warn("invalid JSON '%s'" % artist) 

8587 

8588 

8589def _replace_by(module_function, package=__package__, warn=None, prefix="_"): 

8590 """Try replace decorated function by module.function.""" 

8591 return lambda f: f # imageio: just use what's in here 

8592 

8593 def _warn(e, warn): 

8594 if warn is None: 

8595 warn = "\n Functionality might be degraded or be slow.\n" 

8596 elif warn is True: 

8597 warn = "" 

8598 elif not warn: 

8599 return 

8600 warnings.warn("%s%s" % (e, warn)) 

8601 

8602 try: 

8603 from importlib import import_module 

8604 except ImportError as e: 

8605 _warn(e, warn) 

8606 return identityfunc 

8607 

8608 def decorate(func, module_function=module_function, warn=warn): 

8609 module, function = module_function.split(".") 

8610 try: 

8611 if package: 

8612 module = import_module("." + module, package=package) 

8613 else: 

8614 module = import_module(module) 

8615 except Exception as e: 

8616 _warn(e, warn) 

8617 return func 

8618 try: 

8619 func, oldfunc = getattr(module, function), func 

8620 except Exception as e: 

8621 _warn(e, warn) 

8622 return func 

8623 globals()[prefix + func.__name__] = oldfunc 

8624 return func 

8625 

8626 return decorate 

8627 

8628 

8629def decode_floats(data): 

8630 """Decode floating point horizontal differencing. 

8631 

8632 The TIFF predictor type 3 reorders the bytes of the image values and 

8633 applies horizontal byte differencing to improve compression of floating 

8634 point images. The ordering of interleaved color channels is preserved. 

8635 

8636 Parameters 

8637 ---------- 

8638 data : numpy.ndarray 

8639 The image to be decoded. The dtype must be a floating point. 

8640 The shape must include the number of contiguous samples per pixel 

8641 even if 1. 

8642 

8643 """ 

8644 shape = data.shape 

8645 dtype = data.dtype 

8646 if len(shape) < 3: 

8647 raise ValueError("invalid data shape") 

8648 if dtype.char not in "dfe": 

8649 raise ValueError("not a floating point image") 

8650 littleendian = data.dtype.byteorder == "<" or ( 

8651 sys.byteorder == "little" and data.dtype.byteorder == "=" 

8652 ) 

8653 # undo horizontal byte differencing 

8654 data = data.view("uint8") 

8655 data.shape = shape[:-2] + (-1,) + shape[-1:] 

8656 numpy.cumsum(data, axis=-2, dtype="uint8", out=data) 

8657 # reorder bytes 

8658 if littleendian: 

8659 data.shape = shape[:-2] + (-1,) + shape[-2:] 

8660 data = numpy.swapaxes(data, -3, -2) 

8661 data = numpy.swapaxes(data, -2, -1) 

8662 data = data[..., ::-1] 

8663 # back to float 

8664 data = numpy.ascontiguousarray(data) 

8665 data = data.view(dtype) 

8666 data.shape = shape 

8667 return data 

8668 

8669 

8670@_replace_by("_tifffile.decode_packbits") 

8671def decode_packbits(encoded): 

8672 """Decompress PackBits encoded byte string. 

8673 

8674 PackBits is a simple byte-oriented run-length compression scheme. 

8675 

8676 """ 

8677 func = ord if sys.version[0] == "2" else identityfunc 

8678 result = [] 

8679 result_extend = result.extend 

8680 i = 0 

8681 try: 

8682 while True: 

8683 n = func(encoded[i]) + 1 

8684 i += 1 

8685 if n < 129: 

8686 result_extend(encoded[i : i + n]) 

8687 i += n 

8688 elif n > 129: 

8689 result_extend(encoded[i : i + 1] * (258 - n)) 

8690 i += 1 

8691 except IndexError: 

8692 pass 

8693 return b"".join(result) if sys.version[0] == "2" else bytes(result) 

8694 

8695 

8696@_replace_by("_tifffile.decode_lzw") 

8697def decode_lzw(encoded): 

8698 """Decompress LZW (Lempel-Ziv-Welch) encoded TIFF strip (byte string). 

8699 

8700 The strip must begin with a CLEAR code and end with an EOI code. 

8701 

8702 This implementation of the LZW decoding algorithm is described in (1) and 

8703 is not compatible with old style LZW compressed files like quad-lzw.tif. 

8704 

8705 """ 

8706 len_encoded = len(encoded) 

8707 bitcount_max = len_encoded * 8 

8708 unpack = struct.unpack 

8709 

8710 if sys.version[0] == "2": 

8711 newtable = [chr(i) for i in range(256)] 

8712 else: 

8713 newtable = [bytes([i]) for i in range(256)] 

8714 newtable.extend((0, 0)) 

8715 

8716 def next_code(): 

8717 """Return integer of 'bitw' bits at 'bitcount' position in encoded.""" 

8718 start = bitcount // 8 

8719 s = encoded[start : start + 4] 

8720 try: 

8721 code = unpack(">I", s)[0] 

8722 except Exception: 

8723 code = unpack(">I", s + b"\x00" * (4 - len(s)))[0] 

8724 code <<= bitcount % 8 

8725 code &= mask 

8726 return code >> shr 

8727 

8728 switchbitch = { # code: bit-width, shr-bits, bit-mask 

8729 255: (9, 23, int(9 * "1" + "0" * 23, 2)), 

8730 511: (10, 22, int(10 * "1" + "0" * 22, 2)), 

8731 1023: (11, 21, int(11 * "1" + "0" * 21, 2)), 

8732 2047: (12, 20, int(12 * "1" + "0" * 20, 2)), 

8733 } 

8734 bitw, shr, mask = switchbitch[255] 

8735 bitcount = 0 

8736 

8737 if len_encoded < 4: 

8738 raise ValueError("strip must be at least 4 characters long") 

8739 

8740 if next_code() != 256: 

8741 raise ValueError("strip must begin with CLEAR code") 

8742 

8743 code = 0 

8744 oldcode = 0 

8745 result = [] 

8746 result_append = result.append 

8747 while True: 

8748 code = next_code() # ~5% faster when inlining this function 

8749 bitcount += bitw 

8750 if code == 257 or bitcount >= bitcount_max: # EOI 

8751 break 

8752 if code == 256: # CLEAR 

8753 table = newtable[:] 

8754 table_append = table.append 

8755 lentable = 258 

8756 bitw, shr, mask = switchbitch[255] 

8757 code = next_code() 

8758 bitcount += bitw 

8759 if code == 257: # EOI 

8760 break 

8761 result_append(table[code]) 

8762 else: 

8763 if code < lentable: 

8764 decoded = table[code] 

8765 newcode = table[oldcode] + decoded[:1] 

8766 else: 

8767 newcode = table[oldcode] 

8768 newcode += newcode[:1] 

8769 decoded = newcode 

8770 result_append(decoded) 

8771 table_append(newcode) 

8772 lentable += 1 

8773 oldcode = code 

8774 if lentable in switchbitch: 

8775 bitw, shr, mask = switchbitch[lentable] 

8776 

8777 if code != 257: 

8778 warnings.warn("unexpected end of LZW stream (code %i)" % code) 

8779 

8780 return b"".join(result) 

8781 

8782 

8783@_replace_by("_tifffile.unpack_ints") 

8784def unpack_ints(data, dtype, itemsize, runlen=0): 

8785 """Decompress byte string to array of integers of any bit size <= 32. 

8786 

8787 This Python implementation is slow and only handles itemsizes 1, 2, 4, 8, 

8788 16, 32, and 64. 

8789 

8790 Parameters 

8791 ---------- 

8792 data : byte str 

8793 Data to decompress. 

8794 dtype : numpy.dtype or str 

8795 A numpy boolean or integer type. 

8796 itemsize : int 

8797 Number of bits per integer. 

8798 runlen : int 

8799 Number of consecutive integers, after which to start at next byte. 

8800 

8801 Examples 

8802 -------- 

8803 >>> unpack_ints(b'a', 'B', 1) 

8804 array([0, 1, 1, 0, 0, 0, 0, 1], dtype=uint8) 

8805 >>> unpack_ints(b'ab', 'B', 2) 

8806 array([1, 2, 0, 1, 1, 2, 0, 2], dtype=uint8) 

8807 

8808 """ 

8809 if itemsize == 1: # bitarray 

8810 data = numpy.frombuffer(data, "|B") 

8811 data = numpy.unpackbits(data) 

8812 if runlen % 8: 

8813 data = data.reshape(-1, runlen + (8 - runlen % 8)) 

8814 data = data[:, :runlen].reshape(-1) 

8815 return data.astype(dtype) 

8816 

8817 dtype = numpy.dtype(dtype) 

8818 if itemsize in (8, 16, 32, 64): 

8819 return numpy.frombuffer(data, dtype) 

8820 if itemsize not in (1, 2, 4, 8, 16, 32): 

8821 raise ValueError("itemsize not supported: %i" % itemsize) 

8822 if dtype.kind not in "biu": 

8823 raise ValueError("invalid dtype") 

8824 

8825 itembytes = next(i for i in (1, 2, 4, 8) if 8 * i >= itemsize) 

8826 if itembytes != dtype.itemsize: 

8827 raise ValueError("dtype.itemsize too small") 

8828 if runlen == 0: 

8829 runlen = (8 * len(data)) // itemsize 

8830 skipbits = runlen * itemsize % 8 

8831 if skipbits: 

8832 skipbits = 8 - skipbits 

8833 shrbits = itembytes * 8 - itemsize 

8834 bitmask = int(itemsize * "1" + "0" * shrbits, 2) 

8835 dtypestr = ">" + dtype.char # dtype always big-endian? 

8836 

8837 unpack = struct.unpack 

8838 size = runlen * (len(data) * 8 // (runlen * itemsize + skipbits)) 

8839 result = numpy.empty((size,), dtype) 

8840 bitcount = 0 

8841 for i in range(size): 

8842 start = bitcount // 8 

8843 s = data[start : start + itembytes] 

8844 try: 

8845 code = unpack(dtypestr, s)[0] 

8846 except Exception: 

8847 code = unpack(dtypestr, s + b"\x00" * (itembytes - len(s)))[0] 

8848 code <<= bitcount % 8 

8849 code &= bitmask 

8850 result[i] = code >> shrbits 

8851 bitcount += itemsize 

8852 if (i + 1) % runlen == 0: 

8853 bitcount += skipbits 

8854 return result 

8855 

8856 

8857def unpack_rgb(data, dtype="<B", bitspersample=(5, 6, 5), rescale=True): 

8858 """Return array from byte string containing packed samples. 

8859 

8860 Use to unpack RGB565 or RGB555 to RGB888 format. 

8861 

8862 Parameters 

8863 ---------- 

8864 data : byte str 

8865 The data to be decoded. Samples in each pixel are stored consecutively. 

8866 Pixels are aligned to 8, 16, or 32 bit boundaries. 

8867 dtype : numpy.dtype 

8868 The sample data type. The byteorder applies also to the data stream. 

8869 bitspersample : tuple 

8870 Number of bits for each sample in a pixel. 

8871 rescale : bool 

8872 Upscale samples to the number of bits in dtype. 

8873 

8874 Returns 

8875 ------- 

8876 result : ndarray 

8877 Flattened array of unpacked samples of native dtype. 

8878 

8879 Examples 

8880 -------- 

8881 >>> data = struct.pack('BBBB', 0x21, 0x08, 0xff, 0xff) 

8882 >>> print(unpack_rgb(data, '<B', (5, 6, 5), False)) 

8883 [ 1 1 1 31 63 31] 

8884 >>> print(unpack_rgb(data, '<B', (5, 6, 5))) 

8885 [ 8 4 8 255 255 255] 

8886 >>> print(unpack_rgb(data, '<B', (5, 5, 5))) 

8887 [ 16 8 8 255 255 255] 

8888 

8889 """ 

8890 dtype = numpy.dtype(dtype) 

8891 bits = int(numpy.sum(bitspersample)) 

8892 if not (bits <= 32 and all(i <= dtype.itemsize * 8 for i in bitspersample)): 

8893 raise ValueError("sample size not supported: %s" % str(bitspersample)) 

8894 dt = next(i for i in "BHI" if numpy.dtype(i).itemsize * 8 >= bits) 

8895 data = numpy.frombuffer(data, dtype.byteorder + dt) 

8896 result = numpy.empty((data.size, len(bitspersample)), dtype.char) 

8897 for i, bps in enumerate(bitspersample): 

8898 t = data >> int(numpy.sum(bitspersample[i + 1 :])) 

8899 t &= int("0b" + "1" * bps, 2) 

8900 if rescale: 

8901 o = ((dtype.itemsize * 8) // bps + 1) * bps 

8902 if o > data.dtype.itemsize * 8: 

8903 t = t.astype("I") 

8904 t *= (2**o - 1) // (2**bps - 1) 

8905 t //= 2 ** (o - (dtype.itemsize * 8)) 

8906 result[:, i] = t 

8907 return result.reshape(-1) 

8908 

8909 

8910@_replace_by("_tifffile.reverse_bitorder") 

8911def reverse_bitorder(data): 

8912 """Reverse bits in each byte of byte string or numpy array. 

8913 

8914 Decode data where pixels with lower column values are stored in the 

8915 lower-order bits of the bytes (FillOrder is LSB2MSB). 

8916 

8917 Parameters 

8918 ---------- 

8919 data : byte string or ndarray 

8920 The data to be bit reversed. If byte string, a new bit-reversed byte 

8921 string is returned. Numpy arrays are bit-reversed in-place. 

8922 

8923 Examples 

8924 -------- 

8925 >>> reverse_bitorder(b'\\x01\\x64') 

8926 b'\\x80&' 

8927 >>> data = numpy.array([1, 666], dtype='uint16') 

8928 >>> reverse_bitorder(data) 

8929 >>> data 

8930 array([ 128, 16473], dtype=uint16) 

8931 

8932 """ 

8933 try: 

8934 view = data.view("uint8") 

8935 numpy.take(TIFF.REVERSE_BITORDER_ARRAY, view, out=view) 

8936 except AttributeError: 

8937 return data.translate(TIFF.REVERSE_BITORDER_BYTES) 

8938 except ValueError: 

8939 raise NotImplementedError("slices of arrays not supported") 

8940 

8941 

8942def apply_colormap(image, colormap, contig=True): 

8943 """Return palette-colored image. 

8944 

8945 The image values are used to index the colormap on axis 1. The returned 

8946 image is of shape image.shape+colormap.shape[0] and dtype colormap.dtype. 

8947 

8948 Parameters 

8949 ---------- 

8950 image : numpy.ndarray 

8951 Indexes into the colormap. 

8952 colormap : numpy.ndarray 

8953 RGB lookup table aka palette of shape (3, 2**bits_per_sample). 

8954 contig : bool 

8955 If True, return a contiguous array. 

8956 

8957 Examples 

8958 -------- 

8959 >>> image = numpy.arange(256, dtype='uint8') 

8960 >>> colormap = numpy.vstack([image, image, image]).astype('uint16') * 256 

8961 >>> apply_colormap(image, colormap)[-1] 

8962 array([65280, 65280, 65280], dtype=uint16) 

8963 

8964 """ 

8965 image = numpy.take(colormap, image, axis=1) 

8966 image = numpy.rollaxis(image, 0, image.ndim) 

8967 if contig: 

8968 image = numpy.ascontiguousarray(image) 

8969 return image 

8970 

8971 

8972def reorient(image, orientation): 

8973 """Return reoriented view of image array. 

8974 

8975 Parameters 

8976 ---------- 

8977 image : numpy.ndarray 

8978 Non-squeezed output of asarray() functions. 

8979 Axes -3 and -2 must be image length and width respectively. 

8980 orientation : int or str 

8981 One of TIFF.ORIENTATION names or values. 

8982 

8983 """ 

8984 ORIENTATION = TIFF.ORIENTATION 

8985 orientation = enumarg(ORIENTATION, orientation) 

8986 

8987 if orientation == ORIENTATION.TOPLEFT: 

8988 return image 

8989 elif orientation == ORIENTATION.TOPRIGHT: 

8990 return image[..., ::-1, :] 

8991 elif orientation == ORIENTATION.BOTLEFT: 

8992 return image[..., ::-1, :, :] 

8993 elif orientation == ORIENTATION.BOTRIGHT: 

8994 return image[..., ::-1, ::-1, :] 

8995 elif orientation == ORIENTATION.LEFTTOP: 

8996 return numpy.swapaxes(image, -3, -2) 

8997 elif orientation == ORIENTATION.RIGHTTOP: 

8998 return numpy.swapaxes(image, -3, -2)[..., ::-1, :] 

8999 elif orientation == ORIENTATION.RIGHTBOT: 

9000 return numpy.swapaxes(image, -3, -2)[..., ::-1, :, :] 

9001 elif orientation == ORIENTATION.LEFTBOT: 

9002 return numpy.swapaxes(image, -3, -2)[..., ::-1, ::-1, :] 

9003 

9004 

9005def repeat_nd(a, repeats): 

9006 """Return read-only view into input array with elements repeated. 

9007 

9008 Zoom nD image by integer factors using nearest neighbor interpolation 

9009 (box filter). 

9010 

9011 Parameters 

9012 ---------- 

9013 a : array_like 

9014 Input array. 

9015 repeats : sequence of int 

9016 The number of repetitions to apply along each dimension of input array. 

9017 

9018 Example 

9019 ------- 

9020 >>> repeat_nd([[1, 2], [3, 4]], (2, 2)) 

9021 array([[1, 1, 2, 2], 

9022 [1, 1, 2, 2], 

9023 [3, 3, 4, 4], 

9024 [3, 3, 4, 4]]) 

9025 

9026 """ 

9027 a = numpy.asarray(a) 

9028 reshape = [] 

9029 shape = [] 

9030 strides = [] 

9031 for i, j, k in zip(a.strides, a.shape, repeats): 

9032 shape.extend((j, k)) 

9033 strides.extend((i, 0)) 

9034 reshape.append(j * k) 

9035 return numpy.lib.stride_tricks.as_strided( 

9036 a, shape, strides, writeable=False 

9037 ).reshape(reshape) 

9038 

9039 

9040def reshape_nd(data_or_shape, ndim): 

9041 """Return image array or shape with at least ndim dimensions. 

9042 

9043 Prepend 1s to image shape as necessary. 

9044 

9045 >>> reshape_nd(numpy.empty(0), 1).shape 

9046 (0,) 

9047 >>> reshape_nd(numpy.empty(1), 2).shape 

9048 (1, 1) 

9049 >>> reshape_nd(numpy.empty((2, 3)), 3).shape 

9050 (1, 2, 3) 

9051 >>> reshape_nd(numpy.empty((3, 4, 5)), 3).shape 

9052 (3, 4, 5) 

9053 >>> reshape_nd((2, 3), 3) 

9054 (1, 2, 3) 

9055 

9056 """ 

9057 is_shape = isinstance(data_or_shape, tuple) 

9058 shape = data_or_shape if is_shape else data_or_shape.shape 

9059 if len(shape) >= ndim: 

9060 return data_or_shape 

9061 shape = (1,) * (ndim - len(shape)) + shape 

9062 return shape if is_shape else data_or_shape.reshape(shape) 

9063 

9064 

9065def squeeze_axes(shape, axes, skip="XY"): 

9066 """Return shape and axes with single-dimensional entries removed. 

9067 

9068 Remove unused dimensions unless their axes are listed in 'skip'. 

9069 

9070 >>> squeeze_axes((5, 1, 2, 1, 1), 'TZYXC') 

9071 ((5, 2, 1), 'TYX') 

9072 

9073 """ 

9074 if len(shape) != len(axes): 

9075 raise ValueError("dimensions of axes and shape do not match") 

9076 shape, axes = zip(*(i for i in zip(shape, axes) if i[0] > 1 or i[1] in skip)) 

9077 return tuple(shape), "".join(axes) 

9078 

9079 

9080def transpose_axes(image, axes, asaxes="CTZYX"): 

9081 """Return image with its axes permuted to match specified axes. 

9082 

9083 A view is returned if possible. 

9084 

9085 >>> transpose_axes(numpy.zeros((2, 3, 4, 5)), 'TYXC', asaxes='CTZYX').shape 

9086 (5, 2, 1, 3, 4) 

9087 

9088 """ 

9089 for ax in axes: 

9090 if ax not in asaxes: 

9091 raise ValueError("unknown axis %s" % ax) 

9092 # add missing axes to image 

9093 shape = image.shape 

9094 for ax in reversed(asaxes): 

9095 if ax not in axes: 

9096 axes = ax + axes 

9097 shape = (1,) + shape 

9098 image = image.reshape(shape) 

9099 # transpose axes 

9100 image = image.transpose([axes.index(ax) for ax in asaxes]) 

9101 return image 

9102 

9103 

9104def reshape_axes(axes, shape, newshape, unknown="Q"): 

9105 """Return axes matching new shape. 

9106 

9107 Unknown dimensions are labelled 'Q'. 

9108 

9109 >>> reshape_axes('YXS', (219, 301, 1), (219, 301)) 

9110 'YX' 

9111 >>> reshape_axes('IYX', (12, 219, 301), (3, 4, 219, 1, 301, 1)) 

9112 'QQYQXQ' 

9113 

9114 """ 

9115 shape = tuple(shape) 

9116 newshape = tuple(newshape) 

9117 if len(axes) != len(shape): 

9118 raise ValueError("axes do not match shape") 

9119 

9120 size = product(shape) 

9121 newsize = product(newshape) 

9122 if size != newsize: 

9123 raise ValueError("cannot reshape %s to %s" % (shape, newshape)) 

9124 if not axes or not newshape: 

9125 return "" 

9126 

9127 lendiff = max(0, len(shape) - len(newshape)) 

9128 if lendiff: 

9129 newshape = newshape + (1,) * lendiff 

9130 

9131 i = len(shape) - 1 

9132 prodns = 1 

9133 prods = 1 

9134 result = [] 

9135 for ns in newshape[::-1]: 

9136 prodns *= ns 

9137 while i > 0 and shape[i] == 1 and ns != 1: 

9138 i -= 1 

9139 if ns == shape[i] and prodns == prods * shape[i]: 

9140 prods *= shape[i] 

9141 result.append(axes[i]) 

9142 i -= 1 

9143 else: 

9144 result.append(unknown) 

9145 

9146 return "".join(reversed(result[lendiff:])) 

9147 

9148 

9149def stack_pages(pages, out=None, maxworkers=1, *args, **kwargs): 

9150 """Read data from sequence of TiffPage and stack them vertically. 

9151 

9152 Additional parameters are passed to the TiffPage.asarray function. 

9153 

9154 """ 

9155 npages = len(pages) 

9156 if npages == 0: 

9157 raise ValueError("no pages") 

9158 

9159 if npages == 1: 

9160 return pages[0].asarray(out=out, *args, **kwargs) 

9161 

9162 page0 = next(p for p in pages if p is not None) 

9163 page0.asarray(validate=None) # ThreadPoolExecutor swallows exceptions 

9164 shape = (npages,) + page0.keyframe.shape 

9165 dtype = page0.keyframe.dtype 

9166 out = create_output(out, shape, dtype) 

9167 

9168 if maxworkers is None: 

9169 maxworkers = multiprocessing.cpu_count() // 2 

9170 page0.parent.filehandle.lock = maxworkers > 1 

9171 

9172 filecache = OpenFileCache( 

9173 size=max(4, maxworkers), lock=page0.parent.filehandle.lock 

9174 ) 

9175 

9176 def func(page, index, out=out, filecache=filecache, args=args, kwargs=kwargs): 

9177 """Read, decode, and copy page data.""" 

9178 if page is not None: 

9179 filecache.open(page.parent.filehandle) 

9180 out[index] = page.asarray( 

9181 lock=filecache.lock, reopen=False, validate=False, *args, **kwargs 

9182 ) 

9183 filecache.close(page.parent.filehandle) 

9184 

9185 if maxworkers < 2: 

9186 for i, page in enumerate(pages): 

9187 func(page, i) 

9188 else: 

9189 with concurrent.futures.ThreadPoolExecutor(maxworkers) as executor: 

9190 executor.map(func, pages, range(npages)) 

9191 

9192 filecache.clear() 

9193 page0.parent.filehandle.lock = None 

9194 

9195 return out 

9196 

9197 

9198def clean_offsets_counts(offsets, counts): 

9199 """Return cleaned offsets and byte counts. 

9200 

9201 Remove zero offsets and counts. Use to sanitize _offsets and _bytecounts 

9202 tag values for strips or tiles. 

9203 

9204 """ 

9205 offsets = list(offsets) 

9206 counts = list(counts) 

9207 assert len(offsets) == len(counts) 

9208 j = 0 

9209 for i, (o, b) in enumerate(zip(offsets, counts)): 

9210 if o > 0 and b > 0: 

9211 if i > j: 

9212 offsets[j] = o 

9213 counts[j] = b 

9214 j += 1 

9215 elif b > 0 and o <= 0: 

9216 raise ValueError("invalid offset") 

9217 else: 

9218 warnings.warn("empty byte count") 

9219 if j == 0: 

9220 j = 1 

9221 return offsets[:j], counts[:j] 

9222 

9223 

9224def buffered_read(fh, lock, offsets, bytecounts, buffersize=2**26): 

9225 """Return iterator over blocks read from file.""" 

9226 length = len(offsets) 

9227 i = 0 

9228 while i < length: 

9229 data = [] 

9230 with lock: 

9231 size = 0 

9232 while size < buffersize and i < length: 

9233 fh.seek(offsets[i]) 

9234 bytecount = bytecounts[i] 

9235 data.append(fh.read(bytecount)) 

9236 size += bytecount 

9237 i += 1 

9238 for block in data: 

9239 yield block 

9240 

9241 

9242def create_output(out, shape, dtype, mode="w+", suffix=".memmap"): 

9243 """Return numpy array where image data of shape and dtype can be copied. 

9244 

9245 The 'out' parameter may have the following values or types: 

9246 

9247 None 

9248 An empty array of shape and dtype is created and returned. 

9249 numpy.ndarray 

9250 An existing writable array of compatible dtype and shape. A view of 

9251 the same array is returned after verification. 

9252 'memmap' or 'memmap:tempdir' 

9253 A memory-map to an array stored in a temporary binary file on disk 

9254 is created and returned. 

9255 str or open file 

9256 The file name or file object used to create a memory-map to an array 

9257 stored in a binary file on disk. The created memory-mapped array is 

9258 returned. 

9259 

9260 """ 

9261 if out is None: 

9262 return numpy.zeros(shape, dtype) 

9263 if isinstance(out, str) and out[:6] == "memmap": 

9264 tempdir = out[7:] if len(out) > 7 else None 

9265 with tempfile.NamedTemporaryFile(dir=tempdir, suffix=suffix) as fh: 

9266 return numpy.memmap(fh, shape=shape, dtype=dtype, mode=mode) 

9267 if isinstance(out, numpy.ndarray): 

9268 if product(shape) != product(out.shape): 

9269 raise ValueError("incompatible output shape") 

9270 if not numpy.can_cast(dtype, out.dtype): 

9271 raise ValueError("incompatible output dtype") 

9272 return out.reshape(shape) 

9273 if isinstance(out, pathlib.Path): 

9274 out = str(out) 

9275 return numpy.memmap(out, shape=shape, dtype=dtype, mode=mode) 

9276 

9277 

9278def matlabstr2py(string): 

9279 """Return Python object from Matlab string representation. 

9280 

9281 Return str, bool, int, float, list (Matlab arrays or cells), or 

9282 dict (Matlab structures) types. 

9283 

9284 Use to access ScanImage metadata. 

9285 

9286 >>> matlabstr2py('1') 

9287 1 

9288 >>> matlabstr2py("['x y z' true false; 1 2.0 -3e4; NaN Inf @class]") 

9289 [['x y z', True, False], [1, 2.0, -30000.0], [nan, inf, '@class']] 

9290 >>> d = matlabstr2py("SI.hChannels.channelType = {'stripe' 'stripe'}\\n" 

9291 ... "SI.hChannels.channelsActive = 2") 

9292 >>> d['SI.hChannels.channelType'] 

9293 ['stripe', 'stripe'] 

9294 

9295 """ 

9296 # TODO: handle invalid input 

9297 # TODO: review unboxing of multidimensional arrays 

9298 

9299 def lex(s): 

9300 # return sequence of tokens from matlab string representation 

9301 tokens = ["["] 

9302 while True: 

9303 t, i = next_token(s) 

9304 if t is None: 

9305 break 

9306 if t == ";": 

9307 tokens.extend(("]", "[")) 

9308 elif t == "[": 

9309 tokens.extend(("[", "[")) 

9310 elif t == "]": 

9311 tokens.extend(("]", "]")) 

9312 else: 

9313 tokens.append(t) 

9314 s = s[i:] 

9315 tokens.append("]") 

9316 return tokens 

9317 

9318 def next_token(s): 

9319 # return next token in matlab string 

9320 length = len(s) 

9321 if length == 0: 

9322 return None, 0 

9323 i = 0 

9324 while i < length and s[i] == " ": 

9325 i += 1 

9326 if i == length: 

9327 return None, i 

9328 if s[i] in "{[;]}": 

9329 return s[i], i + 1 

9330 if s[i] == "'": 

9331 j = i + 1 

9332 while j < length and s[j] != "'": 

9333 j += 1 

9334 return s[i : j + 1], j + 1 

9335 if s[i] == "<": 

9336 j = i + 1 

9337 while j < length and s[j] != ">": 

9338 j += 1 

9339 return s[i : j + 1], j + 1 

9340 j = i 

9341 while j < length and s[j] not in " {[;]}": 

9342 j += 1 

9343 return s[i:j], j 

9344 

9345 def value(s, fail=False): 

9346 # return Python value of token 

9347 s = s.strip() 

9348 if not s: 

9349 return s 

9350 if len(s) == 1: 

9351 try: 

9352 return int(s) 

9353 except Exception: 

9354 if fail: 

9355 raise ValueError() 

9356 return s 

9357 if s[0] == "'": 

9358 if fail and s[-1] != "'" or "'" in s[1:-1]: 

9359 raise ValueError() 

9360 return s[1:-1] 

9361 if s[0] == "<": 

9362 if fail and s[-1] != ">" or "<" in s[1:-1]: 

9363 raise ValueError() 

9364 return s 

9365 if fail and any(i in s for i in " ';[]{}"): 

9366 raise ValueError() 

9367 if s[0] == "@": 

9368 return s 

9369 if s in ("true", "True"): 

9370 return True 

9371 if s in ("false", "False"): 

9372 return False 

9373 if s[:6] == "zeros(": 

9374 return numpy.zeros([int(i) for i in s[6:-1].split(",")]).tolist() 

9375 if s[:5] == "ones(": 

9376 return numpy.ones([int(i) for i in s[5:-1].split(",")]).tolist() 

9377 if "." in s or "e" in s: 

9378 try: 

9379 return float(s) 

9380 except Exception: 

9381 pass 

9382 try: 

9383 return int(s) 

9384 except Exception: 

9385 pass 

9386 try: 

9387 return float(s) # nan, inf 

9388 except Exception: 

9389 if fail: 

9390 raise ValueError() 

9391 return s 

9392 

9393 def parse(s): 

9394 # return Python value from string representation of Matlab value 

9395 s = s.strip() 

9396 try: 

9397 return value(s, fail=True) 

9398 except ValueError: 

9399 pass 

9400 result = add2 = [] 

9401 levels = [add2] 

9402 for t in lex(s): 

9403 if t in "[{": 

9404 add2 = [] 

9405 levels.append(add2) 

9406 elif t in "]}": 

9407 x = levels.pop() 

9408 if len(x) == 1 and isinstance(x[0], (list, str)): 

9409 x = x[0] 

9410 add2 = levels[-1] 

9411 add2.append(x) 

9412 else: 

9413 add2.append(value(t)) 

9414 if len(result) == 1 and isinstance(result[0], (list, str)): 

9415 result = result[0] 

9416 return result 

9417 

9418 if "\r" in string or "\n" in string: 

9419 # structure 

9420 d = {} 

9421 for line in string.splitlines(): 

9422 line = line.strip() 

9423 if not line or line[0] == "%": 

9424 continue 

9425 k, v = line.split("=", 1) 

9426 k = k.strip() 

9427 if any(c in k for c in " ';[]{}<>"): 

9428 continue 

9429 d[k] = parse(v) 

9430 return d 

9431 return parse(string) 

9432 

9433 

9434def stripnull(string, null=b"\x00"): 

9435 """Return string truncated at first null character. 

9436 

9437 Clean NULL terminated C strings. For unicode strings use null='\\0'. 

9438 

9439 >>> stripnull(b'string\\x00') 

9440 b'string' 

9441 >>> stripnull('string\\x00', null='\\0') 

9442 'string' 

9443 

9444 """ 

9445 i = string.find(null) 

9446 return string if (i < 0) else string[:i] 

9447 

9448 

9449def stripascii(string): 

9450 """Return string truncated at last byte that is 7-bit ASCII. 

9451 

9452 Clean NULL separated and terminated TIFF strings. 

9453 

9454 >>> stripascii(b'string\\x00string\\n\\x01\\x00') 

9455 b'string\\x00string\\n' 

9456 >>> stripascii(b'\\x00') 

9457 b'' 

9458 

9459 """ 

9460 # TODO: pythonize this 

9461 i = len(string) 

9462 while i: 

9463 i -= 1 

9464 if 8 < byte2int(string[i]) < 127: 

9465 break 

9466 else: 

9467 i = -1 

9468 return string[: i + 1] 

9469 

9470 

9471def asbool(value, true=(b"true", "true"), false=(b"false", "false")): 

9472 """Return string as bool if possible, else raise TypeError. 

9473 

9474 >>> asbool(b' False ') 

9475 False 

9476 

9477 """ 

9478 value = value.strip().lower() 

9479 if value in true: # might raise UnicodeWarning/BytesWarning 

9480 return True 

9481 if value in false: 

9482 return False 

9483 raise TypeError() 

9484 

9485 

9486def astype(value, types=None): 

9487 """Return argument as one of types if possible. 

9488 

9489 >>> astype('42') 

9490 42 

9491 >>> astype('3.14') 

9492 3.14 

9493 >>> astype('True') 

9494 True 

9495 >>> astype(b'Neee-Wom') 

9496 'Neee-Wom' 

9497 

9498 """ 

9499 if types is None: 

9500 types = int, float, asbool, bytes2str 

9501 for typ in types: 

9502 try: 

9503 return typ(value) 

9504 except (ValueError, AttributeError, TypeError, UnicodeEncodeError): 

9505 pass 

9506 return value 

9507 

9508 

9509def format_size(size, threshold=1536): 

9510 """Return file size as string from byte size. 

9511 

9512 >>> format_size(1234) 

9513 '1234 B' 

9514 >>> format_size(12345678901) 

9515 '11.50 GiB' 

9516 

9517 """ 

9518 if size < threshold: 

9519 return "%i B" % size 

9520 for unit in ("KiB", "MiB", "GiB", "TiB", "PiB"): 

9521 size /= 1024.0 

9522 if size < threshold: 

9523 return "%.2f %s" % (size, unit) 

9524 

9525 

9526def identityfunc(arg): 

9527 """Single argument identity function. 

9528 

9529 >>> identityfunc('arg') 

9530 'arg' 

9531 

9532 """ 

9533 return arg 

9534 

9535 

9536def nullfunc(*args, **kwargs): 

9537 """Null function. 

9538 

9539 >>> nullfunc('arg', kwarg='kwarg') 

9540 

9541 """ 

9542 return 

9543 

9544 

9545def sequence(value): 

9546 """Return tuple containing value if value is not a sequence. 

9547 

9548 >>> sequence(1) 

9549 (1,) 

9550 >>> sequence([1]) 

9551 [1] 

9552 

9553 """ 

9554 try: 

9555 len(value) 

9556 return value 

9557 except TypeError: 

9558 return (value,) 

9559 

9560 

9561def product(iterable): 

9562 """Return product of sequence of numbers. 

9563 

9564 Equivalent of functools.reduce(operator.mul, iterable, 1). 

9565 Multiplying numpy integers might overflow. 

9566 

9567 >>> product([2**8, 2**30]) 

9568 274877906944 

9569 >>> product([]) 

9570 1 

9571 

9572 """ 

9573 prod = 1 

9574 for i in iterable: 

9575 prod *= i 

9576 return prod 

9577 

9578 

9579def natural_sorted(iterable): 

9580 """Return human sorted list of strings. 

9581 

9582 E.g. for sorting file names. 

9583 

9584 >>> natural_sorted(['f1', 'f2', 'f10']) 

9585 ['f1', 'f2', 'f10'] 

9586 

9587 """ 

9588 

9589 def sortkey(x): 

9590 return [(int(c) if c.isdigit() else c) for c in re.split(numbers, x)] 

9591 

9592 numbers = re.compile(r"(\d+)") 

9593 return sorted(iterable, key=sortkey) 

9594 

9595 

9596def excel_datetime(timestamp, epoch=datetime.datetime.fromordinal(693594)): 

9597 """Return datetime object from timestamp in Excel serial format. 

9598 

9599 Convert LSM time stamps. 

9600 

9601 >>> excel_datetime(40237.029999999795) 

9602 datetime.datetime(2010, 2, 28, 0, 43, 11, 999982) 

9603 

9604 """ 

9605 return epoch + datetime.timedelta(timestamp) 

9606 

9607 

9608def julian_datetime(julianday, milisecond=0): 

9609 """Return datetime from days since 1/1/4713 BC and ms since midnight. 

9610 

9611 Convert Julian dates according to MetaMorph. 

9612 

9613 >>> julian_datetime(2451576, 54362783) 

9614 datetime.datetime(2000, 2, 2, 15, 6, 2, 783) 

9615 

9616 """ 

9617 if julianday <= 1721423: 

9618 # no datetime before year 1 

9619 return None 

9620 

9621 a = julianday + 1 

9622 if a > 2299160: 

9623 alpha = math.trunc((a - 1867216.25) / 36524.25) 

9624 a += 1 + alpha - alpha // 4 

9625 b = a + (1524 if a > 1721423 else 1158) 

9626 c = math.trunc((b - 122.1) / 365.25) 

9627 d = math.trunc(365.25 * c) 

9628 e = math.trunc((b - d) / 30.6001) 

9629 

9630 day = b - d - math.trunc(30.6001 * e) 

9631 month = e - (1 if e < 13.5 else 13) 

9632 year = c - (4716 if month > 2.5 else 4715) 

9633 

9634 hour, milisecond = divmod(milisecond, 1000 * 60 * 60) 

9635 minute, milisecond = divmod(milisecond, 1000 * 60) 

9636 second, milisecond = divmod(milisecond, 1000) 

9637 

9638 return datetime.datetime(year, month, day, hour, minute, second, milisecond) 

9639 

9640 

9641def byteorder_isnative(byteorder): 

9642 """Return if byteorder matches the system's byteorder. 

9643 

9644 >>> byteorder_isnative('=') 

9645 True 

9646 

9647 """ 

9648 if byteorder == "=" or byteorder == sys.byteorder: 

9649 return True 

9650 keys = {"big": ">", "little": "<"} 

9651 return keys.get(byteorder, byteorder) == keys[sys.byteorder] 

9652 

9653 

9654def recarray2dict(recarray): 

9655 """Return numpy.recarray as dict.""" 

9656 # TODO: subarrays 

9657 result = {} 

9658 for descr, value in zip(recarray.dtype.descr, recarray): 

9659 name, dtype = descr[:2] 

9660 if dtype[1] == "S": 

9661 value = bytes2str(stripnull(value)) 

9662 elif value.ndim < 2: 

9663 value = value.tolist() 

9664 result[name] = value 

9665 return result 

9666 

9667 

9668def xml2dict(xml, sanitize=True, prefix=None): 

9669 """Return XML as dict. 

9670 

9671 >>> xml2dict('<?xml version="1.0" ?><root attr="name"><key>1</key></root>') 

9672 {'root': {'key': 1, 'attr': 'name'}} 

9673 

9674 """ 

9675 from xml.etree import cElementTree as etree # delayed import 

9676 

9677 at = tx = "" 

9678 if prefix: 

9679 at, tx = prefix 

9680 

9681 def astype(value): 

9682 # return value as int, float, bool, or str 

9683 for t in (int, float, asbool): 

9684 try: 

9685 return t(value) 

9686 except Exception: 

9687 pass 

9688 return value 

9689 

9690 def etree2dict(t): 

9691 # adapted from https://stackoverflow.com/a/10077069/453463 

9692 key = t.tag 

9693 if sanitize: 

9694 key = key.rsplit("}", 1)[-1] 

9695 d = {key: {} if t.attrib else None} 

9696 children = list(t) 

9697 if children: 

9698 dd = collections.defaultdict(list) 

9699 for dc in map(etree2dict, children): 

9700 for k, v in dc.items(): 

9701 dd[k].append(astype(v)) 

9702 d = { 

9703 key: { 

9704 k: astype(v[0]) if len(v) == 1 else astype(v) for k, v in dd.items() 

9705 } 

9706 } 

9707 if t.attrib: 

9708 d[key].update((at + k, astype(v)) for k, v in t.attrib.items()) 

9709 if t.text: 

9710 text = t.text.strip() 

9711 if children or t.attrib: 

9712 if text: 

9713 d[key][tx + "value"] = astype(text) 

9714 else: 

9715 d[key] = astype(text) 

9716 return d 

9717 

9718 return etree2dict(etree.fromstring(xml)) 

9719 

9720 

9721def hexdump(bytestr, width=75, height=24, snipat=-2, modulo=2, ellipsis="..."): 

9722 """Return hexdump representation of byte string. 

9723 

9724 >>> hexdump(binascii.unhexlify('49492a00080000000e00fe0004000100')) 

9725 '49 49 2a 00 08 00 00 00 0e 00 fe 00 04 00 01 00 II*.............' 

9726 

9727 """ 

9728 size = len(bytestr) 

9729 if size < 1 or width < 2 or height < 1: 

9730 return "" 

9731 if height == 1: 

9732 addr = b"" 

9733 bytesperline = min(modulo * (((width - len(addr)) // 4) // modulo), size) 

9734 if bytesperline < 1: 

9735 return "" 

9736 nlines = 1 

9737 else: 

9738 addr = b"%%0%ix: " % len(b"%x" % size) 

9739 bytesperline = min(modulo * (((width - len(addr % 1)) // 4) // modulo), size) 

9740 if bytesperline < 1: 

9741 return "" 

9742 width = 3 * bytesperline + len(addr % 1) 

9743 nlines = (size - 1) // bytesperline + 1 

9744 

9745 if snipat is None or snipat == 1: 

9746 snipat = height 

9747 elif 0 < abs(snipat) < 1: 

9748 snipat = int(math.floor(height * snipat)) 

9749 if snipat < 0: 

9750 snipat += height 

9751 

9752 if height == 1 or nlines == 1: 

9753 blocks = [(0, bytestr[:bytesperline])] 

9754 addr = b"" 

9755 height = 1 

9756 width = 3 * bytesperline 

9757 elif height is None or nlines <= height: 

9758 blocks = [(0, bytestr)] 

9759 elif snipat <= 0: 

9760 start = bytesperline * (nlines - height) 

9761 blocks = [(start, bytestr[start:])] # (start, None) 

9762 elif snipat >= height or height < 3: 

9763 end = bytesperline * height 

9764 blocks = [(0, bytestr[:end])] # (end, None) 

9765 else: 

9766 end1 = bytesperline * snipat 

9767 end2 = bytesperline * (height - snipat - 1) 

9768 blocks = [ 

9769 (0, bytestr[:end1]), 

9770 (size - end1 - end2, None), 

9771 (size - end2, bytestr[size - end2 :]), 

9772 ] 

9773 

9774 ellipsis = str2bytes(ellipsis) 

9775 result = [] 

9776 for start, bytestr in blocks: 

9777 if bytestr is None: 

9778 result.append(ellipsis) # 'skip %i bytes' % start) 

9779 continue 

9780 hexstr = binascii.hexlify(bytestr) 

9781 strstr = re.sub(rb"[^\x20-\x7f]", b".", bytestr) 

9782 for i in range(0, len(bytestr), bytesperline): 

9783 h = hexstr[2 * i : 2 * i + bytesperline * 2] 

9784 r = (addr % (i + start)) if height > 1 else addr 

9785 r += b" ".join(h[i : i + 2] for i in range(0, 2 * bytesperline, 2)) 

9786 r += b" " * (width - len(r)) 

9787 r += strstr[i : i + bytesperline] 

9788 result.append(r) 

9789 result = b"\n".join(result) 

9790 if sys.version_info[0] == 3: 

9791 result = result.decode("ascii") 

9792 return result 

9793 

9794 

9795def isprintable(string): 

9796 """Return if all characters in string are printable. 

9797 

9798 >>> isprintable('abc') 

9799 True 

9800 >>> isprintable(b'\01') 

9801 False 

9802 

9803 """ 

9804 string = string.strip() 

9805 if len(string) < 1: 

9806 return True 

9807 if sys.version_info[0] == 3: 

9808 try: 

9809 return string.isprintable() 

9810 except Exception: 

9811 pass 

9812 try: 

9813 return string.decode("utf-8").isprintable() 

9814 except Exception: 

9815 pass 

9816 else: 

9817 if string.isalnum(): 

9818 return True 

9819 printable = ( 

9820 "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRST" 

9821 "UVWXYZ!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c" 

9822 ) 

9823 return all(c in printable for c in string) 

9824 

9825 

9826def clean_whitespace(string, compact=False): 

9827 """Return string with compressed whitespace.""" 

9828 for a, b in ( 

9829 ("\r\n", "\n"), 

9830 ("\r", "\n"), 

9831 ("\n\n", "\n"), 

9832 ("\t", " "), 

9833 (" ", " "), 

9834 ): 

9835 string = string.replace(a, b) 

9836 if compact: 

9837 for a, b in (("\n", " "), ("[ ", "["), (" ", " "), (" ", " "), (" ", " ")): 

9838 string = string.replace(a, b) 

9839 return string.strip() 

9840 

9841 

9842def pformat_xml(xml): 

9843 """Return pretty formatted XML.""" 

9844 try: 

9845 import lxml.etree as etree # delayed import 

9846 

9847 if not isinstance(xml, bytes): 

9848 xml = xml.encode("utf-8") 

9849 xml = etree.parse(io.BytesIO(xml)) 

9850 xml = etree.tostring( 

9851 xml, pretty_print=True, xml_declaration=True, encoding=xml.docinfo.encoding 

9852 ) 

9853 xml = bytes2str(xml) 

9854 except Exception: 

9855 if isinstance(xml, bytes): 

9856 xml = bytes2str(xml) 

9857 xml = xml.replace("><", ">\n<") 

9858 return xml.replace(" ", " ").replace("\t", " ") 

9859 

9860 

9861def pformat(arg, width=79, height=24, compact=True): 

9862 """Return pretty formatted representation of object as string. 

9863 

9864 Whitespace might be altered. 

9865 

9866 """ 

9867 if height is None or height < 1: 

9868 height = 1024 

9869 if width is None or width < 1: 

9870 width = 256 

9871 

9872 npopt = numpy.get_printoptions() 

9873 numpy.set_printoptions(threshold=100, linewidth=width) 

9874 

9875 if isinstance(arg, basestring): 

9876 if arg[:5].lower() in ("<?xml", b"<?xml"): 

9877 if height == 1: 

9878 arg = arg[: 4 * width] 

9879 else: 

9880 arg = pformat_xml(arg) 

9881 elif isinstance(arg, bytes): 

9882 if isprintable(arg): 

9883 arg = bytes2str(arg) 

9884 arg = clean_whitespace(arg) 

9885 else: 

9886 numpy.set_printoptions(**npopt) 

9887 return hexdump(arg, width=width, height=height, modulo=1) 

9888 arg = arg.rstrip() 

9889 elif isinstance(arg, numpy.record): 

9890 arg = arg.pprint() 

9891 else: 

9892 import pprint # delayed import 

9893 

9894 compact = {} if sys.version_info[0] == 2 else dict(compact=compact) 

9895 arg = pprint.pformat(arg, width=width, **compact) 

9896 

9897 numpy.set_printoptions(**npopt) 

9898 

9899 if height == 1: 

9900 arg = clean_whitespace(arg, compact=True) 

9901 return arg[:width] 

9902 

9903 argl = list(arg.splitlines()) 

9904 if len(argl) > height: 

9905 arg = "\n".join(argl[: height // 2] + ["..."] + argl[-height // 2 :]) 

9906 return arg 

9907 

9908 

9909def snipstr(string, width=79, snipat=0.5, ellipsis="..."): 

9910 """Return string cut to specified length. 

9911 

9912 >>> snipstr('abcdefghijklmnop', 8) 

9913 'abc...op' 

9914 

9915 """ 

9916 if ellipsis is None: 

9917 if isinstance(string, bytes): 

9918 ellipsis = b"..." 

9919 else: 

9920 ellipsis = "\u2026" # does not print on win-py3.5 

9921 esize = len(ellipsis) 

9922 

9923 splitlines = string.splitlines() 

9924 # TODO: finish and test multiline snip 

9925 

9926 result = [] 

9927 for line in splitlines: 

9928 if line is None: 

9929 result.append(ellipsis) 

9930 continue 

9931 linelen = len(line) 

9932 if linelen <= width: 

9933 result.append(string) 

9934 continue 

9935 

9936 split = snipat 

9937 if split is None or split == 1: 

9938 split = linelen 

9939 elif 0 < abs(split) < 1: 

9940 split = int(math.floor(linelen * split)) 

9941 if split < 0: 

9942 split += linelen 

9943 if split < 0: 

9944 split = 0 

9945 

9946 if esize == 0 or width < esize + 1: 

9947 if split <= 0: 

9948 result.append(string[-width:]) 

9949 else: 

9950 result.append(string[:width]) 

9951 elif split <= 0: 

9952 result.append(ellipsis + string[esize - width :]) 

9953 elif split >= linelen or width < esize + 4: 

9954 result.append(string[: width - esize] + ellipsis) 

9955 else: 

9956 splitlen = linelen - width + esize 

9957 end1 = split - splitlen // 2 

9958 end2 = end1 + splitlen 

9959 result.append(string[:end1] + ellipsis + string[end2:]) 

9960 

9961 if isinstance(string, bytes): 

9962 return b"\n".join(result) 

9963 else: 

9964 return "\n".join(result) 

9965 

9966 

9967def enumarg(enum, arg): 

9968 """Return enum member from its name or value. 

9969 

9970 >>> enumarg(TIFF.PHOTOMETRIC, 2) 

9971 <PHOTOMETRIC.RGB: 2> 

9972 >>> enumarg(TIFF.PHOTOMETRIC, 'RGB') 

9973 <PHOTOMETRIC.RGB: 2> 

9974 

9975 """ 

9976 try: 

9977 return enum(arg) 

9978 except Exception: 

9979 try: 

9980 return enum[arg.upper()] 

9981 except Exception: 

9982 raise ValueError("invalid argument %s" % arg) 

9983 

9984 

9985def parse_kwargs(kwargs, *keys, **keyvalues): 

9986 """Return dict with keys from keys|keyvals and values from kwargs|keyvals. 

9987 

9988 Existing keys are deleted from kwargs. 

9989 

9990 >>> kwargs = {'one': 1, 'two': 2, 'four': 4} 

9991 >>> kwargs2 = parse_kwargs(kwargs, 'two', 'three', four=None, five=5) 

9992 >>> kwargs == {'one': 1} 

9993 True 

9994 >>> kwargs2 == {'two': 2, 'four': 4, 'five': 5} 

9995 True 

9996 

9997 """ 

9998 result = {} 

9999 for key in keys: 

10000 if key in kwargs: 

10001 result[key] = kwargs[key] 

10002 del kwargs[key] 

10003 for key, value in keyvalues.items(): 

10004 if key in kwargs: 

10005 result[key] = kwargs[key] 

10006 del kwargs[key] 

10007 else: 

10008 result[key] = value 

10009 return result 

10010 

10011 

10012def update_kwargs(kwargs, **keyvalues): 

10013 """Update dict with keys and values if keys do not already exist. 

10014 

10015 >>> kwargs = {'one': 1, } 

10016 >>> update_kwargs(kwargs, one=None, two=2) 

10017 >>> kwargs == {'one': 1, 'two': 2} 

10018 True 

10019 

10020 """ 

10021 for key, value in keyvalues.items(): 

10022 if key not in kwargs: 

10023 kwargs[key] = value 

10024 

10025 

10026def validate_jhove(filename, jhove="jhove", ignore=("More than 50 IFDs",)): 

10027 """Validate TIFF file using jhove -m TIFF-hul. 

10028 

10029 Raise ValueError if jhove outputs an error message unless the message 

10030 contains one of the strings in 'ignore'. 

10031 

10032 JHOVE does not support bigtiff or more than 50 IFDs. 

10033 

10034 See `JHOVE TIFF-hul Module <http://jhove.sourceforge.net/tiff-hul.html>`_ 

10035 

10036 """ 

10037 import subprocess # noqa: delayed import 

10038 

10039 out = subprocess.check_output([jhove, filename, "-m", "TIFF-hul"]) 

10040 if b"ErrorMessage: " in out: 

10041 for line in out.splitlines(): 

10042 line = line.strip() 

10043 if line.startswith(b"ErrorMessage: "): 

10044 error = line[14:].decode("utf8") 

10045 for i in ignore: 

10046 if i in error: 

10047 break 

10048 else: 

10049 raise ValueError(error) 

10050 break 

10051 

10052 

10053def lsm2bin(lsmfile, binfile=None, tile=(256, 256), verbose=True): 

10054 """Convert [MP]TZCYX LSM file to series of BIN files. 

10055 

10056 One BIN file containing 'ZCYX' data are created for each position, time, 

10057 and tile. The position, time, and tile indices are encoded at the end 

10058 of the filenames. 

10059 

10060 """ 

10061 verbose = print_ if verbose else nullfunc 

10062 

10063 if binfile is None: 

10064 binfile = lsmfile 

10065 elif binfile.lower() == "none": 

10066 binfile = None 

10067 if binfile: 

10068 binfile += "_(z%ic%iy%ix%i)_m%%ip%%it%%03iy%%ix%%i.bin" 

10069 

10070 verbose("\nOpening LSM file... ", end="", flush=True) 

10071 start_time = time.time() 

10072 

10073 with TiffFile(lsmfile) as lsm: 

10074 if not lsm.is_lsm: 

10075 verbose("\n", lsm, flush=True) 

10076 raise ValueError("not a LSM file") 

10077 series = lsm.series[0] # first series contains the image data 

10078 shape = series.shape 

10079 axes = series.axes 

10080 dtype = series.dtype 

10081 size = product(shape) * dtype.itemsize 

10082 

10083 verbose("%.3f s" % (time.time() - start_time)) 

10084 # verbose(lsm, flush=True) 

10085 verbose( 

10086 "Image\n axes: %s\n shape: %s\n dtype: %s\n size: %s" 

10087 % (axes, shape, dtype, format_size(size)), 

10088 flush=True, 

10089 ) 

10090 if not series.axes.endswith("TZCYX"): 

10091 raise ValueError("not a *TZCYX LSM file") 

10092 

10093 verbose("Copying image from LSM to BIN files", end="", flush=True) 

10094 start_time = time.time() 

10095 tiles = shape[-2] // tile[-2], shape[-1] // tile[-1] 

10096 if binfile: 

10097 binfile = binfile % (shape[-4], shape[-3], tile[0], tile[1]) 

10098 shape = (1,) * (7 - len(shape)) + shape 

10099 # cache for ZCYX stacks and output files 

10100 data = numpy.empty(shape[3:], dtype=dtype) 

10101 out = numpy.empty((shape[-4], shape[-3], tile[0], tile[1]), dtype=dtype) 

10102 # iterate over Tiff pages containing data 

10103 pages = iter(series.pages) 

10104 for m in range(shape[0]): # mosaic axis 

10105 for p in range(shape[1]): # position axis 

10106 for t in range(shape[2]): # time axis 

10107 for z in range(shape[3]): # z slices 

10108 data[z] = next(pages).asarray() 

10109 for y in range(tiles[0]): # tile y 

10110 for x in range(tiles[1]): # tile x 

10111 out[:] = data[ 

10112 ..., 

10113 y * tile[0] : (y + 1) * tile[0], 

10114 x * tile[1] : (x + 1) * tile[1], 

10115 ] 

10116 if binfile: 

10117 out.tofile(binfile % (m, p, t, y, x)) 

10118 verbose(".", end="", flush=True) 

10119 verbose(" %.3f s" % (time.time() - start_time)) 

10120 

10121 

10122def imshow( 

10123 data, 

10124 title=None, 

10125 vmin=0, 

10126 vmax=None, 

10127 cmap=None, 

10128 bitspersample=None, 

10129 photometric="RGB", 

10130 interpolation=None, 

10131 dpi=96, 

10132 figure=None, 

10133 subplot=111, 

10134 maxdim=32768, 

10135 **kwargs 

10136): 

10137 """Plot n-dimensional images using matplotlib.pyplot. 

10138 

10139 Return figure, subplot and plot axis. 

10140 Requires pyplot already imported C{from matplotlib import pyplot}. 

10141 

10142 Parameters 

10143 ---------- 

10144 bitspersample : int or None 

10145 Number of bits per channel in integer RGB images. 

10146 photometric : {'MINISWHITE', 'MINISBLACK', 'RGB', or 'PALETTE'} 

10147 The color space of the image data. 

10148 title : str 

10149 Window and subplot title. 

10150 figure : matplotlib.figure.Figure (optional). 

10151 Matplotlib to use for plotting. 

10152 subplot : int 

10153 A matplotlib.pyplot.subplot axis. 

10154 maxdim : int 

10155 maximum image width and length. 

10156 kwargs : optional 

10157 Arguments for matplotlib.pyplot.imshow. 

10158 

10159 """ 

10160 isrgb = photometric in ("RGB",) # 'PALETTE', 'YCBCR' 

10161 if data.dtype.kind == "b": 

10162 isrgb = False 

10163 if isrgb and not ( 

10164 data.shape[-1] in (3, 4) or (data.ndim > 2 and data.shape[-3] in (3, 4)) 

10165 ): 

10166 isrgb = False 

10167 photometric = "MINISBLACK" 

10168 

10169 data = data.squeeze() 

10170 if photometric in ("MINISWHITE", "MINISBLACK", None): 

10171 data = reshape_nd(data, 2) 

10172 else: 

10173 data = reshape_nd(data, 3) 

10174 

10175 dims = data.ndim 

10176 if dims < 2: 

10177 raise ValueError("not an image") 

10178 elif dims == 2: 

10179 dims = 0 

10180 isrgb = False 

10181 else: 

10182 if isrgb and data.shape[-3] in (3, 4): 

10183 data = numpy.swapaxes(data, -3, -2) 

10184 data = numpy.swapaxes(data, -2, -1) 

10185 elif not isrgb and ( 

10186 data.shape[-1] < data.shape[-2] // 8 

10187 and data.shape[-1] < data.shape[-3] // 8 

10188 and data.shape[-1] < 5 

10189 ): 

10190 data = numpy.swapaxes(data, -3, -1) 

10191 data = numpy.swapaxes(data, -2, -1) 

10192 isrgb = isrgb and data.shape[-1] in (3, 4) 

10193 dims -= 3 if isrgb else 2 

10194 

10195 if isrgb: 

10196 data = data[..., :maxdim, :maxdim, :maxdim] 

10197 else: 

10198 data = data[..., :maxdim, :maxdim] 

10199 

10200 if photometric == "PALETTE" and isrgb: 

10201 datamax = data.max() 

10202 if datamax > 255: 

10203 data = data >> 8 # possible precision loss 

10204 data = data.astype("B") 

10205 elif data.dtype.kind in "ui": 

10206 if not (isrgb and data.dtype.itemsize <= 1) or bitspersample is None: 

10207 try: 

10208 bitspersample = int(math.ceil(math.log(data.max(), 2))) 

10209 except Exception: 

10210 bitspersample = data.dtype.itemsize * 8 

10211 elif not isinstance(bitspersample, inttypes): 

10212 # bitspersample can be tuple, e.g. (5, 6, 5) 

10213 bitspersample = data.dtype.itemsize * 8 

10214 datamax = 2**bitspersample 

10215 if isrgb: 

10216 if bitspersample < 8: 

10217 data = data << (8 - bitspersample) 

10218 elif bitspersample > 8: 

10219 data = data >> (bitspersample - 8) # precision loss 

10220 data = data.astype("B") 

10221 elif data.dtype.kind == "f": 

10222 datamax = data.max() 

10223 if isrgb and datamax > 1.0: 

10224 if data.dtype.char == "d": 

10225 data = data.astype("f") 

10226 data /= datamax 

10227 else: 

10228 data = data / datamax 

10229 elif data.dtype.kind == "b": 

10230 datamax = 1 

10231 elif data.dtype.kind == "c": 

10232 data = numpy.absolute(data) 

10233 datamax = data.max() 

10234 

10235 if not isrgb: 

10236 if vmax is None: 

10237 vmax = datamax 

10238 if vmin is None: 

10239 if data.dtype.kind == "i": 

10240 dtmin = numpy.iinfo(data.dtype).min 

10241 vmin = numpy.min(data) 

10242 if vmin == dtmin: 

10243 vmin = numpy.min(data > dtmin) 

10244 if data.dtype.kind == "f": 

10245 dtmin = numpy.finfo(data.dtype).min 

10246 vmin = numpy.min(data) 

10247 if vmin == dtmin: 

10248 vmin = numpy.min(data > dtmin) 

10249 else: 

10250 vmin = 0 

10251 

10252 pyplot = sys.modules["matplotlib.pyplot"] 

10253 

10254 if figure is None: 

10255 pyplot.rc("font", family="sans-serif", weight="normal", size=8) 

10256 figure = pyplot.figure( 

10257 dpi=dpi, figsize=(10.3, 6.3), frameon=True, facecolor="1.0", edgecolor="w" 

10258 ) 

10259 try: 

10260 figure.canvas.manager.window.title(title) 

10261 except Exception: 

10262 pass 

10263 size = len(title.splitlines()) if title else 1 

10264 pyplot.subplots_adjust( 

10265 bottom=0.03 * (dims + 2), 

10266 top=0.98 - size * 0.03, 

10267 left=0.1, 

10268 right=0.95, 

10269 hspace=0.05, 

10270 wspace=0.0, 

10271 ) 

10272 subplot = pyplot.subplot(subplot) 

10273 

10274 if title: 

10275 try: 

10276 title = unicode(title, "Windows-1252") 

10277 except TypeError: 

10278 pass 

10279 pyplot.title(title, size=11) 

10280 

10281 if cmap is None: 

10282 if data.dtype.char == "?": 

10283 cmap = "gray" 

10284 elif data.dtype.kind in "buf" or vmin == 0: 

10285 cmap = "viridis" 

10286 else: 

10287 cmap = "coolwarm" 

10288 if photometric == "MINISWHITE": 

10289 cmap += "_r" 

10290 

10291 image = pyplot.imshow( 

10292 numpy.atleast_2d(data[(0,) * dims].squeeze()), 

10293 vmin=vmin, 

10294 vmax=vmax, 

10295 cmap=cmap, 

10296 interpolation=interpolation, 

10297 **kwargs 

10298 ) 

10299 

10300 if not isrgb: 

10301 pyplot.colorbar() # panchor=(0.55, 0.5), fraction=0.05 

10302 

10303 def format_coord(x, y): 

10304 # callback function to format coordinate display in toolbar 

10305 x = int(x + 0.5) 

10306 y = int(y + 0.5) 

10307 try: 

10308 if dims: 

10309 return "%s @ %s [%4i, %4i]" % (curaxdat[1][y, x], current, y, x) 

10310 return "%s @ [%4i, %4i]" % (data[y, x], y, x) 

10311 except IndexError: 

10312 return "" 

10313 

10314 def none(event): 

10315 return "" 

10316 

10317 subplot.format_coord = format_coord 

10318 image.get_cursor_data = none 

10319 image.format_cursor_data = none 

10320 

10321 if dims: 

10322 current = list((0,) * dims) 

10323 curaxdat = [0, data[tuple(current)].squeeze()] 

10324 sliders = [ 

10325 pyplot.Slider( 

10326 pyplot.axes([0.125, 0.03 * (axis + 1), 0.725, 0.025]), 

10327 "Dimension %i" % axis, 

10328 0, 

10329 data.shape[axis] - 1, 

10330 0, 

10331 facecolor="0.5", 

10332 valfmt="%%.0f [%i]" % data.shape[axis], 

10333 ) 

10334 for axis in range(dims) 

10335 ] 

10336 for slider in sliders: 

10337 slider.drawon = False 

10338 

10339 def set_image(current, sliders=sliders, data=data): 

10340 # change image and redraw canvas 

10341 curaxdat[1] = data[tuple(current)].squeeze() 

10342 image.set_data(curaxdat[1]) 

10343 for ctrl, index in zip(sliders, current): 

10344 ctrl.eventson = False 

10345 ctrl.set_val(index) 

10346 ctrl.eventson = True 

10347 figure.canvas.draw() 

10348 

10349 def on_changed(index, axis, data=data, current=current): 

10350 # callback function for slider change event 

10351 index = int(round(index)) 

10352 curaxdat[0] = axis 

10353 if index == current[axis]: 

10354 return 

10355 if index >= data.shape[axis]: 

10356 index = 0 

10357 elif index < 0: 

10358 index = data.shape[axis] - 1 

10359 current[axis] = index 

10360 set_image(current) 

10361 

10362 def on_keypressed(event, data=data, current=current): 

10363 # callback function for key press event 

10364 key = event.key 

10365 axis = curaxdat[0] 

10366 if str(key) in "0123456789": 

10367 on_changed(key, axis) 

10368 elif key == "right": 

10369 on_changed(current[axis] + 1, axis) 

10370 elif key == "left": 

10371 on_changed(current[axis] - 1, axis) 

10372 elif key == "up": 

10373 curaxdat[0] = 0 if axis == len(data.shape) - 1 else axis + 1 

10374 elif key == "down": 

10375 curaxdat[0] = len(data.shape) - 1 if axis == 0 else axis - 1 

10376 elif key == "end": 

10377 on_changed(data.shape[axis] - 1, axis) 

10378 elif key == "home": 

10379 on_changed(0, axis) 

10380 

10381 figure.canvas.mpl_connect("key_press_event", on_keypressed) 

10382 for axis, ctrl in enumerate(sliders): 

10383 ctrl.on_changed(lambda k, a=axis: on_changed(k, a)) 

10384 

10385 return figure, subplot, image 

10386 

10387 

10388def _app_show(): 

10389 """Block the GUI. For use as skimage plugin.""" 

10390 pyplot = sys.modules["matplotlib.pyplot"] 

10391 pyplot.show() 

10392 

10393 

10394def askopenfilename(**kwargs): 

10395 """Return file name(s) from Tkinter's file open dialog.""" 

10396 try: 

10397 from Tkinter import Tk 

10398 import tkFileDialog as filedialog 

10399 except ImportError: 

10400 from tkinter import Tk, filedialog 

10401 root = Tk() 

10402 root.withdraw() 

10403 root.update() 

10404 filenames = filedialog.askopenfilename(**kwargs) 

10405 root.destroy() 

10406 return filenames 

10407 

10408 

10409def main(argv=None): 

10410 """Command line usage main function.""" 

10411 if float(sys.version[0:3]) < 2.7: 

10412 print("This script requires Python version 2.7 or better.") 

10413 print("This is Python version %s" % sys.version) 

10414 return 0 

10415 if argv is None: 

10416 argv = sys.argv 

10417 

10418 import optparse # TODO: use argparse 

10419 

10420 parser = optparse.OptionParser( 

10421 usage="usage: %prog [options] path", 

10422 description="Display image data in TIFF files.", 

10423 version="%%prog %s" % __version__, 

10424 ) 

10425 opt = parser.add_option 

10426 opt("-p", "--page", dest="page", type="int", default=-1, help="display single page") 

10427 opt( 

10428 "-s", 

10429 "--series", 

10430 dest="series", 

10431 type="int", 

10432 default=-1, 

10433 help="display series of pages of same shape", 

10434 ) 

10435 opt( 

10436 "--nomultifile", 

10437 dest="nomultifile", 

10438 action="store_true", 

10439 default=False, 

10440 help="do not read OME series from multiple files", 

10441 ) 

10442 opt( 

10443 "--noplots", 

10444 dest="noplots", 

10445 type="int", 

10446 default=8, 

10447 help="maximum number of plots", 

10448 ) 

10449 opt( 

10450 "--interpol", 

10451 dest="interpol", 

10452 metavar="INTERPOL", 

10453 default="bilinear", 

10454 help="image interpolation method", 

10455 ) 

10456 opt("--dpi", dest="dpi", type="int", default=96, help="plot resolution") 

10457 opt( 

10458 "--vmin", 

10459 dest="vmin", 

10460 type="int", 

10461 default=None, 

10462 help="minimum value for colormapping", 

10463 ) 

10464 opt( 

10465 "--vmax", 

10466 dest="vmax", 

10467 type="int", 

10468 default=None, 

10469 help="maximum value for colormapping", 

10470 ) 

10471 opt( 

10472 "--debug", 

10473 dest="debug", 

10474 action="store_true", 

10475 default=False, 

10476 help="raise exception on failures", 

10477 ) 

10478 opt( 

10479 "--doctest", 

10480 dest="doctest", 

10481 action="store_true", 

10482 default=False, 

10483 help="runs the docstring examples", 

10484 ) 

10485 opt("-v", "--detail", dest="detail", type="int", default=2) 

10486 opt("-q", "--quiet", dest="quiet", action="store_true") 

10487 

10488 settings, path = parser.parse_args() 

10489 path = " ".join(path) 

10490 

10491 if settings.doctest: 

10492 import doctest 

10493 

10494 doctest.testmod(optionflags=doctest.ELLIPSIS) 

10495 return 0 

10496 if not path: 

10497 path = askopenfilename( 

10498 title="Select a TIFF file", filetypes=TIFF.FILEOPEN_FILTER 

10499 ) 

10500 if not path: 

10501 parser.error("No file specified") 

10502 

10503 if any(i in path for i in "?*"): 

10504 path = glob.glob(path) 

10505 if not path: 

10506 print("no files match the pattern") 

10507 return 0 

10508 # TODO: handle image sequences 

10509 path = path[0] 

10510 

10511 if not settings.quiet: 

10512 print("\nReading file structure...", end=" ") 

10513 start = time.time() 

10514 try: 

10515 tif = TiffFile(path, multifile=not settings.nomultifile) 

10516 except Exception as e: 

10517 if settings.debug: 

10518 raise 

10519 else: 

10520 print("\n", e) 

10521 sys.exit(0) 

10522 if not settings.quiet: 

10523 print("%.3f ms" % ((time.time() - start) * 1e3)) 

10524 

10525 if tif.is_ome: 

10526 settings.norgb = True 

10527 

10528 images = [] 

10529 if settings.noplots > 0: 

10530 if not settings.quiet: 

10531 print("Reading image data... ", end=" ") 

10532 

10533 def notnone(x): 

10534 return next(i for i in x if i is not None) 

10535 

10536 start = time.time() 

10537 try: 

10538 if settings.page >= 0: 

10539 images = [(tif.asarray(key=settings.page), tif[settings.page], None)] 

10540 elif settings.series >= 0: 

10541 images = [ 

10542 ( 

10543 tif.asarray(series=settings.series), 

10544 notnone(tif.series[settings.series]._pages), 

10545 tif.series[settings.series], 

10546 ) 

10547 ] 

10548 else: 

10549 images = [] 

10550 for i, s in enumerate(tif.series[: settings.noplots]): 

10551 try: 

10552 images.append( 

10553 (tif.asarray(series=i), notnone(s._pages), tif.series[i]) 

10554 ) 

10555 except ValueError as e: 

10556 images.append((None, notnone(s.pages), None)) 

10557 if settings.debug: 

10558 raise 

10559 else: 

10560 print("\nSeries %i failed: %s... " % (i, e), end="") 

10561 if not settings.quiet: 

10562 print("%.3f ms" % ((time.time() - start) * 1e3)) 

10563 except Exception as e: 

10564 if settings.debug: 

10565 raise 

10566 else: 

10567 print(e) 

10568 

10569 if not settings.quiet: 

10570 print() 

10571 print(TiffFile.__str__(tif, detail=int(settings.detail))) 

10572 print() 

10573 tif.close() 

10574 

10575 if images and settings.noplots > 0: 

10576 try: 

10577 import matplotlib 

10578 

10579 matplotlib.use("TkAgg") 

10580 from matplotlib import pyplot 

10581 except ImportError as e: 

10582 warnings.warn("failed to import matplotlib.\n%s" % e) 

10583 else: 

10584 for img, page, series in images: 

10585 if img is None: 

10586 continue 

10587 vmin, vmax = settings.vmin, settings.vmax 

10588 if "GDAL_NODATA" in page.tags: 

10589 try: 

10590 vmin = numpy.min( 

10591 img[img > float(page.tags["GDAL_NODATA"].value)] 

10592 ) 

10593 except ValueError: 

10594 pass 

10595 if tif.is_stk: 

10596 try: 

10597 vmin = tif.stk_metadata["MinScale"] 

10598 vmax = tif.stk_metadata["MaxScale"] 

10599 except KeyError: 

10600 pass 

10601 else: 

10602 if vmax <= vmin: 

10603 vmin, vmax = settings.vmin, settings.vmax 

10604 if series: 

10605 title = "%s\n%s\n%s" % (str(tif), str(page), str(series)) 

10606 else: 

10607 title = "%s\n %s" % (str(tif), str(page)) 

10608 photometric = "MINISBLACK" 

10609 if page.photometric not in (3,): 

10610 photometric = TIFF.PHOTOMETRIC(page.photometric).name 

10611 imshow( 

10612 img, 

10613 title=title, 

10614 vmin=vmin, 

10615 vmax=vmax, 

10616 bitspersample=page.bitspersample, 

10617 photometric=photometric, 

10618 interpolation=settings.interpol, 

10619 dpi=settings.dpi, 

10620 ) 

10621 pyplot.show() 

10622 

10623 

10624if sys.version_info[0] == 2: 

10625 inttypes = int, long # noqa 

10626 

10627 def print_(*args, **kwargs): 

10628 """Print function with flush support.""" 

10629 flush = kwargs.pop("flush", False) 

10630 print(*args, **kwargs) 

10631 if flush: 

10632 sys.stdout.flush() 

10633 

10634 def bytes2str(b, encoding=None, errors=None): 

10635 """Return string from bytes.""" 

10636 return b 

10637 

10638 def str2bytes(s, encoding=None): 

10639 """Return bytes from string.""" 

10640 return s 

10641 

10642 def byte2int(b): 

10643 """Return value of byte as int.""" 

10644 return ord(b) 

10645 

10646 class FileNotFoundError(IOError): 

10647 pass 

10648 

10649 TiffFrame = TiffPage # noqa 

10650else: 

10651 inttypes = int 

10652 basestring = str, bytes 

10653 unicode = str 

10654 print_ = print 

10655 

10656 def bytes2str(b, encoding=None, errors="strict"): 

10657 """Return unicode string from encoded bytes.""" 

10658 if encoding is not None: 

10659 return b.decode(encoding, errors) 

10660 try: 

10661 return b.decode("utf-8", errors) 

10662 except UnicodeDecodeError: 

10663 return b.decode("cp1252", errors) 

10664 

10665 def str2bytes(s, encoding="cp1252"): 

10666 """Return bytes from unicode string.""" 

10667 return s.encode(encoding) 

10668 

10669 def byte2int(b): 

10670 """Return value of byte as int.""" 

10671 return b 

10672 

10673 

10674if __name__ == "__main__": 

10675 sys.exit(main())