Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to specify colormap when saving tiff stack

I'm using tifffile in python to save out 3-channel tiff stacks, which I then want to read into ImageJ or FIJI. These tiff stacks open as composites in ImageJ with each channel assigned a (presumably default) colormap/LUT. However, the colors that are assigned aren't the colors that make sense for my images. My problem is that I can't figure out how to specify the colormap for each channel when saving the image using tifffile.

For example, I'd like to have the following colormap assignments:

  • ch 0: grays
  • ch 1: green
  • ch 2: red

Here's the code that I'm using to save the files:

# save hyperstack
with tifffile.TiffWriter(filename, bigtiff=False, imagej=True) as tif:
    for i in range(t_stack.shape[0]):
        tif.save(t_stack[i], metadata={'Composite mode': 'composite'})

There must be metadata that's saved with the tiff that holds the channel colormap info because I can manually edit the color assignment in ImageJ and then save it, close it, and then when I open the file up again it retains my manual colormap assignments. So I'm guessing there must be a metadata tag (maybe colormap?) that can be used to specify channel colors, but I can't find any info on what tag or syntax to use.

like image 765
holastello Avatar asked Dec 11 '22 06:12

holastello


2 Answers

Create the private IJMetadata (50839) and IJMetadataByteCounts (50838) TIFF tags on your own and pass them to tifffile.imsave as extratags. IJMetadata contains application internal metadata in a binary format. The color information is in the luts metadata:

import struct
import numpy
import tifffile


def imagej_metadata_tags(metadata, byteorder):
    """Return IJMetadata and IJMetadataByteCounts tags from metadata dict.

    The tags can be passed to the TiffWriter.save function as extratags.

    """
    header = [{'>': b'IJIJ', '<': b'JIJI'}[byteorder]]
    bytecounts = [0]
    body = []

    def writestring(data, byteorder):
        return data.encode('utf-16' + {'>': 'be', '<': 'le'}[byteorder])

    def writedoubles(data, byteorder):
        return struct.pack(byteorder+('d' * len(data)), *data)

    def writebytes(data, byteorder):
        return data.tobytes()

    metadata_types = (
        ('Info', b'info', 1, writestring),
        ('Labels', b'labl', None, writestring),
        ('Ranges', b'rang', 1, writedoubles),
        ('LUTs', b'luts', None, writebytes),
        ('Plot', b'plot', 1, writebytes),
        ('ROI', b'roi ', 1, writebytes),
        ('Overlays', b'over', None, writebytes))

    for key, mtype, count, func in metadata_types:
        if key not in metadata:
            continue
        if byteorder == '<':
            mtype = mtype[::-1]
        values = metadata[key]
        if count is None:
            count = len(values)
        else:
            values = [values]
        header.append(mtype + struct.pack(byteorder+'I', count))
        for value in values:
            data = func(value, byteorder)
            body.append(data)
            bytecounts.append(len(data))

    body = b''.join(body)
    header = b''.join(header)
    data = header + body
    bytecounts[0] = len(header)
    bytecounts = struct.pack(byteorder+('I' * len(bytecounts)), *bytecounts)
    return ((50839, 'B', len(data), data, True),
            (50838, 'I', len(bytecounts)//4, bytecounts, True))


filename = 'FluorescentCells.tif'
image = tifffile.imread(filename)

grays = numpy.tile(numpy.arange(256, dtype='uint8'), (3, 1))
red = numpy.zeros((3, 256), dtype='uint8')
red[0] = numpy.arange(256, dtype='uint8')
green = numpy.zeros((3, 256), dtype='uint8')
green[1] = numpy.arange(256, dtype='uint8')
ijtags = imagej_metadata_tags({'LUTs': [grays, green, red]}, '>')

tifffile.imsave('test_ijmetadata.tif', image, byteorder='>', imagej=True,
                metadata={'mode': 'composite'}, extratags=ijtags)
like image 165
cgohlke Avatar answered Dec 19 '22 08:12

cgohlke


You can pass a number of keyword arguments to tifffile's imsave function. It's not very well documented, so what I found most helpful was to read the docstring for the save function in the TiffWriter class:

https://github.com/blink1073/tifffile/blob/master/tifffile/tifffile.py#L750

For ImageJ metadata specifications, TiffWriter.save then refers to imagej_metadata_tags where you can see what types of data you can store in the variable metadata_types (line 7749):

https://github.com/blink1073/tifffile/blob/master/tifffile/tifffile.py#L7710

metadata_types = (
    ('Info', b'info', 1, _string),
    ('Labels', b'labl', None, _string),
    ('Ranges', b'rang', 1, _doubles),
    ('LUTs', b'luts', None, _ndarray),
    ('Plot', b'plot', 1, _bytes),
    ('ROI', b'roi ', 1, _bytes),
    ('Overlays', b'over', None, _bytes))

You can create LUTs to visualize your data using different colormaps. Presumably your data is uint8, then the LUTs you will need have the shape (3, 256) for the 3 color channels and 256 intensity values. So for gray, green and red LUTs you will need to something along the lines:

import numpy as np
import tifffile

# Create a random test image
im_3frame = np.random.randint(0, 255, size=(3, 150, 250), dtype=np.uint8)
# Intensity value range
val_range = np.arange(256, dtype=np.uint8)
# Gray LUT
lut_gray = np.stack([val_range, val_range, val_range])
# Red LUT
lut_red = np.zeros((3, 256), dtype=np.uint8)
lut_red[0, :] = val_range
# Green LUT
lut_green = np.zeros((3, 256), dtype=np.uint8)
lut_green[1, :] = val_range
# Create ijmetadata kwarg
ijmeta = {'LUTs': [lut_gray, lut_red, lut_green]}
# Save image
tifffile.imsave(
    save_name,
    im_rgb,
    imagej=True,
    metadata={'mode': 'composite'},
    ijmetadata=ijmeta,
) 
like image 32
Jenny Folkesson Avatar answered Dec 19 '22 09:12

Jenny Folkesson