[med-svn] [Git][python-team/modules/tifffile][upstream] New upstream version 20200717
Ole Streicher
gitlab at salsa.debian.org
Sat Jul 18 09:39:37 BST 2020
Ole Streicher pushed to branch upstream at Debian Python Team / DPMT / tifffile
Commits:
2a5bc8b5 by Ole Streicher at 2020-07-18T10:31:29+02:00
New upstream version 20200717
- - - - -
8 changed files:
- CHANGES.rst
- PKG-INFO
- README.rst
- setup.py
- tests/test_tifffile.py
- tifffile.egg-info/PKG-INFO
- tifffile.egg-info/requires.txt
- tifffile/tifffile.py
Changes:
=====================================
CHANGES.rst
=====================================
@@ -1,7 +1,25 @@
Revisions
---------
+2020.7.17
+ Pass 3022 tests.
+ Initial support for writing OME-TIFF (WIP).
+ Return samples as separate dimension in OME series (breaking).
+ Fix modulo dimensions for multiple OME series.
+ Fix some test errors on big endian systems (#18).
+ Fix BytesWarning.
+ Allow to pass TIFF.PREDICTOR values to TiffWriter.save.
+2020.7.4
+ Deprecate support for Python 3.6 (NEP 29).
+ Move pyramidal subresolution series to TiffPageSeries.levels (breaking).
+ Add parser for SVS, SCN, NDPI, and QPI pyramidal series.
+ Read single-file OME-TIFF pyramids.
+ Read NDPI files > 4 GB (#15).
+ Include SubIFDs in generic series.
+ Preliminary support for writing packed integer arrays (#11, WIP).
+ Read more LSM info subrecords.
+ Fix missing ReferenceBlackWhite tag for YCbCr photometrics.
+ Fix reading lossless JPEG compressed DNG files.
2020.6.3
- Pass 2908 tests.
Support os.PathLike file names (#9).
2020.5.30
Re-add pure Python PackBits decoder.
=====================================
PKG-INFO
=====================================
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: tifffile
-Version: 2020.6.3
+Version: 2020.7.17
Summary: Read and write TIFF(r) files
Home-page: https://www.lfd.uci.edu/~gohlke/
Author: Christoph Gohlke
@@ -18,10 +18,11 @@ Description: Read and write TIFF(r) files
Image and metadata can be read from TIFF, BigTIFF, OME-TIFF, STK, LSM, SGI,
NIHImage, ImageJ, MicroManager, FluoView, ScanImage, SEQ, GEL, SVS, SCN, SIS,
- ZIF, QPTIFF, NDPI, and GeoTIFF files.
+ ZIF (Zoomable Image File Format), QPTIFF (QPI), NDPI, and GeoTIFF files.
- Numpy arrays can be written to TIFF, BigTIFF, and ImageJ hyperstack compatible
- files in multi-page, memory-mappable, tiled, predicted, or compressed form.
+ Numpy arrays can be written to TIFF, BigTIFF, OME-TIFF, and ImageJ hyperstack
+ compatible files in multi-page, memory-mappable, tiled, predicted, or
+ compressed form.
A subset of the TIFF specification is supported, mainly uncompressed and
losslessly compressed 8, 16, 32 and 64-bit integer, 16, 32 and 64-bit float,
@@ -36,7 +37,7 @@ Description: Read and write TIFF(r) files
extensions defined by Molecular Devices (Universal Imaging Corporation),
Carl Zeiss MicroImaging, Olympus, Silicon Graphics International,
Media Cybernetics, Molecular Dynamics, PerkinElmer, Hamamatsu, and the
- Open Microscopy Environment consortium respectively.
+ Open Microscopy Environment consortium, respectively.
For command line usage run ``python -m tifffile --help``
@@ -48,23 +49,44 @@ Description: Read and write TIFF(r) files
:License: BSD 3-Clause
- :Version: 2020.6.3
+ :Version: 2020.7.17
Requirements
------------
This release has been tested with the following requirements and dependencies
(other versions may work):
- * `CPython 3.6.8, 3.7.7, 3.8.3 64-bit <https://www.python.org>`_
- * `Numpy 1.16.6, 1.18.4 <https://www.numpy.org>`_
+ * `CPython 3.7.8, 3.8.4, 3.9.0b4 64-bit <https://www.python.org>`_
+ * `Numpy 1.18.5 <https://pypi.org/project/numpy/>`_
* `Imagecodecs 2020.5.30 <https://pypi.org/project/imagecodecs/>`_
(required only for encoding or decoding LZW, JPEG, etc.)
- * `Matplotlib 3.1 <https://www.matplotlib.org>`_ (required only for plotting)
+ * `Matplotlib 3.2.2 <https://pypi.org/project/matplotlib/>`_
+ (required only for plotting)
+ * `Lxml 4.5.2 <https://github.com/lxml/lxml>`_
+ (required only for validating and printing XML)
Revisions
---------
+ 2020.7.17
+ Pass 3022 tests.
+ Initial support for writing OME-TIFF (WIP).
+ Return samples as separate dimension in OME series (breaking).
+ Fix modulo dimensions for multiple OME series.
+ Fix some test errors on big endian systems (#18).
+ Fix BytesWarning.
+ Allow to pass TIFF.PREDICTOR values to TiffWriter.save.
+ 2020.7.4
+ Deprecate support for Python 3.6 (NEP 29).
+ Move pyramidal subresolution series to TiffPageSeries.levels (breaking).
+ Add parser for SVS, SCN, NDPI, and QPI pyramidal series.
+ Read single-file OME-TIFF pyramids.
+ Read NDPI files > 4 GB (#15).
+ Include SubIFDs in generic series.
+ Preliminary support for writing packed integer arrays (#11, WIP).
+ Read more LSM info subrecords.
+ Fix missing ReferenceBlackWhite tag for YCbCr photometrics.
+ Fix reading lossless JPEG compressed DNG files.
2020.6.3
- Pass 2908 tests.
Support os.PathLike file names (#9).
2020.5.30
Re-add pure Python PackBits decoder.
@@ -218,10 +240,11 @@ Description: Read and write TIFF(r) files
Tested on little-endian platforms only.
- Python 32-bit versions are deprecated.
+ Python 32-bit versions are deprecated. Python <= 3.6 are no longer supported.
Tifffile relies on the `imagecodecs <https://pypi.org/project/imagecodecs/>`_
- package for encoding and decoding LZW, JPEG, and other compressed images.
+ package for encoding and decoding LZW, JPEG, and other compressed image
+ segments.
Several TIFF-like formats do not strictly adhere to the TIFF6 specification,
some of which allow file or data sizes to exceed the 4 GB limit:
@@ -238,21 +261,23 @@ Description: Read and write TIFF(r) files
files. The 8-bit UTF-8 encoded OME-XML metadata found in the ImageDescription
tag of the first IFD defines the position of TIFF IFDs in the high
dimensional data. Tifffile can read OME-TIFF files, except when the OME-XML
- metadata are stored in a separate file.
+ metadata are stored in a separate file. Tifffile can write numpy arrays
+ to single-file, non-pyramidal OME-TIFF.
* *LSM* stores all IFDs below 4 GB but wraps around 32-bit StripOffsets.
The StripOffsets of each series and position require separate unwrapping.
The StripByteCounts tag contains the number of bytes for the uncompressed
data. Tifffile can read large LSM files.
* *NDPI* uses some 64-bit offsets in the file header, IFD, and tag structures.
Tag values/offsets can be corrected using high bits stored after IFD
- structures. JPEG compressed tiles with dimensions > 65536 are not readable
- with libjpeg. Tifffile can read NDPI files < 4 GB and decompress large JPEG
- tiles using the imagecodecs library on Windows.
+ structures. JPEG compressed segments with dimensions >65536 or missing
+ restart markers are not readable with libjpeg. Tifffile can read NDPI
+ files > 4 GB. JPEG segments with restart markers and dimensions >65536 can
+ be decoded with the imagecodecs library on Windows.
* *ScanImage* optionally allows corrupt non-BigTIFF files > 2 GB. The values
of StripOffsets and StripByteCounts can be recovered using the constant
differences of the offsets of IFD and tag values throughout the file.
- Tifffile can read such files on Python 3 if the image data are stored
- contiguously in each page.
+ Tifffile can read such files if the image data are stored contiguously in
+ each page.
* *GeoTIFF* sparse files allow strip or tile offsets and byte counts to be 0.
Such segments are implicitly set to 0 or the NODATA value on reading.
Tifffile can read GeoTIFF sparse files.
@@ -285,29 +310,33 @@ Description: Read and write TIFF(r) files
References
----------
- 1. TIFF 6.0 Specification and Supplements. Adobe Systems Incorporated.
- https://www.adobe.io/open/standards/TIFF.html
- 2. TIFF File Format FAQ. https://www.awaresystems.be/imaging/tiff/faq.html
- 3. MetaMorph Stack (STK) Image File Format.
- http://mdc.custhelp.com/app/answers/detail/a_id/18862
- 4. Image File Format Description LSM 5/7 Release 6.0 (ZEN 2010).
- Carl Zeiss MicroImaging GmbH. BioSciences. May 10, 2011
- 5. The OME-TIFF format.
- https://docs.openmicroscopy.org/ome-model/5.6.4/ome-tiff/
- 6. UltraQuant(r) Version 6.0 for Windows Start-Up Guide.
- http://www.ultralum.com/images%20ultralum/pdf/UQStart%20Up%20Guide.pdf
- 7. Micro-Manager File Formats.
- https://micro-manager.org/wiki/Micro-Manager_File_Formats
- 8. Tags for TIFF and Related Specifications. Digital Preservation.
- https://www.loc.gov/preservation/digital/formats/content/tiff_tags.shtml
- 9. ScanImage BigTiff Specification - ScanImage 2016.
- http://scanimage.vidriotechnologies.com/display/SI2016/
- ScanImage+BigTiff+Specification
- 10. CIPA DC-008-2016: Exchangeable image file format for digital still cameras:
- Exif Version 2.31.
- http://www.cipa.jp/std/documents/e/DC-008-Translation-2016-E.pdf
- 11. ZIF, the Zoomable Image File format. http://zif.photo/
- 12. GeoTIFF File Format https://gdal.org/drivers/raster/gtiff.html
+ * TIFF 6.0 Specification and Supplements. Adobe Systems Incorporated.
+ https://www.adobe.io/open/standards/TIFF.html
+ * TIFF File Format FAQ. https://www.awaresystems.be/imaging/tiff/faq.html
+ * The BigTIFF File Format.
+ https://www.awaresystems.be/imaging/tiff/bigtiff.html
+ * MetaMorph Stack (STK) Image File Format.
+ http://mdc.custhelp.com/app/answers/detail/a_id/18862
+ * Image File Format Description LSM 5/7 Release 6.0 (ZEN 2010).
+ Carl Zeiss MicroImaging GmbH. BioSciences. May 10, 2011
+ * The OME-TIFF format.
+ https://docs.openmicroscopy.org/ome-model/latest/
+ * UltraQuant(r) Version 6.0 for Windows Start-Up Guide.
+ http://www.ultralum.com/images%20ultralum/pdf/UQStart%20Up%20Guide.pdf
+ * Micro-Manager File Formats.
+ https://micro-manager.org/wiki/Micro-Manager_File_Formats
+ * ScanImage BigTiff Specification - ScanImage 2016.
+ http://scanimage.vidriotechnologies.com/display/SI2016/
+ ScanImage+BigTiff+Specification
+ * ZIF, the Zoomable Image File format. http://zif.photo/
+ * GeoTIFF File Format https://gdal.org/drivers/raster/gtiff.html
+ * Cloud optimized GeoTIFF.
+ https://github.com/cogeotiff/cog-spec/blob/master/spec.md
+ * Tags for TIFF and Related Specifications. Digital Preservation.
+ https://www.loc.gov/preservation/digital/formats/content/tiff_tags.shtml
+ * CIPA DC-008-2016: Exchangeable image file format for digital still cameras:
+ Exif Version 2.31.
+ http://www.cipa.jp/std/documents/e/DC-008-Translation-2016-E.pdf
Examples
--------
@@ -440,19 +469,21 @@ Description: Read and write TIFF(r) files
... tag_name, tag_value = tag.name, tag.value
... image = page.asarray()
- Save two image series to a TIFF file:
+ Write two numpy arrays to a multi-series OME-TIFF file:
- >>> data0 = numpy.random.randint(0, 255, (301, 219, 3), 'uint8')
- >>> data1 = numpy.random.randint(0, 255, (5, 301, 219), 'uint16')
- >>> with TiffWriter('temp.tif') as tif:
+ >>> data0 = numpy.random.randint(0, 255, (32, 32, 3), 'uint8')
+ >>> data1 = numpy.random.randint(0, 1023, (5, 256, 256), 'uint16')
+ >>> with TiffWriter('temp.ome.tif', ome=True) as tif:
... tif.save(data0, compress=6, photometric='rgb')
- ... tif.save(data1, compress=6, photometric='minisblack', contiguous=False)
+ ... tif.save(data1, photometric='minisblack', contiguous=False,
+ ... metadata=dict(axes='ZYX', SignificantBits=10,
+ ... PositionZ=[0.0, 1.0, 2.0, 3.0, 4.0]))
- Read the second image series from the TIFF file:
+ Read the second image series from the OME-TIFF file:
- >>> series1 = imread('temp.tif', series=1)
+ >>> series1 = imread('temp.ome.tif', series=1)
>>> series1.shape
- (5, 301, 219)
+ (5, 256, 256)
Read an image stack from a series of TIFF files with a file name pattern:
@@ -467,13 +498,46 @@ Description: Read and write TIFF(r) files
>>> data.shape
(1, 2, 64, 64)
- Create a TIFF file from an iterator of tiles:
+ Create a TIFF file from a generator of tiles:
>>> def tiles():
... data = numpy.arange(3*4*16*16, dtype='uint16').reshape((3*4, 16, 16))
... for i in range(data.shape[0]): yield data[i]
>>> imwrite('temp.tif', tiles(), dtype='uint16', shape=(48, 64), tile=(16, 16))
+ Write a tiled, multi-resolution, pyramidal TIFF file using JPEG compression:
+
+ >>> data = numpy.arange(1024*1024*3, dtype='uint8').reshape((1024, 1024, 3))
+ >>> with TiffWriter('temp.tif') as tif:
+ ... options = dict(tile=(256, 256), compress='jpeg', metadata=None)
+ ... tif.save(data, **options)
+ ... # save pyramid levels. In production use resampling to generate levels!
+ ... tif.save(data[::2, ::2], subfiletype=1, **options)
+ ... tif.save(data[::4, ::4], subfiletype=1, **options)
+
+ Access the image levels in the pyramidal TIFF file:
+
+ >>> baseimage = imread('temp.tif')
+ >>> second_level = imread('temp.tif', series=0, level=1)
+ >>> with TiffFile('temp.tif') as tif:
+ ... baseimage = tif.series[0].asarray()
+ ... second_level = tif.series[0].levels[1].asarray()
+
+ Iterate over and decode single JPEG compressed tiles in the TIFF file:
+
+ >>> with TiffFile('temp.tif') as tif:
+ ... fh = tif.filehandle
+ ... for page in tif.pages:
+ ... jpegtables = page.tags.get('JPEGTables', None)
+ ... if jpegtables is not None:
+ ... jpegtables = jpegtables.value
+ ... for index, (offset, bytecount) in enumerate(
+ ... zip(page.dataoffsets, page.databytecounts)
+ ... ):
+ ... fh.seek(offset)
+ ... data = fh.read(bytecount)
+ ... tile, indices, shape = page.decode(data, index, jpegtables)
+
Platform: any
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: BSD License
@@ -481,7 +545,6 @@ Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
=====================================
README.rst
=====================================
@@ -8,10 +8,11 @@ Tifffile is a Python library to
Image and metadata can be read from TIFF, BigTIFF, OME-TIFF, STK, LSM, SGI,
NIHImage, ImageJ, MicroManager, FluoView, ScanImage, SEQ, GEL, SVS, SCN, SIS,
-ZIF, QPTIFF, NDPI, and GeoTIFF files.
+ZIF (Zoomable Image File Format), QPTIFF (QPI), NDPI, and GeoTIFF files.
-Numpy arrays can be written to TIFF, BigTIFF, and ImageJ hyperstack compatible
-files in multi-page, memory-mappable, tiled, predicted, or compressed form.
+Numpy arrays can be written to TIFF, BigTIFF, OME-TIFF, and ImageJ hyperstack
+compatible files in multi-page, memory-mappable, tiled, predicted, or
+compressed form.
A subset of the TIFF specification is supported, mainly uncompressed and
losslessly compressed 8, 16, 32 and 64-bit integer, 16, 32 and 64-bit float,
@@ -26,7 +27,7 @@ STK, LSM, FluoView, SGI, SEQ, GEL, QPTIFF, NDPI, and OME-TIFF, are custom
extensions defined by Molecular Devices (Universal Imaging Corporation),
Carl Zeiss MicroImaging, Olympus, Silicon Graphics International,
Media Cybernetics, Molecular Dynamics, PerkinElmer, Hamamatsu, and the
-Open Microscopy Environment consortium respectively.
+Open Microscopy Environment consortium, respectively.
For command line usage run ``python -m tifffile --help``
@@ -38,23 +39,44 @@ For command line usage run ``python -m tifffile --help``
:License: BSD 3-Clause
-:Version: 2020.6.3
+:Version: 2020.7.17
Requirements
------------
This release has been tested with the following requirements and dependencies
(other versions may work):
-* `CPython 3.6.8, 3.7.7, 3.8.3 64-bit <https://www.python.org>`_
-* `Numpy 1.16.6, 1.18.4 <https://www.numpy.org>`_
+* `CPython 3.7.8, 3.8.4, 3.9.0b4 64-bit <https://www.python.org>`_
+* `Numpy 1.18.5 <https://pypi.org/project/numpy/>`_
* `Imagecodecs 2020.5.30 <https://pypi.org/project/imagecodecs/>`_
(required only for encoding or decoding LZW, JPEG, etc.)
-* `Matplotlib 3.1 <https://www.matplotlib.org>`_ (required only for plotting)
+* `Matplotlib 3.2.2 <https://pypi.org/project/matplotlib/>`_
+ (required only for plotting)
+* `Lxml 4.5.2 <https://github.com/lxml/lxml>`_
+ (required only for validating and printing XML)
Revisions
---------
+2020.7.17
+ Pass 3022 tests.
+ Initial support for writing OME-TIFF (WIP).
+ Return samples as separate dimension in OME series (breaking).
+ Fix modulo dimensions for multiple OME series.
+ Fix some test errors on big endian systems (#18).
+ Fix BytesWarning.
+ Allow to pass TIFF.PREDICTOR values to TiffWriter.save.
+2020.7.4
+ Deprecate support for Python 3.6 (NEP 29).
+ Move pyramidal subresolution series to TiffPageSeries.levels (breaking).
+ Add parser for SVS, SCN, NDPI, and QPI pyramidal series.
+ Read single-file OME-TIFF pyramids.
+ Read NDPI files > 4 GB (#15).
+ Include SubIFDs in generic series.
+ Preliminary support for writing packed integer arrays (#11, WIP).
+ Read more LSM info subrecords.
+ Fix missing ReferenceBlackWhite tag for YCbCr photometrics.
+ Fix reading lossless JPEG compressed DNG files.
2020.6.3
- Pass 2908 tests.
Support os.PathLike file names (#9).
2020.5.30
Re-add pure Python PackBits decoder.
@@ -208,10 +230,11 @@ The API is not stable yet and might change between revisions.
Tested on little-endian platforms only.
-Python 32-bit versions are deprecated.
+Python 32-bit versions are deprecated. Python <= 3.6 are no longer supported.
Tifffile relies on the `imagecodecs <https://pypi.org/project/imagecodecs/>`_
-package for encoding and decoding LZW, JPEG, and other compressed images.
+package for encoding and decoding LZW, JPEG, and other compressed image
+segments.
Several TIFF-like formats do not strictly adhere to the TIFF6 specification,
some of which allow file or data sizes to exceed the 4 GB limit:
@@ -228,21 +251,23 @@ some of which allow file or data sizes to exceed the 4 GB limit:
files. The 8-bit UTF-8 encoded OME-XML metadata found in the ImageDescription
tag of the first IFD defines the position of TIFF IFDs in the high
dimensional data. Tifffile can read OME-TIFF files, except when the OME-XML
- metadata are stored in a separate file.
+ metadata are stored in a separate file. Tifffile can write numpy arrays
+ to single-file, non-pyramidal OME-TIFF.
* *LSM* stores all IFDs below 4 GB but wraps around 32-bit StripOffsets.
The StripOffsets of each series and position require separate unwrapping.
The StripByteCounts tag contains the number of bytes for the uncompressed
data. Tifffile can read large LSM files.
* *NDPI* uses some 64-bit offsets in the file header, IFD, and tag structures.
Tag values/offsets can be corrected using high bits stored after IFD
- structures. JPEG compressed tiles with dimensions > 65536 are not readable
- with libjpeg. Tifffile can read NDPI files < 4 GB and decompress large JPEG
- tiles using the imagecodecs library on Windows.
+ structures. JPEG compressed segments with dimensions >65536 or missing
+ restart markers are not readable with libjpeg. Tifffile can read NDPI
+ files > 4 GB. JPEG segments with restart markers and dimensions >65536 can
+ be decoded with the imagecodecs library on Windows.
* *ScanImage* optionally allows corrupt non-BigTIFF files > 2 GB. The values
of StripOffsets and StripByteCounts can be recovered using the constant
differences of the offsets of IFD and tag values throughout the file.
- Tifffile can read such files on Python 3 if the image data are stored
- contiguously in each page.
+ Tifffile can read such files if the image data are stored contiguously in
+ each page.
* *GeoTIFF* sparse files allow strip or tile offsets and byte counts to be 0.
Such segments are implicitly set to 0 or the NODATA value on reading.
Tifffile can read GeoTIFF sparse files.
@@ -275,29 +300,33 @@ Some libraries are using tifffile to write OME-TIFF files:
References
----------
-1. TIFF 6.0 Specification and Supplements. Adobe Systems Incorporated.
- https://www.adobe.io/open/standards/TIFF.html
-2. TIFF File Format FAQ. https://www.awaresystems.be/imaging/tiff/faq.html
-3. MetaMorph Stack (STK) Image File Format.
- http://mdc.custhelp.com/app/answers/detail/a_id/18862
-4. Image File Format Description LSM 5/7 Release 6.0 (ZEN 2010).
- Carl Zeiss MicroImaging GmbH. BioSciences. May 10, 2011
-5. The OME-TIFF format.
- https://docs.openmicroscopy.org/ome-model/5.6.4/ome-tiff/
-6. UltraQuant(r) Version 6.0 for Windows Start-Up Guide.
- http://www.ultralum.com/images%20ultralum/pdf/UQStart%20Up%20Guide.pdf
-7. Micro-Manager File Formats.
- https://micro-manager.org/wiki/Micro-Manager_File_Formats
-8. Tags for TIFF and Related Specifications. Digital Preservation.
- https://www.loc.gov/preservation/digital/formats/content/tiff_tags.shtml
-9. ScanImage BigTiff Specification - ScanImage 2016.
- http://scanimage.vidriotechnologies.com/display/SI2016/
- ScanImage+BigTiff+Specification
-10. CIPA DC-008-2016: Exchangeable image file format for digital still cameras:
- Exif Version 2.31.
- http://www.cipa.jp/std/documents/e/DC-008-Translation-2016-E.pdf
-11. ZIF, the Zoomable Image File format. http://zif.photo/
-12. GeoTIFF File Format https://gdal.org/drivers/raster/gtiff.html
+* TIFF 6.0 Specification and Supplements. Adobe Systems Incorporated.
+ https://www.adobe.io/open/standards/TIFF.html
+* TIFF File Format FAQ. https://www.awaresystems.be/imaging/tiff/faq.html
+* The BigTIFF File Format.
+ https://www.awaresystems.be/imaging/tiff/bigtiff.html
+* MetaMorph Stack (STK) Image File Format.
+ http://mdc.custhelp.com/app/answers/detail/a_id/18862
+* Image File Format Description LSM 5/7 Release 6.0 (ZEN 2010).
+ Carl Zeiss MicroImaging GmbH. BioSciences. May 10, 2011
+* The OME-TIFF format.
+ https://docs.openmicroscopy.org/ome-model/latest/
+* UltraQuant(r) Version 6.0 for Windows Start-Up Guide.
+ http://www.ultralum.com/images%20ultralum/pdf/UQStart%20Up%20Guide.pdf
+* Micro-Manager File Formats.
+ https://micro-manager.org/wiki/Micro-Manager_File_Formats
+* ScanImage BigTiff Specification - ScanImage 2016.
+ http://scanimage.vidriotechnologies.com/display/SI2016/
+ ScanImage+BigTiff+Specification
+* ZIF, the Zoomable Image File format. http://zif.photo/
+* GeoTIFF File Format https://gdal.org/drivers/raster/gtiff.html
+* Cloud optimized GeoTIFF.
+ https://github.com/cogeotiff/cog-spec/blob/master/spec.md
+* Tags for TIFF and Related Specifications. Digital Preservation.
+ https://www.loc.gov/preservation/digital/formats/content/tiff_tags.shtml
+* CIPA DC-008-2016: Exchangeable image file format for digital still cameras:
+ Exif Version 2.31.
+ http://www.cipa.jp/std/documents/e/DC-008-Translation-2016-E.pdf
Examples
--------
@@ -430,19 +459,21 @@ Iterate over pages and tags in the TIFF file and successively read images:
... tag_name, tag_value = tag.name, tag.value
... image = page.asarray()
-Save two image series to a TIFF file:
+Write two numpy arrays to a multi-series OME-TIFF file:
->>> data0 = numpy.random.randint(0, 255, (301, 219, 3), 'uint8')
->>> data1 = numpy.random.randint(0, 255, (5, 301, 219), 'uint16')
->>> with TiffWriter('temp.tif') as tif:
+>>> data0 = numpy.random.randint(0, 255, (32, 32, 3), 'uint8')
+>>> data1 = numpy.random.randint(0, 1023, (5, 256, 256), 'uint16')
+>>> with TiffWriter('temp.ome.tif', ome=True) as tif:
... tif.save(data0, compress=6, photometric='rgb')
-... tif.save(data1, compress=6, photometric='minisblack', contiguous=False)
+... tif.save(data1, photometric='minisblack', contiguous=False,
+... metadata=dict(axes='ZYX', SignificantBits=10,
+... PositionZ=[0.0, 1.0, 2.0, 3.0, 4.0]))
-Read the second image series from the TIFF file:
+Read the second image series from the OME-TIFF file:
->>> series1 = imread('temp.tif', series=1)
+>>> series1 = imread('temp.ome.tif', series=1)
>>> series1.shape
-(5, 301, 219)
+(5, 256, 256)
Read an image stack from a series of TIFF files with a file name pattern:
@@ -457,9 +488,42 @@ Read an image stack from a series of TIFF files with a file name pattern:
>>> data.shape
(1, 2, 64, 64)
-Create a TIFF file from an iterator of tiles:
+Create a TIFF file from a generator of tiles:
>>> def tiles():
... data = numpy.arange(3*4*16*16, dtype='uint16').reshape((3*4, 16, 16))
... for i in range(data.shape[0]): yield data[i]
>>> imwrite('temp.tif', tiles(), dtype='uint16', shape=(48, 64), tile=(16, 16))
+
+Write a tiled, multi-resolution, pyramidal TIFF file using JPEG compression:
+
+>>> data = numpy.arange(1024*1024*3, dtype='uint8').reshape((1024, 1024, 3))
+>>> with TiffWriter('temp.tif') as tif:
+... options = dict(tile=(256, 256), compress='jpeg', metadata=None)
+... tif.save(data, **options)
+... # save pyramid levels. In production use resampling to generate levels!
+... tif.save(data[::2, ::2], subfiletype=1, **options)
+... tif.save(data[::4, ::4], subfiletype=1, **options)
+
+Access the image levels in the pyramidal TIFF file:
+
+>>> baseimage = imread('temp.tif')
+>>> second_level = imread('temp.tif', series=0, level=1)
+>>> with TiffFile('temp.tif') as tif:
+... baseimage = tif.series[0].asarray()
+... second_level = tif.series[0].levels[1].asarray()
+
+Iterate over and decode single JPEG compressed tiles in the TIFF file:
+
+>>> with TiffFile('temp.tif') as tif:
+... fh = tif.filehandle
+... for page in tif.pages:
+... jpegtables = page.tags.get('JPEGTables', None)
+... if jpegtables is not None:
+... jpegtables = jpegtables.value
+... for index, (offset, bytecount) in enumerate(
+... zip(page.dataoffsets, page.databytecounts)
+... ):
+... fh.seek(offset)
+... data = fh.read(bytecount)
+... tile, indices, shape = page.decode(data, index, jpegtables)
=====================================
setup.py
=====================================
@@ -71,11 +71,11 @@ setup(
# 'imagecodecs>=2020.2.18',
],
extras_require={
- 'all': ['imagecodecs>=2020.2.18', 'matplotlib>=3.1'],
+ 'all': ['imagecodecs>=2020.2.18', 'matplotlib>=3.1', 'lxml'],
},
tests_require=[
'pytest', 'imagecodecs', 'czifile', 'cmapfile', 'oiffile', 'lfdfiles',
- 'roifile'
+ 'roifile', 'lxml',
],
entry_points={
'console_scripts': [
@@ -92,7 +92,6 @@ setup(
'Intended Audience :: Developers',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3 :: Only',
- 'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
=====================================
tests/test_tifffile.py
=====================================
@@ -42,7 +42,7 @@ Private data files are not available due to size and copyright restrictions.
:License: BSD 3-Clause
-:Version: 2020.6.3
+:Version: 2020.7.17
"""
@@ -66,6 +66,8 @@ from numpy.testing import (
assert_allclose,
)
+from lxml import etree
+
import tifffile
try:
@@ -97,6 +99,7 @@ except NameError:
STAR_IMPORTED = None
from tifffile.tifffile import (
+ imagecodecs,
TIFF,
imwrite,
imread,
@@ -112,6 +115,8 @@ from tifffile.tifffile import (
TiffPageSeries,
TiffTag,
TiffTags,
+ OmeXmlError,
+ OmeXml,
lazyattr,
natural_sorted,
stripnull,
@@ -153,6 +158,8 @@ from tifffile.tifffile import (
hexdump,
validate_jhove,
xml2dict,
+ subresolution,
+ asbool,
)
# skip certain tests
@@ -161,7 +168,7 @@ SKIP_EXTENDED = False
SKIP_ROUNDTRIPS = False
SKIP_PUBLIC = False # skip public files
SKIP_PRIVATE = False # skip private files
-SKIP_VALIDATE = True # validate written files with jhove
+SKIP_VALIDATE = True # skip validate written files with jhove
SKIP_CODECS = False
SKIP_32BIT = sys.maxsize < 2**32
SKIP_BE = sys.byteorder == 'big'
@@ -212,13 +219,7 @@ if not os.path.exists(PRIVATE_DIR):
SKIP_PRIVATE = True
if not SKIP_CODECS:
- try:
- import imagecodecs # noqa
- except ImportError:
- SKIP_CODECS = True
- else:
- if imagecodecs is None:
- SKIP_CODECS = True
+ SKIP_CODECS = imagecodecs is None
def config():
@@ -274,6 +275,22 @@ def assert__str__(tif, detail=3):
TiffFile.__str__(tif, detail=i)
+def assert_valid_omexml(omexml, omexsd=None, _schema=[]):
+ """Validate OME-XML schema."""
+ if not _schema:
+ if omexsd is None:
+ omexsd = os.path.join(os.path.dirname(__file__), 'ome.xsd')
+ try:
+ _schema.append(etree.XMLSchema(etree.parse(omexsd)))
+ except Exception:
+ _schema.append(None)
+ if _schema and _schema[0] is not None:
+ if omexml.startswith('<?xml'):
+ omexml = omexml.split('>', 1)[-1]
+ xml = etree.fromstring(omexml)
+ _schema[0].assert_(xml)
+
+
if SKIP_VALIDATE:
def assert_valid(*args, **kwargs):
"""Do not validate TIFF file."""
@@ -384,6 +401,20 @@ def test_issue_infinite_loop():
assert__str__(tif)
+ at pytest.mark.skipif(SKIP_PRIVATE or SKIP_CODECS, reason=REASON)
+def test_issue_jpeg_ia():
+ """Test JPEG compressed intensity image with alpha channel."""
+ # no extrasamples!
+ fname = private_file('issues/jpeg_ia.tiff')
+ with TiffFile(fname) as tif:
+ page = tif.pages[0]
+ assert page.compression == JPEG
+ assert_array_equal(
+ page.asarray(), numpy.array([[[0, 0], [255, 255]]], dtype='uint8')
+ )
+ assert__str__(tif)
+
+
def test_issue_specific_pages():
"""Test read second page."""
data = random_data('uint8', (3, 21, 31))
@@ -634,14 +665,16 @@ def test_issue_valueoffset():
page = tif.pages[0]
# inline value
fh.seek(page.tags['ImageLength'].valueoffset)
- assert page.imagelength == unpack('H', fh.read(2))[0]
+ assert page.imagelength == unpack(tif.byteorder + 'H',
+ fh.read(2))[0]
# separate value
fh.seek(page.tags['Software'].valueoffset)
assert page.software == bytes2str(fh.read(13))
# TiffFrame
page = tif.pages[1].aspage()
fh.seek(page.tags['StripOffsets'].valueoffset)
- assert page.dataoffsets[0] == unpack('I', fh.read(4))[0]
+ assert page.dataoffsets[0] == unpack(tif.byteorder + 'I',
+ fh.read(4))[0]
@pytest.mark.skipif(SKIP_PUBLIC, reason=REASON)
@@ -816,7 +849,7 @@ def test_class_tifftags():
data = random_data('uint8', (21, 31))
with TempFileName('class_tifftags') as fname:
- imwrite(fname, data, description='test', software=None)
+ imwrite(fname, data, description='test', software=False)
with TiffFile(fname) as tif:
tags = tif.pages[0].tags
@@ -938,6 +971,200 @@ def test_class_tifftagregistry():
assert "41483, 'FlashEnergy'" in info
+ at pytest.mark.parametrize(
+ 'shape, storedshape, dtype, axes, error',
+ [
+ # separate and contig
+ ((32, 32), (1, 2, 1, 32, 32, 2), 'uint8', None, ValueError),
+ # depth
+ ((32, 32, 32), (1, 1, 32, 32, 32, 1), 'uint8', None, OmeXmlError),
+ # dtype
+ ((32, 32), (1, 1, 1, 32, 32, 1), 'float16', None, OmeXmlError),
+ # empty
+ ((0, 0), (1, 1, 1, 0, 0, 1), 'uint8', None, OmeXmlError),
+ # not YX
+ ((32, 32), (1, 1, 1, 32, 32, 1), 'uint8', 'XZ', OmeXmlError),
+ # unknown axis
+ ((1, 32, 32), (1, 1, 1, 32, 32, 1), 'uint8', 'KYX', OmeXmlError),
+ # double axis
+ ((1, 32, 32), (1, 1, 1, 32, 32, 1), 'uint8', 'YYX', OmeXmlError),
+ # more than 5 dimensions
+ ((1, 1, 1, 5, 32, 32), (5, 1, 1, 32, 32, 1), 'uint8', None,
+ OmeXmlError),
+ # more than 6 dimensions
+ ((1, 1, 1, 1, 32, 32, 3), (1, 1, 1, 32, 32, 3), 'uint8', None,
+ OmeXmlError),
+ # more than 8 dimensions
+ ((1, 1, 1, 1, 1, 1, 1, 32, 32), (1, 1, 1, 32, 32, 1), 'uint8',
+ 'ARHETZCYX', OmeXmlError),
+ # more than 9 dimensions
+ ((1, 1, 1, 1, 1, 1, 1, 32, 32, 3), (1, 1, 1, 32, 32, 3), 'uint8',
+ 'ARHETZCYXS', OmeXmlError),
+ # double axis
+ ((1, 32, 32), (1, 1, 1, 32, 32, 1), 'uint8', 'YYX', OmeXmlError),
+ # planecount mismatch
+ ((3, 32, 32), (1, 1, 1, 32, 32, 1), 'uint8', 'CYX', ValueError),
+ # stored shape mismatch
+ ((3, 32, 32), (1, 2, 1, 32, 32, 1), 'uint8', 'SYX', ValueError),
+ ((32, 32, 3), (1, 1, 1, 32, 32, 2), 'uint8', 'YXS', ValueError),
+ ((3, 32, 32), (1, 3, 1, 31, 31, 1), 'uint8', 'SYX', ValueError),
+ ((32, 32, 3), (1, 1, 1, 31, 31, 3), 'uint8', 'YXS', ValueError),
+ ((32, 32), (1, 1, 1, 32, 31, 1), 'uint8', None, ValueError),
+ # too many modulo dimensions
+ ((2, 3, 4, 5, 32, 32), (60, 1, 1, 32, 32, 1), 'uint8', 'RHEQYX',
+ OmeXmlError),
+ ]
+)
+def test_class_omexml_fail(shape, storedshape, dtype, axes, error):
+ """Test OmeXml class failures."""
+ metadata = {'axes': axes} if axes else {}
+ ox = OmeXml()
+ with pytest.raises(error):
+ ox.addimage(dtype, shape, storedshape, **metadata)
+
+
+ at pytest.mark.parametrize(
+ 'axes, autoaxes, shape, storedshape, dimorder',
+ [
+ ('YX', 'YX', (32, 32), (1, 1, 1, 32, 32, 1), 'XYCZT'),
+ ('YXS', 'YXS', (32, 32, 1), (1, 1, 1, 32, 32, 1), 'XYCZT'),
+ ('SYX', 'SYX', (1, 32, 32), (1, 1, 1, 32, 32, 1), 'XYCZT'),
+ ('YXS', 'YXS', (32, 32, 3), (1, 1, 1, 32, 32, 3), 'XYCZT'),
+ ('SYX', 'SYX', (3, 32, 32), (1, 3, 1, 32, 32, 1), 'XYCZT'),
+ ('CYX', 'CYX', (5, 32, 32), (5, 1, 1, 32, 32, 1), 'XYCZT'),
+ ('CYXS', 'CYXS', (5, 32, 32, 1), (5, 1, 1, 32, 32, 1), 'XYCZT'),
+ ('CSYX', 'ZCYX', (5, 1, 32, 32), (5, 1, 1, 32, 32, 1), 'XYCZT'), # !
+ ('CYXS', 'CYXS', (5, 32, 32, 3), (5, 1, 1, 32, 32, 3), 'XYCZT'),
+ ('CSYX', 'CSYX', (5, 3, 32, 32), (5, 3, 1, 32, 32, 1), 'XYCZT'),
+ ('TZCYX', 'TZCYX', (3, 4, 5, 32, 32), (60, 1, 1, 32, 32, 1), 'XYCZT'),
+ ('TZCYXS', 'TZCYXS', (3, 4, 5, 32, 32, 1), (60, 1, 1, 32, 32, 1),
+ 'XYCZT'),
+ ('TZCSYX', 'TZCSYX', (3, 4, 5, 1, 32, 32), (60, 1, 1, 32, 32, 1),
+ 'XYCZT'),
+ ('TZCYXS', 'TZCYXS', (3, 4, 5, 32, 32, 3), (60, 1, 1, 32, 32, 3),
+ 'XYCZT'),
+ ('ZTCSYX', '', (3, 4, 5, 3, 32, 32), (60, 3, 1, 32, 32, 1), 'XYCTZ'),
+ ]
+)
+ at pytest.mark.parametrize('metadata', ('axes', None))
+def test_class_omexml(axes, autoaxes, shape, storedshape, dimorder, metadata):
+ """Test OmeXml class."""
+ dtype = 'uint8'
+ if not metadata and dimorder != 'XYCZT':
+ pytest.xfail('')
+ metadata = dict(axes=axes) if metadata else dict()
+ omexml = OmeXml()
+ omexml.addimage(dtype, shape, storedshape, **metadata)
+ assert '\n ' in str(omexml)
+ omexml = omexml.tostring()
+ assert dimorder in omexml
+ if metadata:
+ autoaxes = axes
+ for ax in 'XYCZT':
+ if ax in autoaxes:
+ size = shape[autoaxes.index(ax)]
+ else:
+ size = 1
+ if ax == 'C':
+ size *= storedshape[1] * storedshape[-1]
+ assert f'Size{ax}="{size}"' in omexml
+ assert_valid_omexml(omexml)
+
+
+ at pytest.mark.parametrize(
+ 'axes, shape, storedshape, sizetzc, dimorder',
+ [
+ ('ZAYX', (3, 4, 32, 32), (12, 1, 1, 32, 32, 1), (1, 12, 1),
+ 'XYCZT'),
+ ('AYX', (3, 32, 32), (3, 1, 1, 32, 32, 1), (3, 1, 1),
+ 'XYCZT'),
+ ('APYX', (3, 4, 32, 32), (12, 1, 1, 32, 32, 1), (3, 4, 1),
+ 'XYCZT'),
+ ('TAYX', (3, 4, 32, 32), (12, 1, 1, 32, 32, 1), (12, 1, 1),
+ 'XYCZT'),
+ ('CHYXS', (3, 4, 32, 32, 3), (12, 1, 1, 32, 32, 3), (1, 1, 36),
+ 'XYCZT'),
+ ('CHSYX', (3, 4, 3, 32, 32), (12, 3, 1, 32, 32, 1), (1, 1, 36),
+ 'XYCZT'),
+ ('APRYX', (3, 4, 5, 32, 32), (60, 1, 1, 32, 32, 1), (3, 4, 5),
+ 'XYCZT'),
+ ('TAPYX', (3, 4, 5, 32, 32), (60, 1, 1, 32, 32, 1), (12, 5, 1),
+ 'XYCZT'),
+ ('TZAYX', (3, 4, 5, 32, 32), (60, 1, 1, 32, 32, 1), (3, 20, 1),
+ 'XYCZT'),
+ ('ZCHYX', (3, 4, 5, 32, 32), (60, 1, 1, 32, 32, 1), (1, 3, 20),
+ 'XYCZT'),
+ ('EPYX', (10, 5, 200, 200), (50, 1, 1, 200, 200, 1), (10, 5, 1),
+ 'XYCZT'),
+ ('TQCPZRYX', (2, 3, 4, 5, 6, 7, 32, 32), (5040, 1, 1, 32, 32, 1),
+ (6, 42, 20), 'XYZCT'),
+ ]
+)
+def test_class_omexml_modulo(axes, shape, storedshape, sizetzc, dimorder):
+ """Test OmeXml class with modulo dimensions."""
+ dtype = 'uint8'
+ omexml = OmeXml()
+ omexml.addimage(dtype, shape, storedshape, axes=axes)
+ assert '\n ' in str(omexml)
+ omexml = omexml.tostring()
+ assert dimorder in omexml
+ for ax, size in zip('TZC', sizetzc):
+ assert f'Size{ax}="{size}"' in omexml
+ assert_valid_omexml(omexml)
+
+
+def test_class_omexml_attributes():
+ """Test OmeXml class with attributes and elements."""
+ from uuid import uuid1 # noqa: delayed import
+
+ uuid = str(uuid1())
+ metadata = dict(
+ # document
+ uuid=uuid,
+ Creator=f'test_tifffile.py {tifffile.__version__}',
+ # image
+ axes='ZYXS',
+ Name='ImageName',
+ Acquisitiondate='2011-09-16T10:45:48',
+ Description='Image "Description" < & >\n{test}',
+ SignificantBits=12,
+ PhysicalSizeX=1.1,
+ PhysicalSizeXUnit='nm',
+ PhysicalSizeY=1.2,
+ PhysicalSizeYUnit='\xb5m',
+ PhysicalSizeZ=1.3,
+ PhysicalSizeZUnit='\xc5',
+ TimeIncrement=1.4,
+ TimeIncrementUnit='\xb5s',
+ Channel=dict(Name=['ChannelName']), # one channel with 3 samples
+ Plane=dict(PositionZ=[0.0, 2.0, 4.0]), # 3 Z-planes
+ )
+
+ omexml = OmeXml(**metadata)
+ omexml.addimage('uint16', (3, 32, 32, 3), (3, 1, 1, 32, 32, 3), **metadata)
+ assert '\n ' in str(omexml)
+ omexml = omexml.tostring()
+ assert_valid_omexml(omexml)
+ assert uuid in omexml
+ assert 'SignificantBits="12"' in omexml
+ assert 'SamplesPerPixel="3" Name="ChannelName"' in omexml
+ assert 'TheC="0" TheZ="2" TheT="0" PositionZ="4.0"' in omexml
+
+
+def test_class_omexml_multiimage():
+ """Test OmeXml class with multiple images."""
+ omexml = OmeXml(description='multiimage')
+ omexml.addimage('uint8', (32, 32, 3), (1, 1, 1, 32, 32, 3), name='preview')
+ omexml.addimage('float32', (4, 256, 256), (4, 1, 1, 256, 256, 1), name='1')
+ omexml.addimage('bool', (256, 256), (1, 1, 1, 256, 256, 1), name='mask')
+ assert '\n ' in str(omexml)
+ omexml = omexml.tostring()
+ assert 'TiffData IFD="0" PlaneCount="1"' in omexml
+ assert 'TiffData IFD="1" PlaneCount="4"' in omexml
+ assert 'TiffData IFD="5" PlaneCount="1"' in omexml
+ assert_valid_omexml(omexml)
+
+
def test_func_xml2dict():
"""Test xml2dict function."""
d = xml2dict("""<?xml version="1.0" ?>
@@ -996,7 +1223,8 @@ def test_func_memmap_fail():
"""Test non-native byteorder can not be memory mapped."""
with TempFileName('memmap_fail') as fname:
with pytest.raises(ValueError):
- memmap(fname, shape=(16, 16), dtype='float32', byteorder='>')
+ memmap(fname, shape=(16, 16), dtype='float32',
+ byteorder='>' if sys.byteorder == 'little' else '<')
def test_func_repeat_nd():
@@ -1014,10 +1242,14 @@ def test_func_repeat_nd():
def test_func_byteorder_isnative():
"""Test byteorder_isnative function."""
- assert not byteorder_isnative('>')
- assert byteorder_isnative('<')
- assert byteorder_isnative('=')
assert byteorder_isnative(sys.byteorder)
+ assert byteorder_isnative('=')
+ if sys.byteorder == 'little':
+ assert byteorder_isnative('<')
+ assert not byteorder_isnative('>')
+ else:
+ assert byteorder_isnative('>')
+ assert not byteorder_isnative('<')
def test_func_reshape_nd():
@@ -1147,6 +1379,11 @@ def test_func_natural_sorted():
def test_func_stripnull():
"""Test stripnull function."""
assert stripnull(b'string\x00') == b'string'
+ assert stripnull('string\x00', null='\0') == 'string'
+ assert stripnull(b'string\x00string\x00\x00', first=False
+ ) == b'string\x00string'
+ assert stripnull('string\x00string\x00\x00', null='\0', first=False
+ ) == 'string\x00string'
def test_func_stripascii():
@@ -1178,6 +1415,37 @@ def test_func_transpose_axes():
asaxes='CTZYX').shape == (5, 2, 1, 3, 4)
+def test_func_subresolution():
+ """Test subresolution function."""
+ class a:
+ dtype = 'uint8'
+ axes = 'QzyxS'
+ shape = (3, 256, 512, 1024, 4)
+
+ class b:
+ dtype = 'uint8'
+ axes = 'QzyxS'
+ shape = (3, 128, 256, 512, 4)
+
+ assert subresolution(a, a) == 0
+ assert subresolution(a, b) == 1
+ assert subresolution(a, b, p=2, n=2) == 1
+ assert subresolution(a, b, p=3) is None
+ b.shape = (3, 86, 171, 342, 4)
+ assert subresolution(a, b, p=3) == 1
+ b.shape = (3, 128, 256, 512, 2)
+ assert subresolution(a, b) is None
+ b.shape = (3, 64, 256, 512, 4)
+ assert subresolution(a, b) is None
+ b.shape = (3, 128, 64, 512, 4)
+ assert subresolution(a, b) is None
+ b.shape = (3, 128, 256, 1024, 4)
+ assert subresolution(a, b) is None
+ b.shape = (3, 32, 64, 128, 4)
+ assert subresolution(a, b) == 3
+
+
+ at pytest.mark.skipif(SKIP_BE, reason=REASON)
def test_func_unpack_rgb():
"""Test unpack_rgb function."""
data = struct.pack('BBBB', 0x21, 0x08, 0xFF, 0xFF)
@@ -1192,7 +1460,7 @@ def test_func_unpack_rgb():
def test_func_json_description():
"""Test json_description function."""
descr = json_description((256, 256, 3), axes='YXS')
- assert json.loads(descr) == {"shape": [256, 256, 3], "axes": "YXS"}
+ assert json.loads(descr) == {'shape': [256, 256, 3], 'axes': 'YXS'}
def test_func_json_description_metadata():
@@ -1400,6 +1668,22 @@ def test_func_hexdump():
'................')
+def test_func_asbool():
+ """Test asbool function."""
+ for true in ('TRUE', ' True ', 'true '):
+ assert asbool(true)
+ assert asbool(true.encode())
+ for false in ('FALSE', ' False ', 'false '):
+ assert not asbool(false)
+ assert not asbool(false.encode())
+ assert asbool('ON', ['on'], ['off'])
+ assert asbool('ON', 'on', 'off')
+ with pytest.raises(TypeError):
+ assert asbool('Yes')
+ with pytest.raises(TypeError):
+ assert asbool('True', ['on'], ['off'])
+
+
def test_func_snipstr():
"""Test snipstr function."""
# cut middle
@@ -1849,7 +2133,7 @@ def test_filehandle_bytesio():
with open(FILEHANDLE_NAME, 'rb') as fh:
stream = BytesIO(fh.read())
with FileHandle(stream) as fh:
- assert fh.name == "Unnamed binary stream"
+ assert fh.name == 'Unnamed binary stream'
assert not fh.is_file
assert_filehandle(fh)
@@ -1861,7 +2145,7 @@ def test_filehandle_bytesio_offset():
stream = BytesIO(fh.read())
with FileHandle(stream, offset=FILEHANDLE_OFFSET,
size=FILEHANDLE_LENGTH) as fh:
- assert fh.name == "Unnamed binary stream"
+ assert fh.name == 'Unnamed binary stream'
assert not fh.is_file
assert_filehandle(fh, offset=FILEHANDLE_OFFSET)
@@ -2293,7 +2577,7 @@ def test_read_vips():
with TiffFile(fname) as tif:
assert tif.byteorder == '<'
assert len(tif.pages) == 4
- assert len(tif.series) == 4
+ assert len(tif.series) == 1
# assert page properties
page = tif.pages[0]
assert not page.is_reduced
@@ -2305,10 +2589,13 @@ def test_read_vips():
assert page.samplesperpixel == 3
# assert series properties
series = tif.series[0]
+ assert series.is_pyramid
+ assert len(series.levels) == 4
assert series.shape == (347, 641, 3)
assert series.dtype.name == 'uint8'
assert series.axes == 'YXS'
- series = tif.series[3]
+ # level 3
+ series = series.levels[3]
page = series.pages[0]
assert page.is_reduced
assert page.is_tiled
@@ -3343,6 +3630,36 @@ def test_read_zstd():
assert__str__(tif)
+ at pytest.mark.skipif(SKIP_PRIVATE or SKIP_CODECS, reason=REASON)
+def test_read_cfa():
+ """Test read 14-bit uncompressed and JPEG compressed CFA image."""
+ fname = private_file('DNG/cinemadng/M14-1451_000085_cDNG_uncompressed.dng')
+ with TiffFile(fname) as tif:
+ assert len(tif.pages) == 1
+ page = tif.pages[0]
+ assert page.compression == 1
+ assert page.photometric == CFA
+ assert page.imagewidth == 960
+ assert page.imagelength == 540
+ assert page.bitspersample == 14
+ assert page.tags['CFARepeatPatternDim'].value == (2, 2)
+ assert page.tags['CFAPattern'].value == b'\x00\x01\x01\x02'
+ data = page.asarray()
+
+ fname = private_file('DNG/cinemadng/M14-1451_000085_cDNG_compressed.dng')
+ with TiffFile(fname) as tif:
+ assert len(tif.pages) == 1
+ page = tif.pages[0]
+ assert page.compression == JPEG
+ assert page.photometric == CFA
+ assert page.imagewidth == 960
+ assert page.imagelength == 540
+ assert page.bitspersample == 14
+ assert page.tags['CFARepeatPatternDim'].value == (2, 2)
+ assert page.tags['CFAPattern'].value == b'\x00\x01\x01\x02'
+ assert_array_equal(page.asarray(), data)
+
+
@pytest.mark.skipif(SKIP_PRIVATE or SKIP_CODECS, reason=REASON)
def test_read_lena_be_f16_contig():
"""Test read big endian float16 horizontal differencing."""
@@ -3785,8 +4102,16 @@ def test_read_subifds_array():
"""Test read SubIFDs."""
fname = public_file('Tiff-Library-4J/IFD struct/SubIFDs array E.tif')
with TiffFile(fname) as tif:
- assert len(tif.series) == 1
assert len(tif.pages) == 1
+
+ # make sure no pyramid was detected
+ assert len(tif.series) == 5
+ assert tif.series[0].shape == (1500, 2000, 3)
+ assert tif.series[1].shape == (1200, 1600, 3)
+ assert tif.series[2].shape == (900, 1200, 3)
+ assert tif.series[3].shape == (600, 800, 3)
+ assert tif.series[4].shape == (300, 400, 3)
+
page = tif.pages[0]
assert page.photometric == RGB
assert page.imagewidth == 2000
@@ -3884,6 +4209,30 @@ def test_read_subifd8():
assert__str__(tif)
+def test_read_tiles():
+ """Test iteration over tiles, manually and via page.segments."""
+ data = numpy.arange(600*500*3, dtype='uint8').reshape((600, 500, 3))
+ with TempFileName('read_tiles') as fname:
+ with TiffWriter(fname) as tif:
+ options = dict(tile=(256, 256), compress='jpeg', metadata=None)
+ tif.save(data, **options)
+ tif.save(data[::2, ::2], subfiletype=1, **options)
+ with TiffFile(fname) as tif:
+ fh = tif.filehandle
+ for page in tif.pages:
+ segments = page.segments()
+ jpegtables = page.tags.get('JPEGTables', None)
+ if jpegtables is not None:
+ jpegtables = jpegtables.value
+ for index, (offset, bytecount) in enumerate(
+ zip(page.dataoffsets, page.databytecounts)
+ ):
+ fh.seek(offset)
+ data = fh.read(bytecount)
+ tile, indices, shape = page.decode(data, index, jpegtables)
+ assert_array_equal(tile, next(segments)[0])
+
+
@pytest.mark.skipif(SKIP_PRIVATE or SKIP_32BIT, reason=REASON)
def test_read_lsm_mosaic():
"""Test read LSM: PTZCYX (Mosaic mode), two areas, 32 samples, >4 GB."""
@@ -4567,13 +4916,13 @@ def test_read_stk_112508h100():
@pytest.mark.skipif(SKIP_PRIVATE or SKIP_CODECS, reason=REASON)
-def test_read_ndpi_cmu_1_ndpi():
+def test_read_ndpi_cmu1():
"""Test read Hamamatsu NDPI slide, JPEG."""
fname = private_file('HamamatsuNDPI/CMU-1.ndpi')
with TiffFile(fname) as tif:
assert tif.is_ndpi
assert len(tif.pages) == 5
- assert len(tif.series) == 5
+ assert len(tif.series) == 2
for page in tif.pages:
assert page.ndpi_tags['Model'] == 'NanoZoomer'
# first page
@@ -4596,14 +4945,14 @@ def test_read_ndpi_cmu_1_ndpi():
@pytest.mark.skipif(
SKIP_PRIVATE or SKIP_CODECS or SKIP_32BIT or SKIP_EXTENDED, reason=REASON)
-def test_read_ndpi_cmu_2():
+def test_read_ndpi_cmu2():
"""Test read Hamamatsu NDPI slide, JPEG."""
# JPEG stream too large to be opened with unpatched libjpeg
fname = private_file('HamamatsuNDPI/CMU-2.ndpi')
with TiffFile(fname) as tif:
assert tif.is_ndpi
assert len(tif.pages) == 6
- assert len(tif.series) == 6
+ assert len(tif.series) == 2
for page in tif.pages:
assert page.ndpi_tags['Model'] == 'NanoZoomer'
# first page
@@ -4618,7 +4967,7 @@ def test_read_ndpi_cmu_2():
assert data.shape == (33792, 79872, 3)
assert data.dtype == 'uint8'
# page 5
- page = tif.pages[-1]
+ page = tif.pages[5]
assert page.is_ndpi
assert page.photometric == YCBCR
assert page.compression == JPEG
@@ -4629,14 +4978,61 @@ def test_read_ndpi_cmu_2():
@pytest.mark.skipif(SKIP_PRIVATE or SKIP_CODECS, reason=REASON)
-def test_read_svs_cmu_1():
+def test_read_ndpi_4gb():
+ """Test read > 4GB Hamamatsu NDPI slide, JPEG 103680x188160."""
+ fname = private_file('HamamatsuNDPI/103680x188160.ndpi')
+ with TiffFile(fname) as tif:
+ assert tif.is_ndpi
+ assert len(tif.pages) == 8
+ assert len(tif.series) == 3
+ for page in tif.pages:
+ assert page.ndpi_tags['Model'] == 'C13220'
+ # first page
+ page = tif.pages[0]
+ assert page.is_ndpi
+ assert page.databytecounts == (4461521316,)
+ assert page.photometric == YCBCR
+ assert page.compression == JPEG
+ assert page.shape == (103680, 188160, 3)
+ assert page.ndpi_tags['Magnification'] == 40.0
+ assert page.ndpi_tags['McuStarts'][-1] == 4461516507 # corrected
+ # page 7
+ page = tif.pages[7]
+ assert page.is_ndpi
+ assert page.photometric == MINISBLACK
+ assert page.compression == NONE
+ assert page.shape == (200, 600)
+ assert page.ndpi_tags['Magnification'] == -2.0
+ # assert page.asarray()[226, 629, 0] == 167
+ # first series
+ series = tif.series[0]
+ assert series.kind == 'NDPI'
+ assert series.name == 'S10533009'
+ assert series.shape == (103680, 188160, 3)
+ assert series.is_pyramid
+ assert len(series.levels) == 6
+ assert len(series.pages) == 1
+ # pyramid levels
+ assert series.levels[1].shape == (51840, 94080, 3)
+ assert series.levels[2].shape == (25920, 47040, 3)
+ assert series.levels[3].shape == (12960, 23520, 3)
+ assert series.levels[4].shape == (6480, 11760, 3)
+ assert series.levels[5].shape == (3240, 5880, 3)
+ assert tuple(series.levels[5].asarray()[1000, 1000]) == (222, 165, 200)
+ # cannot decode base levels since JPEG compressed size > 2 GB
+ # series.levels[0].asarray()
+ assert__str__(tif)
+
+
+ at pytest.mark.skipif(SKIP_PRIVATE or SKIP_CODECS, reason=REASON)
+def test_read_svs_cmu1():
"""Test read Aperio SVS slide, JPEG and LZW."""
fname = private_file('AperioSVS/CMU-1.svs')
with TiffFile(fname) as tif:
assert tif.is_svs
assert not tif.is_scanimage
assert len(tif.pages) == 6
- assert len(tif.series) == 6
+ assert len(tif.series) == 4
for page in tif.pages:
svs_description_metadata(page.description)
# first page
@@ -4670,7 +5066,7 @@ def test_read_svs_jp2k_33003_1():
assert tif.is_svs
assert not tif.is_scanimage
assert len(tif.pages) == 6
- assert len(tif.series) == 6
+ assert len(tif.series) == 4
for page in tif.pages:
svs_description_metadata(page.description)
# first page
@@ -5420,7 +5816,7 @@ def test_read_ome_rgb():
series = tif.series[0]
assert series.shape == (3, 720, 1280)
assert series.dtype.name == 'uint8'
- assert series.axes == 'CYX'
+ assert series.axes == 'SYX'
assert series.offset == 17524
# assert data
data = tif.asarray()
@@ -5452,7 +5848,7 @@ def test_read_ome_samplesperpixel():
series = tif.series[0]
assert series.shape == (6, 3, 1024, 1024)
assert series.dtype.name == 'uint8'
- assert series.axes == 'ZCYX'
+ assert series.axes == 'ZSYX'
# assert data
data = tif.asarray()
assert data.shape == (6, 3, 1024, 1024)
@@ -6613,7 +7009,7 @@ def test_read_geotiff_spaf27_markedcorrect():
@pytest.mark.skipif(SKIP_PRIVATE or SKIP_CODECS, reason=REASON)
def test_read_qpi():
- """Test read PerkinElmer-QPI."""
+ """Test read PerkinElmer-QPI, non Pyramid."""
fname = private_file(
'PerkinElmer-QPI/LuCa-7color_[13860,52919]_1x1component_data.tiff')
with TiffFile(fname) as tif:
@@ -6629,6 +7025,14 @@ def test_read_qpi():
assert page.bitspersample == 32
assert page.samplesperpixel == 1
assert page.tags['Software'].value == 'PerkinElmer-QPI'
+ series = tif.series[0]
+ assert series.shape == (8, 1400, 1868)
+ assert series.dtype == 'float32'
+ assert not series.is_pyramid
+ series = tif.series[1]
+ assert series.shape == (350, 467, 3)
+ assert series.dtype == 'uint8'
+ assert not series.is_pyramid
# assert data
image = tif.asarray()
assert image.shape == (8, 1400, 1868)
@@ -6648,7 +7052,7 @@ def test_read_zif():
with TiffFile(fname) as tif:
# assert tif.is_zif
assert len(tif.pages) == 5
- assert len(tif.series) == 5
+ assert len(tif.series) == 1
for page in tif.pages:
assert page.description == ('Created by Objective '
'Pathology Services')
@@ -6664,6 +7068,14 @@ def test_read_zif():
assert page.compression == JPEG
assert page.shape == (195, 130, 3)
assert tuple(page.asarray()[191, 127, :]) == (30, 49, 66)
+ # series
+ series = tif.series[0]
+ assert series.is_pyramid
+ assert len(series.levels) == 5
+ assert series.shape == (3120, 2080, 3)
+ assert tuple(series.asarray()[3110, 2070, :]) == (27, 45, 59)
+ assert series.levels[-1].shape == (195, 130, 3)
+ assert tuple(series.asarray(level=-1)[191, 127, :]) == (30, 49, 66)
assert__str__(tif)
@@ -7265,18 +7677,33 @@ def test_write_is_shaped():
assert__str__(tif)
+def test_write_bytes_str():
+ """Test write bytes in place of 7-bit ascii string."""
+ micron = 'micron \xB5'.encode('latin-1') # can't be encoded as 7-bit ascii
+ data = numpy.arange(4, dtype='uint32').reshape((2, 2))
+ with TempFileName('write_bytes_str') as fname:
+ imwrite(fname, data, description=micron, software=micron,
+ extratags=[(50001, 's', 8, micron, True)])
+ with TiffFile(fname) as tif:
+ page = tif.pages[0]
+ assert page.description == 'micron \xB5'
+ assert page.software == 'micron \xB5'
+ assert page.tags[50001].value == 'micron \xB5'
+
+
def test_write_extratags():
"""Test write extratags."""
data = random_data('uint8', (2, 219, 301))
- description = "Created by TestTiffWriter\nLorem ipsum dolor..."
- pagename = "Page name"
- extratags = [(270, 's', 0, description, True),
- ('PageName', 's', 0, pagename, False),
- (50001, 'b', 1, b'1', True),
- (50002, 'b', 2, b'12', True),
- (50004, 'b', 4, b'1234', True),
- (50008, 'B', 8, b'12345678', True),
- ]
+ description = 'Created by TestTiffWriter\nLorem ipsum dolor...'
+ pagename = 'Page name'
+ extratags = [
+ (270, 's', 0, description, True),
+ ('PageName', 's', 0, pagename, False),
+ (50001, 'b', 1, b'1', True),
+ (50002, 'b', 2, b'12', True),
+ (50004, 'b', 4, b'1234', True),
+ (50008, 'B', 8, b'12345678', True),
+ ]
with TempFileName('extratags') as fname:
imwrite(fname, data, extratags=extratags)
assert_valid(fname)
@@ -7490,6 +7917,49 @@ def test_write_resolution_unit():
assert__str__(tif)
+ at pytest.mark.skipif(SKIP_CODECS, reason=REASON)
+ at pytest.mark.parametrize('bps', [1, 2, 7, 8])
+ at pytest.mark.parametrize('dtype', ['u1', 'u2', 'u4'])
+def test_write_bitspersample(bps, dtype):
+ """Test write with packints."""
+ dtype = numpy.dtype(dtype)
+ bps += (dtype.itemsize // 2) * 8
+ data = numpy.arange(256*256*3, dtype=dtype).reshape((256, 256, 3))
+ with TempFileName(f'write_bitspersample_{dtype.char}{bps}') as fname:
+ # TODO: enable all cases once imagecodecs.packints_encode works
+ if bps == dtype.itemsize * 8:
+ imwrite(fname, data, bitspersample=bps)
+ assert_array_equal(imread(fname), data)
+ else:
+ with pytest.raises(NotImplementedError):
+ imwrite(fname, data, bitspersample=bps)
+ assert_array_equal(imread(fname), data)
+
+
+def test_write_bitspersample_fail():
+ """Test write with packints fails."""
+ data = numpy.arange(32*32*3, dtype='uint32').reshape((32, 32, 3))
+ with TempFileName('write_bitspersample_fail') as fname:
+ with TiffWriter(fname) as tif:
+ # not working with compression
+ with pytest.raises(ValueError):
+ tif.save(data.astype('uint8'), bitspersample=4, compress=6)
+ # dtype.itemsize != bitspersample
+ for dtype in ('int8', 'int16', 'float32', 'uint64'):
+ with pytest.raises(ValueError):
+ tif.save(data.astype(dtype), bitspersample=4)
+ # bitspersample out of data range
+ for bps in (0, 9, 16, 32):
+ with pytest.raises(ValueError):
+ tif.save(data.astype('uint8'), bitspersample=bps)
+ for bps in (1, 8, 17, 32):
+ with pytest.raises(ValueError):
+ tif.save(data.astype('uint16'), bitspersample=bps)
+ for bps in (1, 8, 16, 33, 64):
+ with pytest.raises(ValueError):
+ tif.save(data.astype('uint32'), bitspersample=bps)
+
+
def test_write_compress_none():
"""Test write compress=0."""
data = WRITE_DATA
@@ -7761,7 +8231,7 @@ def test_write_compress_predictor():
"""Test write horizontal differencing."""
data = WRITE_DATA
with TempFileName('compress_predictor') as fname:
- imwrite(fname, data, compress=6, predictor=True)
+ imwrite(fname, data, compress=6, predictor=TIFF.PREDICTOR.HORIZONTAL)
assert_valid(fname)
with TiffFile(fname) as tif:
assert len(tif.pages) == 1
@@ -8557,6 +9027,37 @@ def test_write_extrasamples_planar_rgb():
assert__str__(tif)
+def test_write_cfa():
+ """Test write uncompressed CFA image."""
+ # TODO: write a valid TIFF/EP file
+ data = imread(
+ private_file('DNG/cinemadng/M14-1451_000085_cDNG_uncompressed.dng')
+ )
+ extratags = [
+ (271, 's', 4, 'Make', False),
+ (272, 's', 5, 'Model', False),
+ (33421, 'H', 2, (2, 2), False), # CFARepeatPatternDim
+ (33422, 'B', 4, b'\x00\x01\x01\x02', False), # CFAPattern
+ # (37398, 'B', 4, b'\x01\x00\x00\x00', False), # TIFF/EPStandardID
+ # (37399, 'H', 1, 0) # SensingMethod Undefined
+ # (50706, 'B', 4, b'\x01\x04\x00\x00', False), # DNGVersion
+ ]
+ with TempFileName('write_cfa') as fname:
+ imwrite(fname, data, photometric='CFA', software='Tifffile',
+ datetime=True, extratags=extratags)
+ with TiffFile(fname) as tif:
+ assert len(tif.pages) == 1
+ page = tif.pages[0]
+ assert page.compression == 1
+ assert page.photometric == CFA
+ assert page.imagewidth == 960
+ assert page.imagelength == 540
+ assert page.bitspersample == 16
+ assert page.tags['CFARepeatPatternDim'].value == (2, 2)
+ assert page.tags['CFAPattern'].value == b'\x00\x01\x01\x02'
+ assert_array_equal(page.asarray(), data)
+
+
def test_write_tiled_compressed():
"""Test write compressed tiles."""
data = random_data('uint8', (3, 219, 301))
@@ -8736,6 +9237,68 @@ def test_write_tileiter_separate():
assert_array_equal(numpy.squeeze(segment[0]), data[i])
+def test_write_pyramids():
+ """Test write two pyramids to shaped file."""
+ data = random_data('uint8', (31, 64, 96, 3))
+ with TempFileName('pyramids') as fname:
+ with TiffWriter(fname) as tif:
+ # use pages
+ tif.save(data, tile=(16, 16))
+ # interrupt pyramid, e.g. thumbnail
+ tif.save(data[0, :, :, 0])
+ # pyramid levels
+ tif.save(data[:, ::2, ::2], tile=(16, 16), subfiletype=1)
+ tif.save(data[:, ::4, ::4], tile=(16, 16), subfiletype=1)
+ # second pyramid using sgi with downsampling factor 3
+ tif.save(data, tile=(16, 16, 16))
+ tif.save(data[::3, ::3, ::3], tile=(16, 16, 16), subfiletype=1)
+
+ assert_valid(fname)
+
+ with TiffFile(fname) as tif:
+ assert len(tif.pages) == 3 * 31 + 2 + 1
+ assert len(tif.series) == 3
+
+ series = tif.series[0]
+ assert series.is_pyramid
+ assert len(series.levels) == 3
+ assert len(series.levels[0].pages) == 31
+ assert len(series.levels[1].pages) == 31
+ assert len(series.levels[2].pages) == 31
+ assert series.levels[0].shape == (31, 64, 96, 3)
+ assert series.levels[1].shape == (31, 32, 48, 3)
+ assert series.levels[2].shape == (31, 16, 24, 3)
+
+ series = tif.series[1]
+ assert not series.is_pyramid
+ assert series.shape == (64, 96)
+
+ series = tif.series[2]
+ assert series.is_pyramid
+ assert len(series.levels) == 2
+ assert len(series.levels[0].pages) == 1
+ assert len(series.levels[1].pages) == 1
+ assert series.levels[0].keyframe.is_sgi
+ assert series.levels[1].keyframe.is_sgi
+ assert series.levels[0].shape == (31, 64, 96, 3)
+ assert series.levels[1].shape == (11, 22, 32, 3)
+
+ assert_array_equal(data, tif.asarray())
+ assert_array_equal(data, tif.asarray(series=0, level=0))
+ assert_array_equal(data[:, ::2, ::2],
+ tif.asarray(series=0, level=1))
+ assert_array_equal(data[:, ::4, ::4],
+ tif.asarray(series=0, level=2))
+
+ assert_array_equal(data[0, :, :, 0], tif.asarray(series=1))
+
+ assert_array_equal(data, tif.asarray(series=2))
+ assert_array_equal(data[::3, ::3, ::3],
+ tif.asarray(series=2, level=1))
+
+ assert__str__(tif)
+
+
def test_write_volume():
"""Test write tiled volume."""
data = random_data('uint8', (253, 64, 96))
@@ -8762,6 +9325,33 @@ def test_write_volume():
assert__str__(tif)
+def test_write_volume_png():
+ """Test write tiled volume using an image compressor."""
+ data = random_data('uint8', (16, 64, 96, 3))
+ with TempFileName('volume_png') as fname:
+ imwrite(fname, data, tile=(1, 64, 64), compress='png')
+ assert_valid(fname)
+ with TiffFile(fname) as tif:
+ assert len(tif.pages) == 1
+ page = tif.pages[0]
+ assert page.is_sgi
+ assert page.is_tiled
+ assert page.compression == TIFF.COMPRESSION.PNG
+ assert not page.is_contiguous
+ assert page.planarconfig == CONTIG
+ assert page.photometric == RGB
+ assert page.imagewidth == 96
+ assert page.imagelength == 64
+ assert page.imagedepth == 16
+ assert page.tilewidth == 64
+ assert page.tilelength == 64
+ assert page.tiledepth == 1
+ assert page.samplesperpixel == 3
+ image = tif.asarray()
+ assert_array_equal(data, image)
+ assert__str__(tif)
+
+
def test_write_volume_5d_planar_rgb():
"""Test write 5D array as grayscale volumes."""
shape = (2, 3, 256, 64, 96)
@@ -9269,52 +9859,28 @@ def test_write_imagej_roundtrip():
assert_valid(fname)
-def test_write_imagej_hyperstack():
- """Test write truncated ImageJ hyperstack."""
+ at pytest.mark.parametrize('mmap', [False, True])
+ at pytest.mark.parametrize('truncate', [False, True])
+def test_write_imagej_hyperstack(truncate, mmap):
+ """Test write ImageJ hyperstack."""
shape = (5, 6, 7, 49, 61, 3)
data = numpy.empty(shape, dtype='uint8')
data[:] = numpy.arange(210, dtype='uint8').reshape(5, 6, 7, 1, 1, 1)
- with TempFileName('imagej_hyperstack') as fname:
- imwrite(fname, data, imagej=True, truncate=True)
- # assert file
- with TiffFile(fname) as tif:
- assert not tif.is_bigtiff
- assert len(tif.pages) == 1
- page = tif.pages[0]
- assert page.is_contiguous
- assert page.planarconfig == CONTIG
- assert page.photometric == RGB
- assert page.imagewidth == 61
- assert page.imagelength == 49
- assert page.samplesperpixel == 3
- # assert series properties
- series = tif.series[0]
- assert series.shape == shape
- assert len(series._pages) == 1
- assert len(series.pages) == 1
- assert series.dtype.name == 'uint8'
- assert series.axes == 'TZCYXS'
- # assert data
- image = tif.asarray(out='memmap')
- assert_array_equal(data.squeeze(), image.squeeze())
+ _truncate = ['', '_trunc'][truncate]
+ _memmap = ['', '_memmap'][mmap]
+ with TempFileName(f'imagej_hyperstack{_truncate}{_memmap}') as fname:
+ if mmap:
+ image = memmap(fname, shape=data.shape, dtype=data.dtype,
+ imagej=True, truncate=truncate)
+ image[:] = data
del image
- assert__str__(tif)
- assert_valid(fname)
-
-
-def test_write_imagej_hyperstack_nontrunc():
- """Test write non-truncated ImageJ hyperstack."""
- shape = (5, 6, 7, 49, 61, 3)
- data = numpy.empty(shape, dtype='uint8')
- data[:] = numpy.arange(210, dtype='uint8').reshape(5, 6, 7, 1, 1, 1)
-
- with TempFileName('imagej_hyperstack_nontrunc') as fname:
- imwrite(fname, data, imagej=True)
+ else:
+ imwrite(fname, data, truncate=truncate, imagej=True)
# assert file
with TiffFile(fname) as tif:
assert not tif.is_bigtiff
- assert len(tif.pages) == 210
+ assert len(tif.pages) == 1 if truncate else 210
page = tif.pages[0]
assert page.is_contiguous
assert page.planarconfig == CONTIG
@@ -9326,7 +9892,7 @@ def test_write_imagej_hyperstack_nontrunc():
series = tif.series[0]
assert series.shape == shape
assert len(series._pages) == 1
- assert len(series.pages) == 210
+ assert len(series.pages) == 1 if truncate else 210
assert series.dtype.name == 'uint8'
assert series.axes == 'TZCYXS'
# assert data
@@ -9338,6 +9904,7 @@ def test_write_imagej_hyperstack_nontrunc():
for i, page in enumerate(series.pages):
image = page.asarray()
assert_array_equal(data[i], image)
+ del image
assert__str__(tif)
assert_valid(fname)
@@ -9413,6 +9980,103 @@ def test_write_imagej_raw():
assert__str__(tif)
+ at pytest.mark.skipif(SKIP_EXTENDED, reason=REASON)
+ at pytest.mark.parametrize(
+ 'shape, axes',
+ [
+ ((219, 301, 1), None),
+ ((219, 301, 2), None),
+ ((219, 301, 3), None),
+ ((219, 301, 4), None),
+ ((219, 301, 5), None),
+ ((1, 219, 301), None),
+ ((2, 219, 301), None),
+ ((3, 219, 301), None),
+ ((4, 219, 301), None),
+ ((5, 219, 301), None),
+ ((4, 3, 219, 301), None),
+ ((4, 219, 301, 3), None),
+ ((3, 4, 219, 301), None),
+ ((1, 3, 1, 219, 301), None),
+ ((3, 1, 1, 219, 301), None),
+ ((1, 3, 4, 219, 301), None),
+ ((3, 1, 4, 219, 301), None),
+ ((3, 4, 1, 219, 301), None),
+ ((3, 4, 1, 219, 301, 3), None),
+ ((2, 3, 4, 219, 301), None),
+ ((4, 3, 2, 219, 301, 3), None),
+ ((5, 1, 32, 32), 'CSYX'),
+ ((5, 1, 32, 32), 'ZCYX'),
+ ((2, 3, 4, 219, 301, 3), 'TCZYXS'),
+ ((10, 5, 200, 200), 'EPYX'),
+ ((2, 3, 4, 5, 6, 7, 32, 32, 3), 'TQCPZRYXS'),
+ ]
+)
+def test_write_ome(shape, axes):
+ """Test write OME-TIFF format."""
+ metadata = {'axes': axes} if axes is not None else {}
+ data = random_data('uint8', shape)
+ fname = 'write_ome_{}.ome.tif'.format(str(shape).replace(' ', ''))
+ with TempFileName(fname) as fname:
+ imwrite(fname, data, metadata=metadata)
+ with TiffFile(fname) as tif:
+ image = tif.asarray()
+ omexml = tif.ome_metadata
+ if axes:
+ assert tif.series[0].axes == squeeze_axes(shape, axes)[-1]
+ assert_array_equal(data.squeeze(), image.squeeze())
+ assert_valid_omexml(omexml)
+ assert_valid(fname)
+
+
+ at pytest.mark.skipif(SKIP_PUBLIC, reason=REASON)
+def test_write_ome_copy():
+ """Test re-write OME time-series of volumes."""
+ # 4D (7 time points, 5 focal planes)
+ fname = public_file('OME/bioformats-artificial/4D-series.ome.tiff')
+ with TiffFile(fname) as tif:
+ omexml = tif.ome_metadata
+ data = tif.asarray()
+
+ with TempFileName('ome_copy') as fname:
+ # process OME-XML
+ omexml = omexml.replace('4D-series.ome.tiff', os.path.split(fname)[-1])
+ # omexml = omexml.replace('BigEndian="true"', 'BigEndian="false"')
+ data = data.newbyteorder('>')
+ # save image planes in the order referenced in the OME-XML
+ # make sure storage options (compression, byteorder, photometric mode)
+ # match OME-XML
+ # write OME-XML to first page only
+ with TiffWriter(fname, bigtiff=True, byteorder='>') as tif:
+ for i, image in enumerate(data.reshape(-1, *data.shape[-2:])):
+ description = omexml if i == 0 else None
+ tif.save(image, description=description,
+ photometric='minisblack', metadata=None,
+ contiguous=False)
+
+ with TiffFile(fname) as tif:
+ assert tif.is_ome
+ assert tif.byteorder == '>'
+ assert len(tif.pages) == 35
+ assert len(tif.series) == 1
+ # assert page properties
+ page = tif.pages[0]
+ assert page.is_contiguous
+ assert page.compression == NONE
+ assert page.imagewidth == 439
+ assert page.imagelength == 167
+ assert page.bitspersample == 8
+ assert page.samplesperpixel == 1
+ # assert series properties
+ series = tif.series[0]
+ assert series.shape == (7, 5, 167, 439)
+ assert series.dtype.name == 'int8'
+ assert series.axes == 'TZYX'
+ # assert data
+ assert_array_equal(data, tif.asarray())
+ assert__str__(tif)
+
+
###############################################################################
# Test embedded TIFF files
@@ -9509,7 +10173,7 @@ def assert_embed_micromanager(tif):
assert len(tif.series) == 1
# assert non-tiff micromanager_metadata
tags = tif.micromanager_metadata['Summary']
- assert tags["MicroManagerVersion"] == "1.4.x dev"
+ assert tags["MicroManagerVersion"] == '1.4.x dev'
# assert page properties
page = tif.pages[0]
assert page.is_contiguous
@@ -9527,8 +10191,8 @@ def assert_embed_micromanager(tif):
assert tags['slices'] == 3
assert tags['Ranges'] == (706.0, 5846.0)
tags = tif.micromanager_metadata
- assert tags["FileName"] == "Untitled_1_MMStack.ome.tif"
- assert tags["PixelType"] == "GRAY16"
+ assert tags["FileName"] == 'Untitled_1_MMStack.ome.tif'
+ assert tags["PixelType"] == 'GRAY16'
# assert series properties
series = tif.series[0]
assert series.shape == (5, 3, 512, 512)
@@ -9773,14 +10437,15 @@ def test_depend_lfdfiles():
filename = private_file('SimFCS/simfcs.Z64')
with TempFileName('simfcsz_z64', ext='.tif') as outfile:
- z64 = SimfcsZ64(filename)
- z64.totiff(outfile)
+ with SimfcsZ64(filename) as z64:
+ data = z64.asarray()
+ z64.totiff(outfile)
with TiffFile(outfile) as tif:
assert len(tif.pages) == 256
assert len(tif.series) == 1
assert tif.series[0].shape == (256, 256, 256)
assert tif.series[0].dtype == 'float32'
- assert_array_equal(z64.asarray(), tif.asarray())
+ assert_array_equal(data, tif.asarray())
@pytest.mark.skipif(SKIP_PRIVATE, reason=REASON)
=====================================
tifffile.egg-info/PKG-INFO
=====================================
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: tifffile
-Version: 2020.6.3
+Version: 2020.7.17
Summary: Read and write TIFF(r) files
Home-page: https://www.lfd.uci.edu/~gohlke/
Author: Christoph Gohlke
@@ -18,10 +18,11 @@ Description: Read and write TIFF(r) files
Image and metadata can be read from TIFF, BigTIFF, OME-TIFF, STK, LSM, SGI,
NIHImage, ImageJ, MicroManager, FluoView, ScanImage, SEQ, GEL, SVS, SCN, SIS,
- ZIF, QPTIFF, NDPI, and GeoTIFF files.
+ ZIF (Zoomable Image File Format), QPTIFF (QPI), NDPI, and GeoTIFF files.
- Numpy arrays can be written to TIFF, BigTIFF, and ImageJ hyperstack compatible
- files in multi-page, memory-mappable, tiled, predicted, or compressed form.
+ Numpy arrays can be written to TIFF, BigTIFF, OME-TIFF, and ImageJ hyperstack
+ compatible files in multi-page, memory-mappable, tiled, predicted, or
+ compressed form.
A subset of the TIFF specification is supported, mainly uncompressed and
losslessly compressed 8, 16, 32 and 64-bit integer, 16, 32 and 64-bit float,
@@ -36,7 +37,7 @@ Description: Read and write TIFF(r) files
extensions defined by Molecular Devices (Universal Imaging Corporation),
Carl Zeiss MicroImaging, Olympus, Silicon Graphics International,
Media Cybernetics, Molecular Dynamics, PerkinElmer, Hamamatsu, and the
- Open Microscopy Environment consortium respectively.
+ Open Microscopy Environment consortium, respectively.
For command line usage run ``python -m tifffile --help``
@@ -48,23 +49,44 @@ Description: Read and write TIFF(r) files
:License: BSD 3-Clause
- :Version: 2020.6.3
+ :Version: 2020.7.17
Requirements
------------
This release has been tested with the following requirements and dependencies
(other versions may work):
- * `CPython 3.6.8, 3.7.7, 3.8.3 64-bit <https://www.python.org>`_
- * `Numpy 1.16.6, 1.18.4 <https://www.numpy.org>`_
+ * `CPython 3.7.8, 3.8.4, 3.9.0b4 64-bit <https://www.python.org>`_
+ * `Numpy 1.18.5 <https://pypi.org/project/numpy/>`_
* `Imagecodecs 2020.5.30 <https://pypi.org/project/imagecodecs/>`_
(required only for encoding or decoding LZW, JPEG, etc.)
- * `Matplotlib 3.1 <https://www.matplotlib.org>`_ (required only for plotting)
+ * `Matplotlib 3.2.2 <https://pypi.org/project/matplotlib/>`_
+ (required only for plotting)
+ * `Lxml 4.5.2 <https://github.com/lxml/lxml>`_
+ (required only for validating and printing XML)
Revisions
---------
+ 2020.7.17
+ Pass 3022 tests.
+ Initial support for writing OME-TIFF (WIP).
+ Return samples as separate dimension in OME series (breaking).
+ Fix modulo dimensions for multiple OME series.
+ Fix some test errors on big endian systems (#18).
+ Fix BytesWarning.
+ Allow to pass TIFF.PREDICTOR values to TiffWriter.save.
+ 2020.7.4
+ Deprecate support for Python 3.6 (NEP 29).
+ Move pyramidal subresolution series to TiffPageSeries.levels (breaking).
+ Add parser for SVS, SCN, NDPI, and QPI pyramidal series.
+ Read single-file OME-TIFF pyramids.
+ Read NDPI files > 4 GB (#15).
+ Include SubIFDs in generic series.
+ Preliminary support for writing packed integer arrays (#11, WIP).
+ Read more LSM info subrecords.
+ Fix missing ReferenceBlackWhite tag for YCbCr photometrics.
+ Fix reading lossless JPEG compressed DNG files.
2020.6.3
- Pass 2908 tests.
Support os.PathLike file names (#9).
2020.5.30
Re-add pure Python PackBits decoder.
@@ -218,10 +240,11 @@ Description: Read and write TIFF(r) files
Tested on little-endian platforms only.
- Python 32-bit versions are deprecated.
+ Python 32-bit versions are deprecated. Python <= 3.6 are no longer supported.
Tifffile relies on the `imagecodecs <https://pypi.org/project/imagecodecs/>`_
- package for encoding and decoding LZW, JPEG, and other compressed images.
+ package for encoding and decoding LZW, JPEG, and other compressed image
+ segments.
Several TIFF-like formats do not strictly adhere to the TIFF6 specification,
some of which allow file or data sizes to exceed the 4 GB limit:
@@ -238,21 +261,23 @@ Description: Read and write TIFF(r) files
files. The 8-bit UTF-8 encoded OME-XML metadata found in the ImageDescription
tag of the first IFD defines the position of TIFF IFDs in the high
dimensional data. Tifffile can read OME-TIFF files, except when the OME-XML
- metadata are stored in a separate file.
+ metadata are stored in a separate file. Tifffile can write numpy arrays
+ to single-file, non-pyramidal OME-TIFF.
* *LSM* stores all IFDs below 4 GB but wraps around 32-bit StripOffsets.
The StripOffsets of each series and position require separate unwrapping.
The StripByteCounts tag contains the number of bytes for the uncompressed
data. Tifffile can read large LSM files.
* *NDPI* uses some 64-bit offsets in the file header, IFD, and tag structures.
Tag values/offsets can be corrected using high bits stored after IFD
- structures. JPEG compressed tiles with dimensions > 65536 are not readable
- with libjpeg. Tifffile can read NDPI files < 4 GB and decompress large JPEG
- tiles using the imagecodecs library on Windows.
+ structures. JPEG compressed segments with dimensions >65536 or missing
+ restart markers are not readable with libjpeg. Tifffile can read NDPI
+ files > 4 GB. JPEG segments with restart markers and dimensions >65536 can
+ be decoded with the imagecodecs library on Windows.
* *ScanImage* optionally allows corrupt non-BigTIFF files > 2 GB. The values
of StripOffsets and StripByteCounts can be recovered using the constant
differences of the offsets of IFD and tag values throughout the file.
- Tifffile can read such files on Python 3 if the image data are stored
- contiguously in each page.
+ Tifffile can read such files if the image data are stored contiguously in
+ each page.
* *GeoTIFF* sparse files allow strip or tile offsets and byte counts to be 0.
Such segments are implicitly set to 0 or the NODATA value on reading.
Tifffile can read GeoTIFF sparse files.
@@ -285,29 +310,33 @@ Description: Read and write TIFF(r) files
References
----------
- 1. TIFF 6.0 Specification and Supplements. Adobe Systems Incorporated.
- https://www.adobe.io/open/standards/TIFF.html
- 2. TIFF File Format FAQ. https://www.awaresystems.be/imaging/tiff/faq.html
- 3. MetaMorph Stack (STK) Image File Format.
- http://mdc.custhelp.com/app/answers/detail/a_id/18862
- 4. Image File Format Description LSM 5/7 Release 6.0 (ZEN 2010).
- Carl Zeiss MicroImaging GmbH. BioSciences. May 10, 2011
- 5. The OME-TIFF format.
- https://docs.openmicroscopy.org/ome-model/5.6.4/ome-tiff/
- 6. UltraQuant(r) Version 6.0 for Windows Start-Up Guide.
- http://www.ultralum.com/images%20ultralum/pdf/UQStart%20Up%20Guide.pdf
- 7. Micro-Manager File Formats.
- https://micro-manager.org/wiki/Micro-Manager_File_Formats
- 8. Tags for TIFF and Related Specifications. Digital Preservation.
- https://www.loc.gov/preservation/digital/formats/content/tiff_tags.shtml
- 9. ScanImage BigTiff Specification - ScanImage 2016.
- http://scanimage.vidriotechnologies.com/display/SI2016/
- ScanImage+BigTiff+Specification
- 10. CIPA DC-008-2016: Exchangeable image file format for digital still cameras:
- Exif Version 2.31.
- http://www.cipa.jp/std/documents/e/DC-008-Translation-2016-E.pdf
- 11. ZIF, the Zoomable Image File format. http://zif.photo/
- 12. GeoTIFF File Format https://gdal.org/drivers/raster/gtiff.html
+ * TIFF 6.0 Specification and Supplements. Adobe Systems Incorporated.
+ https://www.adobe.io/open/standards/TIFF.html
+ * TIFF File Format FAQ. https://www.awaresystems.be/imaging/tiff/faq.html
+ * The BigTIFF File Format.
+ https://www.awaresystems.be/imaging/tiff/bigtiff.html
+ * MetaMorph Stack (STK) Image File Format.
+ http://mdc.custhelp.com/app/answers/detail/a_id/18862
+ * Image File Format Description LSM 5/7 Release 6.0 (ZEN 2010).
+ Carl Zeiss MicroImaging GmbH. BioSciences. May 10, 2011
+ * The OME-TIFF format.
+ https://docs.openmicroscopy.org/ome-model/latest/
+ * UltraQuant(r) Version 6.0 for Windows Start-Up Guide.
+ http://www.ultralum.com/images%20ultralum/pdf/UQStart%20Up%20Guide.pdf
+ * Micro-Manager File Formats.
+ https://micro-manager.org/wiki/Micro-Manager_File_Formats
+ * ScanImage BigTiff Specification - ScanImage 2016.
+ http://scanimage.vidriotechnologies.com/display/SI2016/
+ ScanImage+BigTiff+Specification
+ * ZIF, the Zoomable Image File format. http://zif.photo/
+ * GeoTIFF File Format https://gdal.org/drivers/raster/gtiff.html
+ * Cloud optimized GeoTIFF.
+ https://github.com/cogeotiff/cog-spec/blob/master/spec.md
+ * Tags for TIFF and Related Specifications. Digital Preservation.
+ https://www.loc.gov/preservation/digital/formats/content/tiff_tags.shtml
+ * CIPA DC-008-2016: Exchangeable image file format for digital still cameras:
+ Exif Version 2.31.
+ http://www.cipa.jp/std/documents/e/DC-008-Translation-2016-E.pdf
Examples
--------
@@ -440,19 +469,21 @@ Description: Read and write TIFF(r) files
... tag_name, tag_value = tag.name, tag.value
... image = page.asarray()
- Save two image series to a TIFF file:
+ Write two numpy arrays to a multi-series OME-TIFF file:
- >>> data0 = numpy.random.randint(0, 255, (301, 219, 3), 'uint8')
- >>> data1 = numpy.random.randint(0, 255, (5, 301, 219), 'uint16')
- >>> with TiffWriter('temp.tif') as tif:
+ >>> data0 = numpy.random.randint(0, 255, (32, 32, 3), 'uint8')
+ >>> data1 = numpy.random.randint(0, 1023, (5, 256, 256), 'uint16')
+ >>> with TiffWriter('temp.ome.tif', ome=True) as tif:
... tif.save(data0, compress=6, photometric='rgb')
- ... tif.save(data1, compress=6, photometric='minisblack', contiguous=False)
+ ... tif.save(data1, photometric='minisblack', contiguous=False,
+ ... metadata=dict(axes='ZYX', SignificantBits=10,
+ ... PositionZ=[0.0, 1.0, 2.0, 3.0, 4.0]))
- Read the second image series from the TIFF file:
+ Read the second image series from the OME-TIFF file:
- >>> series1 = imread('temp.tif', series=1)
+ >>> series1 = imread('temp.ome.tif', series=1)
>>> series1.shape
- (5, 301, 219)
+ (5, 256, 256)
Read an image stack from a series of TIFF files with a file name pattern:
@@ -467,13 +498,46 @@ Description: Read and write TIFF(r) files
>>> data.shape
(1, 2, 64, 64)
- Create a TIFF file from an iterator of tiles:
+ Create a TIFF file from a generator of tiles:
>>> def tiles():
... data = numpy.arange(3*4*16*16, dtype='uint16').reshape((3*4, 16, 16))
... for i in range(data.shape[0]): yield data[i]
>>> imwrite('temp.tif', tiles(), dtype='uint16', shape=(48, 64), tile=(16, 16))
+ Write a tiled, multi-resolution, pyramidal TIFF file using JPEG compression:
+
+ >>> data = numpy.arange(1024*1024*3, dtype='uint8').reshape((1024, 1024, 3))
+ >>> with TiffWriter('temp.tif') as tif:
+ ... options = dict(tile=(256, 256), compress='jpeg', metadata=None)
+ ... tif.save(data, **options)
+ ... # save pyramid levels. In production use resampling to generate levels!
+ ... tif.save(data[::2, ::2], subfiletype=1, **options)
+ ... tif.save(data[::4, ::4], subfiletype=1, **options)
+
+ Access the image levels in the pyramidal TIFF file:
+
+ >>> baseimage = imread('temp.tif')
+ >>> second_level = imread('temp.tif', series=0, level=1)
+ >>> with TiffFile('temp.tif') as tif:
+ ... baseimage = tif.series[0].asarray()
+ ... second_level = tif.series[0].levels[1].asarray()
+
+ Iterate over and decode single JPEG compressed tiles in the TIFF file:
+
+ >>> with TiffFile('temp.tif') as tif:
+ ... fh = tif.filehandle
+ ... for page in tif.pages:
+ ... jpegtables = page.tags.get('JPEGTables', None)
+ ... if jpegtables is not None:
+ ... jpegtables = jpegtables.value
+ ... for index, (offset, bytecount) in enumerate(
+ ... zip(page.dataoffsets, page.databytecounts)
+ ... ):
+ ... fh.seek(offset)
+ ... data = fh.read(bytecount)
+ ... tile, indices, shape = page.decode(data, index, jpegtables)
+
Platform: any
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: BSD License
@@ -481,7 +545,6 @@ Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
=====================================
tifffile.egg-info/requires.txt
=====================================
@@ -3,3 +3,4 @@ numpy>=1.15.1
[all]
imagecodecs>=2020.2.18
matplotlib>=3.1
+lxml
=====================================
tifffile/tifffile.py
=====================================
The diff for this file was not included because it is too large.
View it on GitLab: https://salsa.debian.org/python-team/modules/tifffile/-/commit/2a5bc8b5fe528ce2d93f5d400258cbe8f2088e3a
--
View it on GitLab: https://salsa.debian.org/python-team/modules/tifffile/-/commit/2a5bc8b5fe528ce2d93f5d400258cbe8f2088e3a
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-med-commit/attachments/20200718/06697d09/attachment-0001.html>
More information about the debian-med-commit
mailing list