Basic Usage

We use the following abbreviations for libraries:

>>> import numpy as np
>>> import voxel as vx

Image I/O

Voxel provides data readers and writers to allow you to read/write image data stored in NIfTI and DICOM standards. These I/O tools create or write from the voxel image class MedicalVolume.

For example to load a DICOM image series, which has multiple echos, with each echo corresponding to a volume, we can do:

>>> dr = vx.DicomReader(num_workers=1, verbose=True)
>>> dr.load("/path/to/dicom/folder", group_by="EchoNumbers")

We can also load specific files in the image series:

>>> dr.load(["file1", "file2", ...], group_by="EchoNumbers")

DICOM image data often has associated metadata. MedicalVolume makes it easy to get and set metadata:

>>> volume = volumes[0]  # first echo time
>>> volume.get_metadata("EchoTime", float)
10.0
>>> volume.set_metadata("EchoTime", 20)
>>> volume.get_metadata("EchoTime", float)
20.0

Similarly, to load a NIfTI volume, we use the NiftiReader class:

>>> nr = vx.NiftiReader()
>>> volume = nr.load("/path/to/nifti/file.nii.gz")

NIfTI volumes can also be loaded in memmap mode. This makes loading much faster and allows easy interaction with larger-than-memory arrays. Only when the volume is modified will the volume be loaded into memory and modified.

>>> volume = nr.load("/path/to/nifti/file", mmap=True)

Images in all supported data formats can also be loaded and written using voxel.read and voxel.write:

>>> import voxel as vx
>>> vx.load("/path/to/dicom/folder", group_by="EchoNumbers")
>>> vx.load("/path/to/nifti/file.nii.gz", mmap=True)

Reformatting Images

Given the multiple different orientation conventions used by different image formats and libraries, reformatting medical images can be difficult to keep track of. Voxel simplifies this by introducing an unambiguous convention for image orientation based on the RAS+ coordinate system, in which all directions point to the increasing direction.

To reformat a MedicalVolume instance (mv) such that the dimensions correspond to superior -> inferior, anterior -> posterior, left -> right, we can do:

>>> mv = mv.reformat(("SI", "AP", "LR"))

To perform the operation in-place (i.e. modifying the existing instance), we can do:

>>> mv = mv.reformat(("SI", "AP", "LR"), inplace=True)

Note, in-place reformatting returns the same MedicalVolume object that was modified in-place (i.e. self) to allow chaining methods together.

We may also want to reformat images to be in the same orientation as other images:

>>> mv = mv.reformat_as(other_image)

Image Slicing and Arithmetic Operations

MedicalVolume supports some array-like functionality, including Python arithmetic operations (+, -, **, /, //), NumPy shape-preserving operations (e.g. np.exp, np.log, np.pow, etc.), and slicing.

>>> mv += 5
>>> mv = mv * mv / mv
>>> mv = np.exp(mv)
>>> mv = mv[:5, :6, :7]

Note, in order to preserve dimensions, slicing cannot be used to reduce dimensions. For example, the first line will throw an error; the second will not:

>>> mv = mv[2]
IndexError: Scalar indices disallowed in spatial dimensions; Use `[x]` or `x:x+1`
>>> mv[2:3]

NumPy Interoperability

In addition to standard shape-preserving universal functions (ufuncs) described above, MedicalVolume also support a subset of other numpy functions that, like the ufuncs, operate on the pixel data in the medical volume:

For example, np.all(mv) is equivalent to np.all(mv.volume). Note, headers are not deep copied. NumPy operations that reduce spatial dimensions are not supported. For example, a 3D volume mv cannot be summed over any two of the first three axes:

>>> np.sum(mv, 0)  # this will raise an error
>>> np.sum(mv)  # this will return a scalar

Choosing A Computing Device

Voxel provides a device class voxel.Device, which allows you to specify which device to use for MedicalVolume operations. It extends the Device class from CuPy. To enable GPU computing support, install the correct build for CuPy on your machine.

To move a MedicalVolume to GPU 0, you can use the MedicalVolume.to() method:

>>> mv_gpu = mv.to(vx.Device(0))

You can also move the image back to the cpu:

>>> mv_cpu = mv_gpu.cpu()  # or mv_gpu.to(vx.Device(-1))

Multi-Library Interoperability

MedicalVolume is also interoperable with popular image data structures with zero-copy, meaning array data will not be copied. Structures currently include the SimpleITK Image, Nibabel Nifti1Image, and PyTorch tensors.

For example, we can use the MedicalVolume.to_sitk() method to convert a MedicalVolume to a SimpleITK image:

>>> sitk_img = mv.to_sitk()

For PyTorch tensors, the zero-copy also applies to tensors on the GPU. Using mv_gpu, which is on GPU 0, from the previous section, we can do:

>>> torch_tensor = mv_gpu.to_torch()
>>> torch.device
cuda:0