Skip to content
RSIP-VisionPublic

About

Medical images I/O python package

Topics

Resources

Contributing

Stars

32 stars

Watchers

4 watching

Forks

Repository files navigation

Python version PyPI version License

medio

Medical image I/O for Python — read and write NIfTI, DICOM, MetaImage, and more in one consistent API.

  • One function reads any format; format auto-detected from path
  • Returns a NumPy array + rich MetaData (affine, orientation, spacing) — no format-specific objects to unwrap
  • Transparent coordinate system normalization between ITK and NiBabel conventions
  • Metadata-only reads (read_meta) for large files when you only need spatial info

Installation

pip install medio

Usage

Read and save any format

import medio

arr, meta = medio.read_img('scan.nii.gz')
print(meta.ornt, meta.spacing)   # e.g. 'LPI', [0.5, 0.5, 1.0]

medio.save_img('out.mhd', arr, meta)

Read metadata only — no pixel data loaded

meta = medio.read_meta('large_scan.nii.gz')
print(meta.spatial_shape)    # (256, 256, 128)
print(meta.affine.spacing)   # [0.98, 0.98, 1.5]

Reorient to a standard orientation

arr, meta = medio.read_img('scan.nii.gz', desired_ornt='RAS')
# arr axes are reordered; meta.affine updated to match

Write a DICOM series from a 3D array

arr, meta = medio.read_img('scan.mhd')
medio.save_dir('dicom_out/', arr, meta)

DICOM series geometry is validated

A DICOM directory is only a valid 3D volume if its slices agree geometrically. Series Instance UID does not guarantee that: scanners emit a localizer/scout under the same Series Instance UID as the acquisition, and reading it as part of the volume silently corrupts the derived slice spacing. read_img and read_meta therefore refuse an inconsistent series instead of returning a wrong image:

medio.read_img('dicom_dir/')
# InconsistentSeriesError: DICOM series mixes slice orientations: 1 of 325 slice(s) are not
# parallel to the first ... typically a localizer/scout slice sharing the Series Instance UID.

# read only the slices that share the dominant geometry (drops the localizer):
arr, meta = medio.read_img('dicom_dir/', keep_dominant_geometry=True)

# opt out entirely and get the previous, unchecked behaviour:
arr, meta = medio.read_img('dicom_dir/', validate_series=False)

The checks are the invariants any single volume must satisfy — one orientation, one in-plane geometry, uniformly spaced slice positions — and they catch missing or duplicated slices as well as localizers. SliceThickness is deliberately not required to equal the slice spacing: gapped and overlapping reconstructions are legitimate.

keep_dominant_geometry filters first and then still validates what remains, so it discards off-geometry slices without hiding a genuine gap in the rest.

Spatial slicing with automatic affine update

from medio.medimg import MedImg

mimg = MedImg(arr, meta)
cropped      = mimg[2:8, 3:7, :]    # origin updated
downsampled  = mimg[::2, ::2, ::1]  # spacing updated

Supported Formats

Format Extensions Default backend
NIfTI .nii, .nii.gz ITK
DICOM directory or .dcm ITK
MetaImage .mhd, .mha ITK
NIfTI (NiBabel) .nii, .nii.gz backend='nib'
DICOM (pydicom) .dcm backend='pdcm'
Other ITK formats .png, .jpg, … ITK

API Reference

read_img

medio.read_img(input_path, desired_ornt=None, backend=None, dtype=None,
               header=False, channels_axis=-1, coord_sys='itk', **kwargs)
→ tuple[np.ndarray, MetaData]
Parameter Type Default Description
input_path path-like — File or DICOM directory
desired_ornt str | None None Reorient to this axis code (e.g. 'RAS')
backend str | None None Force backend: 'itk', 'nib', 'pdcm'
dtype dtype | None None Cast array to this dtype
header bool False Include raw format header in MetaData.header
channels_axis int | None -1 Axis for multi-channel (e.g. RGB) images
coord_sys 'itk' | 'nib' | None 'itk' Coordinate convention for orientation and metadata

**kwargs are passed to the backend. DICOM directories (both backends): series, validate_series, keep_dominant_geometry. ITK-specific: pixel_type, fallback_only. pydicom-specific: globber, allow_default_affine.

DICOM-directory parameter Type Default Description
validate_series bool True Raise InconsistentSeriesError unless the slices form one consistent 3D volume. False restores the previous, unchecked behaviour
keep_dominant_geometry bool False Read only the slices sharing the dominant geometry, discarding e.g. a localizer that shares the Series Instance UID

read_meta

medio.read_meta(input_path, desired_ornt=None, backend=None,
                header=False, coord_sys='itk', **kwargs)
→ MetaData

See read_img for parameters. Reads only spatial metadata without loading pixel data.


save_img

medio.save_img(filename, np_image, metadata, use_original_ornt=True,
               backend=None, dtype=None, channels_axis=None,
               mkdir=False, parents=False, **kwargs)
Parameter Type Default Description
filename path-like — Output file path (format inferred from suffix)
np_image ndarray — Image array
metadata MetaData — Spatial metadata
use_original_ornt bool True Reorient to metadata.orig_ornt before saving
backend str | None None Force backend: 'itk' or 'nib'
dtype dtype | None None Cast before saving
channels_axis int | None None Axis of channel dimension in np_image
mkdir bool False Create the output directory if it doesn't exist
parents bool False Create all missing parent directories

save_dir

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)

Saves a 3D array as a DICOM series of 2D slices.

Parameter Default Description
dirname — Output directory
exist_ok False Allow writing into an existing non-empty directory
allow_dcm_reorient False Reorient to nearest right-handed orientation if needed
pattern 'IM{}.dcm' Filename pattern; {} is replaced with the slice number
metadata_dict None Override or add DICOM tags, e.g. {'0008|0060': 'US'}

MetaData

medio.MetaData(affine, coord_sys='itk', orig_ornt=None, header=None, spatial_shape=None)
Property Type Description
affine Affine 4×4 spatial transform (index space → physical space)
coord_sys str 'itk' or 'nib'
ornt str Current orientation code (e.g. 'LPI'), derived from affine
orig_ornt str Orientation before any reorientation
spacing ndarray Voxel spacing — alias for affine.spacing
header dict | None Raw format header (populated when header=True in read_img)
spatial_shape tuple | None Image dimensions (populated by read_meta)

Methods: .convert(dest_coord_sys) — in-place convention switch; .clone() — deep copy.


Affine

A 4×4 NumPy array subclass with named spatial accessors.

from medio import Affine
import numpy as np

aff = Affine(np.eye(4))
aff = Affine(direction=np.eye(3), spacing=[0.5, 0.5, 1.0], origin=[0., 0., 0.])
coord = aff.index2coord([4, 0, 9])   # map voxel index → physical coordinate

Properties: .spacing, .origin, .direction (all gettable and settable). Method: .clone().

For a mathematical background see NiBabel's affine documentation.


MedImg

Container for an image array + metadata with spatially-aware indexing.

from medio.medimg.medimg import MedImg

mimg = MedImg(arr, meta)                         # from array + metadata
mimg = MedImg.from_file('scan.mhd')   # load from file

Indexing crops or downsamples the array and updates the affine automatically:

Indexing Effect on metadata
mimg[2:8, 3:7, :] origin updated to new start voxel
mimg[::2, ::2, ::1] spacing scaled by step sizes
mimg[..., 5:15] ellipsis supported

Properties: .np_image, .metadata. Method: .save(filename).


Orientation conventions

medio uses ITK convention by default (coord_sys='itk'). Pass coord_sys='nib' to read_img / read_meta to work in NiBabel convention throughout.


License

Apache 2.0 — see LICENSE.

Issues and contributions: github.com/RSIP-Vision/medio/issues

About

Medical images I/O python package

Topics

Resources

Contributing

Stars

32 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages