Writing Algorithms

Custom Algorithms can be used both in the Annotation or the Experiments tabs. An algorithm is a function to be defined in a Python script, with the following signature:

def algorithm(
        image: imfusion.SharedImageSet,
        labels: imfusion.SharedImageSet,
        descriptor: imfusion.labels.DataDescriptor
) -> imfusion.SharedImageSet | None

where image is the currently loaded image, labels the label map of the currently active layer and descriptor the imfusion.labels.DataDescriptor of the currently loaded dataset. The function is expected to return an imfusion.SharedImageSet with the same shape of labels, encoded as unsigned bytes. If it returns None, the label map is considered to be unchanged (i.e. the “Latest Edit Time” is not changed and no undo step is generated).

Note

For more information about the data structures used in the ImFusion SDK, please refer to the documentation of the imfusion Python module itself.

Example 1: Clear labels

This example removes all labels from a label map, similar to the Erase action.

import imfusion as imf
import numpy as np

def algorithm(image, labels):

        # Convert ImFusion SharedImageSet to standard Numpy array with the following shape
        # (num_frames, slices, height, width, channels)
        image_array = np.array(image)
        labels_array = np.array(labels)
        # Create zero labels
        labels_array = 0 * labels_array
        # Create a set of labels from the numpy array
        new_labels = imf.SharedImageSet(labels_array)

        return new_labels

Label Map structure

The above example works because 0 is always the background label. However, more practical scripts will also involve setting labels other than the background. therefore, it is important to understand how label maps are structured.

A label map in ImFusion Labels is stored as an 8-bit image. Each pixel in the label map contains an index that identifies a label. Those indices are defined in the project.

For example a project could define two labels: one for “Bone” with an index of 1 and one for “Soft Tissue” with an index of 2. Inside the label map, every pixel with a value of 1 is therefore related to the “Bone” label, while every pixel with a value of 2 to the “Soft Tissue” label.

Because each pixel can only contain one index, labels cannot overlap. That is a pixel cannot be both “Bone” and “Soft Tissue” in the above example. Overlapping structures can be realized by using different layers. Each layer has a separate label map. The algorithm function only receives the currently active layer, but other layers can be accessed through the descriptor.

The descriptor can also be used to retrieve the label indices as defined by the project. This is demonstrated with the next example.

Example 2: Threshold the image

The following example thresholds the input image and uses the result as the new label map. This example can also be seen as a blueprint for scripts with a more sophisticated method than thresholding for initializing label maps.

import imfusion as imf
import numpy as np

def algorithm(image, labels, descriptor):
        # Create empty container for output label map
        # by cloning only the meta-data but not the pixel data
        new_labels = labels.clone(withData=False)

        # Get the indices for the labels that should be changed.
        # This assumes that the layer has at least one label defined.
        # Alternatively, the label name could also be used, e.g.
        # forground_label = active_layer.annotations["Bone"].value
        active_layer = descriptor.labelmap_layers.active
        forground_label = active_layer.annotations[1].value

        # imf.SharedImageSet can contain multiple images.
        # For demo purposes, this example works on the individual frames.
        for image_frame, labels_frame in zip(image, labels):

                # Get current image and turn into grayscale
                image_array = np.array(image_frame)
                image_grayscale = np.mean(image_array, -1)

                mean_intensity = np.mean(image_grayscale[:])

                # Create label map with 0 everywhere
                new_labels_array = np.zeros(labels_frame.shape, dtype=np.ubyte)

                # Set the new labels values based on a threshold
                new_labels_array[image_grayscale > mean_intensity] = forground_label

                # Add label map to the container
                new_labels.add(imf.SharedImage(new_labels_array))

        return new_labels

Example 3: Set a tag

This example sets a tag without modifing the label map. It assumes that the project defines a true/false tag with a name of “Healthy”.

def algorithm(image, labels, descriptor):
        descriptor.tags["Healthy"] = True

        return None

For further questions, please get in touch with us via the ImFusion Support Forum.

Note

To run an algorithm on all datasets see Python API.