Access to data stored in image files is done via the dedicated classes and functions outlined here. Most classes and algorithms included in MRtrix to handle image data have been written using C++ templates to maximise code re-use. They have also been written explicitly with multi-threading in mind, and this is reflected in some of the design decisions. MRtrix also places no restrictions on the dimensionality, memory layout or data type of an image - it supports an arbitrary number of dimensions, any reasonable set of strides to navigate the data array, and almost all data types available, from bitwise to double precision complex.
MRtrix provides many convenience template functions to operate on the relevant classes, assuming they all conform to the same general interface. For instance, there are simple functions to compute the number of voxels in an image, to ensure the dimensions of two images match, to loop over one or more images, to copy one image into the other, and more advanced functions to smooth images or compute gradients.
The basic architecture used in MRtrix is outlined below, along with an example application to illustrate how the different pieces fit together. The various classes and functions are then described in more detail.
MRtrix3 defines a Header class representing the on-disk attributes of an image. Accessing voxel data is done via the Image class, or via an Adapter class providing a modified view of an Image.
Many functions in MRtrix3 are template calls that can operate on any object that presents the expected interface (i.e. specific methods and attributes). Some functions only require access to the attributes of an image, not the voxel intensities. For these functions, the template argument is typically labelled HeaderType
, and will accept Header, Image or Adapter classes. Other functions will also access image intensities; in such cases, the template argument is labelled ImageType
, and the function will accept Image and Adapter classes (i.e. not the Header class).
The looping functions available in MRtrix3 fall into this category, and illustrate the concept. These can be instantiated from a HeaderType
(since the image attributes are sufficient to construct the loop object), but will operate on ImageType
objects (since they will access the voxel values).
Filter classes are also available for common operations.
This simple example illustrates the use of this functionality, in this case to perform multi-threaded 3x3x3 median filtering of the input image into the output image (similar to what the MR::Filter::Median
filter does), converted to Float32
data type:
The Header class contains modifiable information about an Image as stored on disk - whether this image already exists or is about to be created. This includes:
The Header is designed to be copy-constructible (from another Header or any Image or similar class) so that all copies are completely independent. It is used as-is to retrieve or specify all the relevant information for input and output images, and is designed to be instantiated from existing images, modified to suit, and used as a template for the output image. Instantiating a Header does not load the image data - only when an Image is instantiated (whether directly or from a previously opened Header) is the data actually made available.
For example:
The Image class provides access to the image data, and most of the information provided by the Header. This includes specifically:
The Image class is designed to be lightweight and copy-constructable, so that all copies access the same image data. In essence, all copies of the Image share a common Header via a shared pointer. This is essential for multi-threading, by allowing multiple threads to each have their own instance of an Image, so that they can all concurrently access the image data without affecting each other (although threads do need to ensure they don't write to the same voxel locations concurrently - see Writing multi-threaded applications for details).
The Image class can be used to access on-disk data (existing or newly created images), or on-RAM temporary (scratch) data. The developer can request direct IO where this can benefit performance, which means the data will be accessed in RAM using the same type as requested in the ValueType
template argument (this will involve preloading if the on-disk datatype does not match). Also, the in-RAM layout (strides) of the data can be specified by the developer, to ensure contiguity of the data in RAM in situations where this can affect performance. In general, the Image class will try to access the data via memory-mapping where possible, reverting to a preload strategy otherwise (for example, the data are stored across too many files, or the developer has requested a data layout that doesn't match the image on disk).
For example:
Adapter classes provide a modified view into the data held by another Image. This may involve on-the-fly computation of derived values (e.g. the neighbourhood median, a smoothed version of the data, etc.), or access to selected portions of the data (e.g. a ROI, etc.). They are written to have a semantically identical interface to the Image class, so that they can be used in place of an Image in many of the algorithms (e.g. the looping functions).
For example:
Filter classes are used to implement algorithms that operate on a whole image, and return a different whole image. Typical usage involves creating an instance of the filter class based on the input image, followed by creation of the output image using the filter itself as the template [Header] (it derives from Header). Processing is then invoked using the filter's operator()
method.
MRtrix provides a set of flexible looping functions and classes that support looping over an arbitrary numbers of dimensions, in any order desired, using the Loop and ThreadedLoop functions and associated classes. These enable applications to be written that make no assumptions about the dimensionality of the input data - they will work whether the data are 3D or 5D.
The Iterator class is a simple structure containing basic information related to an image. This includes:
It is used as a placholder for the looping functions, in cases where the loop shouldn't operate on an Image directly.