Creating Empty Images and Metadata

Creating Empty, OME-Compliant Image Arrays and Metadata

OMIO provides utility functions called create_empty_image, create_empty_metadata, and update_metadata_from_image to create empty, OME-compliant image arrays and metadata dictionaries based on user-defined specifications.

import omio as om
import numpy as np
import os

my_image, my_metadata = om.create_empty_image(return_metadata=True)
print(f"Created empty image with shape: {my_image.shape}, "
      f"dtype {my_image.dtype} and axes {my_metadata.get('axes', 'N/A')}."
>>>
Created empty image with shape: (1, 1, 1, 1, 1), dtype uint16 and axes TZCYX.

Without providing any arguments, create_empty_image creates a default empty image with shape (1, 1, 1, 512, 512) and dtype uint16. The axes are OME-compliant TZCYX. With the optional argument return_metadata=True (default is False), the associated metadata dictionary is also returned.

You can customize the created empty image by providing the desired shape and dtype as arguments:

my_image, my_metadata = om.create_empty_image(
    shape=(5, 10, 512, 512),
    dtype=np.uint16,
    return_metadata=True)

print(f"Failed to create empty image. Type of my_image is {type(my_image)} "
      f"and of my_metadata {type(my_metadata)}.")
>>>
Failed to create empty image. Type of my_image is <class 'NoneType'> and of my_metadata <class 'NoneType'>.

The attempt above fails because the provided shape has only 4 dimensions instead of the required 5 dimensions for OME-compliant images. In such cases, OMIO will raise a warning and return None for both image and metadata.

my_image, my_metadata = om.create_empty_image(
    shape=(5, 20, 2, 512, 512),
    dtype=np.uint16,
    return_metadata=True)

print(f"Created empty image with shape: {my_image.shape}, "
      f"dtype {my_image.dtype} and axes {my_metadata.get('axes', 'N/A')}.")
>>>
Created empty image with shape: (5, 20, 2, 512, 512), dtype uint16 and axes TZCYX.

You can now manipulate the created empty image as needed:

# for each timepoint, z-slice and channel, we fill the slice with an increasing integer value:
for t in range(my_image.shape[0]):
    for z in range(my_image.shape[1]):
        for c in range(my_image.shape[2]):
            my_image[t, z, c, :, :] = t * 100 + z * 10 + c

pathname_save = "example_data/custom_created_images/"
os.makedirs(pathname_save, exist_ok=True)

om.imwrite(
    os.path.join(pathname_save, "my_empty_image_filled.ome.tif"),
    my_image, my_metadata)

read_my_image, read_my_metadata = om.imread(
    os.path.join(pathname_save, "my_empty_image_filled.ome.tif"))

om.open_in_napari(read_my_image, read_my_metadata,
     os.path.join(pathname_save, "my_empty_image_filled.ome.tif"))
Custom created image opened in napari

When changing the image shape, for example by cropping or padding the stack,

my_cropped_image = my_image[:, 2:8, :, 100:400, 100:400]  # crop Z and spatial dimensions
print(f"Cropped image shape: {my_cropped_image.shape}")
>>>
Cropped image shape: (5, 6, 2, 300, 300)

you need to update the associated metadata dictionary accordingly. You can do so by manually updating the relevant metadata entries, or by using OMIO’s utility function update_metadata_from_image:

my_cropped_metadata = om.update_metadata_from_image(
    my_metadata,
    my_cropped_image)

print(f"Updated cropped image metadata axes: "
      f"{my_cropped_metadata.get('axes', 'N/A')} "
      f"with shape: {my_cropped_image.shape}.")

om.imwrite(
    os.path.join(pathname_save, "my_cropped_image.ome.tif"),
    my_cropped_image,
    my_cropped_metadata)

read_my_cropped_image, read_my_cropped_metadata = om.imread(
    os.path.join(pathname_save, "my_cropped_image.ome.tif"))

om.open_in_napari(
    read_my_cropped_image,
    read_my_cropped_metadata,
    os.path.join(pathname_save, "my_cropped_image.ome.tif"))
>>>
Updated cropped image metadata axes: TZCYX with shape: (5, 6, 2, 300, 300).
Custom created and cropped image opened in napari