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
Metadata
Release files for medio 0.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| medio-0.7.0.tar.gz | 33.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| medio-0.7.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 74.9 kB
Release files / medio-0.7.0.tar.gz
| Download URL | medio-0.7.0.tar.gz |
|---|---|
| Size | 33.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
231ce401007ba8c12d26e4b4648d03053564b7e9c6bfcfe4be573fdc68ed7865
|
|
BLAKE2b-256 checksum How to use checksums |
9ebdaebd3ecbccd125a1e3aedd713f4ca7200a85a59e6f52f3f64b3edd7c02c1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 17, 2026.
Transparency logRelease files / medio-0.7.0-py3-none-any.whl
| Download URL | medio-0.7.0-py3-none-any.whl |
|---|---|
| Size | 41.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e0b2149d7f92e4b7b327f770b2a9dd9d06142ed83380772b56efaf929c0b13df
|
|
BLAKE2b-256 checksum How to use checksums |
f976fd2e098c54bf024de8a1959ebf136d72b0f701ac562e3aca1a451c475ae9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 17, 2026.
Transparency log