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:
Boolean Functions:
numpy.all()
,numpy.any()
,numpy.where()
Statistics functions:
numpy.mean()
,numpy.sum()
,numpy.std()
,numpy.amin()
,numpy.amax()
,numpy.argmax()
,numpy.argmin()
Rounding functions:
numpy.round()
,numpy.around()
,numpy.round_()
NaN functions:
numpy.nanmean()
,numpy.nansum()
,numpy.nanstd()
,numpy.nan_to_num()
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