Regions of interest (ROIs) and structure sets

A structure/region of interest (ROI) can be represented by either a set of contour points or a binary mask. The ROI class allows an ROI to be loaded from either of these sources and converted from one to the other.

The ROI class

An ROI object, which contains a single ROI, can be created via:

from skrt import ROI
roi = ROI(source)

The source can be from any of the following:

  • A dicom structure set file containing one or more sets of contours;

  • A dictionary of contours, where the keys are slice positions in mm and the values are lists of lists of contour points for each contour on that slice;

  • A nifti file containing a binary mask;

  • A numpy file containing a binary mask;

  • A numpy array.

If the input object is a dicom file, the name of the ROI within that file must be given. In addition, if the input source is a dicom file or contour dictionary, in order to convert the contours to a mask, the user must provide either of the following arguments to the ROI creation:

  • image: Assign an Image object associated with this ROI. The created mask will have the same dimensions as this image.

  • shape, origin, and voxel_sizes: This tells the ROI the dimensions of the pixel array to create when making the mask.

Additional useful arguments are:

  • color: set the colour of this ROI for plotting. If not specified, this will either be read from the dicom file (if given) or the ROI will be assigned a unique colour.

  • name: set the name of this ROI. If the input is a dicom structure set file, this name will be used to select the correct ROI from the file. If not given, the ROI will be given a unique name (“ROI 1” etc)

  • load: if True (default), the input ROI will be converted into a mask/contours automatically upon creation. This can be time consuming if you are creating many ROIs, so setting it to False can save time and allow ROIs to be processed later on-demand.

An Image object associated with this ROI can be set at any time via:

roi.image = image

This image can optionally be plotted in the background when the ROI is plotted.

Plotting an ROI

ROIs can be plotted as contours or a mask or both, and with or without an associated image behind them. The simplest plot command is:

roi.plot()

This will plot a contour in the x-y plane, e.g.:

ROI contour plot

Additional options are:

  • include_image: boolean, set to True to plot the associated image (if one is assigned) in the background;

  • view: specify the orientation (x-y, y-z, or x-z);

  • sl, pos, or idx: specify the slice number, slice position in mm, or slice index;

  • zoom: amount by which to zoom in (will automatically zoom in on the centre of the ROI).

The following plot types are available, set via the plot_type argument:

  • contour: plot a contour

  • mask: plot a binary mask

  • filled: plot a contour on top of a semi-transparent mask

  • centroid: plot a contour with the centroid marked by a cross

  • filled centroid: plot a contour and centroid on top of a semi-transparent mask

Example contour plot with include_image=True:

ROI contour plot with image

Example mask plot:

ROI binary mask plot

Writing out an ROI

An ROI can be written to a nifti or numpy file as a binary mask. This can be done either by specifying a filename with an appropriate extension, e.g.

roi.write('my_roi.nii')

would automatically write to a nifti file.

Alternatively, the ROI’s name can be used to create the filename. This is done by just providing an extension, e.g. if an ROI were called “heart”, the following command:

roi.write(ext='.nii')

would produce a file called heart.nii containing the ROI as a binary mask. An output directory can also be optionally provided, e.g.

roi.write(outdir='my_rois', ext='.npy')

would produce a binary numpy file at my_rois/heart.npy.

Getting geometric properties

The ROI class has various methods for obtaining geometric properties of the ROI:

  • Centroid: get the 3D centroid coordinates (i.e. the centre-of-mass of the ROI) in mm via roi.get_centroid(), or get the 2D coordinates for a single slice via roi.get_centroid(view='x-y', sl=10), for example (the slice position in mm pos or slice index idx can be given instead of slice number sl)

  • Centre: get the 3D midpoint coordinates (i.e. the mean of the maximum extents in each direction, rather than centre of mass) via roi.get_centre(). The 2D midpoint of a slice is obtained in a similar way to the centroid, e.g. roi.get_centre(view='x-y', sl=10).

  • Volume: roi.get_volume(units), where units can either be mm or voxels (default mm).

  • Area: get the area of a given slice of an ROI by running e.g. roi.get_area(view='x-y', sl=10, units='mm'). To get the area of the central slice, can simply run roi.get_area().

  • Length: get ROI length along a given axis by running roi.get_length(axis, units) where axis is x, y, or z and units is mm or voxels.

Structure Sets: the StructureSet class

A structure set is an object that contains multiple ROIs. This is implemented via the StructureSet class.

Loading a structure set

A structure set is created via

from skrt import StructureSet
structure_set = StructureSet(source)

The source can be:

  • The path to a dicom structure set file containing multiple ROIs;

  • The path to a directory containing one or more nifti or numpy files, each containing a binary ROI mask;

  • A list of paths to nifti or numpy ROI mask files.

In addition, more ROIs can be added later via:

structure_set.add_rois(source)

where source is any of the above source types.

Alternatively, single ROIs can be added at a time via any of the valid ROI sources (see above), and with any of the ROI initialisation arguments. An empty StructureSet can be created and then populated, e.g.

structure_set = StructureSet()
structure_set.add_roi('heart.nii', color='red')
structure_set.add_roi('some_structs.dcm', name='lung')

The StructureSet can also be associated with an Image object by specifying the image argument upon creation, or running structure_set.set_image(image). This image will be assigned to all ROIs in the structure set.

Creating a filtered copy of a structure set

Sometimes you may wish to load many ROIs (e.g. from a dicom structure set file) and then filter them by name. This is done via:

structure_set.filter_rois(to_keep, to_remove)

where to_keep and to_remove are optional lists containing ROI names, or wildcards with the * character. First, all of the ROIs belonging to structure_set are checked and only kept if they match the names or wildcards in to_keep. The remaining ROIs are then removed if their names match the names or wildcards in to_remove.

To restore a structure set to its original state (i.e. reload it from its source), run structure_set.reset().

Renaming ROIs

ROIs can be renamed by mapping from one or more possible original names to a single final name. In this way, multiple structure sets where the same ROI might have different names can be standardised to have the same ROI names.

For example, let’s say you wish to rename the right parotid gland to right_parotid, but you know that it has a variety of names across different structure sets. You could do this with the following (assuming my_structure_sets is a list of StructureSet objects:

names_map = {
    'right_parotid': ['right*parotid', 'parotid*right', 'R parotid', 'parotid_R']
}
for structure_set in my_structure_sets:
    structure_set.rename_rois(names_map)

By default, only one ROI per structure set will be renamed; for example, if a structure set for some reason contained both right parotid and R parotid, only the first in the list (right parotid) would be renamed. This behaviour can be turned off by setting first_match_only=False; beware this could lead to duplicate ROI names.

You can also choose to discard any ROIs that aren’t in your renaming map by setting keep_renamed_only=True.

Getting ROIs

Get a list of the ROI objects belonging to the structure set:

structure_set.get_rois()

Get a list of names of the ROI objects:

structure_set.get_roi_names()

Get a dictionary of ROI objects with their names as keys:

structure_set.get_roi_dict()

Get an ROI object with a specific name:

structure_set.get_roi(name)

Print the ROI names:

structure_set.print_rois()

Filtering a structure set

A StructureSet object can be copied to a new StructureSet, optionally with some ROIs filtered/renamed (you might want to do this if you want to preserve the original structure set, while making a filtered version too), via:

filtered = structure_set.filter(names, to_keep, to_remove, keep_renamed_only)

Writing a structure set

The ROIs in a structure set can be written to a directory of nifti or numpy files, via:

structure_set.write(outdir, ext)

where outdir is the output directory and ext is either .nii, .nii.gz or .npy. Dicom writing will be supported in future.

Assigning StructureSets to an Image

Just as ROIs and structure sets can be associated with an image, the Image object can be associated with one or more StructureSet objects. This is done via:

from skrt import Image, StructureSet

image = Image("some_image.nii")
structure_set = StructureSet("roi_directory")

image.add_structure_set(structure_set)

Now, when the Image is plotted, the ROIs in its structure set(s) can be plotted on top. To plot all structure sets, run:

image.plot(structure_set='all')

Note that this could be slow if there are many structure sets containing many structures.

To plot just one structure set, you can also provide the index of the structure set in the list of structure sets belonging to the image, e.g. to plot the most recently added structure set:

image.plot(structure_set=-1)

To add a legend to the plot, set roi_legend=True.

The image’s structure sets can be cleared at any time via

image.clear_structure_sets()