pax_global_header00006660000000000000000000000064145030204600014504gustar00rootroot0000000000000052 comment=689089d9e7fd03863487463a570c60966c6e74f8 pylibtiff-0.6.1/000077500000000000000000000000001450302046000135005ustar00rootroot00000000000000pylibtiff-0.6.1/.conda/000077500000000000000000000000001450302046000146425ustar00rootroot00000000000000pylibtiff-0.6.1/.conda/environment.yml000066400000000000000000000002171450302046000177310ustar00rootroot00000000000000name: pylibtiff channels: - conda-forge dependencies: - setuptools - pip - numpy - pytest - pytest-cov - coveralls - coverage pylibtiff-0.6.1/.conda/pylibtiff.recipe/000077500000000000000000000000001450302046000201005ustar00rootroot00000000000000pylibtiff-0.6.1/.conda/pylibtiff.recipe/build.sh000066400000000000000000000000771450302046000215370ustar00rootroot00000000000000cd $RECIPE_DIR/../ || exit 1 pip install . pytest -v libtiff/ pylibtiff-0.6.1/.conda/pylibtiff.recipe/meta.yaml000066400000000000000000000006571450302046000217220ustar00rootroot00000000000000package: name: pylibtiff version: 0.4.4.dev1 source: git_url: ../ build: number: {{ environ.get('GIT_DESCRIBE_NUMBER', 0) }} string: {{ environ.get('GIT_BUILD_STR', 'NA') }} test: skip: True # tests are in build.sh requirements: build: - numpy - pytest - libtiff # used for testing host: run: - numpy - libtiff about: home: https://github.com/pearu/pylibtiff license: BSD 3-clause pylibtiff-0.6.1/.git_archival.txt000066400000000000000000000002211450302046000167460ustar00rootroot00000000000000node: 689089d9e7fd03863487463a570c60966c6e74f8 node-date: 2023-09-21T05:55:44-05:00 describe-name: v0.6.1 ref-names: HEAD -> master, tag: v0.6.1 pylibtiff-0.6.1/.gitattributes000066400000000000000000000000401450302046000163650ustar00rootroot00000000000000.git_archival.txt export-subst pylibtiff-0.6.1/.github/000077500000000000000000000000001450302046000150405ustar00rootroot00000000000000pylibtiff-0.6.1/.github/dependabot.yml000066400000000000000000000010021450302046000176610ustar00rootroot00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "github-actions" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "monthly" pylibtiff-0.6.1/.github/workflows/000077500000000000000000000000001450302046000170755ustar00rootroot00000000000000pylibtiff-0.6.1/.github/workflows/ci.yaml000066400000000000000000000106001450302046000203510ustar00rootroot00000000000000name: CI # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#concurrency # https://docs.github.com/en/developers/webhooks-and-events/events/github-event-types#pullrequestevent concurrency: group: ${{ github.workflow }}-${{ github.event.number }}-${{ github.event.type }} cancel-in-progress: true on: [push, pull_request] env: CACHE_NUMBER: 0 jobs: lint: name: lint and style checks runs-on: ubuntu-latest steps: - name: Checkout source uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.10" - name: Install dependencies run: | python -m pip install --upgrade pip pip install flake8 flake8-docstrings flake8-debugger flake8-bugbear pytest - name: Install Pylibtiff run: | pip install -e . - name: Run linting run: | flake8 libtiff/ test: runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} needs: [lint] strategy: fail-fast: true matrix: os: ["windows-latest", "ubuntu-latest", "macos-latest"] python-version: ["3.8", "3.9", "3.10"] experimental: [false] system-libtiff: [false] include: - python-version: "3.11" os: "ubuntu-latest" experimental: true system-libtiff: false - python-version: "3.10" os: "ubuntu-latest" experimental: false system-libtiff: true env: PYTHON_VERSION: ${{ matrix.python-version }} OS: ${{ matrix.os }} UNSTABLE: ${{ matrix.experimental }} ACTIONS_ALLOW_UNSECURE_COMMANDS: true steps: - name: Checkout source uses: actions/checkout@v3 - name: Setup Conda Environment uses: conda-incubator/setup-miniconda@v2 with: miniforge-variant: Mambaforge miniforge-version: latest use-mamba: true python-version: ${{ matrix.python-version }} activate-environment: pylibtiff - name: Set cache environment variables shell: bash -l {0} run: | echo "DATE=$(date +'%Y%m%d')" >> $GITHUB_ENV CONDA_PREFIX=$(python -c "import sys; print(sys.prefix)") echo "CONDA_PREFIX=$CONDA_PREFIX" >> $GITHUB_ENV - uses: actions/cache@v3 with: path: ${{ env.CONDA_PREFIX }} key: ${{ matrix.os }}-${{matrix.python-version}}-conda-${{ hashFiles('.conda/environment.yml') }}-${{ env.DATE }}-${{matrix.experimental}}-${{ env.CACHE_NUMBER }} id: cache - name: Update environment run: mamba env update -n pylibtiff -f .conda/environment.yml if: steps.cache.outputs.cache-hit != 'true' - name: Install unstable dependencies if: matrix.experimental == true shell: bash -l {0} # We must get LD_PRELOAD for stdlibc++ or else the manylinux wheels # may break the conda-forge libraries trying to use newer glibc versions run: | python -m pip install \ --index-url https://pypi.anaconda.org/scipy-wheels-nightly/simple/ \ --trusted-host pypi.anaconda.org \ --no-deps --pre --upgrade \ numpy; LD_PRELOAD=$(python -c "import sys; print(sys.prefix)")/lib/libstdc++.so echo "LD_PRELOAD=${LD_PRELOAD}" >> $GITHUB_ENV - name: Install system libtiff if: matrix.system-libtiff == true shell: bash -l {0} run: sudo apt-get install -y libtiff-dev - name: Install conda libtiff if: matrix.system-libtiff == false shell: bash -l {0} run: conda install -y libtiff - name: Install pylibtiff shell: bash -l {0} run: | python -m pip install --no-deps -e . - name: Run unit tests shell: bash -l {0} run: | export LD_PRELOAD=${{ env.LD_PRELOAD }}; pytest --cov=libtiff libtiff/tests - name: Coveralls Parallel uses: AndreMiras/coveralls-python-action@develop with: flag-name: run-${{ matrix.test_number }} parallel: true if: runner.os == 'Linux' coveralls: needs: [test] runs-on: ubuntu-latest steps: - name: Coveralls Finished uses: AndreMiras/coveralls-python-action@develop with: parallel-finished: true pylibtiff-0.6.1/.github/workflows/deploy-sdist.yaml000066400000000000000000000010261450302046000224000ustar00rootroot00000000000000name: Deploy sdist on: release: types: - published jobs: test: runs-on: ubuntu-latest steps: - name: Checkout source uses: actions/checkout@v3 - name: Create sdist shell: bash -l {0} run: | pip install build python -m build -s - name: Publish package to PyPI if: github.event.action == 'published' uses: pypa/gh-action-pypi-publish@v1.8.10 with: user: __token__ password: ${{ secrets.pypi_password }} pylibtiff-0.6.1/.gitignore000066400000000000000000000001161450302046000154660ustar00rootroot00000000000000build dist *.egg-info *.pyc *.pyd __pycache__ libtiff/version.py *.so pylibtiff-0.6.1/LICENSE000066400000000000000000000027501450302046000145110ustar00rootroot00000000000000Copyright (c) 2009-2010 Pearu Peterson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: a. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. b. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. c. Neither the name of the PyLibTiff project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pylibtiff-0.6.1/MANIFEST.in000066400000000000000000000000421450302046000152320ustar00rootroot00000000000000include LICENSE include README.md pylibtiff-0.6.1/README.md000066400000000000000000000321511450302046000147610ustar00rootroot00000000000000 [![Build Status](https://github.com/pearu/pylibtiff/actions/workflows/ci.yaml/badge.svg)](https://github.com/pearu/pylibtiff/actions/workflows/ci.yaml) PyLibTiff is a package that provides: * a wrapper to the [libtiff](http://www.simplesystems.org/libtiff/) library to [Python](http://www.python.org) using [ctypes](http://docs.python.org/library/ctypes.html). * a pure Python module for reading and writing TIFF and LSM files. The images are read as `numpy.memmap` objects so that it is possible to open images that otherwise would not fit to computers RAM. Both TIFF strips and tiles are supported for low-level data storage. There exists many Python packages such as [PIL](http://www.pythonware.com/products/pil/), [FreeImagePy](http://freeimagepy.sourceforge.net/) that support reading and writing TIFF files. The PyLibTiff project was started to have an efficient and direct way to read and write TIFF files using the libtiff library without the need to install any unnecessary packages or libraries. The pure Python module was created for reading "broken" TIFF files such as LSM files that in some places use different interpretation of TIFF tags than what specified in the TIFF specification document. The libtiff library would just fail reading such files. In addition, the pure Python module is more memory efficient as the arrays are returned as memory maps. Support for compressed files is not implemented yet. [tifffile.py](http://www.lfd.uci.edu/~gohlke/code/tifffile.py.html) by Christoph Gohlke is an excellent module for reading TIFF as well as LSM files, it is as fast as libtiff.py by using numpy. # Usage example (libtiff wrapper) # ``` >>> from libtiff import TIFF >>> # to open a tiff file for reading: >>> tif = TIFF.open('filename.tif', mode='r') >>> # to read an image in the currect TIFF directory and return it as numpy array: >>> image = tif.read_image() >>> # to read all images in a TIFF file: >>> for image in tif.iter_images(): # do stuff with image >>> # to open a tiff file for writing: >>> tif = TIFF.open('filename.tif', mode='w') >>> # to write a image to tiff file >>> tif.write_image(image) ``` # Usage example (pure Python module) # ``` >>> from libtiff import TIFFfile, TIFFimage >>> # to open a tiff file for reading >>> tif = TIFFfile('filename.tif') >>> # to return memmaps of images and sample names (eg channel names, SamplesPerPixel>=1) >>> samples, sample_names = tiff.get_samples() >>> # to create a tiff structure from image data >>> tiff = TIFFimage(data, description='') >>> # to write tiff structure to file >>> tiff.write_file('filename.tif', compression='none') # or 'lzw' >>> del tiff # flushes data to disk ``` # Script usage examples # ``` $ libtiff.info -i result_0.tif IFDEntry(tag=ImageWidth, value=512, count=1, offset=None) IFDEntry(tag=ImageLength, value=512, count=1, offset=None) IFDEntry(tag=BitsPerSample, value=32, count=1, offset=None) IFDEntry(tag=Compression, value=1, count=1, offset=None) IFDEntry(tag=PhotometricInterpretation, value=1, count=1, offset=None) IFDEntry(tag=StripOffsets, value=8, count=1, offset=None) IFDEntry(tag=Orientation, value=6, count=1, offset=None) IFDEntry(tag=StripByteCounts, value=1048576, count=1, offset=None) IFDEntry(tag=PlanarConfiguration, value=1, count=1, offset=None) IFDEntry(tag=SampleFormat, value=3, count=1, offset=None) Use --ifd to see the rest of 31 IFD entries data is contiguous: False memory usage is ok: True sample data shapes and names: width : 512 length : 512 samples_per_pixel : 1 planar_config : 1 bits_per_sample : 32 strip_length : 1048576 [('memmap', (32, 512, 512), dtype('float32'))] ['sample0'] ``` ``` $ libtiff.info -i psf_1024_z5_airy1_set1.lsm IFDEntry(tag=NewSubfileType, value=0, count=1, offset=None) IFDEntry(tag=ImageWidth, value=1024, count=1, offset=None) IFDEntry(tag=ImageLength, value=1024, count=1, offset=None) IFDEntry(tag=BitsPerSample, value=8, count=1, offset=None) IFDEntry(tag=Compression, value=1, count=1, offset=None) IFDEntry(tag=PhotometricInterpretation, value=1, count=1, offset=None) IFDEntry(tag=StripOffsets, value=97770, count=1, offset=None) IFDEntry(tag=SamplesPerPixel, value=1, count=1, offset=None) IFDEntry(tag=StripByteCounts, value=1048576, count=1, offset=None) IFDEntry(tag=PlanarConfiguration, value=2, count=1, offset=None) IFDEntry(tag=CZ_LSMInfo, value=CZ_LSMInfo276( MagicNumber=[67127628], StructureSize=[500], DimensionX=[1024], DimensionY=[1024], DimensionZ=[20], DimensionChannels=[1], DimensionTime=[1], SDataType=[1], ThumbnailX=[128], ThumbnailY=[128], VoxelSizeX=[ 2.79017865e-08], VoxelSizeY=[ 2.79017865e-08], VoxelSizeZ=[ 3.60105263e-07], OriginX=[ -2.22222228e-07], OriginY=[ 1.90476196e-07], OriginZ=[ 0.], ScanType=[0], SpectralScan=[0], DataType=[0], OffsetVectorOverlay->DrawingElement(name='OffsetVectorOverlay', size=200, offset=6560), OffsetInputLut->LookupTable(name='OffsetInputLut', size=8388, subblocks=6, channels=1, offset=7560), OffsetOutputLut->LookupTable(name='OffsetOutputLut', size=24836, subblocks=3, channels=3, offset=15948), OffsetChannelColors->ChannelColors (names=['Ch3'], colors=[(255, 0, 0, 0)]), TimeInterval=[ 0.], OffsetChannelDataTypes->None, OffsetScanInformation->recording[size=3535] name = 'psf_1024_z5_airy1_set1' description = ' ' notes = ' ' objective = 'C-Apochromat 63x/1.20 W Korr UV-VIS-IR M27' special scan mode = 'FocusStep' scan type = '' scan mode = 'Stack' number of stacks = 10 lines per plane = 1024 samples per line = 1024 planes per volume = 20 images width = 1024 images height = 1024 images number planes = 20 images number stacks = 1 images number channels = 1 linescan xy size = 512 scan direction = 0 scan directionz = 0 time series = 0 original scan data = 1 zoom x = 5.0000000000000009 zoom y = 5.0000000000000009 zoom z = 1.0 sample 0x = -0.22200000000000006 sample 0y = 0.19000000000000006 sample 0z = 6.8420000000000014 sample spacing = 0.028000000000000008 line spacing = 0.028000000000000008 plane spacing = 0.3600000000000001 rotation = 0.0 nutation = 0.0 precession = 0.0 sample 0time = 39583.598368055624 start scan trigger in = '' start scan trigger out = '' start scan event = 0 start scan time = 0.0 stop scan trigger in = '' stop scan trigger out = '' stop scan event = 0 start scan time = 0.0 use rois = 0 use reduced memory rois = 0 user = 'User Name' usebccorrection = 0 positionbccorrection1 = 0.0 positionbccorrection2 = 0.0 interpolationy = 1 camera binning = 1 camera supersampling = 0 camera frame width = 1388 camera frame height = 1040 camera offsetx = 0.0 camera offsety = 0.0 rt binning = 1 ENTRY0x10000064L = 1 rt frame width = 512 rt frame height = 512 rt region width = 512 rt region height = 512 rt offsetx = 0.0 rt offsety = 0.0 rt zoom = 1.0000000000000004 rt lineperiod = 112.43300000000002 prescan = 0 lasers[size=188] laser[size=80] name = 'HeNe633' acquire = 1 power = 5.0000000000000009 end laser laser[size=84] name = 'DPSS 532-75' acquire = 1 power = 75.000000000000014 end laser end lasers tracks[size=2071] track[size=2047] pixel time = 1.5980000000000003 time between stacks = 1.0 multiplex type = 1 multiplex order = 1 sampling mode = 2 sampling method = 1 sampling number = 8 acquire = 1 name = 'Track' collimator1 position = 16 collimator1 name = 'IR/Vis' collimator2 position = 66 collimator2 name = 'UV/Vis' is bleach track = 0 is bleach after scan number = 0 bleach scan number = 0 trigger in = '' trigger out = '' is ratio track = 0 bleach count = 0 spi center wavelength = 582.53000000000009 id condensor aperture = 'KAB1' condensor aperture = 0.55000000000000016 id condensor revolver = 'FW2' condensor filter = 'HF' id tubelens = 'Tubelens' id tubelens position = 'Lens LSM' transmitted light = 0.0 reflected light = -1.0000000000000002 detection channels[size=695] detection channel[size=671] detector gain first = 700.00000000000011 detector gain last = 700.00000000000011 amplifier gain first = 1.0000000000000002 amplifier gain last = 1.0000000000000002 amplifier offs first = 0.10000000000000002 amplifier offs last = 0.10000000000000002 pinhole diameter = 144.00000000000003 counting trigger = 5.0 acquire = 1 integration mode = 0 special mode = 0 detector name = 'Pmt3' amplifier name = 'Amplifier1' pinhole name = 'PH3' filter set name = 'EF3' filter name = 'LP 650' ENTRY0x70000011L = '' ENTRY0x70000012L = '' integrator name = 'Integrator3' detection channel name = 'Ch3' detector gain bc1 = 0.0 detector gain bc2 = 0.0 amplifier gain bc1 = 0.0 amplifier gain bc2 = 0.0 amplifier offs bc1 = 0.0 amplifier offs bc2 = 0.0 spectral scan channels = 32 spi wavelength start = 415.0 spi wavelength end = 735.0 ENTRY0x70000024L = 575.0 ENTRY0x70000025L = 575.0 dye name = '' dye folder = '' ENTRY0x70000028L = 1.0000000000000004 ENTRY0x70000029L = 0.0 end detection channel end detection channels beam splitters[size=330] beam splitter[size=82] filter set = 'HT' filter = 'HFT 405/514/633' name = 'HT' end beam splitter beam splitter[size=75] filter set = 'NT1' filter = 'Mirror' name = 'NT1' end beam splitter beam splitter[size=76] filter set = 'NT2' filter = 'NFT 565' name = 'NT2' end beam splitter beam splitter[size=73] filter set = 'FW1' filter = 'None' name = 'FW1' end beam splitter end beam splitters illumination channels[size=160] illumination channel[size=136] name = '633' power = 0.30000000000000004 wavelength = 633.0 aquire = 1 power bc1 = 0.0 power bc2 = 0.0 end illumination channel end illumination channels data channels[size=338] data channel[size=314] name = 'Ch3' acquire = 1 acquire = 0 color = 255 sampletype = 1 bitspersample = 8 ratio type = 0 ratio track1 = 0 ratio track2 = 0 ratio channel1 = '' ratio channel2 = '' ratio const1 = 0.0 ratio const2 = 0.0 ratio const3 = 1.0 ratio const4 = 0.0 ratio const5 = 0.0 ratio const6 = 0.0 end data channel end data channels end track end tracks timers[size=24] end timers markers[size=24] end markers end recording, OffsetKsData->OffsetData(name='OffsetKsData', size=8, offset=48590), OffsetTimeStamps->TimeStamps(stamps=[ 2335.75582836]), OffsetEventList->EventList(events=[]), OffsetRoi->DrawingElement(name='OffsetRoi', size=200, offset=6160), OffsetBleachRoi->DrawingElement(name='OffsetBleachRoi', size=200, offset=6360), OffsetNextRecording->None, DisplayAspectX=[ 1.], DisplayAspectY=[ 1.], DisplayAspectZ=[ 1.], DisplayAspectTime=[ 1.], OffsetMeanOfRoisOverlay->DrawingElement(name='OffsetMeanOfRoisOverlay', size=200, offset=6760), OffsetTopoIsolineOverlay->DrawingElement(name='OffsetTopoIsolineOverlay', size=200, offset=7160), OffsetTopoProfileOverlay->DrawingElement(name='OffsetTopoProfileOverlay', size=200, offset=7360), OffsetLinescanOverlay->DrawingElement(name='OffsetLinescanOverlay', size=200, offset=6960), ToolbarFlags=[0], OffsetChannelWavelength->ChannelWavelength (ranges=[(-1.0, -1.0)]), OffsetChannelFactors->ChannelFactors(size=36, offset=40836), ObjectiveSphereCorrection=[ 0.], OffsetUnmixParameters->OffsetData(name='OffsetUnmixParameters', size=124, offset=48466), Reserved=[[40896 44726 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]])) Use --ifd to see the rest of 39 IFD entries data is contiguous: False memory usage is ok: True sample data shapes and names: width : 1024 length : 1024 samples_per_pixel : 1 planar_config : 2 bits_per_sample : 8 strip_length : 1048576 [('memmap', (20, 1024, 1024), dtype('uint8'))] ['Ch3'] [((20, 128, 128), dtype('uint8')), ((20, 128, 128), dtype('uint8')), ((20, 128, 128), dtype('uint8'))] ['red', 'green', 'blue'] ``` pylibtiff-0.6.1/RELEASING.md000066400000000000000000000017451450302046000153420ustar00rootroot00000000000000# Releasing Pylibtiff 1. checkout master branch 2. pull from repo 3. run the unittests 4. Create a tag with the new version number, starting with a 'v', eg: ``` git tag -a v -m "Version " ``` For example if the previous tag was `v0.9.0` and the new release is a patch release, do: ``` git tag -a v0.9.1 -m "Version 0.9.1" ``` See [semver.org](http://semver.org/) on how to write a version number. 5. push changes to github `git push --follow-tags` 6. Verify github action unittests passed. 7. Create a "Release" on GitHub by going to https://github.com/pearu/pylibtiff/releases and clicking "Draft a new release". On the next page enter the newly created tag in the "Tag version" field, "Version X.Y.Z" in the "Release title" field, and record changes made in this release in the "Describe this release" box. Finally click "Publish release". 9. Verify the GitHub actions for deployment succeed and the release is on PyPI. pylibtiff-0.6.1/libtiff/000077500000000000000000000000001450302046000151175ustar00rootroot00000000000000pylibtiff-0.6.1/libtiff/__init__.py000066400000000000000000000017211450302046000172310ustar00rootroot00000000000000"""LibTiff - a Python TIFF library .. currentmodule:: libtiff .. autosummary:: TIFF TIFFfile TiffArray TiffFile TiffFiles TiffChannelsAndFiles """ __autodoc__ = ['libtiff_ctypes', 'tiff', 'tiff_file', 'tiff_files', 'tiff_channels_and_files'] __all__ = ['TIFF', 'TIFF3D', 'TIFFfile', 'TiffArray', 'TiffFile', 'TiffFiles', 'TiffChannelsAndFiles', 'TiffBase'] try: from libtiff.version import version as __version__ # noqa except ModuleNotFoundError: raise ModuleNotFoundError( "No module named libtiff.version. This could mean " "you didn't install 'pylibtiff' properly. Try reinstalling ('pip " "install').") from .libtiff_ctypes import libtiff, TIFF, TIFF3D # noqa: F401 from .tiff import TIFFfile, TIFFimage, TiffArray # noqa: F401 from .tiff_file import TiffFile from .tiff_files import TiffFiles from .tiff_channels_and_files import TiffChannelsAndFiles from .tiff_base import TiffBase pylibtiff-0.6.1/libtiff/libtiff_ctypes.py000066400000000000000000002772401450302046000205130ustar00rootroot00000000000000#!/usr/bin/env python """ Ctypes based wrapper to libtiff library. See TIFF.__doc__ for usage information. Homepage: http://pylibtiff.googlecode.com/ """ # flake8: noqa for F821 from __future__ import print_function import os import sys import numpy as np import ctypes import ctypes.util import struct import collections __all__ = ['libtiff', 'TIFF'] cwd = os.getcwd() try: os.chdir(os.path.dirname(__file__)) if os.name == 'nt': # assume that the directory of the libtiff DLL is in PATH. for lib in ('tiff', 'libtiff', 'libtiff3'): lib = ctypes.util.find_library(lib) if lib is not None: break else: # try default installation path: lib = r'C:\Program Files\GnuWin32\bin\libtiff3.dll' if os.path.isfile(lib): print('You should add %r to PATH environment' ' variable and reboot.' % (os.path.dirname(lib))) else: lib = None else: if hasattr(sys, 'frozen') and sys.platform == 'darwin' and \ os.path.exists('../Frameworks/libtiff.dylib'): # py2app support, see Issue 8. lib = '../Frameworks/libtiff.dylib' else: lib = ctypes.util.find_library('tiff') libtiff = None if lib is None else ctypes.cdll.LoadLibrary(lib) if libtiff is None: try: if sys.platform == "darwin": libtiff = ctypes.cdll.LoadLibrary("libtiff.dylib") elif "win" in sys.platform: libtiff = ctypes.cdll.LoadLibrary("libtiff.dll") else: libtiff = ctypes.cdll.LoadLibrary("libtiff.so") except OSError: raise ImportError('Failed to find TIFF library. Make sure that' ' libtiff is installed and its location is' ' listed in PATH|LD_LIBRARY_PATH|..') finally: os.chdir(cwd) libtiff.TIFFGetVersion.restype = ctypes.c_char_p libtiff.TIFFGetVersion.argtypes = [] libtiff_version_str = libtiff.TIFFGetVersion() i = libtiff_version_str.lower().split().index(b'version') assert i != -1, repr(libtiff_version_str.decode()) libtiff_version = libtiff_version_str.split()[i + 1].decode() libtiff_version_tuple = tuple(int(i) for i in libtiff_version.split('.')) tiff_h_name = 'tiff_h_%s' % (libtiff_version.replace('.', '_')) try: exec(u"import libtiff.{0:s} as tiff_h".format(tiff_h_name)) except ImportError: tiff_h = None def _generate_lines_without_continuations(file_obj): """Parse lines from tiff.h but concatenate lines using a backslahs for continuation.""" line_iter = iter(file_obj) for header_line in line_iter: while header_line.endswith("\\\n"): # line continuation - replace '\' with a single space header_line = header_line[:-2].rstrip() + " " + next(line_iter).lstrip() yield header_line if tiff_h is None: # WARNING: there is not guarantee that the tiff.h found below will # correspond to libtiff version. Although, for clean distros the # probability is high. include_tiff_h = os.path.join(os.path.split(lib)[0], '..', 'include', 'tiff.h') if not os.path.isfile(include_tiff_h): include_tiff_h = os.environ.get('TIFF_HEADER_PATH', include_tiff_h) if not os.path.isfile(include_tiff_h): include_tiff_h = os.path.join(os.path.split(lib)[0], 'include', 'tiff.h') if not os.path.isfile(include_tiff_h): # fix me for windows: include_tiff_h = os.path.join(sys.prefix, 'include', 'tiff.h') # print(include_tiff_h) if not os.path.isfile(include_tiff_h): import glob include_tiff_h = (glob.glob(os.path.join(sys.prefix, 'include', '*linux*', 'tiff.h')) + glob.glob(os.path.join(sys.prefix, 'include', '*kfreebsd*', 'tiff.h')) + [include_tiff_h])[0] if not os.path.isfile(include_tiff_h): # Base it off of the python called include_tiff_h = os.path.realpath(os.path.join(os.path.split( sys.executable)[0], '..', 'include', 'tiff.h')) if not os.path.isfile(include_tiff_h): raise ValueError('Failed to find TIFF header file (may be need to ' 'run: sudo apt-get install libtiff5-dev)') # Read TIFFTAG_* constants for the header file: f = open(include_tiff_h, 'r') lst = [] d = {} for line in _generate_lines_without_continuations(f): if not line.startswith('#define'): continue words = line[7:].lstrip().split() if len(words) > 2: words[1] = ''.join(words[1:]) del words[2:] if len(words) != 2: continue name, value = words if name in ['TIFF_GCC_DEPRECATED', 'TIFF_MSC_DEPRECATED']: continue i = value.find('/*') if i != -1: value = value[:i] if value in d: value = d[value] else: try: value = eval(value) except Exception as msg: print(repr((value, line)), msg) raise d[name] = value lst.append('%s = %s' % (name, value)) f.close() fn = os.path.join(os.path.dirname(os.path.abspath(__file__)), tiff_h_name + '.py') print('Generating %r from %r' % (fn, include_tiff_h)) f = open(fn, 'w') f.write('\n'.join(lst) + '\n') f.close() else: d = tiff_h.__dict__ TIFFTAG_CZ_LSMINFO = 34412 d['TIFFTAG_CZ_LSMINFO'] = TIFFTAG_CZ_LSMINFO define_to_name_map = dict(Orientation={}, Compression={}, PhotoMetric={}, PlanarConfig={}, SampleFormat={}, FillOrder={}, FaxMode={}, TiffTag={} ) name_to_define_map = dict(Orientation={}, Compression={}, PhotoMetric={}, PlanarConfig={}, SampleFormat={}, FillOrder={}, FaxMode={}, TiffTag={} ) for name, value in list(d.items()): if name.startswith('_'): continue globals()[name] = value for n in define_to_name_map: if name.startswith(n.upper()): define_to_name_map[n][value] = name name_to_define_map[n][name] = value # types defined by tiff.h class c_ttag_t(ctypes.c_uint32): pass if libtiff_version_tuple[:2] >= (4, 5): c_tdir_t_base = ctypes.c_uint32 else: c_tdir_t_base = ctypes.c_uint16 class c_tdir_t(c_tdir_t_base): pass class c_tsample_t(ctypes.c_uint16): pass class c_tstrip_t(ctypes.c_uint32): pass class c_ttile_t(ctypes.c_uint32): pass class c_tsize_t(ctypes.c_ssize_t): pass class c_toff_t(ctypes.c_int32): pass class c_tdata_t(ctypes.c_void_p): pass class c_thandle_t(ctypes.c_void_p): pass # types defined for creating custom tags FIELD_CUSTOM = 65 class TIFFDataType(object): """Place holder for the enum in C. typedef enum { TIFF_NOTYPE = 0, /* placeholder */ TIFF_BYTE = 1, /* 8-bit unsigned integer */ TIFF_ASCII = 2, /* 8-bit bytes w/ last byte null */ TIFF_SHORT = 3, /* 16-bit unsigned integer */ TIFF_LONG = 4, /* 32-bit unsigned integer */ TIFF_RATIONAL = 5, /* 64-bit unsigned fraction */ TIFF_SBYTE = 6, /* !8-bit signed integer */ TIFF_UNDEFINED = 7, /* !8-bit untyped data */ TIFF_SSHORT = 8, /* !16-bit signed integer */ TIFF_SLONG = 9, /* !32-bit signed integer */ TIFF_SRATIONAL = 10, /* !64-bit signed fraction */ TIFF_FLOAT = 11, /* !32-bit IEEE floating point */ TIFF_DOUBLE = 12, /* !64-bit IEEE floating point */ TIFF_IFD = 13 /* %32-bit unsigned integer (offset) */ } TIFFDataType; """ ctype = ctypes.c_int TIFF_NOTYPE = 0 TIFF_BYTE = 1 TIFF_ASCII = 2 TIFF_SHORT = 3 TIFF_LONG = 4 TIFF_RATIONAL = 5 TIFF_SBYTE = 6 TIFF_UNDEFINED = 7 TIFF_SSHORT = 8 TIFF_SLONG = 9 TIFF_SRATIONAL = 10 TIFF_FLOAT = 11 TIFF_DOUBLE = 12 TIFF_IFD = 13 ttype2ctype = { TIFFDataType.TIFF_NOTYPE: None, TIFFDataType.TIFF_BYTE: ctypes.c_ubyte, TIFFDataType.TIFF_ASCII: ctypes.c_char_p, TIFFDataType.TIFF_SHORT: ctypes.c_uint16, TIFFDataType.TIFF_LONG: ctypes.c_uint32, TIFFDataType.TIFF_RATIONAL: ctypes.c_double, # Should be unsigned TIFFDataType.TIFF_SBYTE: ctypes.c_byte, TIFFDataType.TIFF_UNDEFINED: ctypes.c_char, TIFFDataType.TIFF_SSHORT: ctypes.c_int16, TIFFDataType.TIFF_SLONG: ctypes.c_int32, TIFFDataType.TIFF_SRATIONAL: ctypes.c_double, TIFFDataType.TIFF_FLOAT: ctypes.c_float, TIFFDataType.TIFF_DOUBLE: ctypes.c_double, TIFFDataType.TIFF_IFD: ctypes.c_uint32 } class TIFFFieldInfo(ctypes.Structure): """ typedef struct { ttag_t field_tag; /* field's tag */ short field_readcount; /* read count/TIFF_VARIABLE/TIFF_SPP */ short field_writecount; /* write count/TIFF_VARIABLE */ TIFFDataType field_type; /* type of associated data */ unsigned short field_bit; /* bit in fieldsset bit vector */ unsigned char field_oktochange; /* if true, can change while writing */ unsigned char field_passcount; /* if true, pass dir count on set */ char *field_name; /* ASCII name */ } TIFFFieldInfo; """ _fields_ = [ ("field_tag", ctypes.c_uint32), ("field_readcount", ctypes.c_short), ("field_writecount", ctypes.c_short), ("field_type", TIFFDataType.ctype), ("field_bit", ctypes.c_ushort), ("field_oktochange", ctypes.c_ubyte), ("field_passcount", ctypes.c_ubyte), ("field_name", ctypes.c_char_p) ] # Custom Tags class TIFFExtender(object): def __init__(self, new_tag_list): self._ParentExtender = None self.new_tag_list = new_tag_list def extender_pyfunc(tiff_struct): libtiff.TIFFMergeFieldInfo(tiff_struct, self.new_tag_list, len(self.new_tag_list)) if self._ParentExtender: self._ParentExtender(tiff_struct) # Just make being a void function more obvious return # ctypes callback function prototype (return void, arguments void # pointer) self.EXT_FUNC = ctypes.CFUNCTYPE(None, ctypes.c_void_p) # ctypes callback function instance self.EXT_FUNC_INST = self.EXT_FUNC(extender_pyfunc) libtiff.TIFFSetTagExtender.restype = ctypes.CFUNCTYPE(None, ctypes.c_void_p) self._ParentExtender = libtiff.TIFFSetTagExtender(self.EXT_FUNC_INST) def add_tags(tag_list): tag_list_array = (TIFFFieldInfo * len(tag_list))(*tag_list) for field_info in tag_list_array: _name = "TIFFTAG_" + str(field_info.field_name).upper() globals()[_name] = field_info.field_tag if field_info.field_writecount > 1 and field_info.field_type != \ TIFFDataType.TIFF_ASCII: tifftags[field_info.field_tag] = ( ttype2ctype[ field_info.field_type] * field_info.field_writecount, lambda _d: _d.contents[:]) else: tifftags[field_info.field_tag] = ( ttype2ctype[field_info.field_type], lambda _d: _d.value) return TIFFExtender(tag_list_array) tifftags = { # TODO: # TIFFTAG_DOTRANGE 2 uint16* # TIFFTAG_HALFTONEHINTS 2 uint16* # TIFFTAG_PAGENUMBER 2 uint16* # TIFFTAG_YCBCRSUBSAMPLING 2 uint16* # TIFFTAG_FAXFILLFUNC 1 TIFFFaxFillFunc* G3/G4 # compression # pseudo-tag # TIFFTAG_JPEGTABLES 2 u_short*,void** count & tables # TIFFTAG_TRANSFERFUNCTION 1 or 3 uint16** 1< 4: # No idea... self.SetField(TIFFTAG_EXTRASAMPLES, [EXTRASAMPLE_UNSPECIFIED] * (depth - 3)) if planar_config == PLANARCONFIG_CONTIG: self.WriteEncodedStrip(0, arr.ctypes.data, size) else: for _n in range(depth): self.WriteEncodedStrip(_n, arr[_n, :, :].ctypes.data, size) self.WriteDirectory() else: depth, height, width = shape size = width * height * arr.itemsize for _n in range(depth): self.SetField(TIFFTAG_IMAGEWIDTH, width) self.SetField(TIFFTAG_IMAGELENGTH, height) self.SetField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK) self.SetField(TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG) self.WriteEncodedStrip(0, arr[_n].ctypes.data, size) self.WriteDirectory() else: raise NotImplementedError(repr(shape)) def write_tiles(self, arr, tile_width=None, tile_height=None, compression=None, write_rgb=False): compression = self._fix_compression(compression) if arr.dtype in np.sctypes['float']: sample_format = SAMPLEFORMAT_IEEEFP elif arr.dtype in np.sctypes['uint'] + [np.bool_]: sample_format = SAMPLEFORMAT_UINT elif arr.dtype in np.sctypes['int']: sample_format = SAMPLEFORMAT_INT elif arr.dtype in np.sctypes['complex']: sample_format = SAMPLEFORMAT_COMPLEXIEEEFP else: raise NotImplementedError(repr(arr.dtype)) shape = arr.shape bits = arr.itemsize * 8 # if the dimensions are not set, get the values from the tags if not tile_width: tile_width = self.GetField("TileWidth") if not tile_height: tile_height = self.GetField("TileLength") if tile_width is None or tile_height is None: raise ValueError("TileWidth and TileLength must be specified") self.SetField(TIFFTAG_COMPRESSION, compression) if compression == COMPRESSION_LZW and sample_format in \ [SAMPLEFORMAT_INT, SAMPLEFORMAT_UINT]: # This field can only be set after compression and before # writing data. Horizontal predictor often improves compression, # but some rare readers might support LZW only without predictor. self.SetField(TIFFTAG_PREDICTOR, PREDICTOR_HORIZONTAL) self.SetField(TIFFTAG_BITSPERSAMPLE, bits) self.SetField(TIFFTAG_SAMPLEFORMAT, sample_format) self.SetField(TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT) self.SetField(TIFFTAG_TILEWIDTH, tile_width) self.SetField(TIFFTAG_TILELENGTH, tile_height) total_written_bytes = 0 if len(shape) == 1: shape = (shape[0], 1) # Same as 2D with height == 1 def write_plane(arr, tile_arr, width, height, plane_index=0, depth_index=0): """ Write all tiles of one plane """ written_bytes = 0 tile_arr = np.ascontiguousarray(tile_arr) # Rows for y in range(0, height, tile_height): # Cols for x in range(0, width, tile_width): # If we are over the edge of the image, use 0 as fill tile_arr[:] = 0 # if the tile is on the edge, it is smaller this_tile_width = min(tile_width, width - x) this_tile_height = min(tile_height, height - y) tile_arr[:this_tile_height, :this_tile_width] = \ arr[y:y + this_tile_height, x:x + this_tile_width] r = self.WriteTile(tile_arr.ctypes.data, x, y, depth_index, plane_index) written_bytes += r.value return written_bytes if len(shape) == 2: height, width = shape self.SetField(TIFFTAG_IMAGEWIDTH, width) self.SetField(TIFFTAG_IMAGELENGTH, height) self.SetField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK) self.SetField(TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG) # if there's only one sample per pixel, there is only one plane tile_arr = np.zeros((tile_height, tile_width), dtype=arr.dtype) total_written_bytes = write_plane(arr, tile_arr, width, height) self.WriteDirectory() elif len(shape) == 3: if write_rgb: # Guess the planar config, with preference for separate planes if shape[2] == 3 or shape[2] == 4: planar_config = PLANARCONFIG_CONTIG height, width, depth = shape else: planar_config = PLANARCONFIG_SEPARATE depth, height, width = shape self.SetField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB) self.SetField(TIFFTAG_IMAGEWIDTH, width) self.SetField(TIFFTAG_IMAGELENGTH, height) self.SetField(TIFFTAG_SAMPLESPERPIXEL, depth) self.SetField(TIFFTAG_PLANARCONFIG, planar_config) if depth == 4: # RGBA self.SetField(TIFFTAG_EXTRASAMPLES, [EXTRASAMPLE_UNASSALPHA], count=1) elif depth > 4: # No idea... self.SetField(TIFFTAG_EXTRASAMPLES, [EXTRASAMPLE_UNSPECIFIED] * (depth - 3), count=(depth - 3)) if planar_config == PLANARCONFIG_CONTIG: # if there is more than one sample per pixel and # it's contiguous in memory, there is only one # plane tile_arr = np.zeros((tile_height, tile_width, depth), dtype=arr.dtype) total_written_bytes = write_plane(arr, tile_arr, width, height) else: # multiple samples per pixel, each sample in one plane tile_arr = np.zeros((tile_height, tile_width), dtype=arr.dtype) for plane_index in range(depth): total_written_bytes += \ write_plane(arr[plane_index], tile_arr, width, height, plane_index) self.WriteDirectory() else: depth, height, width = shape self.SetField(TIFFTAG_IMAGEWIDTH, width) self.SetField(TIFFTAG_IMAGELENGTH, height) self.SetField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK) self.SetField(TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG) self.SetField(TIFFTAG_IMAGEDEPTH, depth) for depth_index in range(depth): # if there's only one sample per pixel, there is # only one plane tile_arr = np.zeros((tile_height, tile_width), dtype=arr.dtype) total_written_bytes += write_plane(arr[depth_index], tile_arr, width, height, 0, depth_index) self.WriteDirectory() else: raise NotImplementedError(repr(shape)) return total_written_bytes def read_one_tile(self, x, y): """Reads one tile from the TIFF image Parameters ---------- x: int X coordinate of a pixel inside the desired tile y: int Y coordinate of a pixel inside the desired tile Returns ------- numpy.array If there's only one sample per pixel, it returns a numpy array with 2 dimensions (x, y) If the image has more than one sample per pixel (SamplesPerPixel > 1), it will return a numpy array with 3 dimensions. If PlanarConfig == PLANARCONFIG_CONTIG, the returned dimensions will be (x, y, sample_index). If PlanarConfig == PLANARCONFIG_SEPARATE, the returned dimensions will be (sample_index, x, y). """ num_tcols = self.GetField("TileWidth") if num_tcols is None: raise ValueError("TIFFTAG_TILEWIDTH must be set to read tiles") num_trows = self.GetField("TileLength") if num_trows is None: num_trows = 1 num_irows = self.GetField("ImageLength") if num_irows is None: num_irows = 1 num_icols = self.GetField("ImageWidth") if num_icols is None: raise ValueError("TIFFTAG_IMAGEWIDTH must be set to read tiles") # this number includes extra samples samples_pp = self.GetField('SamplesPerPixel') if samples_pp is None: # default is 1 samples_pp = 1 planar_config = self.GetField('PlanarConfig') if planar_config is None: # default is contig planar_config = PLANARCONFIG_CONTIG num_idepth = self.GetField("ImageDepth") if num_idepth is None: num_idepth = 1 bits = self.GetField('BitsPerSample') sample_format = self.GetField('SampleFormat') # TODO: might need special support if bits < 8 dtype = self.get_numpy_type(bits, sample_format) if y < 0 or y >= num_irows: raise ValueError("Invalid y value") if x < 0 or x >= num_icols: raise ValueError("Invalid x value") # make x and y be a multiple of TileWidth and TileLength, # and be compatible with ReadTile x -= x % num_tcols y -= y % num_trows # if the tile is in the border, its size should be smaller this_tile_height = min(num_trows, num_irows - y) this_tile_width = min(num_tcols, num_icols - x) def read_plane(tile_plane, plane_index=0, depth_index=0): """Read one plane from TIFF. The TIFF has more than one plane only if it has more than one sample per pixel, and planar_config == PLANARCONFIG_SEPARATE """ # the numpy array should be contigous in memory before # calling ReadTile tile_plane = np.ascontiguousarray(tile_plane) # even if the tile is on the edge, and the final size will # be smaller, the size of the array passed to the ReadTile # function must be (num_tcols, num_trows) # # The image has only one depth (ImageDepth == 1), so # the z parameter is not read r = self.ReadTile(tile_plane.ctypes.data, x, y, depth_index, plane_index) if not r: raise ValueError( "Could not read tile x:%d,y:%d,z:%d,sample:%d from file" % (x, y, depth_index, plane_index)) # check if the tile is on the edge of the image if this_tile_height < num_trows or this_tile_width < num_tcols: # if the tile is on the edge of the image, generate a # smaller tile tile_plane = tile_plane[:this_tile_height, :this_tile_width] return tile_plane if num_idepth == 1: if samples_pp == 1: # the tile plane has always the size of a full tile tile_plane = np.zeros((num_trows, num_tcols), dtype=dtype) # this tile may be smaller than tile_plane tile = read_plane(tile_plane) else: if planar_config == PLANARCONFIG_CONTIG: # the tile plane has always the size of a full tile tile_plane = np.empty((num_trows, num_tcols, samples_pp), dtype=dtype) # this tile may be smaller than tile_plane, # if the tile is on the edge of the image tile = read_plane(tile_plane) else: # the tile plane has always the size of a full tile tile_plane = np.empty((samples_pp, num_trows, num_tcols), dtype=dtype) # this tile may be smaller than tile_plane, # if the tile is on the edge of the image tile = np.empty((samples_pp, this_tile_height, this_tile_width), dtype=dtype) for plane_index in range(samples_pp): tile[plane_index] = read_plane( tile_plane[plane_index], plane_index) else: if samples_pp > 1: raise NotImplementedError( "ImageDepth > 1 and SamplesPerPixel > 1 not implemented") # the tile plane has always the size of a full tile tile_plane = np.zeros((num_idepth, num_trows, num_tcols), dtype=dtype) # this tile may be smaller than tile_plane, # if the tile is on the edge of the image tile = np.empty((num_idepth, this_tile_height, this_tile_width), dtype=dtype) for depth_index in range(num_idepth): # As samples_pp == 1, there's only one plane, so the z # parameter is not read tile[depth_index] = read_plane( tile_plane[depth_index], 0, depth_index) return tile def read_tiles(self, dtype=np.uint8): num_tcols = self.GetField("TileWidth") if num_tcols is None: raise ValueError("TIFFTAG_TILEWIDTH must be set to read tiles") num_trows = self.GetField("TileLength") if num_trows is None: raise ValueError("TIFFTAG_TILELENGTH must be set to read tiles") num_icols = self.GetField("ImageWidth") if num_icols is None: raise ValueError("TIFFTAG_IMAGEWIDTH must be set to read tiles") num_irows = self.GetField("ImageLength") if num_irows is None: num_irows = 1 num_depths = self.GetField("ImageDepth") if num_depths is None: num_depths = 1 # this number includes extra samples samples_pp = self.GetField('SamplesPerPixel') if samples_pp is None: # default is 1 samples_pp = 1 planar_config = self.GetField('PlanarConfig') if planar_config is None: # default is contig planar_config = PLANARCONFIG_CONTIG def read_plane(plane, tmp_tile, plane_index=0, depth_index=0): for y in range(0, num_irows, num_trows): for x in range(0, num_icols, num_tcols): r = self.ReadTile(tmp_tile.ctypes.data, x, y, depth_index, plane_index) if not r: raise ValueError( "Could not read tile x:%d,y:%d,z:%d,sample:%d" " from file" % (x, y, plane_index, depth_index)) # if the tile is on the edge, it is smaller tile_width = min(num_tcols, num_icols - x) tile_height = min(num_trows, num_irows - y) plane[y:y + tile_height, x:x + tile_width] = \ tmp_tile[:tile_height, :tile_width] if samples_pp == 1: if num_depths == 1: # if there's only one sample per pixel there is only # one plane full_image = np.empty((num_irows, num_icols), dtype=dtype, order='C') tmp_tile = np.empty((num_trows, num_tcols), dtype=dtype, order='C') read_plane(full_image, tmp_tile) else: full_image = np.empty((num_depths, num_irows, num_icols), dtype=dtype, order='C') tmp_tile = np.empty((num_trows, num_tcols), dtype=dtype, order='C') for depth_index in range(num_depths): read_plane(full_image[depth_index], tmp_tile, 0, depth_index) else: if planar_config == PLANARCONFIG_CONTIG: # if there is more than one sample per pixel and it's # contiguous in memory, there is only one plane full_image = np.empty((num_irows, num_icols, samples_pp), dtype=dtype, order='C') tmp_tile = np.empty((num_trows, num_tcols, samples_pp), dtype=dtype, order='C') read_plane(full_image, tmp_tile) elif planar_config == PLANARCONFIG_SEPARATE: # multiple samples per pixel, each sample in one plane full_image = np.empty((samples_pp, num_irows, num_icols), dtype=dtype, order='C') tmp_tile = np.empty((num_trows, num_tcols), dtype=dtype, order='C') for plane_index in range(samples_pp): read_plane(full_image[plane_index], tmp_tile, plane_index) else: raise IOError("Unexpected PlanarConfig = %d" % planar_config) return full_image def iter_images(self, verbose=False): """ Iterator of all images in a TIFF file. """ yield self.read_image(verbose=verbose) while not self.LastDirectory(): self.ReadDirectory() yield self.read_image(verbose=verbose) self.SetDirectory(0) def __del__(self): self.close() @debug def FileName(self): return libtiff.TIFFFileName(self) filename = FileName @debug def CurrentRow(self): return libtiff.TIFFCurrentRow(self) currentrow = CurrentRow @debug def CurrentStrip(self): return libtiff.TIFFCurrentStrip(self) currentstrip = CurrentStrip @debug def CurrentTile(self): return libtiff.TIFFCurrentTile(self) currenttile = CurrentTile @debug def CurrentDirectory(self): return libtiff.TIFFCurrentDirectory(self) currentdirectory = CurrentDirectory @debug def LastDirectory(self): return libtiff.TIFFLastDirectory(self) lastdirectory = LastDirectory @debug def ReadDirectory(self): return libtiff.TIFFReadDirectory(self) readdirectory = ReadDirectory @debug def WriteDirectory(self): r = libtiff.TIFFWriteDirectory(self) assert r == 1, repr(r) writedirectory = WriteDirectory @debug def SetDirectory(self, dirnum): return libtiff.TIFFSetDirectory(self, dirnum) setdirectory = SetDirectory @debug def SetSubDirectory(self, diroff): """ Changes the current directory and reads its contents with TIFFReadDirectory. The parameter dirnum specifies the subfile/directory as an integer number, with the first directory numbered zero. SetSubDirectory acts like SetDirectory, except the directory is specified as a file offset instead of an index; this is required for accessing subdirectories linked through a SubIFD tag. Parameters ---------- diroff: int The offset of the subimage. It's important to notice that it is not an index, like dirnum on SetDirectory Returns ------- int On successful return 1 is returned. Otherwise, 0 is returned if dirnum or diroff specifies a non-existent directory, or if an error was encountered while reading the directory's contents. """ return libtiff.TIFFSetSubDirectory(self, diroff) @debug def Fileno(self): return libtiff.TIFFFileno(self) fileno = Fileno @debug def GetMode(self): return libtiff.TIFFGetMode(self) getmode = GetMode @debug def IsTiled(self): return libtiff.TIFFIsTiled(self) istiled = IsTiled @debug def IsByteSwapped(self): return libtiff.TIFFIsByteSwapped(self) isbyteswapped = IsByteSwapped @debug def IsUpSampled(self): return libtiff.TIFFIsUpSampled(self) isupsampled = IsUpSampled # noinspection PyPep8Naming @debug def isMSB2LSB(self): return libtiff.TIFFIsMSB2LSB(self) @debug def NumberOfStrips(self): return libtiff.TIFFNumberOfStrips(self).value numberofstrips = NumberOfStrips @debug def WriteScanline(self, buf, row, sample=0): return libtiff.TIFFWriteScanline(self, buf, row, sample) writescanline = WriteScanline @debug def ReadScanline(self, buf, row, sample=0): return libtiff.TIFFReadScanline(self, buf, row, sample) readscanline = ReadScanline def ScanlineSize(self): return libtiff.TIFFScanlineSize(self).value scanlinesize = ScanlineSize # @debug def ReadRawStrip(self, strip, buf, size): return libtiff.TIFFReadRawStrip(self, strip, buf, size).value readrawstrip = ReadRawStrip def ReadEncodedStrip(self, strip, buf, size): return libtiff.TIFFReadEncodedStrip(self, strip, buf, size).value readencodedstrip = ReadEncodedStrip def StripSize(self): return libtiff.TIFFStripSize(self).value stripsize = StripSize def RawStripSize(self, strip): return libtiff.TIFFRawStripSize(self, strip).value rawstripsize = RawStripSize @debug def WriteRawStrip(self, strip, buf, size): r = libtiff.TIFFWriteRawStrip(self, strip, buf, size) assert r.value == size, repr((r.value, size)) writerawstrip = WriteRawStrip @debug def WriteEncodedStrip(self, strip, buf, size): r = libtiff.TIFFWriteEncodedStrip(self, strip, buf, size) assert r.value == size, repr((r.value, size)) writeencodedstrip = WriteEncodedStrip @debug def ReadTile(self, buf, x, y, z, sample): """ Read and decode a tile of data from an open TIFF file Parameters ---------- buf: array Content read from the tile. The buffer must be large enough to hold an entire tile of data. Applications should call the routine TIFFTileSize to find out the size (in bytes) of a tile buffer. x: int X coordinate of the upper left pixel of the tile. It must be a multiple of TileWidth. y: int Y coordinate of the upper left pixel of the tile. It must be a multiple of TileLength. z: int It is used if the image is deeper than 1 slice (ImageDepth>1) sample: integer It is used only if data are organized in separate planes (PlanarConfiguration=2) Returns ------- int -1 if it detects an error; otherwise the number of bytes in the decoded tile is returned. """ return libtiff.TIFFReadTile(self, buf, x, y, z, sample) @debug def WriteTile(self, buf, x, y, z, sample): """ TIFFWriteTile - encode and write a tile of data to an open TIFF file Parameters ---------- arr: array Content to be written to the tile. The buffer must be contain an entire tile of data. Applications should call the routine TIFFTileSize to find out the size (in bytes) of a tile buffer. x: int X coordinate of the upper left pixel of the tile. It must be a multiple of TileWidth. y: int Y coordinate of the upper left pixel of the tile. It must be a multiple of TileLength. z: int It is used if the image is deeper than 1 slice (ImageDepth>1) sample: integer It is used only if data are organized in separate planes (PlanarConfiguration=2) Returns ------- int -1 if it detects an error; otherwise the number of bytes in the tile is returned. """ r = libtiff.TIFFWriteTile(self, buf, x, y, z, sample) assert r.value >= 0, repr(r.value) return r closed = False def close(self, _libtiff=libtiff): if not self.closed and self.value is not None: _libtiff.TIFFClose(self) self.closed = True return # def (self): return libtiff.TIFF(self) @debug def GetField(self, tag, ignore_undefined_tag=True, count=None): """ Return TIFF field _value with tag. tag can be numeric constant TIFFTAG_ or a string containing . """ if tag in ['PixelSizeX', 'PixelSizeY', 'RelativeTime']: descr = self.GetField('ImageDescription') if not descr: return _i = descr.find(tag) if _i == -1: return _value = eval(descr[_i + len(tag):].lstrip().split()[0]) return _value if isinstance(tag, str): tag = eval('TIFFTAG_' + tag.upper()) t = tifftags.get(tag) if t is None: if not ignore_undefined_tag: print('Warning: no tag %r defined' % tag) return data_type, convert = t if tag == TIFFTAG_COLORMAP: bps = self.GetField("BitsPerSample") if bps is None: print( "Warning: BitsPerSample is required to get ColorMap, " "assuming 8 bps...") bps = 8 elif bps > 16: # There is no way to check whether a field is present without # passing all the arguments. With more than 16 bits, it'd be a # lot of memory needed (and COLORMAP is very unlikely). print("Not trying to read COLORMAP tag with %d bits" % (bps,)) return None num_cmap_elems = 1 << bps data_type *= num_cmap_elems pdt = ctypes.POINTER(data_type) rdata = pdt() gdata = pdt() bdata = pdt() rdata_ptr = ctypes.byref(rdata) gdata_ptr = ctypes.byref(gdata) bdata_ptr = ctypes.byref(bdata) # ignore count, it's not used for colormap r = libtiff.TIFFGetField(self, c_ttag_t(tag), rdata_ptr, gdata_ptr, bdata_ptr) data = (rdata, gdata, bdata) elif isinstance(data_type, tuple): # Variable length array, with the length as first value count_type, data_type = data_type count = count_type() pdt = ctypes.POINTER(data_type) vldata = pdt() r = libtiff.TIFFGetField(self, c_ttag_t(tag), ctypes.byref(count), ctypes.byref(vldata)) data = (count.value, vldata) else: if issubclass(data_type, ctypes.Array): pdt = ctypes.POINTER(data_type) data = pdt() else: data = data_type() if count is None: r = libtiff.TIFFGetField(self, c_ttag_t(tag), ctypes.byref(data)) else: # TODO: is this ever used? Is there any tag that is # accessed like that? r = libtiff.TIFFGetField(self, c_ttag_t(tag), count, ctypes.byref(data)) if not r: # tag not defined for current directory if not ignore_undefined_tag: print( 'Warning: tag %r not defined in currect directory' % tag) return None return convert(data) # @debug def SetField(self, tag, _value, count=None): """ Set TIFF field _value with tag. tag can be numeric constant TIFFTAG_ or a string containing . """ if count is not None: print("Warning: count argument is deprecated") if isinstance(tag, str): tag = eval('TIFFTAG_' + tag.upper()) t = tifftags.get(tag) if t is None: print('Warning: no tag %r defined' % tag) return data_type, convert = t if data_type == ctypes.c_float: data_type = ctypes.c_double if tag == TIFFTAG_COLORMAP: # ColorMap passes 3 values each a c_uint16 pointer try: r_arr, g_arr, b_arr = _value except (TypeError, ValueError): print( "Error: TIFFTAG_COLORMAP expects 3 uint16* arrays as a " "list/tuple of lists") r_arr, g_arr, b_arr = None, None, None if r_arr is None: return bps = self.GetField("BitsPerSample") if bps is None: print( "Warning: BitsPerSample is required to get ColorMap, " "assuming 8 bps...") bps = 8 num_cmap_elems = 1 << bps data_type *= num_cmap_elems r_ptr = data_type(*r_arr) g_ptr = data_type(*g_arr) b_ptr = data_type(*b_arr) r = libtiff.TIFFSetField(self, c_ttag_t(tag), r_ptr, g_ptr, b_ptr) else: count_type = None if isinstance(data_type, tuple): # Variable length => count + data_type of array count_type, data_type = data_type count = len(_value) data_type = data_type * count # make it an array if issubclass(data_type, (ctypes.Array, tuple, list)): data = data_type(*_value) elif issubclass(data_type, ctypes._Pointer): # does not include c_char_p # convert to the base type, ctypes will take care of actually # sending it by reference base_type = data_type._type_ if isinstance(_value, collections.Iterable): data = base_type(*_value) else: data = base_type(_value) else: data = data_type(_value) if count_type is None: r = libtiff.TIFFSetField(self, c_ttag_t(tag), data) else: r = libtiff.TIFFSetField(self, c_ttag_t(tag), count, data) return r def info(self): """ Return a string containing map. """ _l = ['filename: %s' % (self.FileName())] for tagname in ['Artist', 'CopyRight', 'DateTime', 'DocumentName', 'HostComputer', 'ImageDescription', 'InkNames', 'Make', 'Model', 'PageName', 'Software', 'TargetPrinter', 'BadFaxLines', 'ConsecutiveBadFaxLines', 'Group3Options', 'Group4Options', 'ImageDepth', 'ImageWidth', 'ImageLength', 'RowsPerStrip', 'SubFileType', 'TileDepth', 'TileLength', 'TileWidth', 'StripByteCounts', 'StripOffSets', 'TileByteCounts', 'TileOffSets', 'BitsPerSample', 'CleanFaxData', 'Compression', 'DataType', 'FillOrder', 'InkSet', 'Matteing', 'MaxSampleValue', 'MinSampleValue', 'Orientation', 'PhotoMetric', 'PlanarConfig', 'Predictor', 'ResolutionUnit', 'SampleFormat', 'YCBCRPositioning', 'JPEGQuality', 'JPEGColorMode', 'JPEGTablesMode', 'FaxMode', 'SMaxSampleValue', 'SMinSampleValue', # 'Stonits', 'XPosition', 'YPosition', 'XResolution', 'YResolution', 'PrimaryChromaticities', 'ReferenceBlackWhite', 'WhitePoint', 'YCBCRCoefficients', 'PixelSizeX', 'PixelSizeY', 'RelativeTime', 'CZ_LSMInfo' ]: v = self.GetField(tagname) if v: if isinstance(v, int): v = define_to_name_map.get(tagname, {}).get(v, v) _l.append('%s: %s' % (tagname, v)) if tagname == 'CZ_LSMInfo': print(CZ_LSMInfo(self)) return '\n'.join(_l) def copy(self, filename, **kws): """ Copy opened TIFF file to a new file. Use keyword arguments to redefine tag values. Parameters ---------- filename : str Specify the name of file where TIFF file is copied to. compression : {'none', 'lzw', 'deflate', ...} Specify compression scheme. bitspersample : {8,16,32,64,128,256} Specify bit size of a sample. sampleformat : {'uint', 'int', 'float', 'complex'} Specify sample format. """ other = TIFF.open(filename, mode='w') define_rewrite = {} for _name, _value in list(kws.items()): define = TIFF.get_tag_define(_name) assert define is not None if _name == 'compression': _value = TIFF._fix_compression(_value) if _name == 'sampleformat': _value = TIFF._fix_sampleformat(_value) define_rewrite[define] = _value name_define_list = list(name_to_define_map['TiffTag'].items()) self.SetDirectory(0) self.ReadDirectory() while 1: other.SetDirectory(self.CurrentDirectory()) bits = self.GetField('BitsPerSample') sample_format = self.GetField('SampleFormat') assert bits >= 8, repr((bits, sample_format)) itemsize = bits // 8 dtype = self.get_numpy_type(bits, sample_format) for _name, define in name_define_list: orig_value = self.GetField(define) if orig_value is None and define not in define_rewrite: continue if _name.endswith('OFFSETS') or _name.endswith('BYTECOUNTS'): continue if define in define_rewrite: _value = define_rewrite[define] else: _value = orig_value if _value is None: continue other.SetField(define, _value) new_bits = other.GetField('BitsPerSample') new_sample_format = other.GetField('SampleFormat') new_dtype = other.get_numpy_type(new_bits, new_sample_format) assert new_bits >= 8, repr( (new_bits, new_sample_format, new_dtype)) new_itemsize = new_bits // 8 strip_size = self.StripSize() buf = np.zeros(strip_size // itemsize, dtype) for strip in range(self.NumberOfStrips()): elem = self.ReadEncodedStrip(strip, buf.ctypes.data, strip_size) if elem > 0: new_buf = buf.astype(new_dtype) other.WriteEncodedStrip(strip, new_buf.ctypes.data, (elem * new_itemsize) // itemsize) self.ReadDirectory() if self.LastDirectory(): break other.close() class TIFF3D(TIFF): """subclass of TIFF for handling import of 3D (multi-directory) files. like TIFF, but TIFF3D.read_image() will attempt to restore a 3D numpy array when given a multi-image TIFF file; performing the inverse of TIFF_instance.write(numpy.zeros((40, 200, 200))) like so: arr = TIFF3D_instance.read_image() arr.shape # gives (40, 200, 200) if you tried this with a normal TIFF instance, you would get this: arr = TIFF_instance.read_image() arr.shape # gives (200, 200) and you would have to loop over each image by hand with TIFF.iter_images(). """ @classmethod def open(cls, filename, mode='r'): """ just like TIFF.open, except returns a TIFF3D instance. """ try: try: # Python3: it needs bytes for the arguments of type "c_char_p" filename = os.fsencode(filename) # no-op if already bytes except AttributeError: # Python2: it needs str for the arguments of type "c_char_p" if isinstance(filename, unicode): # noqa: F821 filename = filename.encode(sys.getfilesystemencoding()) except Exception as ex: # It's probably going to not work, but let it try print('Warning: filename argument is of wrong type or encoding: %s' % ex) if isinstance(mode, str): mode = mode.encode() # monkey-patch the restype: old_restype = libtiff.TIFFOpen.restype libtiff.TIFFOpen.restype = TIFF3D try: # actually call the library function: tiff = libtiff.TIFFOpen(filename, mode) except Exception: raise finally: # restore the old restype: libtiff.TIFFOpen.restype = old_restype if tiff.value is None: raise TypeError('Failed to open file ' + repr(filename)) return tiff @debug def read_image(self, verbose=False, as3d=True): """ Read image from TIFF and return it as a numpy array. If as3d is passed True (default), will attempt to read multiple directories, and restore as slices in a 3D array. ASSUMES that all images in the tiff file have the same width, height, bits-per-sample, compression, and so on. If you get a segfault, this is probably the problem. """ if not as3d: return TIFF.read_image(self, verbose) # Code is initially copy-paste from TIFF: width = self.GetField('ImageWidth') height = self.GetField('ImageLength') bits = self.GetField('BitsPerSample') sample_format = self.GetField('SampleFormat') compression = self.GetField('Compression') typ = self.get_numpy_type(bits, sample_format) if typ is None: if bits == 1: typ = np.uint8 itemsize = 1 elif bits == 4: typ = np.uint32 itemsize = 4 else: raise NotImplementedError(repr(bits)) else: itemsize = bits / 8 # in order to allocate the numpy array, we must count the directories: # code borrowed from TIFF.iter_images(): depth = 0 while True: depth += 1 if self.LastDirectory(): break self.ReadDirectory() self.SetDirectory(0) # we proceed assuming all directories have the same properties from # above. layer_size = width * height * itemsize # total_size = layer_size * depth arr = np.zeros((depth, height, width), typ) layer = 0 while True: pos = 0 elem = None datal = arr.ctypes.data + layer * layer_size for strip in range(self.NumberOfStrips()): if elem is None: elem = self.ReadEncodedStrip(strip, datal + pos, layer_size) elif elem: elem = self.ReadEncodedStrip(strip, datal + pos, min(layer_size - pos, elem)) pos += elem if self.LastDirectory(): break self.ReadDirectory() layer += 1 self.SetDirectory(0) return arr class CZ_LSMInfo: def __init__(self, tiff): self.tiff = tiff self.filename = tiff.filename() self.offset = tiff.GetField(TIFFTAG_CZ_LSMINFO) self.extract_info() def extract_info(self): if self.offset is None: return _f = libtiff.TIFFFileno(self.tiff) fd = os.fdopen(_f, 'r') pos = fd.tell() self.offset = self.tiff.GetField(TIFFTAG_CZ_LSMINFO) print(os.lseek(_f, 0, 1)) print(pos) # print libtiff.TIFFSeekProc(self.tiff, 0, 1) fd.seek(0) print(struct.unpack('HH', fd.read(4))) print(struct.unpack('I', fd.read(4))) print(struct.unpack('H', fd.read(2))) fd.seek(self.offset) _d = [('magic_number', 'i4'), ('structure_size', 'i4')] print(pos, np.rec.fromfile(fd, _d, 1)) fd.seek(pos) # print hex (struct.unpack('I', fd.read (4))[0]) # fd.close() def __str__(self): return '%s: %s' % (self.filename, self.offset) libtiff.TIFFOpen.restype = TIFF libtiff.TIFFOpen.argtypes = [ctypes.c_char_p, ctypes.c_char_p] libtiff.TIFFFileName.restype = ctypes.c_char_p libtiff.TIFFFileName.argtypes = [TIFF] libtiff.TIFFFileno.restype = ctypes.c_int libtiff.TIFFFileno.argtypes = [TIFF] libtiff.TIFFCurrentRow.restype = ctypes.c_uint32 libtiff.TIFFCurrentRow.argtypes = [TIFF] libtiff.TIFFCurrentStrip.restype = c_tstrip_t libtiff.TIFFCurrentStrip.argtypes = [TIFF] libtiff.TIFFCurrentTile.restype = c_ttile_t libtiff.TIFFCurrentTile.argtypes = [TIFF] libtiff.TIFFCurrentDirectory.restype = c_tdir_t libtiff.TIFFCurrentDirectory.argtypes = [TIFF] libtiff.TIFFLastDirectory.restype = ctypes.c_int libtiff.TIFFLastDirectory.argtypes = [TIFF] libtiff.TIFFReadDirectory.restype = ctypes.c_int libtiff.TIFFReadDirectory.argtypes = [TIFF] libtiff.TIFFWriteDirectory.restype = ctypes.c_int libtiff.TIFFWriteDirectory.argtypes = [TIFF] libtiff.TIFFSetDirectory.restype = ctypes.c_int libtiff.TIFFSetDirectory.argtypes = [TIFF, c_tdir_t] libtiff.TIFFSetSubDirectory.restype = ctypes.c_int libtiff.TIFFSetSubDirectory.argtypes = [TIFF, ctypes.c_uint64] libtiff.TIFFFileno.restype = ctypes.c_int libtiff.TIFFFileno.argtypes = [TIFF] libtiff.TIFFGetMode.restype = ctypes.c_int libtiff.TIFFGetMode.argtypes = [TIFF] libtiff.TIFFIsTiled.restype = ctypes.c_int libtiff.TIFFIsTiled.argtypes = [TIFF] libtiff.TIFFIsByteSwapped.restype = ctypes.c_int libtiff.TIFFIsByteSwapped.argtypes = [TIFF] libtiff.TIFFIsUpSampled.restype = ctypes.c_int libtiff.TIFFIsUpSampled.argtypes = [TIFF] libtiff.TIFFIsMSB2LSB.restype = ctypes.c_int libtiff.TIFFIsMSB2LSB.argtypes = [TIFF] # GetField and SetField arguments are dependent on the tag libtiff.TIFFGetField.restype = ctypes.c_int libtiff.TIFFSetField.restype = ctypes.c_int libtiff.TIFFNumberOfStrips.restype = c_tstrip_t libtiff.TIFFNumberOfStrips.argtypes = [TIFF] libtiff.TIFFWriteScanline.restype = ctypes.c_int libtiff.TIFFWriteScanline.argtypes = [TIFF, c_tdata_t, ctypes.c_uint32, c_tsample_t] libtiff.TIFFReadScanline.restype = ctypes.c_int libtiff.TIFFReadScanline.argtypes = [TIFF, c_tdata_t, ctypes.c_uint32, c_tsample_t] libtiff.TIFFScanlineSize.restype = c_tsize_t libtiff.TIFFScanlineSize.argtypes = [TIFF] libtiff.TIFFReadRawStrip.restype = c_tsize_t libtiff.TIFFReadRawStrip.argtypes = [TIFF, c_tstrip_t, c_tdata_t, c_tsize_t] libtiff.TIFFWriteRawStrip.restype = c_tsize_t libtiff.TIFFWriteRawStrip.argtypes = [TIFF, c_tstrip_t, c_tdata_t, c_tsize_t] libtiff.TIFFReadEncodedStrip.restype = c_tsize_t libtiff.TIFFReadEncodedStrip.argtypes = [TIFF, c_tstrip_t, c_tdata_t, c_tsize_t] libtiff.TIFFWriteEncodedStrip.restype = c_tsize_t libtiff.TIFFWriteEncodedStrip.argtypes = [TIFF, c_tstrip_t, c_tdata_t, c_tsize_t] libtiff.TIFFStripSize.restype = c_tsize_t libtiff.TIFFStripSize.argtypes = [TIFF] libtiff.TIFFRawStripSize.restype = c_tsize_t libtiff.TIFFRawStripSize.argtypes = [TIFF, c_tstrip_t] # For adding custom tags (must be void pointer otherwise callback seg faults) libtiff.TIFFMergeFieldInfo.restype = ctypes.c_int32 libtiff.TIFFMergeFieldInfo.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32] # Tile Support # TODO: # TIFFTileRowSize64 # TIFFTileSize64 # TIFFVTileSize # TIFFVTileSize64 libtiff.TIFFTileRowSize.restype = c_tsize_t libtiff.TIFFTileRowSize.argtypes = [TIFF] libtiff.TIFFTileSize.restype = c_tsize_t libtiff.TIFFTileSize.argtypes = [TIFF] libtiff.TIFFComputeTile.restype = c_ttile_t libtiff.TIFFComputeTile.argtypes = [TIFF, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_uint32, c_tsample_t] libtiff.TIFFCheckTile.restype = ctypes.c_int libtiff.TIFFCheckTile.argtypes = [TIFF, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_uint32, c_tsample_t] libtiff.TIFFNumberOfTiles.restype = c_ttile_t libtiff.TIFFNumberOfTiles.argtypes = [TIFF] libtiff.TIFFReadTile.restype = c_tsize_t libtiff.TIFFReadTile.argtypes = [TIFF, c_tdata_t, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_uint32, c_tsample_t] libtiff.TIFFWriteTile.restype = c_tsize_t libtiff.TIFFWriteTile.argtypes = [TIFF, c_tdata_t, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_uint32, c_tsample_t] libtiff.TIFFReadEncodedTile.restype = ctypes.c_int libtiff.TIFFReadEncodedTile.argtypes = [TIFF, ctypes.c_ulong, ctypes.c_char_p, ctypes.c_ulong] libtiff.TIFFReadRawTile.restype = c_tsize_t libtiff.TIFFReadRawTile.argtypes = [TIFF, c_ttile_t, c_tdata_t, c_tsize_t] libtiff.TIFFReadRGBATile.restype = ctypes.c_int libtiff.TIFFReadRGBATile.argtypes = [TIFF, ctypes.c_uint32, ctypes.c_uint32, ctypes.POINTER(ctypes.c_uint32)] libtiff.TIFFWriteEncodedTile.restype = c_tsize_t libtiff.TIFFWriteEncodedTile.argtypes = [TIFF, c_ttile_t, c_tdata_t, c_tsize_t] libtiff.TIFFWriteRawTile.restype = c_tsize_t libtiff.TIFFWriteRawTile.argtypes = [TIFF, c_ttile_t, c_tdata_t, c_tsize_t] libtiff.TIFFDefaultTileSize.restype = None libtiff.TIFFDefaultTileSize.argtypes = [TIFF, ctypes.c_uint32, ctypes.c_uint32] libtiff.TIFFClose.restype = None libtiff.TIFFClose.argtypes = [TIFF] # Support for TIFF warning and error handlers: TIFFWarningHandler = ctypes.CFUNCTYPE(None, ctypes.c_char_p, # Module ctypes.c_char_p, # Format ctypes.c_void_p) # va_list TIFFErrorHandler = ctypes.CFUNCTYPE(None, ctypes.c_char_p, # Module ctypes.c_char_p, # Format ctypes.c_void_p) # va_list # This has to be at module scope so it is not garbage-collected _null_warning_handler = TIFFWarningHandler(lambda module, fmt, va_list: None) _null_error_handler = TIFFErrorHandler(lambda module, fmt, va_list: None) def suppress_warnings(): libtiff.TIFFSetWarningHandler(_null_warning_handler) def suppress_errors(): libtiff.TIFFSetErrorHandler(_null_error_handler) def _test_custom_tags(): def _tag_write(): a = TIFF.open("/tmp/libtiff_test_custom_tags.tif", "w") a.SetField("ARTIST", "MY NAME") a.SetField("LibtiffTestByte", 42) a.SetField("LibtiffTeststr", "FAKE") a.SetField("LibtiffTestuint16", 42) a.SetField("LibtiffTestMultiuint32", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) a.SetField("XPOSITION", 42.0) a.SetField("PRIMARYCHROMATICITIES", (1.0, 2, 3, 4, 5, 6)) arr = np.ones((512, 512), dtype=np.uint8) arr[:, :] = 255 a.write_image(arr) print("Tag Write: SUCCESS") def _tag_read(): a = TIFF.open("/tmp/libtiff_test_custom_tags.tif", "r") tmp = a.read_image() assert tmp.shape == ( 512, 512), "Image read was wrong shape (%r instead of (512,512))" % ( tmp.shape,) tmp = a.GetField("XPOSITION") assert tmp == 42.0, "XPosition was not read as 42.0" tmp = a.GetField("ARTIST") assert tmp == "MY NAME", "Artist was not read as 'MY NAME'" tmp = a.GetField("LibtiffTestByte") assert tmp == 42, "LibtiffTestbyte was not read as 42" tmp = a.GetField("LibtiffTestuint16") assert tmp == 42, "LibtiffTestuint16 was not read as 42" tmp = a.GetField("LibtiffTestMultiuint32") assert tmp == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], "LibtiffTestMultiuint32 was not read as [1,2,3," \ "4,5,6,7,8,9,10]" tmp = a.GetField("LibtiffTeststr") assert tmp == "FAKE", "LibtiffTeststr was not read as 'FAKE'" tmp = a.GetField("PRIMARYCHROMATICITIES") assert tmp == [1.0, 2.0, 3.0, 4.0, 5.0, 6.0], "PrimaryChromaticities was not read as [1.0," \ "2.0,3.0,4.0,5.0,6.0]" print("Tag Read: SUCCESS") # Define a C structure that says how each tag should be used test_tags = [ TIFFFieldInfo(40100, 1, 1, TIFFDataType.TIFF_BYTE, FIELD_CUSTOM, True, False, "LibtiffTestByte"), TIFFFieldInfo(40103, 10, 10, TIFFDataType.TIFF_LONG, FIELD_CUSTOM, True, False, "LibtiffTestMultiuint32"), TIFFFieldInfo(40102, 1, 1, TIFFDataType.TIFF_SHORT, FIELD_CUSTOM, True, False, "LibtiffTestuint16"), TIFFFieldInfo(40101, -1, -1, TIFFDataType.TIFF_ASCII, FIELD_CUSTOM, True, False, "LibtiffTeststr") ] # Add tags to the libtiff library # Keep pointer to extender object, no gc: test_extender = add_tags(test_tags) # noqa: F841 _tag_write() _tag_read() def _test_tile_write(): a = TIFF.open("/tmp/libtiff_test_tile_write.tiff", "w") data_array = np.tile(list(range(500)), (1, 6)).astype(np.uint8) a.SetField("TileWidth", 512) a.SetField("TileLength", 528) # tile_width and tile_height is not set, write_tiles get these values from # TileWidth and TileLength tags assert a.write_tiles(data_array) == (512 * 528) * 6,\ "could not write tile images" # 1D print("Tile Write: Wrote array of shape %r" % (data_array.shape,)) # 2D Arrays data_array = np.tile(list(range(500)), (2500, 6)).astype(np.uint8) assert a.write_tiles(data_array, 512, 528) == (512 * 528) * 5 * 6,\ "could not write tile images" # 2D print("Tile Write: Wrote array of shape %r" % (data_array.shape,)) # 3D Arrays, 3rd dimension as last dimension data_array = np.array(range(2500 * 3000 * 3)).reshape( 2500, 3000, 3).astype(np.uint8) assert a.write_tiles(data_array, 512, 528, None, True) \ == (512 * 528) * 5 * 6 * 3,\ "could not write tile images" # 3D print("Tile Write: Wrote array of shape %r" % (data_array.shape,)) # 3D Arrays, 3rd dimension as first dimension data_array = np.array(range(2500 * 3000 * 3)).reshape( 3, 2500, 3000).astype(np.uint8) assert a.write_tiles(data_array, 512, 528, None, True)\ == (512 * 528) * 5 * 6 * 3,\ "could not write tile images" # 3D print("Tile Write: Wrote array of shape %r" % (data_array.shape,)) # Grayscale image with 3 depths data_array = np.array(range(2500 * 3000 * 3)).reshape( 3, 2500, 3000).astype(np.uint8) written_bytes = a.write_tiles(data_array, 512, 528) assert written_bytes == 512 * 528 * 5 * 6 * 3,\ "could not write tile images, written_bytes: %s" % (written_bytes,) print("Tile Write: Wrote array of shape %r" % (data_array.shape,)) print("Tile Write: SUCCESS") def _test_tile_read(filename="/tmp/libtiff_test_tile_write.tiff"): import sys if filename is None: if len(sys.argv) != 2: print("Run `libtiff.py ` for testing.") return filename = sys.argv[1] a = TIFF.open(filename, "r") # 1D Arrays (doesn't make much sense to tile) a.SetDirectory(0) # expected tag values for the first image tags = [ {"tag": "ImageWidth", "exp_value": 3000}, {"tag": "ImageLength", "exp_value": 1}, {"tag": "TileWidth", "exp_value": 512}, {"tag": "TileLength", "exp_value": 528}, {"tag": "BitsPerSample", "exp_value": 8}, {"tag": "Compression", "exp_value": 1}, ] # assert tag values for tag in tags: field_value = a.GetField(tag['tag']) assert field_value == tag['exp_value'],\ repr((tag['tag'], tag['exp_value'], field_value)) data_array = a.read_tiles() print("Tile Read: Read array of shape %r" % (data_array.shape,)) assert data_array.shape == (1, 3000), "tile data read was the wrong shape" test_array = np.array(list(range(500)) * 6).astype(np.uint8).flatten() assert np.nonzero(data_array.flatten() != test_array)[0].shape[0] == 0,\ "tile data read was not the same as the expected data" print("Tile Read: Data is the same as expected from tile write test") # 2D Arrays (doesn't make much sense to tile) a.SetDirectory(1) # expected tag values for the second image tags = [ {"tag": "ImageWidth", "exp_value": 3000}, {"tag": "ImageLength", "exp_value": 2500}, {"tag": "TileWidth", "exp_value": 512}, {"tag": "TileLength", "exp_value": 528}, {"tag": "BitsPerSample", "exp_value": 8}, {"tag": "Compression", "exp_value": 1}, ] # assert tag values for tag in tags: field_value = a.GetField(tag['tag']) assert field_value == tag['exp_value'],\ repr((tag['tag'], tag['exp_value'], field_value)) data_array = a.read_tiles() print("Tile Read: Read array of shape %r" % (data_array.shape,)) assert data_array.shape == (2500, 3000),\ "tile data read was the wrong shape" test_array = np.tile(list(range(500)), (2500, 6)).astype(np.uint8).flatten() assert np.nonzero(data_array.flatten() != test_array)[0].shape[0] == 0,\ "tile data read was not the same as the expected data" print("Tile Read: Data is the same as expected from tile write test") # 3D Arrays, 3rd dimension as last dimension a.SetDirectory(2) # expected tag values for the third image tags = [ {"tag": "ImageWidth", "exp_value": 3000}, {"tag": "ImageLength", "exp_value": 2500}, {"tag": "TileWidth", "exp_value": 512}, {"tag": "TileLength", "exp_value": 528}, {"tag": "BitsPerSample", "exp_value": 8}, {"tag": "Compression", "exp_value": 1}, ] # assert tag values for tag in tags: field_value = a.GetField(tag['tag']) assert field_value == tag['exp_value'],\ repr(tag['tag'], tag['exp_value'], field_value) data_array = a.read_tiles() print("Tile Read: Read array of shape %r" % (data_array.shape,)) assert data_array.shape == (2500, 3000, 3),\ "tile data read was the wrong shape" test_array = np.array(range(2500 * 3000 * 3)).reshape( 2500, 3000, 3).astype(np.uint8).flatten() assert np.nonzero(data_array.flatten() != test_array)[0].shape[0] == 0,\ "tile data read was not the same as the expected data" print("Tile Read: Data is the same as expected from tile write test") # 3D Arrays, 3rd dimension as first dimension a.SetDirectory(3) # expected tag values for the third image tags = [ {"tag": "ImageWidth", "exp_value": 3000}, {"tag": "ImageLength", "exp_value": 2500}, {"tag": "TileWidth", "exp_value": 512}, {"tag": "TileLength", "exp_value": 528}, {"tag": "BitsPerSample", "exp_value": 8}, {"tag": "Compression", "exp_value": 1}, ] # assert tag values for tag in tags: field_value = a.GetField(tag['tag']) assert field_value == tag['exp_value'],\ repr(tag['tag'], tag['exp_value'], field_value) data_array = a.read_tiles() print("Tile Read: Read array of shape %r" % (data_array.shape,)) assert data_array.shape == (3, 2500, 3000),\ "tile data read was the wrong shape" test_array = np.array(range(2500 * 3000 * 3)).reshape( 3, 2500, 3000).astype(np.uint8).flatten() assert np.nonzero(data_array.flatten() != test_array)[0].shape[0] == 0,\ "tile data read was not the same as the expected data" print("Tile Read: Data is the same as expected from tile write test") # Grayscale image with 3 depths a.SetDirectory(4) # expected tag values for the third image tags = [ {"tag": "ImageWidth", "exp_value": 3000}, {"tag": "ImageLength", "exp_value": 2500}, {"tag": "TileWidth", "exp_value": 512}, {"tag": "TileLength", "exp_value": 528}, {"tag": "BitsPerSample", "exp_value": 8}, {"tag": "Compression", "exp_value": 1}, {"tag": "ImageDepth", "exp_value": 3} ] # assert tag values for tag in tags: field_value = a.GetField(tag['tag']) assert field_value == tag['exp_value'],\ repr([tag['tag'], tag['exp_value'], field_value]) data_array = a.read_tiles() print("Tile Read: Read array of shape %r" % (data_array.shape,)) assert data_array.shape == (3, 2500, 3000),\ "tile data read was the wrong shape" test_array = np.array(range(2500 * 3000 * 3)).reshape( 3, 2500, 3000).astype(np.uint8).flatten() assert np.nonzero(data_array.flatten() != test_array)[0].shape[0] == 0,\ "tile data read was not the same as the expected data" print("Tile Read: Data is the same as expected from tile write test") print("Tile Read: SUCCESS") def _test_read_one_tile(): filename = "/tmp/libtiff_test_tile_write.tiff" tiff = TIFF.open(filename, "r") # the first image is 1 pixel high tile = tiff.read_one_tile(0, 0) assert tile.shape == (1, 512), repr(tile.shape) # second image, 3000 x 2500 tiff.SetDirectory(1) tile = tiff.read_one_tile(0, 0) assert tile.shape == (528, 512), repr(tile.shape) tile = tiff.read_one_tile(512, 528) assert tile.shape == (528, 512), repr(tile.shape) # test tile on the right border tile = tiff.read_one_tile(2560, 528) assert tile.shape == (528, 440), repr(tile.shape) # test tile on the bottom border tile = tiff.read_one_tile(512, 2112) assert tile.shape == (388, 512), repr(tile.shape) # test tile on the right and bottom borders tile = tiff.read_one_tile(2560, 2112) assert tile.shape == (388, 440), repr(tile.shape) # test x and y values not multiples of the tile width and height tile = tiff.read_one_tile(530, 600) assert tile[0][0] == 12, tile[0][0] # test negative x try: tiff.read_one_tile(-5, 0) raise AssertionError( "An exception must be raised with invalid (x, y) values") except ValueError as inst: assert inst.message == "Invalid x value", repr(inst.message) # test y greater than the image height try: tiff.read_one_tile(0, 5000) raise AssertionError( "An exception must be raised with invalid (x, y) values") except ValueError as inst: assert inst.message == "Invalid y value", repr(inst.message) # RGB image sized 3000 x 2500, PLANARCONFIG_SEPARATE tiff.SetDirectory(3) tile = tiff.read_one_tile(0, 0) assert tile.shape == (3, 528, 512), repr(tile.shape) # get the tile on the lower bottom corner tile = tiff.read_one_tile(2999, 2499) assert tile.shape == (3, 388, 440), repr(tile.shape) # Grayscale image sized 3000 x 2500, 3 depths tiff.SetDirectory(4) tile = tiff.read_one_tile(0, 0) assert tile.shape == (3, 528, 512), repr(tile.shape) # get the tile on the lower bottom corner tile = tiff.read_one_tile(2999, 2499) assert tile.shape == (3, 388, 440), repr(tile.shape) def _test_tiled_image_read(filename="/tmp/libtiff_test_tile_write.tiff"): """ Tests opening a tiled image """ def assert_image_tag(tiff, tag_name, expected_value): value = tiff.GetField(tag_name) assert value == expected_value,\ ('%s expected to be %d, but it\'s %d' % (tag_name, expected_value, value)) # _test_tile_write is called here just to make sure that the image # is saved, even if the order of the tests changed _test_tile_write() tiff = TIFF.open(filename, "r") # sets the current image to the second image tiff.SetDirectory(1) # test tag values assert_image_tag(tiff, 'ImageWidth', 3000) assert_image_tag(tiff, 'ImageLength', 2500) assert_image_tag(tiff, 'TileWidth', 512) assert_image_tag(tiff, 'TileLength', 528) assert_image_tag(tiff, 'BitsPerSample', 8) assert_image_tag(tiff, 'Compression', COMPRESSION_NONE) # noqa: F821 # read the image to a NumPy array arr = tiff.read_image() # test image NumPy array dimensions assert arr.shape[0] == 2500, \ 'Image width expected to be 2500, but it\'s %d' % (arr.shape[0]) assert arr.shape[1] == 3000, \ 'Image height expected to be 3000, but it\'s %d' % (arr.shape[1]) # generates the same array that was generated for the image data_array = np.array(list(range(500)) * 6).astype(np.uint8) # tests if the array from the read image is the same of the original image assert (data_array == arr).all(), \ 'The read tiled image is different from the generated image' def _test_tags_write(): tiff = TIFF.open('/tmp/libtiff_tags_write.tiff', mode='w') tmp = tiff.SetField("Artist", "A Name") assert tmp == 1, "Tag 'Artist' was not written properly" tmp = tiff.SetField("DocumentName", "") assert tmp == 1, "Tag 'DocumentName' with empty string was not written " \ "properly" tmp = tiff.SetField("PrimaryChromaticities", [1, 2, 3, 4, 5, 6]) assert tmp == 1, "Tag 'PrimaryChromaticities' was not written properly" tmp = tiff.SetField("BitsPerSample", 8) assert tmp == 1, "Tag 'BitsPerSample' was not written properly" tmp = tiff.SetField("ColorMap", [[x * 256 for x in range(256)]] * 3) assert tmp == 1, "Tag 'ColorMap' was not written properly" arr = np.zeros((100, 100), np.uint8) tiff.write_image(arr) print("Tag Write: SUCCESS") def _test_tags_read(filename=None): import sys if filename is None: if len(sys.argv) != 2: filename = '/tmp/libtiff_tags_write.tiff' if not os.path.isfile(filename): print('Run `%s ` for testing.' % (__file__)) return else: filename = sys.argv[1] tiff = TIFF.open(filename) tmp = tiff.GetField("Artist") assert tmp == "A Name", "Tag 'Artist' did not read the correct value (" \ "Got '%s'; Expected 'A Name')" % (tmp,) tmp = tiff.GetField("DocumentName") assert tmp == "", "Tag 'DocumentName' did not read the correct value (" \ "Got '%s'; Expected empty string)" % (tmp,) tmp = tiff.GetField("PrimaryChromaticities") assert tmp == [1, 2, 3, 4, 5, 6], "Tag 'PrimaryChromaticities' did not read the " \ "correct value (Got '%r'; Expected '[1,2,3,4,5,6]'" % ( tmp,) tmp = tiff.GetField("BitsPerSample") assert tmp == 8, "Tag 'BitsPerSample' did not read the correct value (" \ "Got %s; Expected 8)" % (str(tmp),) tmp = tiff.GetField("ColorMap") try: assert len( tmp) == 3, "Tag 'ColorMap' should be three arrays, found %d" % \ len(tmp) assert len(tmp[ 0]) == 256, "Tag 'ColorMap' should be three arrays " \ "of 256 elements, found %d elements" % \ len(tmp[0]) assert len(tmp[ 1]) == 256, "Tag 'ColorMap' should be three arrays " \ "of 256 elements, found %d elements" % \ len(tmp[1]) assert len(tmp[ 2]) == 256, "Tag 'ColorMap' should be three arrays " \ "of 256 elements, found %d elements" % \ len(tmp[2]) except TypeError: print( "Tag 'ColorMap' has the wrong shape of 3 arrays of 256 elements " "each") return print("Tag Read: SUCCESS") def _test_read(filename=None): import sys import time if filename is None: if len(sys.argv) != 2: filename = '/tmp/libtiff_test_write.tiff' if not os.path.isfile(filename): print('Run `libtiff.py ` for testing.') return else: filename = sys.argv[1] print('Trying to open', filename, '...', end=' ') tiff = TIFF.open(filename) print('ok') print('Trying to show info ...\n', '-' * 10) print(tiff.info()) print('-' * 10, 'ok') print('Trying show images ...') t = time.time() _i = 0 for image in tiff.iter_images(verbose=True): # print image.min(), image.max(), image.mean () _i += 1 print('\tok', (time.time() - t) * 1e3, 'ms', _i, 'images') def _test_write(): tiff = TIFF.open('/tmp/libtiff_test_write.tiff', mode='w') arr = np.zeros((5, 6), np.uint32) for _i in range(arr.shape[0]): for j in range(arr.shape[1]): arr[_i, j] = _i + 10 * j print(arr) tiff.write_image(arr) del tiff def _test_write_float(): tiff = TIFF.open('/tmp/libtiff_test_write.tiff', mode='w') arr = np.zeros((5, 6), np.float64) for _i in range(arr.shape[0]): for j in range(arr.shape[1]): arr[_i, j] = _i + 10 * j print(arr) tiff.write_image(arr) del tiff tiff = TIFF.open('/tmp/libtiff_test_write.tiff', mode='r') print(tiff.info()) arr2 = tiff.read_image() print(arr2) def _test_write_rgba(): tiff = TIFF.open('/tmp/libtiff_test_write.tiff', mode='w') arr = np.zeros((5, 6, 4), np.uint8) for i in np.ndindex(*arr.shape): arr[i] = 20 * i[0] + 10 * i[1] + i[2] print(arr) tiff.write_image(arr, write_rgb=True) del tiff tiff = TIFF.open('/tmp/libtiff_test_write.tiff', mode='r') print(tiff.info()) arr2 = tiff.read_image() print(arr2) np.testing.assert_array_equal(arr, arr2) def _test_tree(): # Write a TIFF image with the following tree structure: # Im0 --SubIFD--> Im0,1 ---> Im0,2 ---> Im0,3 # | # V # Im1 tiff = TIFF.open('/tmp/libtiff_test_write.tiff', mode='w') arr = np.zeros((5, 6), np.uint32) for i in np.ndindex(*arr.shape): arr[i] = i[0] + 20 * i[1] print(arr) n = 3 tiff.SetField("SubIFD", [0] * n) tiff.write_image(arr) for i in range(n): arr[0, 0] = i tiff.write_image(arr) arr[0, 0] = 255 tiff.write_image(arr) del tiff tiff = TIFF.open('/tmp/libtiff_test_write.tiff', mode='r') print(tiff.info()) n = 0 for im in tiff.iter_images(verbose=True): print(im) n += 1 assert n == 2 def _test_copy(): tiff = TIFF.open('/tmp/libtiff_test_compression.tiff', mode='w') arr = np.zeros((5, 6), np.uint32) for _i in range(arr.shape[0]): for j in range(arr.shape[1]): arr[_i, j] = 1 + _i + 10 * j # from scipy.stats import poisson # arr = poisson.rvs (arr) tiff.SetField('ImageDescription', 'Hey\nyou') tiff.write_image(arr, compression='lzw') del tiff tiff = TIFF.open('/tmp/libtiff_test_compression.tiff', mode='r') print(tiff.info()) arr2 = tiff.read_image() assert (arr == arr2).all(), 'arrays not equal' for compression in ['none', 'lzw', 'deflate']: for sampleformat in ['int', 'uint', 'float']: for bitspersample in [256, 128, 64, 32, 16, 8]: if sampleformat == 'float' and ( bitspersample < 32 or bitspersample > 128): continue if sampleformat in ['int', 'uint'] and bitspersample > 64: continue # print compression, sampleformat, bitspersample tiff.copy('/tmp/libtiff_test_copy2.tiff', compression=compression, imagedescription='hoo', sampleformat=sampleformat, bitspersample=bitspersample) tiff2 = TIFF.open('/tmp/libtiff_test_copy2.tiff', mode='r') arr3 = tiff2.read_image() assert (arr == arr3).all(), 'arrays not equal %r' % ( (compression, sampleformat, bitspersample),) print('test copy ok') if __name__ == '__main__': _test_custom_tags() _test_tile_write() _test_tile_read() _test_read_one_tile() _test_tiled_image_read() _test_tags_write() _test_tags_read() _test_write_float() _test_write_rgba() _test_tree() _test_write() _test_read() _test_copy() pylibtiff-0.6.1/libtiff/lsm.py000066400000000000000000001436541450302046000163010ustar00rootroot00000000000000""" lsm - implements TIFF CZ_LSM support. """ # Author: Pearu Peterson # Created: April 2010 __all__ = ['scaninfo', 'timestamps', 'eventlist', 'channelcolors', 'channelwavelength', 'inputlut', 'outputlut', 'vectoroverlay', 'roi', 'bleachroi', 'meanofroisoverlay', 'topoisolineoverlay', 'topoprofileoverlay', 'linescanoverlay', 'channelfactors', 'channeldatatypes', 'lsmblock', 'lsminfo'] import sys import numpy CZ_LSMInfo_tag = 0x866C tiff_module_dict = None verbose_lsm_memory_usage = False def IFDEntry_lsm_str_hook(entry): lst = [] for name in entry.value.dtype.names: func = CZ_LSMOffsetField_readers.get(name) if func is not None: lst.append('\n %s->%s' % (name, func(entry, debug=False))) else: v = entry.value[name] lst.append('\n %s=%s' % (name, v)) return 'IFDEntry(tag=%s, value=%s(%s))' % ( entry.tag_name, entry.type_name, ', '.join(lst)) def IFDEntry_lsm_init_hook(ifdentry): """Make tiff.IFDENTRYEntry CZ_LSM aware. """ global tiff_module_dict if ifdentry.tag == CZ_LSMInfo_tag: # replace type,count=(BYTE,500) with (CZ_LSMInfo, 1) reserved_bytes = (ifdentry.count - CZ_LSMInfo_dtype_fields_size) CZ_LSMInfo_type = (CZ_LSMInfo_tag, reserved_bytes) if CZ_LSMInfo_type not in tiff_module_dict['type2dtype']: dtype = numpy.dtype(CZ_LSMInfo_dtype_fields + [ ('Reserved', numpy.dtype('(%s,)u4' % (reserved_bytes // 4)))]) CZ_LSMInfo_type_name = 'CZ_LSMInfo%s' % (reserved_bytes) CZ_LSMInfo_type_size = dtype.itemsize tiff_module_dict['type2dtype'][CZ_LSMInfo_type] = dtype tiff_module_dict['type2name'][CZ_LSMInfo_type] \ = CZ_LSMInfo_type_name tiff_module_dict['type2bytes'][CZ_LSMInfo_type] \ = CZ_LSMInfo_type_size tiff_module_dict['tag_name2value'][CZ_LSMInfo_type_name] \ = CZ_LSMInfo_tag # assert ifdentry.count==CZ_LSMInfo_type_size, # `ifdentry.count,CZ_LSMInfo_type_size` ifdentry.type = CZ_LSMInfo_type ifdentry.count = 1 ifdentry.tiff.is_lsm = True ifdentry.str_hook = IFDEntry_lsm_str_hook else: if not hasattr(ifdentry.tiff, 'is_lsm'): ifdentry.tiff.is_lsm = False def IFDEntry_lsm_finalize_hook(ifdentry): if ifdentry.tag == CZ_LSMInfo_tag: blockstart, blockend = None, None for name in ifdentry.value.dtype.names: func = CZ_LSMOffsetField_readers.get(name) if func is not None: s = func(ifdentry, debug=False) if s is not None: start = s.offset end = start + s.get_size() if blockstart is None: blockstart, blockend = start, end else: blockstart, blockend = ( min(blockstart, start), max(blockend, end)) if verbose_lsm_memory_usage: ifdentry.memory_usage.append( (start, end, ifdentry.tag_name + ' ' + name[6:])) if verbose_lsm_memory_usage: for offset in ifdentry.value['Reserved'][0]: if offset: ifdentry.memory_usage.append( (offset, offset, 'start of a unknown reserved field')) else: ifdentry.memory_usage.append((blockstart, blockend, 'lsmblock')) ifdentry.block_range = (blockstart, blockend) ifdentry.tiff.lsminfo = scaninfo(ifdentry, debug=False) ifdentry.tiff.lsmblock = lsmblock(ifdentry, debug=False) ifdentry.tiff.lsmentry = ifdentry.value def register(tiff_dict): """Register CZ_LSM support in tiff module. """ global tiff_module_dict tiff_module_dict = tiff_dict tiff_dict['IFDEntry_init_hooks'].append(IFDEntry_lsm_init_hook) tiff_dict['IFDEntry_finalize_hooks'].append(IFDEntry_lsm_finalize_hook) def lsminfo(ifdentry, new_lsmblock_start=None): if ifdentry.tag != CZ_LSMInfo_tag: return target = ifdentry.value.copy() old_lsmblock_start = ifdentry.block_range[0] if new_lsmblock_start is None: new_lsmblock_start = old_lsmblock_start offset_diff = new_lsmblock_start - old_lsmblock_start i = 0 for name in ifdentry.value.dtype.names: dt = ifdentry.value.dtype[name] value_ref = target[name] value = value_ref[0] if name.startswith('Offset') and value != 0: # print 'changing',name,'from',value,'to', value_ref += offset_diff # print value_ref[0] elif name == 'Reserved': # assuming that unknown values in Reserved field are # offsets: for i in range(len(value)): if value[i] != 0: if value[i] >= ifdentry.block_range[0] \ and value[i] <= ifdentry.block_range[1]: # print 'chaning Reserved[%s] from %s to' % # (i, value[i]), value[i] += offset_diff # print value[i] else: sys.stderr.write( 'Reserved[%s]=%s is out of block range %s.' 'lsminfo might not be correct.' % (i, value[i], ifdentry.block_range)) i += dt.itemsize return target class LSMBlock: def __init__(self, ifdentry): self.ifdentry = ifdentry self._offset = None @property def offset(self): if self._offset is None: self._offset = self.ifdentry.block_range[0] return self._offset def get_size(self): return self.ifdentry.block_range[1] - self.offset def __str__(self): return '%s(offset=%s, size=%s, end=%s)' % ( self.__class__.__name__, self.offset, self.get_size(), self.offset + self.get_size()) def get_data(self, new_offset): raise NotImplementedError(repr(new_offset)) def toarray(self, target=None, new_offset=None): sz = self.get_size() if target is None: target = numpy.zeros((sz, ), dtype=numpy.ubyte) if new_offset is None: new_offset = self.offset target[:sz] = self.ifdentry.tiff.data[self.offset:self.offset + sz] return target def lsmblock(ifdentry, debug=True): if ifdentry.tag != CZ_LSMInfo_tag: return r = LSMBlock(ifdentry) if debug: arr = r.toarray() offset = r.offset arr2 = ifdentry.tiff.data[offset:offset + arr.nbytes] assert (arr == arr2).all() return r class ScanInfoEntry: """ Holds scan information entry data structure. """ def __init__(self, entry, type_name, label, data): self._offset = None self.record = (entry, type_name, label, data) self.footer = None if type_name == 'ASCII': self.type = 2 self.header = numpy.array( [entry, 2, len(data) + 1], dtype=numpy.uint32).view( dtype=numpy.ubyte) elif type_name == 'LONG': self.type = 4 self.header = numpy.array( [entry, 4, 4], dtype=numpy.uint32).view(dtype=numpy.ubyte) elif type_name == 'DOUBLE': self.type = 5 self.header = numpy.array( [entry, 5, 8], dtype=numpy.uint32).view(dtype=numpy.ubyte) elif type_name == 'SUBBLOCK': self.type = 0 self.header = numpy.array( [entry, 0, 0], dtype=numpy.uint32).view(dtype=numpy.ubyte) else: raise NotImplementedError(repr(self.record)) def __repr__(self): return '%s%r' % (self.__class__.__name__, self.record) @property def is_subblock(self): return self.record[1] == 'SUBBLOCK' @property def label(self): return self.record[2] @property def data(self): return self.record[3] def get(self, label): if self.label == label: if self.is_subblock: return self return self.data if self.is_subblock and self.data is not None: lst = [] for entry in self.data: r = entry.get(label) if isinstance(r, list): lst.extend(r) elif r is not None: lst.append(r) if not lst: return return lst @property def offset(self): if self._offset is None: self._offset = self.ifdentry.value['OffsetScanInformation'][0] return self._offset def get_size(self): """ Return total memory size in bytes needed to fit the entry to memory. """ (entry, type_name, label, data) = self.record if type_name == 'SUBBLOCK': if data is None: return 12 size = 0 for item in data: size += item.get_size() return 12 + size if type_name == 'LONG': return 12 + 4 if type_name == 'DOUBLE': return 12 + 8 if type_name == 'ASCII': return 12 + len(data) + 1 raise NotImplementedError(repr(self.record)) def toarray(self, target=None): if target is None: target = numpy.zeros((self.get_size(),), dtype=numpy.ubyte) dtype = target.dtype (entry, type_name, label, data) = self.record target[:12] = self.header if type_name == 'SUBBLOCK': if data is not None: n = 12 for item in data: item.toarray(target[n:]) n += item.get_size() elif type_name == 'ASCII': target[12:12 + len(data) + 1] = numpy.array( [data + '\0']).view(dtype=dtype) elif type_name == 'LONG': target[12:12 + 4] = numpy.array( [data], dtype=numpy.uint32).view(dtype=dtype) elif type_name == 'DOUBLE': target[12:12 + 8] = numpy.array( [data], dtype=numpy.float64).view(dtype=dtype) else: raise NotImplementedError(repr(self.record)) return target def tostr(self, tab='', short=False): (entry, type_name, label, data) = self.record if hasattr(self, 'parent') and self.parent is not None: parent_label = self.parent.record[2] else: parent_label = '' if type_name == 'SUBBLOCK': if data is None: return '%s%s %s' % (tab[:-2], label, parent_label) lst = ['%s%s[size=%s]' % (tab, label, self.get_size())] for item in data: if not short or item.data: lst.append(item.tostr(tab=tab + ' ', short=short)) return '\n'.join(lst) if label.startswith(parent_label): label = label[len(parent_label):] return '%s%s = %r' % (tab, label, data) __str__ = tostr def append(self, entry): assert self.record[1] == 'SUBBLOCK', repr(self.record) self.record[3].append(entry) def scaninfo(ifdentry, debug=True): """ Return LSM scan information. Parameters ---------- ifdentry : IFDEntry debug: bool Enable consistency checks. Returns ------- record : ScanInfoEntry """ if ifdentry.tag != CZ_LSMInfo_tag: return # assert ifdentry.is_lsm n = n1 = ifdentry.value['OffsetScanInformation'][0] if not n: return record = None while 1: entry, type, size = ifdentry.tiff.get_values(n, 'LONG', 3) n += 12 label, type_name = scaninfo_map.get(entry, (None, None)) if label is None: type_name = {0: 'SUBBLOCK', 2: 'ASCII', 4: 'LONG', 5: 'DOUBLE'}.get(type) if type_name is None: raise NotImplementedError(repr((hex(entry), type, size))) label = 'ENTRY%s' % (hex(entry)) # scaninfo_map[entry] = label, type_name if debug: sys.stderr.write('lsm.scaninfo: undefined %s entry %s' ' in subblock %r\n' % (type_name, hex(entry), record.record[2])) pass if type_name == 'SUBBLOCK': assert type == 0, repr((hex(entry), type, size)) if label == 'end': entry = ScanInfoEntry(entry, type_name, label, None) entry.parent = record record.append(entry) if record.parent is None: break record.parent.append(record) record = record.parent else: prev_record = record record = ScanInfoEntry(entry, type_name, label, []) record.parent = prev_record assert size == 0, repr((hex(entry), type, size)) continue if type_name == 'ASCII': assert type == 2, repr((hex(entry), type, size)) value = ifdentry.tiff.get_string(n, size - 1) elif type_name == 'LONG': assert type == 4, repr((hex(entry), type, size, scaninfo_map[entry])) value = ifdentry.tiff.get_long(n) elif type_name == 'DOUBLE': assert type == 5, repr((hex(entry), type, size)) value = ifdentry.tiff.get_double(n) else: raise NotImplementedError( repr((type_name, hex(entry), type, size))) entry = ScanInfoEntry(entry, type_name, label, value) entry.parent = record n += size record.append(entry) if debug: size = n - n1 record_size = record.get_size() assert size == record_size, repr((size, record_size)) arr = record.toarray() arr2 = ifdentry.tiff.data[n1:n1 + size] assert (arr == arr2).all() record.offset = n1 return record class TimeStamps: """ Holds LSM time stamps information. """ def __init__(self, ifdentry): self.ifdentry = ifdentry self._offset = None self._header = None self._stamps = None @property def offset(self): if self._offset is None: self._offset = self.ifdentry.value['OffsetTimeStamps'][0] return self._offset @property def header(self): if self._header is None: offset = self.offset self._header = self.ifdentry.tiff.get_value( offset, CZ_LSMTimeStamps_header_dtype) return self._header @property def stamps(self): if self._stamps is None: n = self.header['NumberTimeStamps'] self._stamps = self.ifdentry.tiff.get_values( self.offset + self.header.dtype.itemsize, numpy.float64, int(n)) return self._stamps def __str__(self): return '%s(stamps=%s)' % (self.__class__.__name__, self.stamps) def get_size(self): return 8 + self.stamps.nbytes def toarray(self, target=None): sz = self.get_size() if target is None: target = numpy.zeros((sz,), dtype=numpy.ubyte) dtype = target.dtype header = numpy.array([sz, self.stamps.size], dtype=numpy.int32).view(dtype=dtype) assert header.nbytes == 8, repr(header.nbytes) data = self.stamps.view(dtype=dtype) target[:header.nbytes] = header target[header.nbytes:header.nbytes + data.nbytes] = data return target def timestamps(ifdentry, debug=True): """ Return LSM time stamp information. """ if ifdentry.tag != CZ_LSMInfo_tag: return # assert ifdentry.is_lsm offset = ifdentry.value['OffsetTimeStamps'][0] if not offset: return None r = TimeStamps(ifdentry) if debug: arr = r.toarray() arr2 = ifdentry.tiff.data[offset:offset + arr.nbytes] assert (arr == arr2).all() return r class EventEntry: def __init__(self, entry_size, time, event_type, unknown, description): self.record = entry_size, time, event_type, unknown, description @property def time(self): return self.record[1] @property def type(self): return self.record[2] @property def type_name(self): r = {0: 'marker', 1: 'timer change', 2: 'cleach start', 3: 'bleach stop', 4: 'trigger'}.get(self.type) if r is None: r = 'EventType%s' % (self.type) return r @property def description(self): return self.record[4] def __str__(self): return 'EventEntry(time=%r, type=%r, description=%r)' % ( self.time, self.type_name, self.description) def get_size(self): return 4 + 8 + 4 + 4 + len(self.description) + 1 def toarray(self, target=None): sz = self.get_size() if target is None: target = numpy.zeros((sz,), dtype=numpy.ubyte) dtype = target.dtype target[:4].view(dtype=numpy.uint32)[0] = sz target[4:4 + 8].view(dtype=numpy.float64)[0] = self.time target[12:16].view(dtype=numpy.uint32)[0] = self.type target[16:20].view(dtype=numpy.uint32)[0] = self.record[3] ln = len(self.description) target[20:20 + ln] = numpy.array([self.description]).view(dtype=dtype) target[20 + ln] = 0 return target class EventList: """ Holds LSM event list information. """ def __init__(self, ifdentry): self.ifdentry = ifdentry self._offset = None self._header = None self._events = None @property def offset(self): if self._offset is None: self._offset = self.ifdentry.value['OffsetEventList'][0] return self._offset @property def header(self): if self._header is None: self._header = self.ifdentry.tiff.get_value( self.offset, CZ_LSMEventList_header_dtype) return self._header @property def events(self): if self._events is None: n = self.header['NumberEvents'] offset = self.offset + self.header.nbytes self._events = [] for _ in range(n): entry_size = self.ifdentry.tiff.get_value(offset, numpy.uint32) time = self.ifdentry.tiff.get_value(offset + 4, numpy.float64) event_type = self.ifdentry.tiff.get_value( offset + 4 + 8, numpy.uint32) unknown = self.ifdentry.tiff.get_value( offset + 4 + 8 + 4, numpy.uint32) descr = self.ifdentry.tiff.get_string(offset + 4 + 8 + 4 + 4) self._events.append(EventEntry( entry_size, time, event_type, unknown, descr)) offset += entry_size return self._events def __str__(self): return '%s(events=[%s])' % ( self.__class__.__name__, ','.join(map(str, self.events))) def get_size(self): s = self.header.nbytes for event in self.events: s += event.get_size() return s def toarray(self, target=None): sz = self.get_size() if target is None: target = numpy.zeros((sz, ), dtype=numpy.ubyte) dtype = target.dtype header = numpy.array([sz, len(self.events)], dtype=numpy.int32).view(dtype=dtype) target[:header.nbytes] = header offset = header.nbytes for event in self.events: event.toarray(target[offset:]) offset += event.get_size() return target def eventlist(ifdentry, debug=True): """ Return LSM event list information. """ if ifdentry.tag != CZ_LSMInfo_tag: return # assert ifdentry.is_lsm offset = ifdentry.value['OffsetEventList'][0] if not offset: return None r = EventList(ifdentry) if debug: arr = r.toarray() arr2 = ifdentry.tiff.data[offset:offset + arr.nbytes] assert (arr == arr2).all() return r class ChannelWavelength: """ Holds LSM channel wavelength information. """ def __init__(self, ifdentry): self.ifdentry = ifdentry self._offset = None self._ranges = None @property def offset(self): if self._offset is None: self._offset = self.ifdentry.value['OffsetChannelWavelength'][0] return self._offset @property def ranges(self): if self._ranges is None: self._ranges = [] offset = self.offset n = self.ifdentry.tiff.get_value(offset, numpy.int32) for i in range(n): start, end = self.ifdentry.tiff.get_values( offset + 4 + i * (8 + 8), numpy.float64, 2) self._ranges.append((start, end)) return self._ranges def __str__(self): return '%s (ranges=%s)' % (self.__class__.__name__, self.ranges) def get_size(self): return 4 + len(self.ranges) * 16 def toarray(self, target=None): sz = self.get_size() if target is None: target = numpy.zeros((sz, ), dtype=numpy.ubyte) dtype = target.dtype target[:4].view(dtype=numpy.int32)[0] = len(self.ranges) data = numpy.array(self.ranges).ravel() target[4:4 + data.nbytes] = data.view(dtype=dtype) return target def channelwavelength(ifdentry, debug=True): """ Return LSM wavelength range information. """ if ifdentry.tag != CZ_LSMInfo_tag: return # assert ifdentry.is_lsm offset = ifdentry.value['OffsetChannelWavelength'][0] if not offset: return None r = ChannelWavelength(ifdentry) if debug: arr = r.toarray() arr2 = ifdentry.tiff.data[offset:offset + arr.nbytes] assert (arr == arr2).all() return r class ChannelColors: """ Holds LSM channel name and color information. """ def __init__(self, ifdentry): self.ifdentry = ifdentry self._offset = None self._header = None self._names = None self._colors = None self._mono = None @property def offset(self): if self._offset is None: self._offset = self.ifdentry.value['OffsetChannelColors'][0] return self._offset @property def mono(self): if self._mono is None: self._mono = not not self.header[5] return self._mono @property def header(self): if self._header is None: self._header = self.ifdentry.tiff.get_values( self.offset, numpy.int32, 10) return self._header @property def names(self): if self._names is None: header = self.header n = header[2] offset = self.offset + header[4] + 4 self._names = [] for _ in range(n): name = self.ifdentry.tiff.get_string(offset) offset += len(name) + 1 + 4 self._names.append(name) return self._names @property def colors(self): if self._colors is None: header = self.header n = header[1] offset = self.offset + header[3] self._colors = [] for _ in range(n): color = self.ifdentry.tiff.get_values(offset, numpy.uint8, 4) offset += color.nbytes self._colors.append(tuple(color)) return self._colors def __str__(self): return '%s (names=%s, colors=%s)' % ( self.__class__.__name__, self.names, self.colors) def get_size(self): s = 10 * 4 for name in self.names: s += len(name) + 1 + 4 for color in self.colors: s += len(color) return s def toarray(self, target=None): sz = self.get_size() if target is None: target = numpy.zeros((sz,), dtype=numpy.ubyte) dtype = target.dtype header = numpy.array([sz, len(self.colors), len(self.names), 0, 0, self.mono, 0, 0, 0, 0], dtype=numpy.int32) names = '' for name in self.names: names += '\x04\0\0\0' + name + '\x00' noffset = sz - len(names) names = numpy.array([names]).view(dtype=dtype) colors = numpy.array(self.colors, dtype=numpy.uint8).ravel() coffset = noffset - colors.nbytes header[3] = coffset header[4] = noffset target[:header.nbytes] = header.view(dtype=dtype) target[coffset:coffset + colors.nbytes] = colors target[noffset:noffset + names.nbytes] = names return target def channelcolors(ifdentry, debug=True): """ Return LSM channel name and color information. """ if ifdentry.tag != CZ_LSMInfo_tag: return # assert ifdentry.is_lsm offset = ifdentry.value['OffsetChannelColors'][0] if not offset: return None r = ChannelColors(ifdentry) if debug: arr = r.toarray() arr2 = ifdentry.tiff.data[offset:offset + arr.nbytes] assert (arr == arr2).all() return r class ChannelFactors: def __init__(self, ifdentry): self.ifdentry = ifdentry self._offset = None self._data = None @property def offset(self): if self._offset is None: self._offset = self.ifdentry.value['OffsetChannelFactors'][0] return self._offset @property def data(self): if self._data is None: n = self.ifdentry.tiff.get_value(self.offset, numpy.int32) sz = 4 + n # n should be equal to 32*nofchannels self._data = self.ifdentry.tiff.get_values( self.offset, numpy.ubyte, sz) return self._data def __str__(self): return '%s(size=%r, offset=%r)' % ( self.__class__.__name__, self.get_size(), self.offset) def get_size(self): return self.data.nbytes def toarray(self, target=None): sz = self.get_size() if target is None: target = numpy.zeros((sz,), dtype=numpy.ubyte) dtype = target.dtype target[:self.data.nbytes] = self.data.view(dtype=dtype) return target def channelfactors(ifdentry, debug=True): if ifdentry.tag != CZ_LSMInfo_tag: return # assert ifdentry.is_lsm offset = ifdentry.value['OffsetChannelFactors'][0] if not offset: return r = ChannelFactors(ifdentry) if debug: arr = r.toarray() arr2 = ifdentry.tiff.data[offset:offset + arr.nbytes] assert (arr == arr2).all() return r class ChannelDataTypes: def __init__(self, ifdentry): self.ifdentry = ifdentry self._offset = None self._data = None @property def offset(self): if self._offset is None: self._offset = self.ifdentry.value['OffsetChannelDataTypes'][0] return self._offset @property def data(self): if self._data is None: channels = self.ifdentry.value['DimensionChannels'] sz = channels * 4 self._data = self.ifdentry.tiff.get_values( self.offset, numpy.ubyte, sz) return self._data def __str__(self): return '%s(size=%r, offset=%r)' % ( self.__class__.__name__, self.get_size(), self.offset) def get_size(self): return self.data.nbytes def toarray(self, target=None): sz = self.get_size() if target is None: target = numpy.zeros((sz, ), dtype=numpy.ubyte) dtype = target.dtype target[:self.data.nbytes] = self.data.view(dtype=dtype) return target def channeldatatypes(ifdentry, debug=True): if ifdentry.tag != CZ_LSMInfo_tag: return # assert ifdentry.is_lsm offset = ifdentry.value['OffsetChannelDataTypes'][0] if not offset: return r = ChannelDataTypes(ifdentry) if debug: arr = r.toarray() arr2 = ifdentry.tiff.data[offset:offset + arr.nbytes] assert (arr == arr2).all() return r class OffsetData: def __init__(self, ifdentry, offset_name): self.ifdentry = ifdentry self.offset_name = offset_name self._offset = None self._data = None @property def offset(self): if self._offset is None: self._offset = self.ifdentry.value[self.offset_name][0] return self._offset @property def data(self): if self._data is None: sz = self.ifdentry.tiff.get_value(self.offset, numpy.uint32) self._data = self.ifdentry.tiff.get_values( self.offset, numpy.ubyte, sz) return self._data def __str__(self): return '%s(name=%r, size=%r, offset=%r)' % ( self.__class__.__name__, self.offset_name, self.get_size(), self.offset) def get_size(self): return self.data.nbytes def toarray(self, target=None): sz = self.get_size() if target is None: target = numpy.zeros((sz, ), dtype=numpy.ubyte) dtype = target.dtype target[:self.data.nbytes] = self.data.view(dtype=dtype) return target def offsetdata(ifdentry, offset_name, debug=True): if ifdentry.tag != CZ_LSMInfo_tag: return # assert ifdentry.is_lsm offset = ifdentry.value[offset_name][0] if not offset: return r = OffsetData(ifdentry, offset_name) if debug: arr = r.toarray() arr2 = ifdentry.tiff.data[offset:offset + arr.nbytes] assert (arr == arr2).all() return r class DrawingElement: def __init__(self, ifdentry, offset_name): self.ifdentry = ifdentry self.offset_name = offset_name self._offset = None self._data = None @property def offset(self): if self._offset is None: self._offset = self.ifdentry.value[self.offset_name][0] return self._offset @property def data(self): if self._data is None: # n = self.ifdentry.tiff.get_value(self.offset, numpy.int32) sz = self.ifdentry.tiff.get_value(self.offset + 4, numpy.int32) self._data = self.ifdentry.tiff.get_values( self.offset, numpy.ubyte, sz) return self._data def __str__(self): return '%s(name=%r, size=%r, offset=%r)' % ( self.__class__.__name__, self.offset_name, self.get_size(), self.offset) def get_size(self): return self.data.nbytes def toarray(self, target=None): sz = self.get_size() if target is None: target = numpy.zeros((sz, ), dtype=numpy.ubyte) dtype = target.dtype target[:self.data.nbytes] = self.data.view(dtype=dtype) return target def drawingelement(ifdentry, offset_name, debug=True): if ifdentry.tag != CZ_LSMInfo_tag: return # assert ifdentry.is_lsm offset = ifdentry.value[offset_name][0] if not offset: return r = DrawingElement(ifdentry, offset_name) if debug: arr = r.toarray() arr2 = ifdentry.tiff.data[offset:offset + arr.nbytes] assert (arr == arr2).all() return r class LookupTable: def __init__(self, ifdentry, offset_name): self.ifdentry = ifdentry self.offset_name = offset_name self._offset = None self._data = None @property def offset(self): if self._offset is None: self._offset = self.ifdentry.value[self.offset_name][0] return self._offset @property def data(self): if self._data is None: sz = self.ifdentry.tiff.get_value(self.offset, numpy.uint32) self._data = self.ifdentry.tiff.get_values( self.offset, numpy.ubyte, sz) return self._data def __str__(self): nsubblocks = self.ifdentry.tiff.get_value(self.offset + 4, numpy.uint32) nchannels = self.ifdentry.tiff.get_value(self.offset + 8, numpy.uint32) return '%s(name=%r, size=%r, subblocks=%r, channels=%r, offset=%r)' \ % (self.__class__.__name__, self.offset_name, self.get_size(), nsubblocks, nchannels, self.offset) def get_size(self): return self.data.nbytes def toarray(self, target=None): sz = self.get_size() if target is None: target = numpy.zeros((sz, ), dtype=numpy.ubyte) dtype = target.dtype target[:self.data.nbytes] = self.data.view(dtype=dtype) return target def lookuptable(ifdentry, offset_name, debug=True): if ifdentry.tag != CZ_LSMInfo_tag: return offset = ifdentry.value[offset_name][0] if not offset: return r = LookupTable(ifdentry, offset_name) if debug: arr = r.toarray() arr2 = ifdentry.tiff.data[offset:offset + arr.nbytes] assert (arr == arr2).all() return r def inputlut(ifdentry, debug=True): return lookuptable( ifdentry, 'OffsetInputLut', debug=debug) def outputlut(ifdentry, debug=True): return lookuptable( ifdentry, 'OffsetOutputLut', debug=debug) def vectoroverlay(ifdentry, debug=True): return drawingelement( ifdentry, 'OffsetVectorOverlay', debug=debug) def roi(ifdentry, debug=True): return drawingelement( ifdentry, 'OffsetRoi', debug=debug) def bleachroi(ifdentry, debug=True): return drawingelement( ifdentry, 'OffsetBleachRoi', debug=debug) def meanofroisoverlay(ifdentry, debug=True): return drawingelement( ifdentry, 'OffsetMeanOfRoisOverlay', debug=debug) def topoisolineoverlay(ifdentry, debug=True): return drawingelement( ifdentry, 'OffsetTopoIsolineOverlay', debug=debug) def topoprofileoverlay(ifdentry, debug=True): return drawingelement( ifdentry, 'OffsetTopoProfileOverlay', debug=debug) def linescanoverlay(ifdentry, debug=True): return drawingelement( ifdentry, 'OffsetLinescanOverlay', debug=debug) CZ_LSMOffsetField_readers = dict( OffsetChannelWavelength=channelwavelength, OffsetTimeStamps=timestamps, OffsetEventList=eventlist, OffsetChannelColors=channelcolors, OffsetScanInformation=scaninfo, OffsetInputLut=inputlut, OffsetOutputLut=outputlut, OffsetChannelFactors=channelfactors, OffsetChannelDataTypes=channeldatatypes, OffsetUnmixParameters=lambda ifdentry, debug=True: offsetdata( ifdentry, 'OffsetUnmixParameters', debug=debug), OffsetNextRecording=lambda ifdentry, debug=True: offsetdata( ifdentry, 'OffsetNextRecording', debug=debug), OffsetKsData=lambda ifdentry, debug=True: offsetdata( ifdentry, 'OffsetKsData', debug=debug), OffsetVectorOverlay=vectoroverlay, OffsetRoi=roi, OffsetBleachRoi=bleachroi, OffsetMeanOfRoisOverlay=meanofroisoverlay, OffsetTopoIsolineOverlay=topoisolineoverlay, OffsetTopoProfileOverlay=topoprofileoverlay, OffsetLinescanOverlay=linescanoverlay, ) CZ_LSMInfo_dtype_fields = [ ('MagicNumber', numpy.uint32), ('StructureSize', numpy.int32), ('DimensionX', numpy.int32), ('DimensionY', numpy.int32), ('DimensionZ', numpy.int32), ('DimensionChannels', numpy.int32), ('DimensionTime', numpy.int32), # 1: uint8, 2: uint12, 5: float32, 0: see OffsetChannelDataTypes: ('SDataType', numpy.int32), ('ThumbnailX', numpy.int32), ('ThumbnailY', numpy.int32), ('VoxelSizeX', numpy.float64), ('VoxelSizeY', numpy.float64), ('VoxelSizeZ', numpy.float64), ('OriginX', numpy.float64), ('OriginY', numpy.float64), ('OriginZ', numpy.float64), ('ScanType', numpy.uint16), # 0:xyz-scan, 1:z-scan, 2:line-scan 3:time xy, # 4: time xz (ver>=2.0), 5: time mean roi (ver>=2.0), # 6: time xyz (ver>=2.3), 7: spline scan (ver>=2.5), # 8: spline plane xz (ver>=2.5) # 9: time spline plane xz (ver>=2.5) # 10: point mode (ver>=3.0) ('SpectralScan', numpy.uint16), # 0:off, 1:on (ver>=3.0) ('DataType', numpy.uint32), # 0: original, 1: calculated, 2: animation ('OffsetVectorOverlay', numpy.uint32), ('OffsetInputLut', numpy.uint32), ('OffsetOutputLut', numpy.uint32), ('OffsetChannelColors', numpy.uint32), ('TimeInterval', numpy.float64), ('OffsetChannelDataTypes', numpy.uint32), ('OffsetScanInformation', numpy.uint32), ('OffsetKsData', numpy.uint32), ('OffsetTimeStamps', numpy.uint32), ('OffsetEventList', numpy.uint32), ('OffsetRoi', numpy.uint32), ('OffsetBleachRoi', numpy.uint32), ('OffsetNextRecording', numpy.uint32), ('DisplayAspectX', numpy.float64), ('DisplayAspectY', numpy.float64), ('DisplayAspectZ', numpy.float64), ('DisplayAspectTime', numpy.float64), ('OffsetMeanOfRoisOverlay', numpy.uint32), ('OffsetTopoIsolineOverlay', numpy.uint32), ('OffsetTopoProfileOverlay', numpy.uint32), ('OffsetLinescanOverlay', numpy.uint32), ('ToolbarFlags', numpy.uint32), ('OffsetChannelWavelength', numpy.uint32), ('OffsetChannelFactors', numpy.uint32), ('ObjectiveSphereCorrection', numpy.float64), ('OffsetUnmixParameters', numpy.uint32), # ('Reserved', numpy.dtype('(69,)u4')), # depends on the version of LSM file ] CZ_LSMInfo_dtype_fields_size = 0 for item in CZ_LSMInfo_dtype_fields: CZ_LSMInfo_dtype_fields_size += item[1]().itemsize CZ_LSMTimeStamps_header_dtype = numpy.dtype([ ('Size', numpy.int32), ('NumberTimeStamps', numpy.int32), # ('TimeStamp', numpy.float64) ]) CZ_LSMEventList_header_dtype = numpy.dtype([ ('Size', numpy.int32), ('NumberEvents', numpy.int32), # ('Event', EventListEntry) ]) scaninfo_map = { 0x0ffffffff: ('end', 'SUBBLOCK'), 0x010000000: ('recording', 'SUBBLOCK'), 0x010000001: ('recording name', 'ASCII'), 0x010000002: ('recording description', 'ASCII'), 0x010000003: ('recording notes', 'ASCII'), 0x010000004: ('recording objective', 'ASCII'), 0x010000005: ('recording processing summary', 'ASCII'), 0x010000006: ('recording special scan mode', 'ASCII'), 0x010000007: ('recording scan type', 'ASCII'), 0x010000008: ('recording scan mode', 'ASCII'), 0x010000009: ('recording number of stacks', 'LONG'), 0x01000000a: ('recording lines per plane', 'LONG'), 0x01000000b: ('recording samples per line', 'LONG'), 0x01000000c: ('recording planes per volume', 'LONG'), 0x01000000d: ('recording images width', 'LONG'), 0x01000000e: ('recording images height', 'LONG'), 0x01000000f: ('recording images number planes', 'LONG'), 0x010000010: ('recording images number stacks', 'LONG'), 0x010000011: ('recording images number channels', 'LONG'), 0x010000012: ('recording linescan xy size', 'LONG'), 0x010000013: ('recording scan direction', 'LONG'), 0x010000014: ('recording time series', 'LONG'), 0x010000015: ('recording original scan data', 'LONG'), 0x010000016: ('recording zoom x', 'DOUBLE'), 0x010000017: ('recording zoom y', 'DOUBLE'), 0x010000018: ('recording zoom z', 'DOUBLE'), 0x010000019: ('recording sample 0x', 'DOUBLE'), 0x01000001a: ('recording sample 0y', 'DOUBLE'), 0x01000001b: ('recording sample 0z', 'DOUBLE'), 0x01000001c: ('recording sample spacing', 'DOUBLE'), 0x01000001d: ('recording line spacing', 'DOUBLE'), 0x01000001e: ('recording plane spacing', 'DOUBLE'), 0x01000001f: ('recording plane width', 'DOUBLE'), 0x010000020: ('recording plane height', 'DOUBLE'), 0x010000021: ('recording volume depth', 'DOUBLE'), # 0x010000022: ('recording ', ''), 0x010000023: ('recording nutation', 'DOUBLE'), # 0x010000024: ('recording ', ''), # 0x010000025: ('recording ', ''), # 0x010000026: ('recording ', ''), # 0x010000027: ('recording ', ''), # 0x010000028: ('recording ', ''), # 0x010000029: ('recording ', ''), # 0x01000002a: ('recording ', ''), # 0x01000002b: ('recording ', ''), # 0x01000002c: ('recording ', ''), # 0x01000002d: ('recording ', ''), # 0x01000002e: ('recording ', ''), # 0x01000002f: ('recording ', ''), # 0x010000030: ('recording ', ''), # 0x010000031: ('recording ', ''), # 0x010000032: ('recording ', ''), # 0x010000033: ('recording ', ''), 0x010000034: ('recording rotation', 'DOUBLE'), 0x010000035: ('recording precession', 'DOUBLE'), 0x010000036: ('recording sample 0time', 'DOUBLE'), 0x010000037: ('recording start scan trigger in', 'ASCII'), 0x010000038: ('recording start scan trigger out', 'ASCII'), 0x010000039: ('recording start scan event', 'LONG'), # 0x01000003a: ('recording ', ''), # 0x01000003b: ('recording ', ''), # 0x01000003c: ('recording ', ''), # 0x01000003d: ('recording ', ''), # 0x01000003e: ('recording ', ''), # 0x01000003f: ('recording ', ''), 0x010000040: ('recording start scan time', 'DOUBLE'), 0x010000041: ('recording stop scan trigger in', 'ASCII'), 0x010000042: ('recording stop scan trigger out', 'ASCII'), 0x010000043: ('recording stop scan event', 'LONG'), 0x010000044: ('recording start scan time', 'DOUBLE'), 0x010000045: ('recording use rois', 'LONG'), 0x010000046: ('recording use reduced memory rois', 'LONG'), 0x010000047: ('recording user', 'ASCII'), 0x010000048: ('recording usebccorrection', 'LONG'), 0x010000049: ('recording positionbccorrection1', 'DOUBLE'), # 0x01000004a: ('recording ', ''), # 0x01000004b: ('recording ', ''), # 0x01000004c: ('recording ', ''), # 0x01000004d: ('recording ', ''), # 0x01000004e: ('recording ', ''), # 0x01000004f: ('recording ', ''), 0x010000050: ('recording positionbccorrection2', 'DOUBLE'), 0x010000051: ('recording interpolationy', 'LONG'), 0x010000052: ('recording camera binning', 'LONG'), 0x010000053: ('recording camera supersampling', 'LONG'), 0x010000054: ('recording camera frame width', 'LONG'), 0x010000055: ('recording camera frame height', 'LONG'), 0x010000056: ('recording camera offsetx', 'DOUBLE'), 0x010000057: ('recording camera offsety', 'DOUBLE'), # 0x010000058: ('recording ', ''), 0x010000059: ('recording rt binning', 'LONG'), 0x01000005a: ('recording rt frame width', 'LONG'), 0x01000005b: ('recording rt frame height', 'LONG'), 0x01000005c: ('recording rt region width', 'LONG'), 0x01000005d: ('recording rt region height', 'LONG'), 0x01000005e: ('recording rt offsetx', 'DOUBLE'), 0x01000005f: ('recording rt offsety', 'DOUBLE'), 0x010000060: ('recording rt zoom', 'DOUBLE'), 0x010000061: ('recording rt lineperiod', 'DOUBLE'), 0x010000062: ('recording prescan', 'LONG'), 0x010000063: ('recording scan directionz', 'LONG'), # 0x010000064: ('recording ', 'LONG'), 0x030000000: ('lasers', 'SUBBLOCK'), 0x050000000: ('laser', 'SUBBLOCK'), 0x050000001: ('laser name', 'ASCII'), 0x050000002: ('laser acquire', 'LONG'), 0x050000003: ('laser power', 'DOUBLE'), 0x020000000: ('tracks', 'SUBBLOCK'), 0x040000000: ('track', 'SUBBLOCK'), 0x040000001: ('track multiplex type', 'LONG'), 0x040000002: ('track multiplex order', 'LONG'), 0x040000003: ('track sampling mode', 'LONG'), 0x040000004: ('track sampling method', 'LONG'), 0x040000005: ('track sampling number', 'LONG'), 0x040000006: ('track acquire', 'LONG'), 0x040000007: ('track sample observation time', 'DOUBLE'), # 0x04000000b: ('track time between stacks', 'DOUBLE'), 0x04000000c: ('track name', 'ASCII'), 0x04000000d: ('track collimator1 name', 'ASCII'), 0x04000000e: ('track collimator1 position', 'LONG'), 0x04000000f: ('track collimator2 name', 'ASCII'), 0x040000010: ('track collimator2 position', 'LONG'), 0x040000011: ('track is bleach track', 'LONG'), 0x040000012: ('track is bleach after scan number', 'LONG'), 0x040000013: ('track bleach scan number', 'LONG'), 0x040000014: ('track trigger in', 'ASCII'), 0x040000015: ('track trigger out', 'ASCII'), 0x040000016: ('track is ratio track', 'LONG'), 0x040000017: ('track bleach count', 'LONG'), 0x040000018: ('track spi center wavelength', 'DOUBLE'), 0x040000019: ('track pixel time', 'DOUBLE'), 0x040000020: ('track id condensor frontlens', 'ASCII'), 0x040000021: ('track condensor frontlens', 'LONG'), 0x040000022: ('track id field stop', 'ASCII'), 0x040000023: ('track field stop value', 'DOUBLE'), 0x040000024: ('track id condensor aperture', 'ASCII'), 0x040000025: ('track condensor aperture', 'DOUBLE'), 0x040000026: ('track id condensor revolver', 'ASCII'), 0x040000027: ('track condensor filter', 'ASCII'), 0x040000028: ('track id transmission filter1', 'ASCII'), 0x040000029: ('track id transmission1', 'DOUBLE'), 0x040000030: ('track id transmission filter2', 'ASCII'), 0x040000031: ('track if transmission2', 'DOUBLE'), 0x040000032: ('track repeat bleach', 'LONG'), 0x040000033: ('track enable spot bleach pos', 'LONG'), 0x040000034: ('track spot bleach posx', 'DOUBLE'), 0x040000035: ('track spot bleach posy', 'DOUBLE'), 0x040000036: ('track bleach position z', 'DOUBLE'), 0x040000037: ('track id tubelens', 'ASCII'), 0x040000038: ('track id tubelens position', 'ASCII'), 0x040000039: ('track transmitted light', 'DOUBLE'), 0x04000003a: ('track reflected light', 'DOUBLE'), 0x04000003b: ('track simultan grab and bleach', 'LONG'), 0x04000003c: ('track bleach pixel time', 'DOUBLE'), 0x060000000: ('detection channels', 'SUBBLOCK'), 0x070000000: ('detection channel', 'SUBBLOCK'), 0x070000001: ('detection channel integration mode', 'LONG'), 0x070000002: ('detection channel special mode', 'LONG'), 0x070000003: ('detection channel detector gain first', 'DOUBLE'), 0x070000004: ('detection channel detector gain last', 'DOUBLE'), 0x070000005: ('detection channel amplifier gain first', 'DOUBLE'), 0x070000006: ('detection channel amplifier gain last', 'DOUBLE'), 0x070000007: ('detection channel amplifier offs first', 'DOUBLE'), 0x070000008: ('detection channel amplifier offs last', 'DOUBLE'), 0x070000009: ('detection channel pinhole diameter', 'DOUBLE'), 0x07000000a: ('detection channel counting trigger', 'DOUBLE'), 0x07000000b: ('detection channel acquire', 'LONG'), 0x07000000c: ('detection channel detector name', 'ASCII'), 0x07000000d: ('detection channel amplifier name', 'ASCII'), 0x07000000e: ('detection channel pinhole name', 'ASCII'), 0x07000000f: ('detection channel filter set name', 'ASCII'), 0x070000010: ('detection channel filter name', 'ASCII'), 0x070000013: ('detection channel integrator name', 'ASCII'), 0x070000014: ('detection channel detection channel name', 'ASCII'), 0x070000015: ('detection channel detector gain bc1', 'DOUBLE'), 0x070000016: ('detection channel detector gain bc2', 'DOUBLE'), 0x070000017: ('detection channel amplifier gain bc1', 'DOUBLE'), 0x070000018: ('detection channel amplifier gain bc2', 'DOUBLE'), 0x070000019: ('detection channel amplifier offs bc1', 'DOUBLE'), 0x070000020: ('detection channel amplifier offs bc2', 'DOUBLE'), 0x070000021: ('detection channel spectral scan channels', 'LONG'), 0x070000022: ('detection channel spi wavelength start', 'DOUBLE'), 0x070000023: ('detection channel spi wavelength end', 'DOUBLE'), 0x070000026: ('detection channel dye name', 'ASCII'), 0x070000027: ('detection channel dye folder', 'ASCII'), 0x080000000: ('illumination channels', 'SUBBLOCK'), 0x090000000: ('illumination channel', 'SUBBLOCK'), 0x090000001: ('illumination channel name', 'ASCII'), 0x090000002: ('illumination channel power', 'DOUBLE'), 0x090000003: ('illumination channel wavelength', 'DOUBLE'), 0x090000004: ('illumination channel aquire', 'LONG'), 0x090000005: ('illumination channel detection channel name', 'ASCII'), 0x090000006: ('illumination channel power bc1', 'DOUBLE'), 0x090000007: ('illumination channel power bc2', 'DOUBLE'), 0x0A0000000: ('beam splitters', 'SUBBLOCK'), 0x0B0000000: ('beam splitter', 'SUBBLOCK'), 0x0B0000001: ('beam splitter filter set', 'ASCII'), 0x0B0000002: ('beam splitter filter', 'ASCII'), 0x0B0000003: ('beam splitter name', 'ASCII'), 0x0C0000000: ('data channels', 'SUBBLOCK'), 0x0D0000000: ('data channel', 'SUBBLOCK'), 0x0D0000001: ('data channel name', 'ASCII'), # 0x0D0000002: ('data channel', ''), 0x0D0000003: ('data channel acquire', 'LONG'), 0x0D0000004: ('data channel color', 'LONG'), 0x0D0000005: ('data channel sampletype', 'LONG'), 0x0D0000006: ('data channel bitspersample', 'LONG'), 0x0D0000007: ('data channel ratio type', 'LONG'), 0x0D0000008: ('data channel ratio track1', 'LONG'), 0x0D0000009: ('data channel ratio track2', 'LONG'), 0x0D000000a: ('data channel ratio channel1', 'ASCII'), 0x0D000000b: ('data channel ratio channel2', 'ASCII'), 0x0D000000c: ('data channel ratio const1', 'DOUBLE'), 0x0D000000d: ('data channel ratio const2', 'DOUBLE'), 0x0D000000e: ('data channel ratio const3', 'DOUBLE'), 0x0D000000f: ('data channel ratio const4', 'DOUBLE'), 0x0D0000010: ('data channel ratio const5', 'DOUBLE'), 0x0D0000011: ('data channel ratio const6', 'DOUBLE'), 0x0D0000012: ('data channel ratio first images1', 'LONG'), 0x0D0000013: ('data channel ratio first images2', 'LONG'), 0x0D0000014: ('data channel dye name', 'ASCII'), 0x0D0000015: ('data channel dye folder', 'ASCII'), 0x0D0000016: ('data channel spectrum', 'ASCII'), 0x0D0000017: ('data channel acquire', 'LONG'), 0x011000000: ('timers', 'SUBBLOCK'), 0x012000000: ('timer', 'SUBBLOCK'), 0x012000001: ('timer name', 'ASCII'), 0x012000003: ('timer interval', 'DOUBLE'), 0x012000004: ('timer trigger in', 'ASCII'), 0x012000005: ('timer trigger out', 'ASCII'), 0x012000006: ('timer activation time', 'DOUBLE'), 0x012000007: ('timer activation number', 'LONG'), 0x013000000: ('markers', 'SUBBLOCK'), 0x014000000: ('marker', 'SUBBLOCK'), 0x014000001: ('marker name', 'ASCII'), 0x014000002: ('marker description', 'ASCII'), 0x014000003: ('marker trigger in', 'ASCII'), 0x014000004: ('marker trigger out', 'ASCII'), } pylibtiff-0.6.1/libtiff/lzw.py000066400000000000000000000174651450302046000163220ustar00rootroot00000000000000""" Encoder and decoder of Lempel-Ziv-Welch algorithm for TIFF. This module is obsolete, use tif_lzw extension module instead. """ # Author: Pearu Peterson # Created: May 2010 import numpy default_backend = 'bittools' # default_backend='bittools' if default_backend == 'bitarray': from bitarray import bitarray if default_backend == 'bittools': from bittools import setword, getword CODECLEAR = 256 CODEEOI = 257 CODESTART = 258 def encode_bitarray(seq, max_bits=12): """ Compress sequence using Lempel-Ziv-Welch algorithm for TIFF. Parameters ---------- seq : {str, numpy.ndarray} max_bits : int Specify maximum bits for encoding table. Returns ------- bseq : bitarray See also -------- decode_bitarray """ if isinstance(seq, numpy.ndarray): seq = seq.tostring() r = bitarray(0, endian='little') write = r.fromword init_table = [(chr(code), code) for code in range(256)] table = {} table_get = table.get table_clear = table.clear table_update = table.update sup_code2 = (1 << max_bits) - 2 next_code = CODESTART bits = 9 max_code = (1 << bits) s = '' table_update(init_table) write(CODECLEAR, bits) for c in seq: s1 = s + c if s1 in table: s = s1 else: write(table_get(s), bits) table[s1] = next_code next_code += 1 s = c if next_code == sup_code2: write(table_get(s), bits) write(CODECLEAR, bits) s = '' table_clear() table_update(init_table) next_code = CODESTART bits = 9 max_code = (1 << bits) elif next_code == max_code: bits += 1 max_code = (1 << bits) if s: write(table_get(s), bits) write(CODEEOI, bits) return r def encode_bittools(seq, max_bits=12): """ Compress sequence using Lempel-Ziv-Welch algorithm for TIFF. Parameters ---------- seq : {str, numpy.ndarray} max_bits : int Specify maximum bits for encoding table. Returns ------- bseq : numpy.ndarray See also -------- decode_bittools """ if isinstance(seq, numpy.ndarray): nbytes = seq.nbytes * 2 seq = seq.tostring() else: nbytes = len(seq) * 2 r = numpy.zeros((nbytes,), dtype=numpy.ubyte) init_table = [(chr(code), code) for code in range(256)] table = {} table_get = table.get table_clear = table.clear table_update = table.update sup_code2 = (1 << max_bits) - 2 next_code = CODESTART bits = 9 max_code = (1 << bits) s = '' table_update(init_table) index = setword(r, 0, bits, CODECLEAR, 1) for c in seq: s1 = s + c if s1 in table: s = s1 else: index = setword(r, index, bits, table_get(s), 1) table[s1] = next_code next_code += 1 s = c if next_code == sup_code2: index = setword(r, index, bits, table_get(s), 1) index = setword(r, index, bits, CODECLEAR, 1) s = '' table_clear() table_update(init_table) next_code = CODESTART bits = 9 max_code = (1 << bits) elif next_code == max_code: bits += 1 max_code = (1 << bits) if s: index = setword(r, index, bits, table_get(s), 1) index = setword(r, index, bits, CODEEOI) bytes = index // 8 if 8 * bytes < index: bytes += 1 return r[:bytes] def decode_bitarray(bseq): """ Decompress Lempel-Ziv-Welch encoded sequence. Parameters ---------- bseq : {bitarray, numpy.ndarray} Returns ------- seq : str See also -------- encode_bitarray """ if isinstance(bseq, numpy.ndarray): bseq = bitarray(bseq, endian='little') assert bseq.endian() == 'little', repr(bseq.endian()) read = bseq.toword table = [chr(code) for code in range(256)] + ['CODECLEAR', 'CODEEOI'] table_append = table.append table_len = table.__len__ bits = 9 max_code2 = (1 << bits) - 2 i = 0 seq = [] seq_append = seq.append while True: code = read(i, bits) i += bits if code == CODEEOI: break elif code == CODECLEAR: del table[CODESTART:] bits = 9 max_code2 = (1 << bits) - 2 code = read(i, bits) i += bits old_str = table[code] seq_append(old_str) else: ln = table_len() if code < ln: s = table[code] table_append(old_str + s[0]) old_str = s else: old_str = old_str + old_str[0] table_append(old_str) seq_append(old_str) if ln == max_code2: bits += 1 max_code2 = (1 << bits) - 2 return ''.join(seq) def decode_bittools(bseq): """ Decompress Lempel-Ziv-Welch encoded sequence. Parameters ---------- bseq : numpy.ndarray Returns ------- seq : str See also -------- encode_bittools """ table = [chr(code) for code in range(256)] + ['CODECLEAR', 'CODEEOI'] table_append = table.append table_len = table.__len__ bits = 9 max_code2 = (1 << bits) - 2 i = 0 seq = [] seq_append = seq.append while True: code, i = getword(bseq, i, bits) if code == CODEEOI: break elif code == CODECLEAR: del table[CODESTART:] bits = 9 max_code2 = (1 << bits) - 2 code, i = getword(bseq, i, bits) old_str = table[code] seq_append(old_str) else: ln = table_len() if code < ln: s = table[code] table_append(old_str + s[0]) old_str = s else: old_str = old_str + old_str[0] table_append(old_str) seq_append(old_str) if ln == max_code2: bits += 1 max_code2 = (1 << bits) - 2 return ''.join(seq) # print 'backend:', default_backend if default_backend == 'bitarray': encode = encode_bitarray decode = decode_bitarray def encode_array(arr): return encode_bitarray(arr).toarray() if default_backend == 'bittools': encode = encode_array = encode_bittools # noqa: F811 decode = decode_bittools def test_lzw(): for s in ['TOBEORNOTTOBEORTOBEORNOT', '/WED/WE/WEE/WEB/WET'][:0]: r = encode(s) a = decode(r) assert a == s, repr((a, s)) if 1: f = open(__file__) s = f.read() f.close() r = encode(s) a = decode(r) assert a == s print('ok') import sys import os import time for fn in sys.argv[1:]: if not os.path.exists(fn): continue t0 = time.time() f = open(fn, 'rb') s = f.read() f.close() t = time.time() - t0 print('Reading %s took %.3f seconds, bytes = %s' % (fn, t, len(s))) t0 = time.time() r = encode(s) t = time.time() - t0 sz = len(r) if default_backend == 'bitarray': sz //= 8 print('Encoding took %.3f seconds, compress ratio = %.3f,' ' Kbytes per second = %.3f' % (t, len(s) / sz, len(s) / t / 1024)) t0 = time.time() s1 = decode(r) t = time.time() - t0 print('Decoding took %.3f seconds, Kbytes per second = %.3f' % (t, (sz / t) / 1024)) assert s1 == s if __name__ == '__main__': test_lzw() pylibtiff-0.6.1/libtiff/script_options.py000066400000000000000000000072141450302046000205540ustar00rootroot00000000000000__all__ = ['set_formatter', 'set_info_options', 'set_convert_options'] from optparse import NO_DEFAULT from optparse import TitledHelpFormatter class MyHelpFormatter(TitledHelpFormatter): def format_option(self, option): old_help = option.help default = option.default if isinstance(default, str) and ' ' in default: default = repr(default) if option.help is None: option.help = 'Specify a %s.' % (option.type) if option.type == 'choice': choices = [] for choice in option.choices: if choice == option.default: if ' ' in choice: choice = repr(choice) choice = '[' + choice + ']' else: if ' ' in choice: choice = repr(choice) choices.append(choice) option.help = '%s Choices: %s.' % (option.help, ', '.join(choices)) else: if default != NO_DEFAULT: if option.action == 'store_false': option.help = '%s Default: %s.' % ( option.help, not default) else: option.help = '%s Default: %s.' % ( option.help, default) result = TitledHelpFormatter.format_option(self, option) option.help = old_help return result help_formatter = MyHelpFormatter() def set_formatter(parser): """Set customized help formatter. """ parser.formatter = help_formatter def set_convert_options(parser): set_formatter(parser) parser.set_usage('%prog [options] -i INPUTPATH [-o OUTPUTPATH]') parser.set_description('Convert INPUTPATH to OUTPUTPATH.') parser.add_option('--input-path', '-i', type=str, metavar='INPUTPATH', help='Specify INPUTPATH.') parser.add_option('--output-path', '-o', type=str, metavar='OUTPUTPATH', help='Specify OUTPUTPATH.') parser.add_option('--compression', type='choice', default='none', choices=['none', 'lzw'], help='Specify compression.') parser.add_option( '--slice', type='string', help=('Specify slice using form ":,' ':,:"') ) def set_info_options(parser): set_formatter(parser) parser.set_usage('%prog [options] -i INPUTPATH') parser.set_description('Show INPUTPATHs information.') parser.add_option('--input-path', '-i', type=str, metavar='INPUTPATH', help='Specify INPUTPATH.') parser.add_option('--memory-usage', action='store_true', default=False, help='Show TIFF file memory usage.') parser.add_option('--no-memory-usage', dest='memory_usage', action='store_false', help='See --memory-usage.') parser.add_option('--ifd', action='store_true', default=False, help=('Show all TIFF file image file directory.' ' By default, only the first IFD is shown.')) parser.add_option('--no-ifd', dest='ifd', action='store_false', help='See --ifd.') parser.add_option('--human', action='store_true', default=False, help='Show human readable values') parser.add_option('--no-human', dest='human', action='store_false', help='See --human.') pylibtiff-0.6.1/libtiff/scripts/000077500000000000000000000000001450302046000166065ustar00rootroot00000000000000pylibtiff-0.6.1/libtiff/scripts/__init__.py000066400000000000000000000000001450302046000207050ustar00rootroot00000000000000pylibtiff-0.6.1/libtiff/scripts/convert.py000066400000000000000000000070451450302046000206460ustar00rootroot00000000000000#!/usr/bin/env python # -*- python -*- # Author: Pearu Peterson # Created: May 2010 # flake8: noqa import os description_template = ''' DimensionX: %(DimensionX)s DimensionY: %(DimensionY)s DimensionZ: %(DimensionZ)s VoxelSizeX: %(VoxelSizeX)s VoxelSizeY: %(VoxelSizeY)s VoxelSizeZ: %(VoxelSizeZ)s NofStacks: 1 RotationAngle: %(RotationAngle)s PixelTime: %(PixelTime)s ENTRY_OBJECTIVE: %(Objective)s Objective: %(Objective)s ExcitationWavelength: %(ExcitationWavelength)s MicroscopeType: %(MicroscopeType)s ChannelName: %(ChannelName)s OriginalFile: %(OriginalFile)s ''' def runner(parser, options, args): if not hasattr(parser, 'runner'): options.output_path = None assert not args, repr(args) if options.input_path is None: parser.error('Expected --input-path but got nothing') input_path = options.input_path output_path = options.output_path if output_path is None: b, e = os.path.splitext(input_path) b = os.path.basename(b) output_path = b + '_%(channel_name)s_%(slice)s.tif' from libtiff.tiff import TIFFfile, TIFFimage tiff = TIFFfile(input_path) samples, sample_names = tiff.get_samples() description = [] for ifd in tiff.IFD: s = ifd.get('ImageDescription') if s is not None: description.append(s.value.tostring()) init_description = '\n'.join(description) samples_list, names_list = tiff.get_samples() while samples_list: samples = samples_list.pop() if options.slice is not None: exec('samples = samples[%s]' % (options.slice)) name = names_list.pop() if tiff.is_lsm: voxel_sizes = [tiff.lsmentry[ 'VoxelSize' + x][0] for x in 'XYZ'] # m # us, integration is >=70% of # the pixel time pixel_time = tiff.lsminfo.get( 'track pixel time')[0] rotation = tiff.lsminfo.get('recording rotation')[0] # deg objective = tiff.lsminfo.get( 'recording objective')[0] # objective description excitation_wavelength = tiff.lsminfo.get( 'illumination channel wavelength')[0] # nm description = description_template % (dict( DimensionX=samples.shape[2], DimensionY=samples.shape[1], DimensionZ=samples.shape[0], VoxelSizeX=voxel_sizes[0], VoxelSizeY=voxel_sizes[1], VoxelSizeZ=voxel_sizes[2], RotationAngle=rotation, PixelTime=pixel_time, Objective=objective, MicroscopeType='confocal', OriginalFile=os.path.abspath(input_path), ExcitationWavelength=excitation_wavelength, ChannelName=name, )) + init_description description += '\n'+tiff.lsminfo.tostr(short=True) else: description = init_description tif = TIFFimage(samples, description=description) fn = output_path % dict(channel_name=name, slice=options.slice) tif.write_file(fn, compression=getattr(options, 'compression', 'none')) return def main(): from optparse import OptionParser from libtiff.script_options import set_convert_options parser = OptionParser() set_convert_options(parser) if hasattr(parser, 'runner'): parser.runner = runner options, args = parser.parse_args() runner(parser, options, args) return if __name__ == '__main__': main() pylibtiff-0.6.1/libtiff/scripts/info.py000066400000000000000000000044661450302046000201250ustar00rootroot00000000000000#!/usr/bin/env python # -*- python-mode -*- # Author: Pearu Peterson # Created: May 2010 # flake8: noqa import os # noqa: F401 def runner(parser, options, args): if not hasattr(parser, 'runner'): options.output_path = None assert not args, repr(args) if options.input_path is None: parser.error('Expected --input-path but got nothing') input_path = options.input_path import libtiff tiff = libtiff.tiff.TIFFfile(input_path) if options.memory_usage: print('Memory usage:') print('-------------') tiff.check_memory_usage() human = options.get(human=False) print(human) ifd0 = tiff.IFD[0] if options.ifd: for i, ifd in enumerate(tiff.IFD): print('IFD%s:' % (i)) print('------') if human: print(ifd.human()) else: print(ifd) else: if human: print(ifd0.human()) else: print(ifd0) if len(tiff.IFD) > 1: print('Use --ifd to see the rest of %s IFD entries' % (len(tiff.IFD)-1)) print('data is contiguous:', tiff.is_contiguous()) if tiff.check_memory_usage(verbose=False): print('memory usage is ok') else: print('memory usage has inconsistencies:') print('-----') tiff.check_memory_usage(verbose=True) print('-----') for subfile_type in tiff.get_subfile_types(): ifd0 = tiff.get_first_ifd(subfile_type=subfile_type) for sample_index, n in enumerate(ifd0.get_sample_names()): print('Sample %s in subfile %s:' % (sample_index, subfile_type)) arr = tiff.get_tiff_array(sample_index=sample_index, subfile_type=subfile_type) print(' shape=', arr.shape) print(' dtype=', arr.dtype) print(' pixel_sizes=', arr.get_pixel_sizes()) def main(): from optparse import OptionParser from libtiff.script_options import set_info_options from libtiff.utils import Options parser = OptionParser() set_info_options(parser) if hasattr(parser, 'runner'): parser.runner = runner options, args = parser.parse_args() runner(parser, Options(options), args) return if __name__ == '__main__': main() pylibtiff-0.6.1/libtiff/src/000077500000000000000000000000001450302046000157065ustar00rootroot00000000000000pylibtiff-0.6.1/libtiff/src/bittools.c000066400000000000000000000143351450302046000177170ustar00rootroot00000000000000#include #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION #define PY_ARRAY_UNIQUE_SYMBOL bittools_PyArray_API #include "numpy/arrayobject.h" #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif #if PY_MAJOR_VERSION >= 3 #define IS_PY3K #endif #define CHAR_BITS 8 #define CHAR_BITS_EXP 3 /* i/8 == i>>3 */ #define BITS(bytes) (((unsigned long)(bytes)) << CHAR_BITS_EXP) #define BYTES(bits) (((bits) == 0) ? 0 : ((((bits) - 1) >> CHAR_BITS_EXP) + 1)) #define NBYTES(bits) ((bits) >> CHAR_BITS_EXP) #define BITMASK(i,width) (((unsigned long) 1) << (((i))%(width))) #define DATAPTR(data, i) ((char*)(data) + ((i)>>CHAR_BITS_EXP)) #define DATA(data, i) (*DATAPTR((data),(i))) #define GETBIT(value, i, width) ((value & BITMASK((i),(width))) ? 1 : 0) #define ARRGETBIT(arr, i) ((DATA(PyArray_DATA((PyArrayObject*)arr), (i)) & BITMASK((i),CHAR_BITS)) ? 1 : 0) static PyObject *getbit(PyObject *self, PyObject *args, PyObject *kwds) { PyObject* arr = NULL; char bit = 0; Py_ssize_t index = 0; static char* kwlist[] = {"array", "index", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|On:getbit", kwlist, &arr, &index)) return NULL; if (!PyArray_Check(arr)) { PyErr_SetString(PyExc_TypeError,"first argument must be array object"); return NULL; } if (index >= BITS(PyArray_NBYTES((PyArrayObject*)arr))) { PyErr_SetString(PyExc_IndexError,"bit index out of range"); return NULL; } bit = ARRGETBIT(arr, index); return Py_BuildValue("b",bit); } static PyObject *setbit(PyObject *self, PyObject *args, PyObject *kwds) { PyObject* arr = NULL; char bit = 0, opt=0; Py_ssize_t index = 0; static char* kwlist[] = {"array", "index", "bit", "opt", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Onbb:setbit", kwlist, &arr, &index, &bit, &opt)) return NULL; if (!opt) { if (!PyArray_Check(arr)) { PyErr_SetString(PyExc_TypeError,"first argument must be array object"); return NULL; } if (NBYTES(index) >= PyArray_NBYTES((PyArrayObject*)arr)) { PyErr_SetString(PyExc_IndexError,"bit index out of range"); return NULL; } } if (bit) DATA(PyArray_DATA((PyArrayObject*)arr), index) |= BITMASK(index, CHAR_BITS); else DATA(PyArray_DATA((PyArrayObject*)arr), index) &= ~BITMASK(index, CHAR_BITS); Py_INCREF(Py_None); return Py_None; } static PyObject *getword(PyObject *self, PyObject *args, PyObject *kwds) { PyObject* arr = NULL; Py_ssize_t index = 0; Py_ssize_t width = 0, i; static char* kwlist[] = {"array", "index", "width", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Onn:getword", kwlist, &arr, &index, &width)) return NULL; if (!PyArray_Check(arr)) { PyErr_SetString(PyExc_TypeError,"first argument must be array object"); return NULL; } if (((index+width-1) >= BITS(PyArray_NBYTES((PyArrayObject*)arr))) || (width<0)) { PyErr_SetString(PyExc_IndexError,"bit index out of range"); return NULL; } // fast code, at least 3x if (width<=32) { npy_uint32 x = *((npy_uint64*)DATAPTR(PyArray_DATA((PyArrayObject*)arr), index)) >> (index % CHAR_BITS); return Py_BuildValue("kn",x & (NPY_MAX_UINT32>>(32-width)), index+width); } // generic code if (width<=64) { npy_uint64 word = 0; for (i=0; i= BITS(PyArray_NBYTES((PyArrayObject*)arr)) || width<0) { printf("index,width,nbits=%ld,%ld,%ld\n", index, width, BITS(PyArray_NBYTES((PyArrayObject*)arr))); PyErr_SetString(PyExc_IndexError,"bit index out of range"); return NULL; } if (width>64) { PyErr_SetString(PyExc_ValueError,"bit width must not be larger than 64"); return NULL; } } for (i=0; i #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION #define PY_ARRAY_UNIQUE_SYMBOL tif_lzw_PyArray_API #include "numpy/arrayobject.h" #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif #if PY_MAJOR_VERSION >= 3 #define IS_PY3K #endif typedef signed int tmsize_t; typedef tmsize_t tsize_t; typedef npy_uint8 uint8; typedef npy_uint16 uint16; typedef struct { uint8* tif_data; /* compression scheme private data */ uint8* tif_rawdata; /* raw data buffer */ tmsize_t tif_rawdatasize; /* # of bytes in raw data buffer */ uint8* tif_rawcp; /* current spot in raw buffer */ tmsize_t tif_rawcc; /* bytes unread from raw buffer */ } TIFF; //#include "tif_predict.h" #include /* * NB: The 5.0 spec describes a different algorithm than Aldus * implements. Specifically, Aldus does code length transitions * one code earlier than should be done (for real LZW). * Earlier versions of this library implemented the correct * LZW algorithm, but emitted codes in a bit order opposite * to the TIFF spec. Thus, to maintain compatibility w/ Aldus * we interpret MSB-LSB ordered codes to be images written w/ * old versions of this library, but otherwise adhere to the * Aldus "off by one" algorithm. * * Future revisions to the TIFF spec are expected to "clarify this issue". */ //#define LZW_COMPAT /* include backwards compatibility code */ /* * Each strip of data is supposed to be terminated by a CODE_EOI. * If the following #define is included, the decoder will also * check for end-of-strip w/o seeing this code. This makes the * library more robust, but also slower. */ #define LZW_CHECKEOS /* include checks for strips w/o EOI code */ #define MAXCODE(n) ((1L<<(n))-1) /* * The TIFF spec specifies that encoded bit * strings range from 9 to 12 bits. */ #define BITS_MIN 9 /* start with 9 bits */ #define BITS_MAX 12 /* max of 12 bit strings */ /* predefined codes */ #define CODE_CLEAR 256 /* code to clear string table */ #define CODE_EOI 257 /* end-of-information code */ #define CODE_FIRST 258 /* first free code entry */ #define CODE_MAX MAXCODE(BITS_MAX) #define HSIZE 9001L /* 91% occupancy */ #define HSHIFT (13-8) #ifdef LZW_COMPAT /* NB: +1024 is for compatibility with old files */ #define CSIZE (MAXCODE(BITS_MAX)+1024L) #else #define CSIZE (MAXCODE(BITS_MAX)+1L) #endif /* * State block for each open TIFF file using LZW * compression/decompression. Note that the predictor * state block must be first in this data structure. */ typedef struct { // TIFFPredictorState predict; /* predictor super class */ unsigned short nbits; /* # of bits/code */ unsigned short maxcode; /* maximum code for lzw_nbits */ unsigned short free_ent; /* next free entry in hash table */ long nextdata; /* next bits of i/o */ long nextbits; /* # of valid bits in lzw_nextdata */ //int rw_mode; /* preserve rw_mode from init */ } LZWBaseState; #define lzw_nbits base.nbits #define lzw_maxcode base.maxcode #define lzw_free_ent base.free_ent #define lzw_nextdata base.nextdata #define lzw_nextbits base.nextbits /* * Encoding-specific state. */ typedef uint16 hcode_t; /* codes fit in 16 bits */ typedef struct { long hash; hcode_t code; } hash_t; /* * Decoding-specific state. */ typedef struct code_ent { struct code_ent *next; unsigned short length; /* string len, including this token */ unsigned char value; /* data value */ unsigned char firstchar; /* first token of string */ } code_t; //typedef int (*decodeFunc)(TIFF*, uint8*, tmsize_t, uint16); typedef struct { LZWBaseState base; /* Decoding specific data */ long dec_nbitsmask; /* lzw_nbits 1 bits, right adjusted */ long dec_restart; /* restart count */ #ifdef LZW_CHECKEOS tmsize_t dec_bitsleft; /* available bits in raw data */ #endif //decodeFunc dec_decode; /* regular or backwards compatible */ code_t* dec_codep; /* current recognized code */ code_t* dec_oldcodep; /* previously recognized code */ code_t* dec_free_entp; /* next free entry */ code_t* dec_maxcodep; /* max available entry */ code_t* dec_codetab; /* kept separate for small machines */ /* Encoding specific data */ int enc_oldcode; /* last code encountered */ long enc_checkpoint; /* point at which to clear table */ #define CHECK_GAP 10000 /* enc_ratio check interval */ long enc_ratio; /* current compression ratio */ long enc_incount; /* (input) data bytes encoded */ long enc_outcount; /* encoded (output) bytes */ uint8* enc_rawlimit; /* bound on tif_rawdata buffer */ hash_t* enc_hashtab; /* kept separate for small machines */ } LZWCodecState; #define LZWState(tif) ((LZWBaseState*) (tif)->tif_data) #define DecoderState(tif) ((LZWCodecState*) LZWState(tif)) #define EncoderState(tif) ((LZWCodecState*) LZWState(tif)) //static int LZWDecode(TIFF* tif, uint8* op0, tmsize_t occ0, uint16 s); #ifdef LZW_COMPAT static int LZWDecodeCompat(TIFF* tif, uint8* op0, tmsize_t occ0, uint16 s); #endif static void cl_hash(LZWCodecState*); /* * LZW Decoder. */ #ifdef LZW_CHECKEOS /* * This check shouldn't be necessary because each * strip is suppose to be terminated with CODE_EOI. */ #define NextCode(_tif, _sp, _bp, _code, _get) { \ if ((_sp)->dec_bitsleft < (tmsize_t)nbits) { \ /*TIFFWarningExt(_tif->tif_clientdata, module,*/ \ /* "LZWDecode: Strip %d not terminated with EOI code", */ \ /* _tif->tif_curstrip);*/ \ _code = CODE_EOI; \ } else { \ _get(_sp,_bp,_code); \ (_sp)->dec_bitsleft -= nbits; \ } \ } #else #define NextCode(tif, sp, bp, code, get) get(sp, bp, code) #endif #if 1 static int LZWSetupDecode(TIFF* tif) { LZWCodecState* sp = DecoderState(tif); int code; if( sp == NULL ) { /* * Allocate state block so tag methods have storage to record * values. */ //tif->tif_data = (uint8*) TIFFmalloc(sizeof(LZWCodecState)); tif->tif_data = (uint8*) malloc(sizeof(LZWCodecState)); if (tif->tif_data == NULL) { //TIFFErrorExt(tif->tif_clientdata, module, "No space for LZW state block"); return (0); } DecoderState(tif)->dec_codetab = NULL; //DecoderState(tif)->dec_decode = NULL; /* * Setup predictor setup. */ //(void) TIFFPredictorInit(tif); sp = DecoderState(tif); } assert(sp != NULL); if (sp->dec_codetab == NULL) { //sp->dec_codetab = (code_t*)_TIFFmalloc(CSIZE*sizeof (code_t)); sp->dec_codetab = (code_t*)malloc(CSIZE*sizeof (code_t)); if (sp->dec_codetab == NULL) { //TIFFErrorExt(tif->tif_clientdata, module, // "No space for LZW code table"); return (0); } /* * Pre-load the table. */ code = 255; do { sp->dec_codetab[code].value = code; sp->dec_codetab[code].firstchar = code; sp->dec_codetab[code].length = 1; sp->dec_codetab[code].next = NULL; } while (code--); /* * Zero-out the unused entries */ //_TIFFmemset(&sp->dec_codetab[CODE_CLEAR], 0, // (CODE_FIRST - CODE_CLEAR) * sizeof (code_t)); memset(&sp->dec_codetab[CODE_CLEAR], 0, (CODE_FIRST - CODE_CLEAR) * sizeof (code_t)); } return (1); } /* * Setup state for decoding a strip. */ static int LZWPreDecode(TIFF* tif) { LZWCodecState *sp = DecoderState(tif); //(void) s; assert(sp != NULL); if( sp->dec_codetab == NULL ) { //tif->tif_setupdecode( tif ); LZWSetupDecode(tif); } /* * Check for old bit-reversed codes. */ if (tif->tif_rawdata[0] == 0 && (tif->tif_rawdata[1] & 0x1)) { #ifdef LZW_COMPAT if (!sp->dec_decode) { TIFFWarningExt(tif->tif_clientdata, module, "Old-style LZW codes, convert file"); /* * Override default decoding methods with * ones that deal with the old coding. * Otherwise the predictor versions set * above will call the compatibility routines * through the dec_decode method. */ tif->tif_decoderow = LZWDecodeCompat; tif->tif_decodestrip = LZWDecodeCompat; tif->tif_decodetile = LZWDecodeCompat; /* * If doing horizontal differencing, must * re-setup the predictor logic since we * switched the basic decoder methods... */ (*tif->tif_setupdecode)(tif); sp->dec_decode = LZWDecodeCompat; } sp->lzw_maxcode = MAXCODE(BITS_MIN); #else /* !LZW_COMPAT */ //if (!sp->dec_decode) { //TIFFErrorExt(tif->tif_clientdata, module, // "Old-style LZW codes not supported"); //sp->dec_decode = LZWDecode; //} return (0); #endif/* !LZW_COMPAT */ } else { sp->lzw_maxcode = MAXCODE(BITS_MIN)-1; //sp->dec_decode = LZWDecode; } sp->lzw_nbits = BITS_MIN; sp->lzw_nextbits = 0; sp->lzw_nextdata = 0; sp->dec_restart = 0; sp->dec_nbitsmask = MAXCODE(BITS_MIN); #ifdef LZW_CHECKEOS sp->dec_bitsleft = tif->tif_rawcc << 3; #endif sp->dec_free_entp = sp->dec_codetab + CODE_FIRST; /* * Zero entries that are not yet filled in. We do * this to guard against bogus input data that causes * us to index into undefined entries. If you can * come up with a way to safely bounds-check input codes * while decoding then you can remove this operation. */ //_TIFFmemset(sp->dec_free_entp, 0, (CSIZE-CODE_FIRST)*sizeof (code_t)); memset(sp->dec_free_entp, 0, (CSIZE-CODE_FIRST)*sizeof (code_t)); sp->dec_oldcodep = &sp->dec_codetab[-1]; sp->dec_maxcodep = &sp->dec_codetab[sp->dec_nbitsmask-1]; return (1); } /* * Decode a "hunk of data". */ #define GetNextCode(sp, bp, code) { \ nextdata = (nextdata<<8) | *(bp)++; \ nextbits += 8; \ if (nextbits < nbits) { \ nextdata = (nextdata<<8) | *(bp)++; \ nextbits += 8; \ } \ code = (hcode_t)((nextdata >> (nextbits-nbits)) & nbitsmask); \ nextbits -= nbits; \ } static void codeLoop(TIFF* tif) { //TIFFErrorExt(tif->tif_clientdata, module, // "Bogus encoding, loop in the code table; scanline %d", // tif->tif_row); } static int LZWDecode(TIFF* tif, uint8* op0, tmsize_t occ0) { LZWCodecState *sp = DecoderState(tif); char *op = (char*) op0; long occ = (long) occ0; char *tp; unsigned char *bp; hcode_t code; int len; long nbits, nextbits, nextdata, nbitsmask; code_t *codep, *free_entp, *maxcodep, *oldcodep; //(void) s; assert(sp != NULL); assert(sp->dec_codetab != NULL); /* Fail if value does not fit in long. */ if ((tmsize_t) occ != occ0) return (0); /* * Restart interrupted output operation. */ if (sp->dec_restart) { long residue; codep = sp->dec_codep; residue = codep->length - sp->dec_restart; if (residue > occ) { /* * Residue from previous decode is sufficient * to satisfy decode request. Skip to the * start of the decoded string, place decoded * values in the output buffer, and return. */ sp->dec_restart += occ; do { codep = codep->next; } while (--residue > occ && codep); if (codep) { tp = op + occ; do { *--tp = codep->value; codep = codep->next; } while (--occ && codep); } return (1); } /* * Residue satisfies only part of the decode request. */ op += residue, occ -= residue; tp = op; do { int t; --tp; t = codep->value; codep = codep->next; *tp = t; } while (--residue && codep); sp->dec_restart = 0; } bp = (unsigned char *)tif->tif_rawcp; nbits = sp->lzw_nbits; nextdata = sp->lzw_nextdata; nextbits = sp->lzw_nextbits; nbitsmask = sp->dec_nbitsmask; oldcodep = sp->dec_oldcodep; free_entp = sp->dec_free_entp; maxcodep = sp->dec_maxcodep; while (occ > 0) { NextCode(tif, sp, bp, code, GetNextCode); if (code == CODE_EOI) break; if (code == CODE_CLEAR) { free_entp = sp->dec_codetab + CODE_FIRST; //_TIFFmemset(free_entp, 0, // (CSIZE - CODE_FIRST) * sizeof (code_t)); memset(free_entp, 0, (CSIZE - CODE_FIRST) * sizeof (code_t)); nbits = BITS_MIN; nbitsmask = MAXCODE(BITS_MIN); maxcodep = sp->dec_codetab + nbitsmask-1; NextCode(tif, sp, bp, code, GetNextCode); if (code == CODE_EOI) break; if (code >= CODE_CLEAR) { //TIFFErrorExt(tif->tif_clientdata, tif->tif_name, //"LZWDecode: Corrupted LZW table at scanline %d", // tif->tif_row); return (0); } *op++ = (char)code, occ--; oldcodep = sp->dec_codetab + code; continue; } codep = sp->dec_codetab + code; /* * Add the new entry to the code table. */ if (free_entp < &sp->dec_codetab[0] || free_entp >= &sp->dec_codetab[CSIZE]) { //TIFFErrorExt(tif->tif_clientdata, module, // "Corrupted LZW table at scanline %d", // tif->tif_row); return (0); } free_entp->next = oldcodep; if (free_entp->next < &sp->dec_codetab[0] || free_entp->next >= &sp->dec_codetab[CSIZE]) { //TIFFErrorExt(tif->tif_clientdata, module, // "Corrupted LZW table at scanline %d", // tif->tif_row); return (0); } free_entp->firstchar = free_entp->next->firstchar; free_entp->length = free_entp->next->length+1; free_entp->value = (codep < free_entp) ? codep->firstchar : free_entp->firstchar; if (++free_entp > maxcodep) { if (++nbits > BITS_MAX) /* should not happen */ nbits = BITS_MAX; nbitsmask = MAXCODE(nbits); maxcodep = sp->dec_codetab + nbitsmask-1; } oldcodep = codep; if (code >= 256) { /* * Code maps to a string, copy string * value to output (written in reverse). */ if(codep->length == 0) { // TIFFErrorExt(tif->tif_clientdata, module, // "Wrong length of decoded string: " // "data probably corrupted at scanline %d", // tif->tif_row); return (0); } if (codep->length > occ) { /* * String is too long for decode buffer, * locate portion that will fit, copy to * the decode buffer, and setup restart * logic for the next decoding call. */ sp->dec_codep = codep; do { codep = codep->next; } while (codep && codep->length > occ); if (codep) { sp->dec_restart = (long)occ; tp = op + occ; do { *--tp = codep->value; codep = codep->next; } while (--occ && codep); if (codep) codeLoop(tif); } break; } len = codep->length; tp = op + len; do { int t; --tp; t = codep->value; codep = codep->next; *tp = t; } while (codep && tp > op); if (codep) { codeLoop(tif); break; } assert(occ >= len); op += len, occ -= len; } else *op++ = (char)code, occ--; } tif->tif_rawcp = (uint8*) bp; sp->lzw_nbits = (unsigned short) nbits; sp->lzw_nextdata = nextdata; sp->lzw_nextbits = nextbits; sp->dec_nbitsmask = nbitsmask; sp->dec_oldcodep = oldcodep; sp->dec_free_entp = free_entp; sp->dec_maxcodep = maxcodep; return (occ); // return extra bytes for resizing result array } #ifdef LZW_COMPAT /* * Decode a "hunk of data" for old images. */ #define GetNextCodeCompat(sp, bp, code) { \ nextdata |= (unsigned long) *(bp)++ << nextbits; \ nextbits += 8; \ if (nextbits < nbits) { \ nextdata |= (unsigned long) *(bp)++ << nextbits;\ nextbits += 8; \ } \ code = (hcode_t)(nextdata & nbitsmask); \ nextdata >>= nbits; \ nextbits -= nbits; \ } static int LZWDecodeCompat(TIFF* tif, uint8* op0, tmsize_t occ0, uint16 s) { static const char module[] = "LZWDecodeCompat"; LZWCodecState *sp = DecoderState(tif); char *op = (char*) op0; long occ = (long) occ0; char *tp; unsigned char *bp; int code, nbits; long nextbits, nextdata, nbitsmask; code_t *codep, *free_entp, *maxcodep, *oldcodep; (void) s; assert(sp != NULL); /* Fail if value does not fit in long. */ if ((tmsize_t) occ != occ0) return (0); /* * Restart interrupted output operation. */ if (sp->dec_restart) { long residue; codep = sp->dec_codep; residue = codep->length - sp->dec_restart; if (residue > occ) { /* * Residue from previous decode is sufficient * to satisfy decode request. Skip to the * start of the decoded string, place decoded * values in the output buffer, and return. */ sp->dec_restart += occ; do { codep = codep->next; } while (--residue > occ); tp = op + occ; do { *--tp = codep->value; codep = codep->next; } while (--occ); return (1); } /* * Residue satisfies only part of the decode request. */ op += residue, occ -= residue; tp = op; do { *--tp = codep->value; codep = codep->next; } while (--residue); sp->dec_restart = 0; } bp = (unsigned char *)tif->tif_rawcp; nbits = sp->lzw_nbits; nextdata = sp->lzw_nextdata; nextbits = sp->lzw_nextbits; nbitsmask = sp->dec_nbitsmask; oldcodep = sp->dec_oldcodep; free_entp = sp->dec_free_entp; maxcodep = sp->dec_maxcodep; while (occ > 0) { NextCode(tif, sp, bp, code, GetNextCodeCompat); if (code == CODE_EOI) break; if (code == CODE_CLEAR) { free_entp = sp->dec_codetab + CODE_FIRST; _TIFFmemset(free_entp, 0, (CSIZE - CODE_FIRST) * sizeof (code_t)); nbits = BITS_MIN; nbitsmask = MAXCODE(BITS_MIN); maxcodep = sp->dec_codetab + nbitsmask; NextCode(tif, sp, bp, code, GetNextCodeCompat); if (code == CODE_EOI) break; if (code >= CODE_CLEAR) { TIFFErrorExt(tif->tif_clientdata, tif->tif_name, "LZWDecode: Corrupted LZW table at scanline %d", tif->tif_row); return (0); } *op++ = code, occ--; oldcodep = sp->dec_codetab + code; continue; } codep = sp->dec_codetab + code; /* * Add the new entry to the code table. */ if (free_entp < &sp->dec_codetab[0] || free_entp >= &sp->dec_codetab[CSIZE]) { TIFFErrorExt(tif->tif_clientdata, module, "Corrupted LZW table at scanline %d", tif->tif_row); return (0); } free_entp->next = oldcodep; if (free_entp->next < &sp->dec_codetab[0] || free_entp->next >= &sp->dec_codetab[CSIZE]) { TIFFErrorExt(tif->tif_clientdata, module, "Corrupted LZW table at scanline %d", tif->tif_row); return (0); } free_entp->firstchar = free_entp->next->firstchar; free_entp->length = free_entp->next->length+1; free_entp->value = (codep < free_entp) ? codep->firstchar : free_entp->firstchar; if (++free_entp > maxcodep) { if (++nbits > BITS_MAX) /* should not happen */ nbits = BITS_MAX; nbitsmask = MAXCODE(nbits); maxcodep = sp->dec_codetab + nbitsmask; } oldcodep = codep; if (code >= 256) { /* * Code maps to a string, copy string * value to output (written in reverse). */ if(codep->length == 0) { TIFFErrorExt(tif->tif_clientdata, module, "Wrong length of decoded " "string: data probably corrupted at scanline %d", tif->tif_row); return (0); } if (codep->length > occ) { /* * String is too long for decode buffer, * locate portion that will fit, copy to * the decode buffer, and setup restart * logic for the next decoding call. */ sp->dec_codep = codep; do { codep = codep->next; } while (codep->length > occ); sp->dec_restart = occ; tp = op + occ; do { *--tp = codep->value; codep = codep->next; } while (--occ); break; } assert(occ >= codep->length); op += codep->length, occ -= codep->length; tp = op; do { *--tp = codep->value; } while( (codep = codep->next) != NULL ); } else *op++ = code, occ--; } tif->tif_rawcp = (uint8*) bp; sp->lzw_nbits = nbits; sp->lzw_nextdata = nextdata; sp->lzw_nextbits = nextbits; sp->dec_nbitsmask = nbitsmask; sp->dec_oldcodep = oldcodep; sp->dec_free_entp = free_entp; sp->dec_maxcodep = maxcodep; if (occ > 0) { #if defined(__WIN32__) && defined(_MSC_VER) TIFFErrorExt(tif->tif_clientdata, module, "Not enough data at scanline %d (short %I64d bytes)", tif->tif_row, (unsigned __int64) occ); #else TIFFErrorExt(tif->tif_clientdata, module, "Not enough data at scanline %d (short %llu bytes)", tif->tif_row, (unsigned long long) occ); #endif return (0); } return (1); } #endif /* LZW_COMPAT */ #endif /* if 1 */ /* * LZW Encoding. */ static int LZWSetupEncode(TIFF* tif) { //static const char module[] = "LZWSetupEncode"; LZWCodecState* sp = EncoderState(tif); assert(sp != NULL); //sp->enc_hashtab = (hash_t*) _TIFFmalloc(HSIZE*sizeof (hash_t)); sp->enc_hashtab = (hash_t*) malloc(HSIZE*sizeof (hash_t)); if (sp->enc_hashtab == NULL) { //TIFFErrorExt(tif->tif_clientdata, module, // "No space for LZW hash table"); return (0); } return (1); } /* * Reset encoding state at the start of a strip. */ static int LZWPreEncode(TIFF* tif/*, uint16 s*/) { LZWCodecState *sp = EncoderState(tif); //(void) s; assert(sp != NULL); if( sp->enc_hashtab == NULL ) { //tif->tif_setupencode( tif ); LZWSetupEncode(tif); } sp->lzw_nbits = BITS_MIN; sp->lzw_maxcode = MAXCODE(BITS_MIN); sp->lzw_free_ent = CODE_FIRST; sp->lzw_nextbits = 0; sp->lzw_nextdata = 0; sp->enc_checkpoint = CHECK_GAP; sp->enc_ratio = 0; sp->enc_incount = 0; sp->enc_outcount = 0; /* * The 4 here insures there is space for 2 max-sized * codes in LZWEncode and LZWPostDecode. */ sp->enc_rawlimit = tif->tif_rawdata + tif->tif_rawdatasize-1 - 4; cl_hash(sp); /* clear hash table */ sp->enc_oldcode = (hcode_t) -1; /* generates CODE_CLEAR in LZWEncode */ return (1); } #define CALCRATIO(sp, rat) { \ if (incount > 0x007fffff) { /* NB: shift will overflow */\ rat = outcount >> 8; \ rat = (rat == 0 ? 0x7fffffff : incount/rat); \ } else \ rat = (incount<<8) / outcount; \ } #define PutNextCode(op, c) { \ nextdata = (nextdata << nbits) | c; \ nextbits += nbits; \ *op++ = (unsigned char)(nextdata >> (nextbits-8)); \ nextbits -= 8; \ if (nextbits >= 8) { \ *op++ = (unsigned char)(nextdata >> (nextbits-8)); \ nextbits -= 8; \ } \ outcount += nbits; \ } /* * Encode a chunk of pixels. * * Uses an open addressing double hashing (no chaining) on the * prefix code/next character combination. We do a variant of * Knuth's algorithm D (vol. 3, sec. 6.4) along with G. Knott's * relatively-prime secondary probe. Here, the modular division * first probe is gives way to a faster exclusive-or manipulation. * Also do block compression with an adaptive reset, whereby the * code table is cleared when the compression ratio decreases, * but after the table fills. The variable-length output codes * are re-sized at this point, and a CODE_CLEAR is generated * for the decoder. */ #define FLUSHDATA(LST, DATA, DATASIZE) { \ npy_intp dims[] = {(DATASIZE)}; \ PyObject *arr = PyArray_EMPTY(1, dims, NPY_UBYTE, 0); \ memcpy(PyArray_DATA((PyArrayObject*)arr), (DATA), dims[0]); \ PyList_Append(lst, arr); \ } static int LZWEncode(TIFF* tif, uint8* bp, tmsize_t cc/*, uint16 s*/ ,PyObject* lst) { register LZWCodecState *sp = EncoderState(tif); register long fcode; register hash_t *hp; register int h, c; hcode_t ent; long disp; long incount, outcount, checkpoint; long nextdata, nextbits; int free_ent, maxcode, nbits; uint8* op; uint8* limit; //(void) s; if (sp == NULL) return (0); assert(sp->enc_hashtab != NULL); /* * Load local state. */ incount = sp->enc_incount; outcount = sp->enc_outcount; checkpoint = sp->enc_checkpoint; nextdata = sp->lzw_nextdata; nextbits = sp->lzw_nextbits; free_ent = sp->lzw_free_ent; maxcode = sp->lzw_maxcode; nbits = sp->lzw_nbits; op = tif->tif_rawcp; limit = sp->enc_rawlimit; ent = sp->enc_oldcode; if (ent == (hcode_t) -1 && cc > 0) { /* * NB: This is safe because it can only happen * at the start of a strip where we know there * is space in the data buffer. */ PutNextCode(op, CODE_CLEAR); ent = *bp++; cc--; incount++; } while (cc > 0) { c = *bp++; cc--; incount++; fcode = ((long)c << BITS_MAX) + ent; h = (c << HSHIFT) ^ ent; /* xor hashing */ #ifdef _WINDOWS /* * Check hash index for an overflow. */ if (h >= HSIZE) h -= HSIZE; #endif hp = &sp->enc_hashtab[h]; if (hp->hash == fcode) { ent = hp->code; continue; } if (hp->hash >= 0) { /* * Primary hash failed, check secondary hash. */ disp = HSIZE - h; if (h == 0) disp = 1; do { /* * Avoid pointer arithmetic 'cuz of * wraparound problems with segments. */ if ((h -= disp) < 0) h += HSIZE; hp = &sp->enc_hashtab[h]; if (hp->hash == fcode) { ent = hp->code; goto hit; } } while (hp->hash >= 0); } /* * New entry, emit code and add to table. */ /* * Verify there is space in the buffer for the code * and any potential Clear code that might be emitted * below. The value of limit is setup so that there * are at least 4 bytes free--room for 2 codes. */ if (op > limit) { tif->tif_rawcc = (tmsize_t)(op - tif->tif_rawdata); //TIFFFlushData1(tif); FLUSHDATA(lst, tif->tif_rawdata, tif->tif_rawcc); op = tif->tif_rawdata; } PutNextCode(op, ent); ent = c; hp->code = free_ent++; hp->hash = fcode; if (free_ent == CODE_MAX-1) { /* table is full, emit clear code and reset */ cl_hash(sp); sp->enc_ratio = 0; incount = 0; outcount = 0; free_ent = CODE_FIRST; PutNextCode(op, CODE_CLEAR); nbits = BITS_MIN; maxcode = MAXCODE(BITS_MIN); } else { /* * If the next entry is going to be too big for * the code size, then increase it, if possible. */ if (free_ent > maxcode) { nbits++; assert(nbits <= BITS_MAX); maxcode = (int) MAXCODE(nbits); } else if (incount >= checkpoint) { long rat; /* * Check compression ratio and, if things seem * to be slipping, clear the hash table and * reset state. The compression ratio is a * 24+8-bit fractional number. */ checkpoint = incount+CHECK_GAP; CALCRATIO(sp, rat); if (rat <= sp->enc_ratio) { cl_hash(sp); sp->enc_ratio = 0; incount = 0; outcount = 0; free_ent = CODE_FIRST; PutNextCode(op, CODE_CLEAR); nbits = BITS_MIN; maxcode = MAXCODE(BITS_MIN); } else sp->enc_ratio = rat; } } hit: ; } /* * Restore global state. */ sp->enc_incount = incount; sp->enc_outcount = outcount; sp->enc_checkpoint = checkpoint; sp->enc_oldcode = ent; sp->lzw_nextdata = nextdata; sp->lzw_nextbits = nextbits; sp->lzw_free_ent = free_ent; sp->lzw_maxcode = maxcode; sp->lzw_nbits = nbits; tif->tif_rawcp = op; return (1); } /* * Finish off an encoded strip by flushing the last * string and tacking on an End Of Information code. */ static int LZWPostEncode(TIFF* tif, PyObject* lst) { register LZWCodecState *sp = EncoderState(tif); uint8* op = tif->tif_rawcp; long nextbits = sp->lzw_nextbits; long nextdata = sp->lzw_nextdata; long outcount = sp->enc_outcount; int nbits = sp->lzw_nbits; if (op > sp->enc_rawlimit) { tif->tif_rawcc = (tmsize_t)(op - tif->tif_rawdata); //TIFFFlushData1(tif); FLUSHDATA(lst, tif->tif_rawdata, tif->tif_rawcc); op = tif->tif_rawdata; } if (sp->enc_oldcode != (hcode_t) -1) { PutNextCode(op, sp->enc_oldcode); sp->enc_oldcode = (hcode_t) -1; } PutNextCode(op, CODE_EOI); if (nextbits > 0) *op++ = (unsigned char)(nextdata << (8-nextbits)); tif->tif_rawcc = (tmsize_t)(op - tif->tif_rawdata); FLUSHDATA(lst, tif->tif_rawdata, tif->tif_rawcc); return (1); } /* * Reset encoding hash table. */ static void cl_hash(LZWCodecState* sp) { register hash_t *hp = &sp->enc_hashtab[HSIZE-1]; register long i = HSIZE-8; do { i -= 8; hp[-7].hash = -1; hp[-6].hash = -1; hp[-5].hash = -1; hp[-4].hash = -1; hp[-3].hash = -1; hp[-2].hash = -1; hp[-1].hash = -1; hp[ 0].hash = -1; hp -= 8; } while (i >= 0); for (i += 8; i > 0; i--, hp--) hp->hash = -1; } static void LZWCleanup(TIFF* tif) { //(void)TIFFPredictorCleanup(tif); assert(tif->tif_data != 0); if (DecoderState(tif)->dec_codetab) //_TIFFfree(DecoderState(tif)->dec_codetab); free(DecoderState(tif)->dec_codetab); if (EncoderState(tif)->enc_hashtab) //_TIFFfree(EncoderState(tif)->enc_hashtab); free(EncoderState(tif)->enc_hashtab); //_TIFFfree(tif->tif_data); free(tif->tif_data); tif->tif_data = NULL; //_TIFFSetDefaultCompressionState(tif); } #if 0 int TIFFInitLZW(TIFF* tif/*, int scheme*/) { //static const char module[] = "TIFFInitLZW"; //assert(scheme == COMPRESSION_LZW); /* * Allocate state block so tag methods have storage to record values. */ //tif->tif_data = (uint8*) _TIFFmalloc(sizeof (LZWCodecState)); tif->tif_data = (uint8*) malloc(sizeof (LZWCodecState)); if (tif->tif_data == NULL) goto bad; DecoderState(tif)->dec_codetab = NULL; //DecoderState(tif)->dec_decode = NULL; EncoderState(tif)->enc_hashtab = NULL; //LZWState(tif)->rw_mode = tif->tif_mode; /* * Install codec methods. */ /* tif->tif_fixuptags = LZWFixupTags; tif->tif_setupdecode = LZWSetupDecode; tif->tif_predecode = LZWPreDecode; tif->tif_decoderow = LZWDecode; tif->tif_decodestrip = LZWDecode; tif->tif_decodetile = LZWDecode; tif->tif_setupencode = LZWSetupEncode; tif->tif_preencode = LZWPreEncode; tif->tif_postencode = LZWPostEncode; tif->tif_encoderow = LZWEncode; tif->tif_encodestrip = LZWEncode; tif->tif_encodetile = LZWEncode; tif->tif_cleanup = LZWCleanup; */ /* * Setup predictor setup. */ //(void) TIFFPredictorInit(tif); return (1); bad: //TIFFErrorExt(tif->tif_clientdata, module, // "No space for LZW state block"); return (0); } #endif /* if 0 */ static PyObject *py_decode(PyObject *self, PyObject *args, PyObject *kwds) { PyObject* arr = NULL; PyObject* result = NULL; TIFF tif; static char* kwlist[] = {"arr","size",NULL}; long occ; npy_intp dims[] = {0}; PyArray_Dims newshape; if (!PyArg_ParseTupleAndKeywords(args, kwds, "Oi", kwlist, &arr, dims)) return NULL; if (!PyArray_Check(arr)) { PyErr_SetString(PyExc_TypeError,"first argument must be array object"); return NULL; } /* TIFFInitLZW */ tif.tif_data = (uint8*) malloc(sizeof (LZWCodecState)); DecoderState(&tif)->dec_codetab = NULL; //DecoderState(&tif)->dec_decode = NULL; EncoderState(&tif)->enc_hashtab = NULL; //LZWState(&tif)->rw_mode = tif->tif_mode; /* eof TIFFInitLZW */ tif.tif_rawcp = tif.tif_rawdata = (uint8*)PyArray_DATA((PyArrayObject*)arr); tif.tif_rawcc = tif.tif_rawdatasize = PyArray_NBYTES((PyArrayObject*)arr); result = PyArray_EMPTY(1, dims, NPY_UBYTE, 0); if (result!=NULL) { LZWPreDecode(&tif); occ = LZWDecode(&tif, (uint8*)PyArray_DATA((PyArrayObject*)result), PyArray_NBYTES((PyArrayObject*)result)); LZWCleanup(&tif); if (occ>0) { dims[0] -= occ; newshape.ptr = dims; newshape.len = 1; #if NPY_API_VERSION < 7 if (PyArray_Resize((PyArrayObject*)result, &newshape, 0, PyArray_CORDER)==NULL) #else if (PyArray_Resize((PyArrayObject*)result, &newshape, 0, NPY_CORDER)==NULL) #endif return NULL; } } return result; } static PyObject *py_encode(PyObject *self, PyObject *args, PyObject *kwds) { PyObject* arr = NULL; PyObject* lst = PyList_New(0); PyObject* result = NULL; npy_intp dims[] = {0}; int buffer_size; int i, j; TIFF tif; static char* kwlist[] = {"arr",NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &arr)) return NULL; if (!PyArray_Check(arr)) { PyErr_SetString(PyExc_TypeError,"first argument must be array object"); return NULL; } /* TIFFInitLZW */ tif.tif_data = (uint8*) malloc(sizeof (LZWCodecState)); DecoderState(&tif)->dec_codetab = NULL; //DecoderState(&tif)->dec_decode = NULL; EncoderState(&tif)->enc_hashtab = NULL; //LZWState(&tif)->rw_mode = tif->tif_mode; /* eof TIFFInitLZW */ // create buffer buffer_size = PyArray_NBYTES((PyArrayObject*)arr); if (buffer_size > (1<<20)) buffer_size = (1<<20); tif.tif_rawcp = tif.tif_rawdata = (uint8*)malloc(buffer_size * sizeof(uint8)); tif.tif_rawdatasize = buffer_size; // encode LZWPreEncode(&tif); LZWEncode(&tif, (uint8*)PyArray_DATA((PyArrayObject*)arr), PyArray_NBYTES((PyArrayObject*)arr), lst); LZWPostEncode(&tif, lst); // get result from buffer lst if (PyList_GET_SIZE(lst)==1) { result = PyList_GET_ITEM(lst, 0); Py_INCREF(result); } else { dims[0] = 0; for (i=0; i 7: assert word == arr[0], repr((width, word, arr[0], bstr, dtype)) bittools.setword(arr2, 0, width, word) assert bittools.getword(arr2, 0, width)[0] == word assert tobinary(arr2)[:width] == bstr[:width], \ repr((tobinary(arr2)[:width], bstr[:width])) if __name__ == '__main__': test_setgetbit() test_setgetword() test_wordbits() pylibtiff-0.6.1/libtiff/tests/000077500000000000000000000000001450302046000162615ustar00rootroot00000000000000pylibtiff-0.6.1/libtiff/tests/test_libtiff_ctypes.py000066400000000000000000000005571450302046000227070ustar00rootroot00000000000000import pytest import numpy as np from libtiff import TIFFimage lt = pytest.importorskip('libtiff.libtiff_ctypes') def test_issue69(tmp_path): itype = np.uint32 image = np.array([[[1, 2, 3], [4, 5, 6]]], itype) fn = str(tmp_path / "issue69.tif") tif = TIFFimage(image) tif.write_file(fn) del tif tif = lt.TIFF3D.open(fn) tif.close() pylibtiff-0.6.1/libtiff/tests/test_lzw.py000066400000000000000000000017031450302046000205070ustar00rootroot00000000000000 import numpy # from tempfile import mktemp # from libtiff import TIFFfile, TIFF try: import pytest except ImportError: pass else: # when running inside source directory: pytest.importorskip('libtiff.tif_lzw') from libtiff.tif_lzw import encode as c_encode, decode as c_decode # def TIFFencode(arr): # fn = mktemp('.tif') # tif = TIFF.open(fn, 'w+') # tif.write_image(arr.view(numpy.uint8), compression='lzw') # tif.close() # tif = TIFFfile(fn) # data, names = tif.get_samples(leave_compressed=True) # return data[0][0] def test_encode(): for arr in [ numpy.array([7, 7, 7, 8, 8, 7, 7, 6, 6], numpy.uint8), numpy.array(list(range(400000))).astype(numpy.uint8), numpy.array([1, 3, 7, 15, 31, 63], numpy.uint8)]: rarr = c_encode(arr) arr2 = c_decode(rarr, arr.nbytes) assert arr2.nbytes == arr.nbytes and (arr2 == arr).all(), \ repr((arr2, arr)) pylibtiff-0.6.1/libtiff/tests/test_simple.py000066400000000000000000000024231450302046000211640ustar00rootroot00000000000000import os from tempfile import mktemp from numpy import (uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64, complex64, complex128, array, random) from libtiff import TIFF def test_write_read(): for itype in [uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64, complex64, complex128]: image = array([[1, 2, 3], [4, 5, 6]], itype) fn = mktemp('.tif') tif = TIFF.open(fn, 'w') tif.write_image(image) tif.close() tif = TIFF.open(fn, 'r') image2 = tif.read_image() tif.close() os.remove(fn) assert image.dtype == image2.dtype assert (image == image2).all() def test_slicing(): shape = (16, 16) image = random.randint(255, size=shape) for i in range(shape[0]): for j in range(shape[1]): image1 = image[:i + 1, :j + 1] fn = mktemp('.tif') tif = TIFF.open(fn, 'w') tif.write_image(image1) tif.close() tif = TIFF.open(fn, 'r') image2 = tif.read_image() tif.close() assert (image1 == image2).all(), repr((i, j)) os.remove(fn) pylibtiff-0.6.1/libtiff/tests/test_tiff_array.py000066400000000000000000000040521450302046000220210ustar00rootroot00000000000000 import os import sys import atexit from tempfile import mktemp from numpy import (uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64, complex64, complex128, random) from libtiff import TIFF from libtiff import TIFFfile, TIFFimage import pytest @pytest.mark.skipif(sys.platform == "darwin", reason="OSX can't resize mmap") def test_simple_slicing(): for planar_config in [1, 2]: for compression in [None, 'lzw']: for itype in [uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64, complex64, complex128]: image = random.randint(0, 100, size=(10, 6, 7)).astype(itype) fn = mktemp('.tif') if 0: if planar_config == 2: continue tif = TIFF.open(fn, 'w') tif.write_image(image, compression=compression) tif.close() else: tif = TIFFimage(image) tif.write_file(fn, compression=compression, planar_config=planar_config) del tif tif = TIFFfile(fn) arr = tif.get_tiff_array() data = arr[:] assert len(data) == len(image), repr(len(data)) assert image.dtype == data.dtype, repr((image.dtype, data[0].dtype)) assert (image == data).all() assert arr.shape == image.shape _indices = [0, slice(None), slice(0, 2), slice(0, 5, 2)] for _i0 in _indices[:1]: for i1 in _indices: for i2 in _indices: sl = (_i0, i1, i2) assert (arr[sl] == image[sl]).all(), repr(sl) tif.close() atexit.register(os.remove, fn) pylibtiff-0.6.1/libtiff/tests/test_tiff_file.py000066400000000000000000000041421450302046000216220ustar00rootroot00000000000000 import os import sys import atexit from tempfile import mktemp from numpy import (uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64, complex64, complex128, array, ones) from libtiff import TIFF from libtiff import TIFFfile, TIFFimage import pytest @pytest.mark.skipif(sys.platform == "darwin", reason="OSX can't resize mmap") def test_write_read(): for compression in [None, 'lzw']: for itype in [uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64, complex64, complex128]: image = array([[1, 2, 3], [4, 5, 6]], itype) fn = mktemp('.tif') if 0: tif = TIFF.open(fn, 'w') tif.write_image(image, compression=compression) tif.close() else: tif = TIFFimage(image) tif.write_file(fn, compression=compression) del tif tif = TIFFfile(fn) data, names = tif.get_samples() assert names == ['sample0'], repr(names) assert len(data) == 1, repr(len(data)) assert image.dtype == data[0].dtype, repr( (image.dtype, data[0].dtype)) assert (image == data[0]).all() tif.close() atexit.register(os.remove, fn) def test_issue19(): size = 1024 * 32 # 1GB # size = 1024*63 # almost 4GB, test takes about 60 seconds but succeeds image = ones((size, size), dtype=uint8) # print('image size:', image.nbytes / 1024**2, 'MB') fn = mktemp('issue19.tif') tif = TIFFimage(image) try: tif.write_file(fn) except OSError as msg: if 'Not enough storage is available to process this command'\ in str(msg): # Happens in Appveyour CI del tif atexit.register(os.remove, fn) return else: raise del tif tif = TIFFfile(fn) tif.get_tiff_array()[:] # expected failure tif.close() atexit.register(os.remove, fn) pylibtiff-0.6.1/libtiff/tests/test_tiff_image.py000066400000000000000000000055141450302046000217710ustar00rootroot00000000000000import os import sys import atexit from tempfile import mktemp from numpy import (uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64, array, zeros, dtype, complex64, complex128, issubdtype, integer) from libtiff import TIFFfile, TIFFimage, TIFF import pytest SUPPORTED_DTYPES = [uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64, complex64, complex128] @pytest.mark.skipif(sys.platform == "darwin", reason="OSX can't resize mmap") def test_rw_rgb(): itype = uint8 dt = dtype(dict(names=list('rgb'), formats=[itype] * 3)) image = zeros((2, 3), dtype=dt) image['r'][:, 0] = 250 image['g'][:, 1] = 251 image['b'][:, 2] = 252 fn = mktemp('.tif') tif = TIFFimage(image) tif.write_file(fn, compression='lzw') # , samples='rgb') del tif tif = TIFFfile(fn) data, names = tif.get_samples() tif.close() atexit.register(os.remove, fn) assert itype == data[0].dtype, repr((itype, data[0].dtype)) assert (image['r'] == data[0]).all() assert (image['g'] == data[1]).all() assert (image['b'] == data[2]).all() @pytest.mark.skipif(sys.platform == "darwin", reason="OSX can't resize mmap") @pytest.mark.parametrize("itype", SUPPORTED_DTYPES) @pytest.mark.parametrize("compression", ["none", "lzw"]) def test_write_read(compression, itype): image = array([[1, 2, 3], [4, 5, 6]], itype) fn = mktemp('.tif') tif = TIFFimage(image) tif.write_file(fn, compression=compression) del tif tif = TIFFfile(fn) data, names = tif.get_samples() tif.close() atexit.register(os.remove, fn) assert names == ['sample0'], repr(names) assert len(data) == 1, repr(len(data)) assert image.dtype == data[0].dtype, repr( (image.dtype, data[0].dtype)) assert (image == data[0]).all() @pytest.mark.skipif(sys.platform == "darwin", reason="OSX can't resize mmap") @pytest.mark.parametrize("itype", SUPPORTED_DTYPES) def test_write_lzw(itype): if issubdtype(itype, integer): # avoid overflow failure from numpy for integer types image = array([list(range(10000))]).astype(itype) else: image = array([list(range(10000))], itype) fn = mktemp('.tif') tif = TIFFimage(image) tif.write_file(fn, compression='lzw') del tif # os.system('wc %s; echo %s' % (fn, image.nbytes)) tif = TIFF.open(fn, 'r') image2 = tif.read_image() tif.close() atexit.register(os.remove, fn) for i in range(image.size): if image.flat[i] != image2.flat[i]: # print(repr((i, image.flat[i - 5:i + 5].view(dtype=uint8), # image2.flat[i - 5:i + 5].view(dtype=uint8)))) break assert image.dtype == image2.dtype assert (image == image2).all() pylibtiff-0.6.1/libtiff/tiff.py000066400000000000000000000022051450302046000164200ustar00rootroot00000000000000""" tiff - implements a numpy.memmap based TIFF file reader and writer allowing manipulating TIFF files that have sizes larger than available memory in computer. Usage: >>> tiff = TIFFfile('') >>> samples, sample_names = tiff.get_samples() >>> arr = tiff.get_tiff_array(sample_index=0, subfile_type=0) >>> tiff = TIFFimage(data, description=) >>> tiff.write_file (, compression='none'|'lzw') >>> del tiff # flush data to disk """ # Author: Pearu Peterson # Created: April 2010 __all__ = ['TIFFfile', 'TIFFimage', 'TiffArray'] import os import sys from .tiff_file import TIFFfile from .tiff_image import TIFFimage from .tiff_array import TiffArray def main(): filename = sys.argv[1] if not os.path.isfile(filename): raise ValueError('File %r does not exists' % (filename)) t = TIFFfile(filename) t.show_memory_usage() e = t.IFD[0].entries[-1] assert e.is_lsm from . import lsm print(lsm.lsmblock(e)) print(lsm.lsminfo(e, 0)) # print lsm.filestructure(e) # print lsm.timestamps(e) # print lsm.channelwavelength(e) if __name__ == '__main__': main() pylibtiff-0.6.1/libtiff/tiff_array.py000066400000000000000000000071721450302046000176260ustar00rootroot00000000000000""" Implements an array of TIFF sample images. """ # Author: Pearu Peterson # Created: Nov 2010 import sys import numpy __all__ = ['TiffArray'] class TiffArray: """ Holds a sequence of homogeneous TiffPlane's. TiffPlane's are homogeneous if they contain sample images of same shape and type. Otherwise TiffPlane's may contain images from different TIFF files with different pixel content. """ def __init__(self, planes): self.planes = [] self.shape = () self.dtype = None list(map(self.append, planes)) def __len__(self): return self.shape[0] def __iter__(self): for plane in self.planes: yield plane def __getitem__(self, index): try: if isinstance(index, int): if self.sample_index is None: print(self.shape) return self.planes[index][()] elif isinstance(index, slice): indices = list(range(*index.indices(self.shape[0]))) r = numpy.empty((len(indices), ) + self.shape[1:], dtype=self.dtype) for i, j in enumerate(indices): r[i] = self.planes[j][()] return r elif isinstance(index, tuple): if len(index) == 0: return self[:] if len(index) == 1: return self[index[0]] index0 = index[0] if isinstance(index0, int): return self.planes[index0][index[1:]] elif isinstance(index0, slice): indices = list(range(*index0.indices(self.shape[0]))) for i, j in enumerate(indices): s = self.planes[j][index[1:]] if i == 0: r = numpy.empty((len(indices), ) + s.shape, dtype=self.dtype) r[i] = s return r except IOError as msg: sys.stderr.write('%s.__getitem__:\n%s\n' % (self.__class__.__name__, msg)) sys.stderr.flush() return None raise NotImplementedError(repr(index)) def append(self, plane): """ Append tiff plane to tiff array. """ if self.planes: if not self.planes[0].check_same_shape_and_type(plane): raise TypeError('planes are not homogeneous (same shape and' ' sample type), expected %s but got %s' % ((self.planes[0].shape, self.dtype), (plane.shape, plane.dtype))) self.shape = (self.shape[0] + 1,) + self.shape[1:] else: self.dtype = plane.dtype self.shape = (1, ) + plane.shape self.sample_index = plane.sample_index self.planes.append(plane) def extend(self, other): """ Extend tiff array with the content of another. """ list(map(self.append, other.planes)) def get_voxel_sizes(self): """ Return ZYX voxel sizes in microns. """ return self.planes[0].ifd.get_voxel_sizes() def get_pixel_sizes(self): """ Return YX pixel sizes in microns. """ return self.planes[0].ifd.get_pixel_sizes() def get_time(self, index=0): """ Return time parameter of a plane. """ return self.planes[index].time @property def nbytes(self): return self.shape[0] * self.shape[1] \ * self.shape[2] * self.dtype.itemsize pylibtiff-0.6.1/libtiff/tiff_base.py000066400000000000000000000000331450302046000174070ustar00rootroot00000000000000 class TiffBase: pass pylibtiff-0.6.1/libtiff/tiff_channels_and_files.py000066400000000000000000000040231450302046000222770ustar00rootroot00000000000000 from .tiff_base import TiffBase class TiffChannelsAndFiles(TiffBase): """Represent a collection of TIFF files as a single TIFF source object. See also -------- TiffFile, TiffFiles """ def __init__(self, channels_files_map): """Parameters ---------- channels_files_map : dict A dictionary of channel names and TIFF files (``TiffFiles`` instances) """ self.channels_files_map = channels_files_map def get_tiff_array(self, channel, sample_index=0, subfile_type=0, assume_one_image_per_file=False): """ Return an array of images for given channel. Parameters ---------- channel : str The name of a channel. sample_index : int Specify sample within a pixel. subfile_type : int Specify TIFF NewSubfileType used for collecting sample images. assume_one_image_per_file : bool When True then it is assumed that each TIFF file contains exactly one image and all images have the same parameters. This knowledge speeds up tiff_array construction as only the first TIFF file is opened for reading image parameters. The other TIFF files are opened only when particular images are accessed. Returns ------- tiff_array : TiffArray Array of sample images. The array has rank equal to 3. """ return self.channels_files_map[channel].get_tiff_array( sample_index=sample_index, subfile_type=subfile_type, assume_one_image_per_file=assume_one_image_per_file) def get_info(self): lst = [] for channel, tiff in list(self.channels_files_map.items()): lst.append('Channel %s:' % (channel)) lst.append('-' * len(lst[-1])) lst.append(tiff.get_info()) return '\n'.join(lst) def close(self): for tiff in self.channels_files_map.values(): tiff.close() pylibtiff-0.6.1/libtiff/tiff_data.py000066400000000000000000000213601450302046000174140ustar00rootroot00000000000000""" Defines data for TIFF manipulations. """ __all__ = ['type2name', 'name2type', 'type2bytes', 'type2dtype', 'tag_value2name', 'tag_name2value', 'tag_value2type', 'LittleEndianNumpyDTypes', 'BigEndianNumpyDTypes', 'default_tag_values', 'sample_format_map'] import numpy # tag_info = ''' # standard tags: NewSubfileType FE LONG 1 SubfileType FF SHORT 1 ImageWidth 100 SHORT|LONG 1 ImageLength 101 SHORT|LONG 1 BitsPerSample 102 SHORT SamplesPerPixel Compression 103 SHORT 1 Uncompressed 1 CCITT1D 2 Group3Fax 3 Group4Fax 4 LZW 5 JPEG 6 PackBits 32773 PhotometricInterpretation 106 SHORT 1 WhiteIsZero 0 BlackIsZero 1 RGB 2 RGBPalette 3 TransparencyMask 4 CMYK 5 YCbCr 6 CIELab 8 Threshholding 107 SHORT 1 CellWidth 108 SHORT 1 CellLength 109 SHORT 1 FillOrder 10A SHORT 1 DocumentName 10D ASCII ImageDescription 10E ASCII Make 10F ASCII Model 110 ASCII StripOffsets 111 SHORT|LONG StripsPerImage Orientation 112 SHORT 1 TopLeft 1 TopRight 2 BottomRight 3 BottomLeft 4 LeftTop 5 RightTop 6 RightBottom 7 LeftBottom 8 SamplesPerPixel 115 SHORT 1 RowsPerStrip 116 SHORT|LONG 1 StripByteCounts 117 LONG|SHORT StripsPerImage MinSampleValue 118 SHORT SamplesPerPixel MaxSampleValue 119 SHORT SamplesPerPixel XResolution 11A RATIONAL 1 YResolution 11B RATIONAL 1 PlanarConfiguration 11C SHORT 1 Chunky 1 Planar 2 PageName 11D ASCII XPosition 11E DOUBLE YPosition 11F DOUBLE FreeOffsets 120 LONG FreeByteCounts 121 LONG GrayResponseUnit 122 SHORT 1 GrayResponseCurve 123 SHORT 2**BitsPerSample T4Options 124 LONG 1 T6Options 125 LONG 1 ResolutionUnit 128 SHORT 1 PageNumber 129 SHORT 2 TransferFunction 12D SHORT (1|SamplesPerPixel)*2**BitsPerSample Software 131 ASCII DateTime 132 ASCII 20 Artist 13B ASCII HostComputer 13C ASCII Predictor 13D SHORT 1 WhitePoint 13E RATIONAL 2 PrimaryChromaticities 13F RATIONAL 6 ColorMap 140 SHORT 3*(2**BitsPerSample) HalftoneHints 141 SHORT 2 TileWidth 142 SHORT|LONG 1 TileLength 143 SHORT|LONG 1 TileOffsets 144 LONG TilesPerImage TileByteCounts 145 SHORT|LONG TilesPerImage InkSet 14C SHORT 1 InkNames 14D ASCII # noqa: E501 NumberOfInks 14E SHORT 1 DotRange 150 BYTE|SHORT 2|2*NumberOfInks TargetPrinter 151 ASCII any ExtraSamples 152 BYTE SampleFormat 153 SHORT SamplesPerPixel SMinSampleValue 154 Any SamplesPerPixel SMaxSampleValue 155 Any SamplesPerPixel TransferRange 156 SHORT 6 JPEGProc 200 SHORT 1 JPEGInterchangeFormat 201 LONG 1 JPEGInterchangeFormatLength 202 LONG 1 JPEGRestartInterval 203 SHORT 1 JPEGLosslessPredictos 205 SHORT SamplesPerPixel JPEGPointTransforms 206 SHORT SamplesPerPixel JPEGQTables 207 LONG SamplesPerPixel JPEGDCTables 208 LONG SamplesPerPixel JPEGACTables 209 LONG SamplesPerPixel YCbCrCoefficients 211 RATIONAL 3 YCbCrSubSampling 212 SHORT 2 YCbCrPositioning 213 SHORT 1 ReferenceBlackWhite 214 LONG 2*SamplesPerPixel Copyright 8298 ASCII Any # non-standard tags: CZ_LSMInfo 866C CZ_LSM # EXIF tags, see # http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html EXIF_IFDOffset 8769 SHORT 1 EXIF_ExposureTime 829a RATIONAL 1 EXIF_FNumber 829d RATIONAL 1 EXIF_ExposureProgram 8822 SHORT 1 EXIF_SpectralSensitivity 8824 ASCII EXIF_ISOSpeedRatings 8827 SHORT 1 EXIF_OECF 8828 UNDEFINED EXIF_ExifVersion 9000 UNDEFINED 4 EXIF_DateTimeOriginal 9003 ASCII EXIF_DateTimeDigitized 9004 ASCII EXIF_ComponentsConfiguration 9101 UNDEFINED 4 EXIF_CompressedBitsPerPixel 9102 RATIONAL 1 EXIF_ShutterSpeedValue 9201 SRATIONAL 1 EXIF_ApertureValue 9202 RATIONAL 1 EXIF_BrightnessValue 9203 SRATIONAL 1 EXIF_ExposureBiasValue 9204 SRATIONAL 1 EXIF_MaxApertureValue 9205 RATIONAL 1 EXIF_SubjectDistance 9206 RATIONAL 1 EXIF_MeteringMode 9207 SHORT 1 EXIF_LightSource 9208 SHORT 1 EXIF_Flash 9209 SHORT 1 EXIF_FocalLength 920a RATIONAL 1 EXIF_SubjectArea 9214 SHORT 2|3|4 EXIF_MakerNote 927c UNDEFINED EXIF_UserComment 9286 UNDEFINED EXIF_SubsecTime 9290 ASCII EXIF_SubsecTimeOriginal 9291 ASCII EXIF_SubsecTimeDigitized 9292 ASCII EXIF_FlashpixVersion a000 UNDEFINED 4 EXIF_ColorSpace a001 SHORT 1 EXIF_PixelXDimension a002 SHORT!LONG 1 EXIF_PixelYDimension a003 SHORT!LONG 1 EXIF_RelatedSoundFile a004 ASCII 13 EXIF_FlashEnergy a20b RATIONAL 1 EXIF_SpatialFrequencyResponse a20c UNDEFINED EXIF_FocalPlaneXResolution a20e RATIONAL 1 EXIF_FocalPlaneYResolution a20f RATIONAL 1 EXIF_FocalPlaneResolutionUnit a210 SHORT 1 EXIF_SubjectLocation a214 SHORT 2 EXIF_ExposureIndex a215 RATIONAL 1 EXIF_SensingMethod a217 SHORT 1 EXIF_FileSource a300 UNDEFINED 1 EXIF_SceneType a301 UNDEFINED 1 EXIF_CFAPattern a302 UNDEFINED EXIF_CustomRendered a401 SHORT 1 EXIF_ExposureMode a402 SHORT 1 EXIF_WhiteBalance a403 SHORT 1 EXIF_DigitalZoomRatio a404 RATIONAL 1 EXIF_FocalLengthIn35mmFilm a405 SHORT 1 EXIF_SceneCaptureType a406 SHORT 1 EXIF_GainControl a407 SHORT 1 EXIF_Contrast a408 SHORT 1 EXIF_Saturation a409 SHORT 1 EXIF_Sharpness a40a SHORT 1 EXIF_DeviceSettingDescription a40b UNDEFINED EXIF_SubjectDistanceRange a40c SHORT 1 EXIF_ImageUniqueID a420 ASCII 33 ''' default_tag_values = dict(BitsPerSample=8, SampleFormat=1, RowsPerStrip=2**32 - 1, SamplesPerPixel=1, ExtraSamples=None, PlanarConfiguration=1, Compression=1, Predictor=1, NewSubfileType=0, Orientation=1, MaxSampleValue=None, MinSampleValue=None, DateTime=None, Artist=None, HostComputer=None, Software=None, ImageDescription=None, DocumentName=None, ResolutionUnit=2, XResolution=1, YResolution=1, FillOrder=1, XPosition=None, YPosition=None, Make=None, Model=None, Copyright=None,) rational = numpy.dtype([('numer', numpy.uint32), ('denom', numpy.uint32)]) srational = numpy.dtype([('numer', numpy.int32), ('denom', numpy.int32)]) type2name = {1: 'BYTE', 2: 'ASCII', 3: 'SHORT', 4: 'LONG', 5: 'RATIONAL', # two longs, lsm uses it for float64 6: 'SBYTE', 7: 'UNDEFINED', 8: 'SSHORT', 9: 'SLONG', 10: 'SRATIONAL', 11: 'FLOAT', 12: 'DOUBLE', } name2type = dict((v, k) for k, v in list(type2name.items())) name2type['SHORT|LONG'] = name2type['LONG'] name2type['LONG|SHORT'] = name2type['LONG'] type2bytes = {1: 1, 2: 1, 3: 2, 4: 4, 5: 8, 6: 1, 7: 1, 8: 2, 9: 4, 10: 8, 11: 4, 12: 8} type2dtype = {1: numpy.uint8, 2: numpy.uint8, 3: numpy.uint16, 4: numpy.uint32, 5: rational, 6: numpy.int8, 8: numpy.int16, 9: numpy.int32, 10: srational, 11: numpy.float32, 12: numpy.float64} tag_value2name = {} tag_name2value = {} tag_value2type = {} for line in tag_info.split('\n'): if not line or line.startswith('#'): continue if line[0] == ' ': pass else: n, h, t = line.split()[:3] h = eval('0x' + h) tag_value2name[h] = n tag_value2type[h] = t tag_name2value[n] = h sample_format_map = {1: 'uint', 2: 'int', 3: 'float', None: 'uint', 6: 'complex'} class NumpyDTypes: def get_dtype(self, sample_format, bits_per_sample): format = sample_format_map[sample_format] dtypename = '%s%s' % (format, bits_per_sample) return getattr(self, dtypename) class LittleEndianNumpyDTypes(NumpyDTypes): uint8 = numpy.dtype('u1') uint16 = numpy.dtype('>u2') uint32 = numpy.dtype('>u4') uint64 = numpy.dtype('>u8') int8 = numpy.dtype('>i1') int16 = numpy.dtype('>i2') int32 = numpy.dtype('>i4') int64 = numpy.dtype('>i8') float32 = numpy.dtype('>f4') float64 = numpy.dtype('>f8') complex64 = numpy.dtype('>c8') complex128 = numpy.dtype('>c16') @property def type2dt(self): return dict((k, numpy.dtype(v).newbyteorder('>')) for k, v in list(type2dtype.items())) BigEndianNumpyDTypes = BigEndianNumpyDTypes() pylibtiff-0.6.1/libtiff/tiff_file.py000066400000000000000000001245331450302046000174300ustar00rootroot00000000000000""" Provides TIFFfile class. """ # Author: Pearu Peterson # Created: June 2010 __all__ = ['TIFFfile', 'TiffFile'] import os import sys import shutil import warnings import numpy import mmap from .tiff_data import type2name, name2type, type2bytes, tag_value2name from .tiff_data import LittleEndianNumpyDTypes, BigEndianNumpyDTypes, \ default_tag_values, sample_format_map from .utils import bytes2str, isindisk from .tiff_base import TiffBase from .tiff_sample_plane import TiffSamplePlane from .tiff_array import TiffArray from . import lsm from . import tif_lzw IFDEntry_init_hooks = [] IFDEntry_finalize_hooks = [] IOError_too_many_open_files_hint = '''%s ====================================================================== Ubuntu Linux users: Check `ulimit -n`. To increase the number of open files limits, add the following lines * hard nofile 16384 * soft nofile 16384 to /etc/security/limits.conf and run `sudo start procps` ====================================================================== ''' OSError_operation_not_permitted_hint = '''%s ====================================================================== The exception may be due to unsufficient access rights or due to opening too many files for the given file system (NFS, for instance). ====================================================================== ''' class TIFFfile(TiffBase): """ Hold a TIFF file image stack that is accessed via memmap. To access image, use get_tiff_array method. Attributes ---------- filename : str data : memmap (or array when use_memmap is False) IFD : IFD-list See also -------- TiffFiles, TiffChannelsAndFiles """ def close(self): if hasattr(self, 'data'): if self.verbose: sys.stdout.write('Closing TIFF file %r\n' % (self.filename)) sys.stdout.flush() for ifd in self.IFD: ifd.close() if self.use_memmap: # newer numpy does not have memmap.close anymore [May 2012] # self.data.base.close() pass del self.data __del__ = close def __init__(self, filename, mode='r', first_byte=0, verbose=False, local_cache=None, use_memmap=True): """ local_cache : {None, str} Specify path to local cache. Local cache will be used to temporarily store files from external devises such as NFS. """ self.verbose = verbose self.first_byte = first_byte self.use_memmap = use_memmap try: if local_cache is not None: cache_filename = local_cache + '/' + filename if os.path.exists(cache_filename): filename = cache_filename elif not isindisk(filename): assert isindisk(local_cache), repr(local_cache) dirname = os.path.dirname(cache_filename) if not os.path.isdir(dirname): os.makedirs(dirname) shutil.copyfile(filename, cache_filename) filename = cache_filename if verbose: sys.stdout.write('Opening file %r\n' % (filename)) sys.stdout.flush() if mode != 'r': raise NotImplementedError(repr(mode)) if not os.path.isfile(filename): raise ValueError('file does not exists') if not os.stat(filename).st_size: raise ValueError('file has zero size') if use_memmap: self.data = numpy.memmap(filename, dtype=numpy.ubyte, mode=mode) else: assert mode == 'r', repr(mode) f = open(filename, 'rb') self.data = numpy.frombuffer(f.read(), dtype=numpy.ubyte) f.close() except IOError as msg: if 'Too many open files' in str(msg): raise IOError(IOError_too_many_open_files_hint % msg) if 'Operation not permitted' in str(msg): raise IOError(OSError_operation_not_permitted_hint % msg) raise except OSError as msg: if 'Operation not permitted' in str(msg): raise OSError(OSError_operation_not_permitted_hint % msg) raise except mmap.error as msg: if 'Too many open files' in str(msg): raise mmap.error(IOError_too_many_open_files_hint % msg) raise self.filename = filename self.memory_usage = [(self.data.nbytes, self.data.nbytes, 'eof')] byteorder = self.data[first_byte:first_byte + 2].view( dtype=numpy.uint16)[0] if byteorder == 0x4949: self.endian = 'little' self.dtypes = LittleEndianNumpyDTypes elif byteorder == 0x4d4d: self.endian = 'big' self.dtypes = BigEndianNumpyDTypes else: raise ValueError('unrecognized byteorder: %s' % (hex(byteorder))) magic = self.get_uint16(first_byte + 2) if magic != 42: raise ValueError('wrong magic number for TIFF file: %s' % (magic)) self.IFD0 = IFD0 = first_byte + self.get_uint32(first_byte + 4) self.memory_usage.append((first_byte, first_byte + 8, 'file header')) n = self.get_uint16(IFD0) IFD_list = [] IFD_offset = IFD0 while IFD_offset: n = self.get_uint16(IFD_offset) ifd = IFD(self) exif_offset = 0 for i in range(n): entry = IFDEntry(ifd, self, IFD_offset + 2 + i * 12) ifd.append(entry) if entry.tag == 0x8769: # TIFFTAG_EXIFIFD exif_offset = entry.value ifd.finalize() IFD_list.append(ifd) self.memory_usage.append((IFD_offset, IFD_offset + 2 + n * 12 + 4, 'IFD%s entries(%s)' % ( len(IFD_list), len(ifd)))) IFD_offset = self.get_uint32(IFD_offset + 2 + n * 12) if IFD_offset == 0 and exif_offset != 0: IFD_offset = exif_offset exif_offset = 0 if verbose: sys.stdout.write( '\rIFD information read: %s..' % (len(IFD_list))) sys.stdout.flush() self.IFD = IFD_list if verbose: sys.stdout.write(' done\n') sys.stdout.flush() self.time = None def set_time(self, time): self.time = time def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.filename) def get_uint16(self, offset): return self.data[offset:offset + 2].view(dtype=self.dtypes.uint16)[0] def get_uint32(self, offset): return self.data[offset:offset + 4].view(dtype=self.dtypes.uint32)[0] def get_int16(self, offset): return self.data[offset:offset + 2].view(dtype=self.dtypes.int16)[0] def get_int32(self, offset): return self.data[offset:offset + 4].view(dtype=self.dtypes.int32)[0] def get_float32(self, offset): return self.data[offset:offset + 4].view(dtype=self.dtypes.float32)[0] def get_float64(self, offset): return self.data[offset:offset + 8].view(dtype=self.dtypes.float64)[0] get_short = get_uint16 get_long = get_uint32 get_double = get_float64 def get_value(self, offset, typ): values = self.get_values(offset, typ, 1) if values is not None: return values[0] def get_values(self, offset, typ, count): if isinstance(typ, numpy.dtype): dtype = typ bytes = typ.itemsize elif isinstance(typ, type) and issubclass(typ, numpy.generic): dtype = typ bytes = typ().itemsize else: if isinstance(typ, str): ntyp = typ typ = name2type.get(typ) else: ntyp = str(typ) dtype = self.dtypes.type2dt.get(typ) bytes = type2bytes.get(typ) if dtype is None or bytes is None: warnings.warn('incomplete info for type=%r [%r]: dtype=%s, bytes=%s\n' % ( typ, ntyp, dtype, bytes), stacklevel=2) return return self.data[offset:offset + bytes * count].view(dtype=dtype) def get_string(self, offset, length=None): if length is None: i = 0 while self.data[offset + i]: i += 1 length = i string = self.get_values(offset, 'BYTE', length).tostring() return string def check_memory_usage(self, verbose=True): '''Check memory usage of TIFF fields and blocks. Returns ------- ok : bool Return False if unknown or overlapping memory areas have been detected. ''' lst = [] lst.extend(self.memory_usage) for ifd in self.IFD: lst.extend(ifd.memory_usage) lst.sort() last_end = None ok = True for start, end, resource in lst: if last_end: if last_end != start: if verbose: print('--- unknown %s bytes' % (start - last_end)) ok = False if start < last_end and verbose: print('--- overlapping memory area') if verbose: print('%s..%s[%s] contains %s' % ( start, end, end - start, resource)) last_end = end return ok def is_contiguous(self): end = None for i, ifd in enumerate(self.IFD): strip_offsets = ifd.get('StripOffsets').value strip_nbytes = ifd.get('StripByteCounts').value if not ifd.is_contiguous(): return False if i == 0: pass else: if isinstance(strip_offsets, numpy.ndarray): start = strip_offsets[0] else: start = strip_offsets if end != start: return False if isinstance(strip_offsets, numpy.ndarray): end = strip_offsets[-1] + strip_nbytes[-1] else: end = strip_offsets + strip_nbytes return True def get_contiguous(self): """ Return memmap of a stack of images. """ if not self.is_contiguous(): raise ValueError('Image stack data not contiguous') ifd0 = self.IFD[0] ifd1 = self.IFD[-1] width = ifd0.get('ImageWidth').value length = ifd0.get('ImageLength').value assert width == ifd1.get('ImageWidth').value assert length == ifd1.get('ImageLength').value depth = len(self.IFD) compression = ifd0.get('Compression').value if compression != 1: raise ValueError( 'Unable to get contiguous image stack from compressed data') bits_per_sample = ifd0.get('BitsPerSample').value # photo_interp = ifd0.get('PhotometricInterpretation').value # planar_config = ifd0.get('PlanarConfiguration').value strip_offsets0 = ifd0.get('StripOffsets').value # strip_nbytes0 = ifd0.get('StripByteCounts').value strip_offsets1 = ifd1.get('StripOffsets').value strip_nbytes1 = ifd1.get('StripByteCounts').value samples_per_pixel = ifd1.get('SamplesPerPixel').value assert samples_per_pixel == 1, repr(samples_per_pixel) if isinstance(bits_per_sample, numpy.ndarray): dtype = getattr(self.dtypes, 'uint%s' % (bits_per_sample[0])) else: dtype = getattr(self.dtypes, 'uint%s' % (bits_per_sample)) if isinstance(strip_offsets0, numpy.ndarray): start = strip_offsets0[0] end = strip_offsets1[-1] + strip_nbytes1[-1] else: start = strip_offsets0 end = strip_offsets1 + strip_nbytes1 return self.data[start:end].view(dtype=dtype).reshape( (depth, width, length)) def get_subfile_types(self): """ Return a list of subfile types. """ s = set() for ifd in self.IFD: s.add(ifd.get_value('NewSubfileType')) return sorted(s) def get_depth(self, subfile_type=0): depth = 0 for ifd in self.IFD: if ifd.get_value('NewSubfileType') == subfile_type: depth += 1 return depth def get_first_ifd(self, subfile_type=0): """Return the first IFD entry with given subfile type. Parameters ---------- subfile_type : {0, 1, 2, 4} Specify subfile type. 0: image, 1: reduced image, 2: single page, 4: transparency mask. Returns ------- ifd : IFDEntry """ for ifd in self.IFD: if ifd.get_value('NewSubfileType') == subfile_type: return ifd def get_tiff_array(self, sample_index=0, subfile_type=0): """ Create array of sample images. Parameters ---------- sample_index : int Specify sample within a pixel. subfile_type : int Specify TIFF NewSubfileType used for collecting sample images. Returns ------- tiff_array : TiffArray Array of sample images. The array has rank equal to 3. """ planes = [] index = 0 time_lst = self.time for ifd in self.IFD: if ifd.get_value('NewSubfileType', subfile_type) != subfile_type: # subfile_type: 0: image, 1: reduced image, # 2: single page, 4: transparency mask continue plane = TiffSamplePlane(ifd, sample_index=sample_index) if time_lst is not None: plane.set_time(time_lst[index]) planes.append(plane) index += 1 tiff_array = TiffArray(planes) return tiff_array def get_samples(self, subfile_type=0, verbose=False): """Return samples and sample names. Parameters ---------- subfile_type : {0, 1} Specify subfile type. Subfile type 1 corresponds to reduced resolution image. verbose : bool When True the print out information about samples Returns ------- samples : list List of numpy.memmap arrays of samples sample_names : list List of the corresponding sample names """ lst = [] i = 0 step = 0 can_return_memmap = True ifd_lst = [ifd for ifd in self.IFD if ifd.get_value('NewSubfileType') == subfile_type] depth = len(ifd_lst) full_l = [] for ifd in ifd_lst: if not ifd.is_contiguous(): raise NotImplementedError('none contiguous strips') strip_offsets = ifd.get_value('StripOffsets') strip_nbytes = ifd.get_value('StripByteCounts') lst.append( (strip_offsets[0], strip_offsets[-1] + strip_nbytes[-1])) for off, nb in zip(strip_offsets, strip_nbytes): full_l.append((off, off + nb)) if i == 0: compression = ifd.get_value('Compression') if compression != 1: can_return_memmap = False width = ifd.get_value('ImageWidth') length = ifd.get_value('ImageLength') samples_per_pixel = ifd.get_value('SamplesPerPixel') planar_config = ifd.get_value('PlanarConfiguration') bits_per_sample = ifd.get_value('BitsPerSample') sample_format = ifd.get_value('SampleFormat')[0] photometric_interpretation = ifd.get_value( 'PhotometricInterpretation') if self.is_lsm or not isinstance(strip_offsets, numpy.ndarray): strips_per_image = 1 else: strips_per_image = len(strip_offsets) format = sample_format_map.get(sample_format) if format is None: print( 'Warning(TIFFfile.get_samples): unsupported' ' sample_format=%s is mapped to uint' % ( sample_format)) format = 'uint' dtype_lst = [] bits_per_pixel = 0 for j in range(samples_per_pixel): bits = bits_per_sample[j] bits_per_pixel += bits dtype = getattr(self.dtypes, '%s%s' % (format, bits)) dtype_lst.append(dtype) bytes_per_pixel = bits_per_pixel // 8 assert 8 * bytes_per_pixel == bits_per_pixel, repr( bits_per_pixel) bytes_per_row = width * bytes_per_pixel strip_length = lst[-1][1] - lst[-1][0] strip_length_str = bytes2str(strip_length) bytes_per_image = length * bytes_per_row rows_per_strip = bytes_per_image // ( bytes_per_row * strips_per_image) if bytes_per_image % (bytes_per_row * strips_per_image): rows_per_strip += 1 assert rows_per_strip == ifd.get_value('RowsPerStrip', rows_per_strip), \ repr((rows_per_strip, ifd.get_value('RowsPerStrip'), bytes_per_image, bytes_per_row, strips_per_image, self.filename)) else: assert width == ifd.get_value('ImageWidth', width), repr( (width, ifd.get_value('ImageWidth'))) assert length == ifd.get_value('ImageLength', length), repr( (length, ifd.get_value('ImageLength'))) # assert samples_per_pixel == ifd.get( # 'SamplesPerPixel').value, `samples_per_pixel, ifd.get( # 'SamplesPerPixel').value` assert planar_config == ifd.get_value('PlanarConfiguration', planar_config) if can_return_memmap: assert strip_length == lst[-1][1] - lst[-1][0], repr( (strip_length, lst[-1][1] - lst[-1][0])) else: strip_length = max(strip_length, lst[-1][1] - lst[-1][0]) strip_length_str = ' < ' + bytes2str(strip_length) assert (bits_per_sample == ifd.get_value( 'BitsPerSample', bits_per_sample)).all(), repr( (bits_per_sample, ifd.get_value('BitsPerSample'))) if i > 0: if i == 1: step = lst[-1][0] - lst[-2][1] assert step >= 0, repr((step, lst[-2], lst[-1])) else: if step != lst[-1][0] - lst[-2][1]: can_return_memmap = False i += 1 if verbose: bytes_per_image_str = bytes2str(bytes_per_image) print(''' width : %(width)s length : %(length)s depth : %(depth)s sample_format : %(format)s samples_per_pixel : %(samples_per_pixel)s planar_config : %(planar_config)s bits_per_sample : %(bits_per_sample)s bits_per_pixel : %(bits_per_pixel)s bytes_per_pixel : %(bytes_per_pixel)s bytes_per_row : %(bytes_per_row)s bytes_per_image : %(bytes_per_image_str)s strips_per_image : %(strips_per_image)s rows_per_strip : %(rows_per_strip)s strip_length : %(strip_length_str)s ''' % (locals())) if photometric_interpretation == 2: assert samples_per_pixel == 3, repr(samples_per_pixel) sample_names = ['red', 'green', 'blue'] else: sample_names = ['sample%s' % (j) for j in range(samples_per_pixel)] depth = i if not can_return_memmap: if planar_config == 1: if samples_per_pixel == 1: i = 0 arr = numpy.empty(depth * bytes_per_image, dtype=self.dtypes.uint8) bytes_per_strip = rows_per_strip * bytes_per_row for start, end in full_l: if compression == 1: # none d = self.data[start:end] elif compression == 5: # lzw d = self.data[start:end] d = tif_lzw.decode(d, bytes_per_strip) arr[i:i + d.nbytes] = d i += d.nbytes arr = arr.view(dtype=dtype_lst[0]).reshape( (depth, length, width)) return [arr], sample_names else: i = 0 arr = numpy.empty(depth * bytes_per_image, dtype=self.dtypes.uint8) bytes_per_strip = rows_per_strip * bytes_per_row for start, end in full_l: sys.stdout.write("%s:%s," % (start, end)) sys.stdout.flush() if compression == 1: # none d = self.data[start:end] elif compression == 5: # lzw d = self.data[start:end] d = tif_lzw.decode(d, bytes_per_strip) arr[i:i + d.nbytes] = d i += d.nbytes dt = numpy.dtype( dict(names=sample_names, formats=dtype_lst)) arr = arr.view(dtype=dt).reshape((depth, length, width)) return [arr[n] for n in arr.dtype.names], arr.dtype.names raise NotImplementedError( repr((depth, bytes_per_image, samples_per_pixel))) else: raise NotImplementedError(repr(planar_config)) start = lst[0][0] end = lst[-1][1] if start > step: arr = self.data[start - step: end].reshape( (depth, strip_length + step)) k = step elif end <= self.data.size - step: arr = self.data[start: end + step].reshape( (depth, strip_length + step)) k = 0 else: raise NotImplementedError(repr((start, end, step))) sys.stdout.flush() if planar_config == 2: if self.is_lsm: # LSM510: one strip per image plane channel if subfile_type == 0: sample_names = self.lsminfo.get('data channel name') elif subfile_type == 1: sample_names = ['red', 'green', 'blue'] assert samples_per_pixel == 3, repr(samples_per_pixel) else: raise NotImplementedError(repr(subfile_type)) samples = [] for j in range(samples_per_pixel): bytes = bits_per_sample[j] // 8 * width * length tmp = arr[:, k:k + bytes] tmp = tmp.view(dtype=dtype_lst[j]) tmp = tmp.reshape((depth, length, width)) samples.append(tmp) k += bytes return samples, sample_names raise NotImplementedError(repr((planar_config, self.is_lsm))) elif planar_config == 1: samples = [] bytes = sum( bits_per_sample[:samples_per_pixel]) // 8 * width * length bytes_per_sample = bits_per_sample // 8 for j in range(samples_per_pixel): i0 = k + j * bytes_per_sample[j] # print j, i0, i0+bytes, samples_per_pixel, arr.shape tmp = arr[:, i0:i0 + bytes:samples_per_pixel] tmp = numpy.array(tmp.reshape((tmp.size,))) tmp = tmp.view(dtype=dtype_lst[j]) tmp = tmp.reshape((depth, length, width)) samples.append(tmp) # k += bytes return samples, sample_names else: raise NotImplementedError(repr(planar_config)) def get_info(self): """ Return basic information about the file. """ lst = [] subfile_types = self.get_subfile_types() lst.append('Number of subfile types: %s' % (len(subfile_types))) for subfile_type in subfile_types: ifd = self.get_first_ifd(subfile_type=subfile_type) lst.append('-' * 50) lst.append('Subfile type: %s' % (subfile_type)) lst.append('Number of images: %s' % ( self.get_depth(subfile_type=subfile_type))) for tag in ['ImageLength', 'ImageWidth', 'SamplesPerPixel', 'ExtraSamples', 'SampleFormat', 'Compression', 'Predictor', 'PhotometricInterpretation', 'Orientation', 'PlanarConfiguration', 'MinSampleValue', 'MaxSampleValue', 'XResolution', 'YResolution', 'ResolutionUnit', 'XPosition', 'YPosition', 'DocumentName', 'Software', 'HostComputer', 'Artist', 'DateTime', 'Make', 'Model', 'Copyright', 'ImageDescription', ]: v = ifd.get_value(tag, human=True) if v is None: continue if tag == 'ImageDescription' and subfile_type == 0: if v.startswith(' 0: raise IndexError(repr(index)) return self.offset[0] return self.values[index] def add_value(self, value): if isinstance(value, (list, tuple)): list(map(self.add_value, value)) elif self.type_name == 'ASCII': value = str(value) if self.count[0] == 0: self.values.append(value) else: self.values[0] += value self.count[0] = len(self.values[0]) + 1 elif self.type_nbytes <= 4: self.count[0] += 1 if self.count[0] == 1: self.offset[0] = value elif self.count[0] == 2: self.values.append(self.offset[0]) self.values.append(value) self.offset[0] = 0 else: self.values.append(value) else: self.count[0] += 1 self.values.append(value) def set_value(self, value): assert self.type_name != 'ASCII', repr(self) if self.count[0]: self.count[0] -= 1 if self.values: del self.values[-1] self.add_value(value) def set_offset(self, offset): self.offset[0] = offset def toarray(self, target=None): if self.offset_is_value: return if target is None: target = numpy.zeros((self.nbytes,), dtype=numpy.ubyte) dtype = target.dtype offset = 0 # print(self.values) # print(dtype) if self.type_name == 'ASCII': data = numpy.array([self.values[0] + '\0']) # print(type(data), data) data = numpy.array([self.values[0] + '\0'], dtype='|S{}'.format(len(self.values[0]) + 1)).view(dtype=numpy.ubyte) # print(type(data), data) target[offset:offset + self.nbytes] = data else: for value in self.values: dtype = self.type_dtype if self.type_name == 'RATIONAL' and isinstance(value, (int, float)): dtype = numpy.float64 target[offset:offset + self.type_nbytes].view(dtype=dtype)[ 0] = value offset += self.type_nbytes return target class TIFFimage: """ Hold an image stack that can be written to TIFF file. """ def __init__(self, data, description=''): """ data : {list, numpy.ndarray} Specify image data as a list of images or as an array with rank<=3. """ # dtype = None if isinstance(data, list): image = data[0] self.length, self.width = image.shape self.depth = len(data) dtype = image.dtype elif isinstance(data, numpy.ndarray): shape = data.shape dtype = data.dtype if len(shape) == 1: self.width, = shape self.length = 1 self.depth = 1 data = [[data]] elif len(shape) == 2: self.length, self.width = shape self.depth = 1 data = [data] elif len(shape) == 3: self.depth, self.length, self.width = shape else: raise NotImplementedError(repr(shape)) else: raise NotImplementedError(repr(type(data))) self.data = data self.dtype = dtype self.description = description # noinspection PyProtectedMember def write_file(self, filename, compression='none', strip_size=2 ** 13, planar_config=1, validate=False, verbose=None): """ Write image data to TIFF file. Parameters ---------- filename : str compression : {'none', 'lzw'} strip_size : int Specify the size of uncompressed strip. planar_config : int validate : bool When True then check compression by decompression. verbose : {bool, None} When True then write progress information to stdout. When None then verbose is assumed for data that has size over 1MB. Returns ------- compression : float Compression factor. """ if verbose is None: nbytes = self.depth * self.length * self.width * \ self.dtype.itemsize verbose = nbytes >= 1024 ** 2 if os.path.splitext(filename)[1].lower() not in ['.tif', '.tiff']: filename += '.tif' if verbose: sys.stdout.write('Writing TIFF records to %s\n' % filename) sys.stdout.flush() compression_map = dict(packbits=32773, none=1, lzw=5, jpeg=6, ccitt1d=2, group3fax=3, group4fax=4 ) compress_map = dict(none=lambda _data: _data, lzw=tif_lzw.encode) decompress_map = dict(none=lambda _data, _bytes: _data, lzw=tif_lzw.decode) compress = compress_map.get(compression or 'none', None) if compress is None: raise NotImplementedError(repr(compression)) decompress = decompress_map.get(compression or 'none', None) # compute tif file size and create image file directories data image_directories = [] total_size = 8 data_size = 0 image_data_size = 0 for i, image in enumerate(self.data): if verbose: sys.stdout.write('\r creating records: %5s%% done ' % ( int(100.0 * i / len(self.data)))) sys.stdout.flush() if image.dtype.kind == 'V' and len( image.dtype.names) == 3: # RGB image sample_format = dict(u=1, i=2, f=3, c=6).get( image.dtype.fields[image.dtype.names[0]][0].kind) bits_per_sample = [image.dtype.fields[f][0].itemsize * 8 for f in image.dtype.names] samples_per_pixel = 3 photometric_interpretation = 2 else: # gray scale image sample_format = dict(u=1, i=2, f=3, c=6).get(image.dtype.kind) bits_per_sample = image.dtype.itemsize * 8 samples_per_pixel = 1 photometric_interpretation = 1 if sample_format is None: print('Warning(TIFFimage.write_file): unknown data kind %r, ' 'mapping to void' % image.dtype.kind) sample_format = 4 length, width = image.shape bytes_per_row = width * image.dtype.itemsize rows_per_strip = min(length, int(numpy.ceil( float(strip_size) / bytes_per_row))) strips_per_image = int( numpy.floor(float( length + rows_per_strip - 1) / rows_per_strip)) assert bytes_per_row * rows_per_strip * \ strips_per_image >= image.nbytes d = dict(ImageWidth=width, ImageLength=length, Compression=compression_map.get(compression, 1), PhotometricInterpretation=photometric_interpretation, PlanarConfiguration=planar_config, Orientation=1, ResolutionUnit=1, XResolution=1, YResolution=1, SamplesPerPixel=samples_per_pixel, RowsPerStrip=rows_per_strip, BitsPerSample=bits_per_sample, SampleFormat=sample_format, ) if i == 0: d.update(dict( ImageDescription=self.description, Software='http://code.google.com/p/pylibtiff/')) entries = [] for tagname, value in list(d.items()): entry = TIFFentry(tagname) entry.add_value(value) entries.append(entry) total_size += 12 + entry.nbytes data_size += entry.nbytes strip_byte_counts = TIFFentry('StripByteCounts') strip_offsets = TIFFentry('StripOffsets') entries.append(strip_byte_counts) entries.append(strip_offsets) # strip_offsets and strip_byte_counts will be filled in the next # loop if strips_per_image == 1: assert strip_byte_counts.type_nbytes <= 4 assert strip_offsets.type_nbytes <= 4 total_size += 2 * 12 else: total_size += 2 * 12 + strips_per_image * ( strip_byte_counts.type_nbytes + strip_offsets.type_nbytes) data_size += strips_per_image * ( strip_byte_counts.type_nbytes + strip_offsets.type_nbytes) # image data: total_size += image.nbytes data_size += image.nbytes image_data_size += image.nbytes # records for nof IFD entries and offset to the next IFD: total_size += 2 + 4 # entries must be sorted by tag number entries.sort(key=lambda x: x.tag) strip_info = strip_offsets, strip_byte_counts, strips_per_image, \ rows_per_strip, bytes_per_row image_directories.append((entries, strip_info, image)) tif = numpy.memmap(filename, dtype=numpy.ubyte, mode='w+', shape=(total_size,)) # noinspection PyProtectedMember def tif_write(_tif, _offset, _data): end = _offset + _data.nbytes if end > _tif.size: size_incr = int( float(end - _tif.size) / 1024 ** 2 + 1) * 1024 ** 2 new_size = _tif.size + size_incr assert end <= new_size, repr( (end, _tif.size, size_incr, new_size)) # sys.stdout.write('resizing: %s -> %s\n' % (tif.size, # new_size)) # tif.resize(end, refcheck=False) _base = _tif._mmap if _base is None: _base = _tif.base _base.resize(new_size) new_tif = numpy.ndarray.__new__(numpy.memmap, (_base.size(),), dtype=_tif.dtype, buffer=_base) new_tif._parent = _tif new_tif.__array_finalize__(_tif) _tif = new_tif _tif[_offset:end] = _data return _tif # write TIFF header tif[:2].view(dtype=numpy.uint16)[0] = 0x4949 # low-endian tif[2:4].view(dtype=numpy.uint16)[0] = 42 # magic number tif[4:8].view(dtype=numpy.uint32)[0] = 8 # offset to the first IFD offset = 8 data_offset = total_size - data_size image_data_offset = total_size - image_data_size first_data_offset = data_offset first_image_data_offset = image_data_offset start_time = time.time() compressed_data_size = 0 for i, (entries, strip_info, image) in enumerate(image_directories): strip_offsets, strip_byte_counts, strips_per_image, \ rows_per_strip, bytes_per_row = strip_info # write the nof IFD entries tif[offset:offset + 2].view(dtype=numpy.uint16)[0] = len(entries) offset += 2 assert offset <= first_data_offset, repr( (offset, first_data_offset)) # write image data data = image.view(dtype=numpy.ubyte).reshape((image.nbytes,)) for j in range(strips_per_image): c = rows_per_strip * bytes_per_row k = j * c c -= max((j + 1) * c - image.nbytes, 0) assert c > 0, repr(c) orig_strip = data[k:k + c] # type: numpy.ndarray strip = compress(orig_strip) if validate: test_strip = decompress(strip, orig_strip.nbytes) if (orig_strip != test_strip).any(): raise RuntimeError( 'Compressed data is corrupted: cannot recover ' 'original data') compressed_data_size += strip.nbytes # print strip.size, strip.nbytes, strip.shape, # tif[image_data_offset:image_data_offset+strip.nbytes].shape strip_offsets.add_value(image_data_offset) strip_byte_counts.add_value(strip.nbytes) tif = tif_write(tif, image_data_offset, strip) image_data_offset += strip.nbytes # if j == 0: # first = strip_offsets[0] # last = strip_offsets[-1] + strip_byte_counts[-1] # write IFD entries for entry in entries: data_size = entry.nbytes if data_size: entry.set_offset(data_offset) assert data_offset + data_size <= total_size, repr( (data_offset + data_size, total_size)) r = entry.toarray(tif[data_offset:data_offset + data_size]) assert r.nbytes == data_size data_offset += data_size assert data_offset <= first_image_data_offset, repr( (data_offset, first_image_data_offset, i)) tif[offset:offset + 12] = entry.record offset += 12 assert offset <= first_data_offset, repr( (offset, first_data_offset, i)) # write offset to the next IFD tif[offset:offset + 4].view(dtype=numpy.uint32)[0] = offset + 4 offset += 4 assert offset <= first_data_offset, repr( (offset, first_data_offset)) if verbose: sys.stdout.write( '\r filling records: %5s%% done (%s/s)%s' % (int(100.0 * (i + 1) / len(image_directories)), bytes2str(int(float(image_data_offset - first_image_data_offset) / (time.time() - start_time))), ' ' * 2)) if (i + 1) == len(image_directories): sys.stdout.write('\n') sys.stdout.flush() # last offset must be 0 tif[offset - 4:offset].view(dtype=numpy.uint32)[0] = 0 compression = 1 / (float(compressed_data_size) / image_data_size) if compressed_data_size != image_data_size: sdiff = image_data_size - compressed_data_size total_size -= sdiff base = tif._mmap if base is None: base = tif.base base.resize(total_size) if verbose: sys.stdout.write( ' resized records: %s -> %s (compression: %.2fx)\n' % (bytes2str(total_size + sdiff), bytes2str(total_size), compression)) sys.stdout.flush() del tif # flushing return compression pylibtiff-0.6.1/libtiff/tiff_sample_plane.py000066400000000000000000000273751450302046000211570ustar00rootroot00000000000000""" Implements TIFF sample plane. """ # Author: Pearu Peterson # Created: Jan 2011 import numpy from . import tif_lzw __all__ = ['TiffSamplePlane'] def set_array(output_array, input_array): dtype = numpy.uint8 numpy.frombuffer(output_array.data, dtype=dtype)[:] = numpy.frombuffer( input_array.data, dtype=dtype) class TiffSamplePlane: """ Image of a single sample in a TIFF image file directory. """ def __init__(self, ifd, sample_index=0): """Construct TiffSamplePlane instance. Parameters ---------- ifd : `libtiff.tiff_file.IFDEntry` sample_index : int Specify sample index. When None then interpret pixel as a sample. """ self.ifd = ifd self.sample_index = sample_index self.planar_config = planar_config = ifd.get_value( 'PlanarConfiguration') self.samples_per_pixel = samples_per_pixel = ifd.get_value( 'SamplesPerPixel') if sample_index is not None and sample_index >= samples_per_pixel: raise IndexError('sample index %r must be less that nof samples %r' % (sample_index, samples_per_pixel)) pixels_per_row = ifd.get_value('ImageWidth') rows_of_pixels = ifd.get_value('ImageLength') self.shape = (int(rows_of_pixels), int(pixels_per_row)) rows_per_strip = ifd.get_value('RowsPerStrip') strips_per_image = (rows_of_pixels + rows_per_strip - 1) // rows_per_strip rows_per_strip = min(rows_of_pixels, rows_per_strip) self.rows_per_strip = rows_per_strip self.strip_offsets = ifd.get_value('StripOffsets') self.strip_nbytes = ifd.get_value('StripByteCounts') self.sample_format = ifd.get_value('SampleFormat') self.bits_per_sample = bits_per_sample = ifd.get_value('BitsPerSample') bits_per_pixel = sum(bits_per_sample) assert bits_per_pixel % 8 == 0, repr((bits_per_pixel, bits_per_sample)) bytes_per_pixel = bits_per_pixel // 8 bytes_per_row = bytes_per_pixel * pixels_per_row bytes_per_strip = rows_per_strip * bytes_per_row sample_names = ifd.get_sample_names() pixel_dtype = ifd.get_pixel_dtype() sample_offset = 0 if sample_index is None: dtype = pixel_dtype sample_names = ['pixel'] sample_name = 'pixel' else: dtype = ifd.get_sample_dtypes()[sample_index] sample_name = sample_names[sample_index] if planar_config == 1: sample_offset = sum(bits_per_sample[:sample_index]) // 8 bytes_per_row = pixels_per_row * bytes_per_pixel # uncompressed sample_offset = 0 if planar_config == 1 or sample_index is None: bytes_per_sample_row = bytes_per_row else: bytes_per_sample_row = bytes_per_row // samples_per_pixel self.dtype = dtype self.pixel_dtype = pixel_dtype self.bytes_per_pixel = bytes_per_pixel self.bytes_per_row = bytes_per_row self.bytes_per_sample_image = bytes_per_sample_row * rows_of_pixels self.uncompressed_bytes_per_strip = bytes_per_strip self.compression = compression = ifd.get_value('Compression') self.sample_name = sample_name self.sample_offset = sample_offset self.bytes_per_sample_row = bytes_per_sample_row self.strips_per_image = strips_per_image self.is_contiguous = compression == 1 and ifd.is_contiguous() time = None descr = str(ifd.get_value('ImageDescription', human=True)) if descr is not None: if descr.startswith(' self.shape[0] or index < 0: raise IndexError('Row index %r out of bounds [0,%r]' % (index, self.shape[0] - 1)) if self.planar_config == 1: # RGBRGB.. strip_index, row_index = divmod(index, self.rows_per_strip) else: # RR..GG..BB.. index2 = self.sample_index * self.shape[0] + index strip_index, row_index = divmod(index2, self.rows_per_strip) start = self.strip_offsets[strip_index] stop = start + self.strip_nbytes[strip_index] if self.compression == 1: strip = self.ifd.tiff.data[start:stop] else: compressed_strip = self.ifd.tiff.data[start:stop] if self.compression == 5: # lzw strip = tif_lzw.decode(compressed_strip, self.uncompressed_bytes_per_strip) else: raise NotImplementedError(repr(self.compression)) start = row_index * self.bytes_per_sample_row + self.sample_offset stop = start + self.bytes_per_sample_row + self.sample_offset if isinstance(subindex, tuple): if len(subindex) == 1: subindex = subindex[0] if self.planar_config == 1: if isinstance(subindex, int): start = start + subindex * self.bytes_per_pixel stop = start + self.bytes_per_pixel return strip[start:stop].view( dtype=self.pixel_dtype)[self.sample_name][0] row = strip[start:stop].view( dtype=self.pixel_dtype)[self.sample_name] if not row.size: print(self.get_topology()) else: row = strip[start:stop].view(dtype=self.dtype) if subindex is not None: return row[subindex] return row def get_rows(self, index, subindex=None): if isinstance(index, int): r = self.get_row(index, subindex=subindex) return r.reshape((1, ) + r.shape) if isinstance(index, slice): indices = list(range(*index.indices(self.shape[0]))) for i, j in enumerate(indices): s = self.get_row(j, subindex=subindex) if i == 0: r = numpy.empty((len(indices), ) + s.shape, dtype=self.dtype) r[i] = s return r if isinstance(index, tuple): if len(index) == 1: return self[index[0]] raise NotImplementedError(repr(index)) def get_image(self): if self.is_contiguous: if self.planar_config == 1: start = self.strip_offsets[0] + self.sample_offset stop = self.strip_offsets[-1] + self.strip_nbytes[-1] image = self.ifd.tiff.data[start:stop].view( dtype=self.pixel_dtype) image = image[self.sample_name].reshape(self.shape) return image else: if self.sample_index is None: start = self.strip_offsets[0] else: start = self.strip_offsets[0] + self.sample_index * \ self.bytes_per_sample_image stop = start + self.bytes_per_sample_image image = self.ifd.tiff.data[start:stop] image = image.view(dtype=self.dtype).reshape(self.shape) return image else: image = numpy.empty((self.bytes_per_sample_image, ), dtype=numpy.uint8) offset = 0 for strip_index in range(len(self.strip_offsets)): start = self.strip_offsets[strip_index] stop = start + self.strip_nbytes[strip_index] if self.compression == 1: strip = self.ifd.tiff.data[start:stop] else: compressed_strip = self.ifd.tiff.data[start:stop] if self.compression == 5: # lzw strip = tif_lzw.decode( compressed_strip, self.uncompressed_bytes_per_strip) else: raise NotImplementedError(repr(self.compression)) target = image[offset:offset + strip.nbytes] if target.nbytes < strip.nbytes: print('%s.get_image warning: tiff data contains %s extra' 'bytes (compression=%r) that are ignored' % (self.__class__.__name__, strip.nbytes - target.nbytes, self.compression)) image[offset:offset + strip.nbytes] = strip[:target.nbytes] offset += strip.nbytes image = image.view(dtype=self.dtype).reshape(self.shape) return image def __len__(self): return self.shape[0] def __getitem__(self, index): if isinstance(index, int): return self.get_row(index) elif isinstance(index, slice): return self.get_image()[index] elif isinstance(index, tuple): if len(index) == 0: return self.get_image() if len(index) == 1: return self[index[0]] index0 = index[0] if isinstance(index0, int): return self.get_row(index0, index[1:]) return self.get_image()[index] raise NotImplementedError(repr(index)) class TiffSamplePlaneLazy(TiffSamplePlane): def __init__(self, tiff_file_getter): self.tiff_file_getter = tiff_file_getter self.time = None self._ifd = None @property def ifd(self): ifd = self._ifd if ifd is None: tiff = self.tiff_file_getter() assert len(tiff.IFD) == 1, repr(len(tiff.IFD)) self._ifd = ifd = tiff.IFD[0] return ifd @property def strip_offsets(self): return self.ifd.get_value('StripOffsets') @property def strip_nbytes(self): return self.ifd.get_value('StripByteCounts') @property def compression(self): return self.ifd.get_value('Compression') @property def is_contiguous(self): return self.compression == 1 and self.ifd.is_contiguous() def copy_attrs(self, other): for attr in ['sample_index', 'planar_config', 'samples_per_pixel', 'shape', 'rows_per_strip', 'sample_format', 'bits_per_sample', 'dtype', 'pixel_dtype', 'bytes_per_pixel', 'bytes_per_row', 'bytes_per_sample_image', 'uncompressed_bytes_per_strip', 'sample_name', 'sample_offset', 'bytes_per_sample_row', 'strips_per_image']: setattr(self, attr, getattr(other, attr)) pylibtiff-0.6.1/libtiff/utils.py000066400000000000000000000142071450302046000166350ustar00rootroot00000000000000# Author: Pearu Peterson # Created: June 2010 __all__ = ['bytes2str', 'isindisk'] import os import optparse VERBOSE = False def isindisk(path): """ Return True if path is stored in a local disk. """ return os.major(os.stat(path).st_dev) in [3, # HD 8, # SCSI ] def bytes2str(bytes): lst = [] Pbytes = bytes // 1024**5 if Pbytes: lst.append('%sPi' % (Pbytes)) bytes = bytes - 1024**5 * Pbytes Tbytes = bytes // 1024**4 if Tbytes: lst.append('%sTi' % (Tbytes)) bytes = bytes - 1024**4 * Tbytes Gbytes = bytes // 1024**3 if Gbytes: lst.append('%sGi' % (Gbytes)) bytes = bytes - 1024**3 * Gbytes Mbytes = bytes // 1024**2 if Mbytes: lst.append('%sMi' % (Mbytes)) bytes = bytes - 1024**2 * Mbytes kbytes = bytes // 1024 if kbytes: lst.append('%sKi' % (kbytes)) bytes = bytes - 1024 * kbytes if bytes: lst.append('%s' % (bytes)) if not lst: return '0 bytes' return '+'.join(lst) + ' bytes' class Options(optparse.Values): """Holds option keys and values. Examples -------- >>> from iocbio.utils import Options >>> options = Options(a='abc', n=4) >>> print options {'a': 'abc', 'n': 4} >>> options.get(n=5) 4 >>> options.get(m=5) 5 >>> print options {'a': 'abc', 'm': 5, 'n': 4} >>> options2 = Options(options) >>> options.get(k = 6) >>> print options2 # note that updating options will update also options2 {'a': 'abc', 'm': 5, 'n': 4, 'k': 6} See also -------- __init__ """ def __init__(self, *args, **kws): """Construct Options instance. The following constructions are supported: + construct Options instance from keyword arguments:: Options(key1 = value1, key2 = value2, ...) + construct Options instance from :pythonlib:`optparse`.Values instance and override with keyword arguments:: Options(, key1 = value1, ...) + construct Options instance from Options instance:: Options(, key1 = value1, ...) Note that both Options instances will share options data. See also -------- Options """ if len(args) == 0: optparse.Values.__init__(self, kws) elif len(args) == 1: arg = args[0] if isinstance(arg, Options): self.__dict__ = arg.__dict__ self.__dict__.update(**kws) elif isinstance(arg, optparse.Values): optparse.Values.__init__(self, arg.__dict__) self.__dict__.update(**kws) elif isinstance(arg, type(None)): optparse.Values.__init__(self, kws) else: raise NotImplementedError(repr(arg)) else: raise NotImplementedError(repr(args)) def get(self, **kws): """Return option value. For example, ``options.get(key = default_value)`` will return the value of an option with ``key``. If such an option does not exist then update ``options`` and return ``default_value``. Parameters ---------- key = default_value Specify option key and its default value. Returns ------- value Value of the option. See also -------- Options """ assert len(kws) == 1, repr(kws) key, default = list(kws.items())[0] if key not in self.__dict__: if VERBOSE: print('Options.get: adding new option: %s=%r' % (key, default)) self.__dict__[key] = default value = self.__dict__[key] if value is None: value = self.__dict__[key] = default return value def splitcommandline(line): items, stopchar = splitquote(line) result = [] for item in items: if item[0] == item[-1] and item[0] in '\'"': result.append(item[1:-1]) else: result.extend(item.split()) return result def splitquote(line, stopchar=None, lower=False, quotechars='"\''): """ Fast LineSplitter. Copied from The F2Py Project. """ items = [] i = 0 while 1: try: char = line[i] i += 1 except IndexError: break lst = [] l_append = lst.append nofslashes = 0 if stopchar is None: # search for string start while 1: if char in quotechars and not nofslashes % 2: stopchar = char i -= 1 break if char == '\\': nofslashes += 1 else: nofslashes = 0 l_append(char) try: char = line[i] i += 1 except IndexError: break if not lst: continue item = ''.join(lst) if lower: item = item.lower() items.append(item) continue if char == stopchar: # string starts with quotechar l_append(char) try: char = line[i] i += 1 except IndexError: if lst: item = str(''.join(lst)) items.append(item) break # else continued string while 1: if char == stopchar and not nofslashes % 2: l_append(char) stopchar = None break if char == '\\': nofslashes += 1 else: nofslashes = 0 l_append(char) try: char = line[i] i += 1 except IndexError: break if lst: item = str(''.join(lst)) items.append(item) return items, stopchar pylibtiff-0.6.1/pyproject.toml000066400000000000000000000003151450302046000164130ustar00rootroot00000000000000[build-system] requires = ["setuptools>=60", "wheel", "setuptools_scm[toml]>=8.0", 'oldest-supported-numpy'] build-backend = "setuptools.build_meta" [tool.setuptools_scm] write_to = "libtiff/version.py" pylibtiff-0.6.1/setup.cfg000066400000000000000000000003311450302046000153160ustar00rootroot00000000000000[flake8] max-line-length = 120 #ignore = D100,D101,D102,D103,D105,D106,D107 # Ignore all docstring errors for now ignore = D [coverage:run] relative_files = True omit = libtiff/version.py libtiff/tiff_h_*.py pylibtiff-0.6.1/setup.py000066400000000000000000000047041450302046000152170ustar00rootroot00000000000000#!/usr/bin/env python import os from setuptools import find_packages, Extension, setup import numpy as np try: # HACK: https://github.com/pypa/setuptools_scm/issues/190#issuecomment-351181286 # Stop setuptools_scm from including all repository files import setuptools_scm.integration setuptools_scm.integration.find_files = lambda _: [] except ImportError: pass def setup_package(): with open("README.md", "r") as readme: long_description = readme.read() metadata = dict( name='pylibtiff', author='Pearu Peterson', author_email='pearu.peterson@gmail.com', license='https://github.com/pearu/pylibtiff/blob/master/LICENSE', url='https://github.com/pearu/pylibtiff', classifiers=[ "Development Status :: 3 - Alpha", "Intended Audience :: Science/Research", "License :: OSI Approved", "Programming Language :: Python", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Topic :: Scientific/Engineering", "Topic :: Software Development", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Operating System :: Unix", "Operating System :: MacOS", ], description='PyLibTiff: a Python tiff library.', long_description=long_description, long_description_content_type='text/markdown', install_requires=['numpy>=1.13.3'], python_requires='>=3.8', extras_require={ 'bitarray': ['bitarray'], }, include_package_data=True, packages=find_packages(), ext_modules=[ Extension(name="libtiff.bittools", sources=[os.path.join("libtiff", "src", "bittools.c")], include_dirs=[np.get_include()]), Extension(name="libtiff.tif_lzw", sources=[os.path.join("libtiff", "src", "tif_lzw.c")], include_dirs=[np.get_include()]), ], entry_points={ 'console_scripts': [ 'libtiff.info = libtiff.scripts.info:main', 'libtiff.convert = libtiff.scripts.convert:main', ], }, ) setup(**metadata) if __name__ == '__main__': setup_package()