Medical images I/O python package
Medical images I/O python package
This package unifies the io engines of itk, nibabel, and pydicom (including dicom-numpy) packages in a simple and comprehensive interface.
It includes conversion between the metadata conventions, reorientations, affine matrix computation for itk and pydicom and saving dicom series or file.
First, make sure you have the latest pip version (better to close PyCharm or any other program that uses the environments):
(<env-name>) >pip install -U pip
Install medio with:
(<env-name>) >pip install -U medio
This will install the medio python package and its dependencies in your environment.
The dependencies are:
A conda environment .yml file is in the project's root. Some dicom images may require installation of additional packages, gdcm for example.
There are 3 main functions in medio: read_img
, save_img
and save_dir
.
from medio import read_img, save_img
# read a dicom series from a folder
array, metadata = read_img('data/dicom-folder/', desired_ornt='IAR')
# do your stuff and save in any format
save_img('ct.nii.gz', array, metadata, backend='nib')
medio.read_img(input_path, desired_ornt=None, backend=None, dtype=None, header=False, channels_axis=-1, coord_sys='itk', **kwargs)
input_path
: path-likeseries
can help.Optional parameters:
desired_ornt
: orientation string or Nonecoord_sys
(itk by default).
See also Orientation).desired_ornt
is the same as the original image's orientation, no reorientation is performed.backend
: 'nib', 'itk', 'pydicom', 'pdcm', or Nonedtype
: numpy data-type or Nonearray.astype(dtype)
on the returned image array.header
: boolmetadata.header
attribute that stores the raw
metadata of the file as a dictionary.input_path
), and not used during saving.channels_axis
: int or Nonechannels_axis
. If None, the backend's original convention is used.coord_sys
: 'itk', 'nib', or Nonedesired_ornt
parameter and the returned metadata.
None means that the backend will determine coord_sys
, but it can lead to a backend-dependent
array and metadata.**kwargs
are additional per-backend optional parameters:
'itk' backend:
pixel_type=itk.SS
: itk pixel-type or Noneitk.SS
- Signed Short).
Other common pixel types are: itk.UC
- uint8, itk.US
- uint16.itk.ctype
in order to convert C-types to itk types. For example:itk.ctype('unsigned short') == itk.US
fallback_only=True
: boolpixel_type
is used
(pixel_type
must not be None in this case).itk.imread(input_path)
fails, using fallback_only=True
will result in a slightly
inferior performance. If you know what is pixel-type of the image beforehand, you can set it
with pixel_type
and use fallback_only=False
.series=None
: str, int or Noneinput_path
is a directory that contains multiple dicom series, selecting a specific one is
possible with the series
argument. It can be the exact series instance UID (str), an int
between 0 and n-1, where n is the number of series in the directory, or None.series
is None and there are multiple series in the directory, a detailed error message
is raised.'pydicom' backend
globber='*'
: strallow_default_affine=False
: boolseries=None
: str, int or Noneseries
in itk backend, see the explanation above.medio.save_img(filename, np_image, metadata, use_original_ornt=True, backend=None, dtype=None, channels_axis=None, mkdir=False, parents=False, **kwargs)
filename
: path-likenp_image
: numpy.ndarraymetadata
: medio.MetaDatamedio.read_img
for example. In the absence of a known
metadata, a default one can be constructed with medio.MetaData(np.eye(4))
.Optional parameters:
use_original_ornt
: boolmetadata.orig_ornt
or not.backend
: 'nib', 'itk' or Nonedtype
: numpy data-type or Nonenp_image.astype(dtype)
. Note that not every dtype is
supported in saving, so make sure what is the dtype of the image array you want to save.channels_axis
: int or Nonechannels_axis
of np_image
.mkdir
: boolfilename
.parents
: boolmkdir=True
. If True, creates also the parent directories.'itk' backend optional parameters (**kwargs
):
allow_dcm_reorient=False
: boolcompression=False
: boolfilename
also compresses
the image.medio.save_dir(dirname, np_image, metadata, use_original_ornt=True, dtype=None, channels_axis=None, parents=False, exist_ok=False, allow_dcm_reorient=False, **kwargs)
Save a 3d numpy array np_image
as a dicom series of 2d slices in the directory dirname
(itk
backend).
dirname
: path-likeexist_ok
is True).The other parameters: np_image
, metadata
, use_original_ornt
, dtype
, channels_axis
,
parents
and allow_dcm_reorient
are equivalent to those of save_img.
Additional optional parameters (**kwargs
):
exist_ok
: boolpattern='IM{}.dcm'
: str{}
') for the slice number.
For example, one can use: pattern={:03d}
.metadata_dict=None
: dict or Nonemetadata_dict={'0008|0060': 'US'}
will override the default 'CT' modality and set it to 'US'
(ultrasound).medio.Affine
The affine of an image is a transformation between the index space of the array to the physical 3d
space. The Affine class is a subclass of numpy.ndarray with some special properties (attributes):
spacing
, origin
, and direction
, which can be accessed and set. The method index2coord
maps
the indices to the physical space, clone
clones the affine.
This class includes also some static methods for affine construction from its components (spacing, origin and direction) and also the inverse methods for getting the spacing, origin and direction matrix from a general affine matrix.
For a mathematical explanation about the affine matrix see NiBabel's affine documentation.
Some usage examples:
>>> import numpy as np
>>> from medio import Affine
>>> affine1 = Affine(np.eye(4))
>>> affine2 = Affine(direction=np.eye(3), spacing=[0.33, 1, 0.33], origin=[-90.3, 10, 1.44])
>>> index = [4, 0, 9]
>>> coord = affine2.index2coord(index)
>>> print(coord)
[-88.98 10. 4.41]
medio.MetaData
Together with the image's numpy array, the MetaData object is a necessary component for the I/O functions.
A MetaData object 'metadata' is mainly comprised of:
metadata.affine
: the affine (of class Affine)metadata.coord_sys
: coordinate system ('itk' or 'nib')metadata.orig_ornt
: the original orientation of the image (used for saving)metadata.header
: a dictionary that includes additional metadata properties when header=True
in read_img
, otherwise NoneOther properties of the metadata are derived from the affine:
metadata.spacing
: voxels spacing (a reference to metadata.affine.spacing
)metadata.ornt
: the current image orientation (depends on the coordinate system coord_sys
)All these properties can be viewed easily in the console:
>>> import medio
>>> array, metadata = medio.read_img('avg152T1_LR_nifti.nii.gz', header=False)
>>> print(metadata)
Affine:
[[ -2. 0. 0. 90.]
[ 0. 2. 0. -126.]
[ 0. 0. 2. -72.]
[ 0. 0. 0. 1.]]
Spacing: [2. 2. 2.]
Coordinate system: nib
Orientation: LAS
Original orientation: LAS
Header: None
The MetaData method metadata.is_right_handed_ornt()
checks for a right handed orientation
according to the determinant of the direction matrix (metadata.affine.direction
). This method can
be useful before saving a dicom file or series, which should have a right-handed orientation.
The method clone
clones the metadata object, convert
converts the metadata in-place to the
given coordinate system.
The orientation of a 3d image is string of length 3 that is derived from its affine and coordinate
system (the convention). It denotes along which physical axis we move when we increase a single
index out of i, j, k
in the expression np_image[i, j, k]
.
For example, 'RAS' orientation in itk:
'RAS' in nib - also 'RAS+':
Note that the conventions are opposite. For stability reasons, we use itk convention by default
for read_img
's argument desired_ornt
, although one can choose otherwise with the parameter
coord_sys
.
For further discussion see NiBabel's image orientation documentation.
Some operations on an image affect also its metadata, for example resizing, rotations and cropping.
The class MedImg (medio.medimg.medimg.MedImg
) holds an image array with its metadata, and
supports some of these operations through the indexing syntax:
>>> from medio.medimg.medimg import MedImg
>>> mimg = MedImg(np_image, metadata)
>>> new_mimg = mimg[:, 4:-4, ::3]
>>> print(new_mimg.metadata)
Ellipsis ('...') syntax is also supported. This indexing allows cropping and basic down-sampling, along with correct metadata update.