.. _algorithms: 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: .. code-block:: python 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 :class:`imfusion.labels.DataDescriptor` of the currently loaded dataset. The function is expected to return an :class:`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 <../python/images.html>`_. Example 1: Clear labels -------------------------- This example removes all labels from a label map, similar to the Erase action. .. code-block:: python 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. .. code-block:: python 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 :ref:`tag ` without modifing the label map. It assumes that the project defines a true/false tag with a name of "Healthy". .. code-block:: python 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 :ref:`bindings`.