pearu/pylibtiff

Adding ImageDepth support to pylibtiff

markemus opened this issue · 5 comments

Hey @pearu, I wrote some code to allow pylibtiff to read 3D TIFF images. It's a modified version of the TIFF.read_tiles() method in libtiff_ctypes.py. Currently it's a monkeypatch that is applied to libtiff at runtime.

If you're interested in it, I'd be happy to refactor it to be part of:

  1. read_tiles(), or
  2. a separate method, TIFF.read_z_tiles()

I'm hopefully also going to be doing this for write_tiles() in the near future.

Thanks for writing this great library!

Here's the code:

"""Adds a method to libtiff.TIFF to read z-stack tiff images (images with an ImageDepth tag).
Import this module anywhere in your code but before instantiating the TIFF class to apply the patch."""
import numpy as np

from libtiff import TIFF


def read_z_tiles(self, dtype=np.uint8):
    num_tcols = int(self.GetField("TileWidth")/2)
    num_trows = int(self.GetField("TileLength")/2)
    num_icols = int(self.GetField("ImageWidth")/2)
    num_irows = int(self.GetField("ImageLength")/2)
    num_depths = self.GetField("ImageDepth")
    # this number includes extra samples
    samples_pp = self.GetField('SamplesPerPixel') * 2

    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):
                # input data is 2x as many dims per pixel as parameters state.
                r = self.ReadTile(tmp_tile.ctypes.data, x*2, y*2, 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]

    full_image = np.zeros((num_depths, num_irows, num_icols, samples_pp), dtype=dtype, order='C')
    tmp_tile = np.zeros((num_trows, num_tcols, samples_pp), dtype=dtype, order='C')

    for depth_index in range(num_depths):
        read_plane(full_image[depth_index], tmp_tile, depth_index=depth_index)

    return full_image

# Apply monkeypatch
TIFF.read_z_tiles = read_z_tiles```

Just to be clear, this is for color image stacks- the current version already should support 3D grayscale stacks. So I guess these are really "4D" stacks.

pearu commented

Thanks for reporting the issue and sorry for the long delay in responding.
Could you submit your changes as a PR?

I've developed it more since then, including code for rewriting the data into separate directories. I'll post a PR when I get a chance.

@pearu I opened the PR- not sure if you get an alert when that happens.

pearu commented

@markemus thanks for the PR. I'll give it a review and I believe github should send any relevant messages to you.