pythontiffimagejfiji

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:

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.


Solution

  • 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)