Python: copy all metadata from one multipage tif to another

Hi there,

I have 3D image stacks saved as multipage tif that can be read smoothly with ImageJ/Fiji and icy. They do contain some image-format-related metadata and some custom ones. I am processing the stacks with Python, and saving each processed stack as a new multipage tif file (for some reason it has to be a tif file). I’d like the processed stack to retain all metadata from the original stack.

  • I tried to copy metadata from the original tif file into the new one using tifffile (retrieving the imagej_metadata and setting them as metadata when I write the processed stack into a new file). It is not satisfactory: some of the image information I see via ImageJ/Fiji has not been transferred and the stack is considered as a time serie in icy, so some image-format-related metadata are lost in the process.
  • I explored python-bioformats. From there I can easily retrieve all metadata from the input file, but I fail to find a way to incorporate them into the new tif file I am writing (I have the feeling it would be feasible with Jython but not Python?).
  • Using PIL, I (unsuccessfully) attempted to bluntly copy the original tif file and modify each image slice content in-place in the copy to avoid having to deal with metadata at all.

Inspired from other posts, I managed to hardcode that some specific metadata entries get written in the new file, but this is too ad-hoc. I cannot a priori know all metadata entries the input file holds, and I don’t want to lose information. Is there a simple way to just copy everything from the original tif? I would have not anticipated this to be a too crazy task - what am I missing?

Many thanks for your help!

2 Likes

Have you already read this answer regarding tiffile to store metadata:

It cites:

You can also write ImageJ metadata with ijmetadata , for which the acceptable types are listed in the source code here.

This older thread might also be a help:

http://imagej.1557.x6.nabble.com/Get-the-number-of-frames-from-a-multiple-tiff-header-td5012283.html

The thread also cites this plugin to read the header information:

https://imagej.nih.gov/ij/plugins/tiff-tags.html

1 Like

Thank you for the reply!

I had found this thread indeed, and it seems that getting the tags in each slice with tif.pages[z].tags is a good way to retrieve all metadata (tif[z].image_description as suggested in the StackOverflow thread does not work). My problem is then that I do not find a way to write them in the new file I create, as tifffile seems to only allow me to provide metadata for the entire stack at once (and not slice-by-slice).

Hi Virgine,
The setMetadata(“Label”, string) macro function allows to write slice-specific metadata to the image.
Would that be enough?

Jerome

1 Like

Thank you, Jerome. Processing the file in Python and fixing metadata slice-by-slice afterward with an ImageJ macro could be a last resort solution, but I do not like it much as it involves going twice through the full dataset with two different scripts :confused:

In this case, would using pyimagej be an option for you? I haven’t tried using #bio-formats (or #scifio) from #pyimagej to save files with metadata, but that way, it should be possible to do the processing in Python and still use the Java API to save the file with all required metadata.

1 Like

Is there a simple way to just copy everything from the original tif? I would have not anticipated this to be a too crazy task - what am I missing?

I think it is practically impossible to copy all metadata from just any TIFF® (Thousands of Incompatible File Formats) file to another and guarantee that the other file is not corrupted. There are too many metadata formats found in TIFF files. The metadata might reference how, where, and what values of image data are stored in the file. Many software require images or metadata to be stored in certain ways, often not quite TIFF compliant. Even when in-place editing the image data in the file is possible, some metadata such as previews or image statistics might get out of sync.

I tried to copy metadata from the original tif file into the new one using tifffile (retrieving the imagej_metadata and setting them as metadata when I write the processed stack into a new file). It is not satisfactory: some of the image information I see via ImageJ/Fiji has not been transferred and the stack is considered as a time serie in icy, so some image-format-related metadata are lost in the process.

Since no sample file was provided, I assume you want to process simple ImageJ hyperstack compatible files.

Reading selected metadata from an existing file and creating an ImageJ hyperstack compatible file from scratch using that metadata is possible with tifffile but some knowledge of the undocumented ImageJ hyperstack format is required.

For example, using the confocal-series.tif file from the Fiji samples:

from tifffile import TiffFile, imwrite
from tifffile.tifffile import imagej_description_metadata, transpose_axes

with TiffFile('confocal-series.tif') as tif:
    assert tif.is_imagej
    # print detailed information about the file
    print(tif.__str__(detail=2))
    # get image resolution from TIFF tags
    tags = tif.pages[0].tags
    x_resolution = tags['XResolution'].value
    y_resolution = tags['YResolution'].value
    resolution_unit = tags['ResolutionUnit'].value
    # parse ImageJ metadata from the ImageDescription tag
    ij_description = tags['ImageDescription'].value
    ij_description_metadata = imagej_description_metadata(ij_description)
    # get ImageJ app metadata
    ij_metadata = tags['IJMetadata'].value
    # read the whole image stack and get the axes order
    series = tif.series[0]
    ij_hyperstack = series.asarray()
    ij_hyperstack_axes = series.axes

# process ij_hyperstack array; make sure not to change the array type or shape
...

# saving ImageJ hyperstack requires a 6 dimensional array in axes order TZCYXS
ij_hyperstack = transpose_axes(ij_hyperstack, ij_hyperstack_axes, 'TZCYXS')

# remove conflicting entries from the ImageJ metadata
ij_description_metadata = {k: v for k, v in ij_description_metadata.items()
                           if k not in 'ImageJ images channels slices frames'}

# write image and metadata to an ImageJ hyperstack compatible file
imwrite('confocal-series.out.tif', ij_hyperstack,
        resolution=(x_resolution, y_resolution, resolution_unit),
        imagej=True, metadata=ij_description_metadata, ijmetadata=ij_metadata)

# print detailed information about the created file
with TiffFile('confocal-series.out.tif') as tif:
    print(tif.__str__(detail=2))

Beware that tifffile writes little-endian TIFF files by default while ImageJ writes big-endian.

tifffile seems to only allow me to provide metadata for the entire stack at once (and not slice-by-slice)

It is possible to create a TIFF file of individual 2D images, but that will not create an ImageJ hyperstack compatible file:

with tifffile.TiffWriter('file.tiff') as tif:
    for image in image_stack:
       tif.save(image, contiguous=False, **other_parameters)

attempted to bluntly copy the original tif file and modify each image slice content in-place in the copy to avoid having to deal with metadata at all.

In case those files have the whole image stack stored contiguously (such as in ImageJ hyperstacks), tifffile allows to directly memory-map the image data as numpy arrays. For example:

from tifffile import memmap

# memory-map the ImageJ hyperstack
ij_hyperstack = memmap('confocal-series.tif')
# process the ij_hyperstack array in-place or write results to it
# e.g. zero the second channel
ij_hyperstack[:, 1] = 0
# write any changes in the array back to the file
ij_hyperstack.flush()
6 Likes

Wow, thank you, this is extremely clear: so the initial assumption that it is possible to copy everything is basically wrong!

Your solution that retrieves tif.series[0] looks like a successful version of what I tried to do when I mentioned “in-place editing”. Many thanks for the suggestion!

On a side note: the data do not belong to me and are under a confidentiality agreement, which is why I tried to explain the problem as best as I could without providing a sample file.

1 Like

Really cool, I did not know one could “call” the ImageJ Java API via Python. Thanks, Jan!