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