Skip to content

API reference

Generated from pydcm's docstrings. The top-level pydcm module re-exports the core read / write / decode API plus every specialist surface below.

Read / write / decode

The core read / write / decode API. See Behaviour notes for the short list of deliberate behaviours worth knowing.

pydcm

pydcm — decode DICOM pixels for NumPy / PyTorch.

A compiled native extension decodes EVERY transfer syntax (JPEG / JPEG-2000 / HTJ2K / JPEG-LS / JPEG-XL / RLE) to native integer pixels — or Hounsfield units — with no gdcm/pylibjpeg plugins, and assembles a directory of slices into a 3D volume.

import pydcm
arr = pydcm.decode("scan.dcm")             # ndarray [frames, rows, cols(, samples)]
hu  = pydcm.decode("ct.dcm", rescale=True)  # float32 Hounsfield units
vol = pydcm.load_series("ct_dir/")          # spatially-ordered 3D HU volume

from torch.utils.data import DataLoader
ds = pydcm.DICOMDataset("study_dir/", to_torch=True)
for batch in DataLoader(ds, batch_size=8, num_workers=4):
    ...

NOT a medical device — not for clinical or diagnostic use; research/engineering only.

decode

decode(
    path,
    frame: int = 0,
    *,
    rescale: bool = False,
    to_torch: bool = False,
    with_meta: bool = False
)

Decode a DICOM file's pixels to an array.

Parameters

path : str | os.PathLike A Part-10 DICOM file (any transfer syntax). frame : int 1-based frame to extract; 0 (default) returns all frames. rescale : bool True → real-world values (HU for CT) as float32 (per-frame rescale applied); False (default) → native stored integers (lossless). to_torch : bool Return a torch.Tensor instead of a NumPy array. with_meta : bool Also return the geometry sidecar (rescale_slope/intercept, pixel_spacing, image_position/orientation_patient, slice_thickness, window_center/width, modality, *_instance_uid, …).

Returns

ndarray (or Tensor), shape [frames, rows, cols(, samples)] — or (array, meta) when with_meta=True.

dcmread

dcmread(
    fp,
    defer_size=None,
    stop_before_pixels=False,
    force=False,
    specific_tags=None,
    *,
    charset_override: str = "",
    **_ignored
) -> Dataset

Read a DICOM Part-10 file into a :class:Dataset (pydicom-compatible).

fp may be a path, an os.PathLike, raw bytes, or a readable binary file-like object (e.g. io.BytesIO) — matching pydicom. charset_override forces a SpecificCharacterSet when a file omits/misdeclares one. stop_before_pixels / defer_size / specific_tags are accepted for signature compatibility (pixels are always lazy here). A non-DICOM input raises :class:~pydcm.errors.InvalidDicomError unless force=True.

dcmwrite

dcmwrite(path, dataset: Dataset, **_ignored) -> None

Write dataset to path as Part-10 (pydicom-compatible name).

pixel_array

pixel_array(src, *, index=None, **_kw)

Decode pixels from a Dataset / path / binary file-like (pydicom 3.x).

iter_pixels

iter_pixels(src, **_kw)

Yield each frame's pixel array lazily (pydicom 3.x).

generate_uid

generate_uid(
    prefix: str | None = PYDICOM_ROOT_UID, entropy_srcs=None
) -> UID

Return a unique :class:UID (pydicom-compatible).

With entropy_srcs the result is deterministic for that input; otherwise it is random. prefix=None yields a 2.25. UUID-derived UID. Reuses the native canonical generator (_native.mint_uid) — no separate UID arithmetic in Python.

Pixels

pydcm.pixels

pydicom 3.x-compatible pixel helpers (pydcm.pixels). Decoding reuses the native engine; the LUT/windowing/colour helpers apply the standard PS3.3 formulas to an array.

unpack_bits

unpack_bits(ds)

Decode 1-bit packed PixelData (BitsAllocated=1) to a {0,1} uint8 array [frames, rows, cols] (PS3.5 §8.1.1, little bit order — matches pydicom).

decode_uncompressed

decode_uncompressed(ds)

Decode uncompressed little-endian PixelData held in-memory to a stored-int ndarray [frames, rows, cols(, samples)] — pure numpy buffer reinterpretation (no native engine, no backing file), matching pydicom's numpy handler. The native decoder stays authoritative for files and for compressed / Big-Endian / Deflated syntaxes.

pack_bits

pack_bits(arr, pad: bool = True) -> bytes

Pack a binary {0,1} :class:numpy.ndarray into bytes for 1-bit Pixel Data (PS3.5 §8.1.1, little bit order — inverse of :func:unpack_bits, pydicom-compatible).

apply_rescale

apply_rescale(arr, ds)

Apply the linear Modality LUT (arr * RescaleSlope + RescaleIntercept), pydicom-compatible. Use :func:apply_modality_lut when a Modality LUT Sequence may be present; this is the rescale-only path.

pixel_array

pixel_array(src, *, index=None, **_kw)

Decode pixels from a Dataset / path / binary file-like (pydicom 3.x).

iter_pixels

iter_pixels(src, **_kw)

Yield each frame's pixel array lazily (pydicom 3.x).

apply_modality_lut

apply_modality_lut(arr, ds)

Modality LUT (Rescale Slope/Intercept → e.g. Hounsfield units), PS3.3 C.11.1.

apply_voi_lut

apply_voi_lut(arr, ds, index=0)

VOI LUT / windowing (PS3.3 C.11.2), matching pydicom's output range.

Applies a VOI LUT Sequence if present, else Window Center/Width with the VOILUTFunction (LINEAR / LINEAR_EXACT / SIGMOID). The output is scaled to [0, 2**BitsStored - 1] like pydicom (NOT [0,1]).

apply_voi

apply_voi(arr, ds, index=0)

Apply a VOI LUT Sequence (0028,3010) if present; else return arr unchanged.

apply_windowing

apply_windowing(arr, ds, index=0)

Linear/sigmoid Window Center/Width (PS3.3 C.11.2.1.2), pydicom output range.

convert_color_space

convert_color_space(
    arr, current, desired, *, per_frame=False
)

Convert between RGB and YBR_FULL/YBR_FULL_422 (PS3.3 C.7.6.3.1.2).

apply_color_lut

apply_color_lut(arr, ds=None, palette=None)

Map PALETTE COLOR indices to an RGB array via the Palette Color LUTs (PS3.3 C.7.6.3.1.6).

apply_presentation_lut

apply_presentation_lut(arr, ds)

Apply a Presentation LUT (Sequence or INVERSE shape) to arr, pydicom-compatible.

Returns P-values; if no Presentation LUT module is present, returns arr unchanged. Modality/VOI LUTs (if any) must be applied first.

as_pixel_options

as_pixel_options(ds, **kwargs)

Return the Image Pixel module element values from ds as a dict (pydicom-compatible).

compress

compress(
    ds,
    transfer_syntax_uid,
    arr=None,
    *,
    encoding_plugin="",
    encapsulate_ext=False,
    generate_instance_uid=True,
    **kwargs
)

Compress ds in place to transfer_syntax_uid (delegates to the native engine).

decompress

decompress(
    ds,
    *,
    as_rgb=True,
    generate_instance_uid=True,
    decoding_plugin="",
    **kwargs
)

Decompress ds's Pixel Data in place to native encoding (delegates to native).

set_pixel_data

set_pixel_data(
    ds,
    arr,
    photometric_interpretation,
    bits_stored,
    *,
    generate_instance_uid=True
)

Set ds's Pixel Data + Image Pixel module elements from arr (pydicom 3.0).

File-sets (DICOMDIR)

pydcm.fileset

pydicom-compatible DICOMDIR / File-set reading (pydcm.FileSet).

A File-set is a DICOMDIR plus the instance files it indexes. dcmread already parses the DICOMDIR's DirectoryRecordSequence (PATIENT→STUDY→SERIES→IMAGE), so this is a thin navigation layer over those records — it reuses dcmread for both the DICOMDIR and each referenced instance; no separate DICOM parsing.

fs = pydcm.FileSet("/media/DICOMDIR")
for inst in fs:                     # FileInstance per leaf record
    ds = inst.load()               # -> a Dataset (reuses dcmread)
for inst in fs.find(PatientID="1"):
    ...

FileSet

A DICOMDIR File-set — iterate :class:FileInstance leaves, find + load.

Construct from a DICOMDIR path or an already-read DICOMDIR :class:Dataset.

find

find(load: bool = False, **filters)

Return instances matching filters (record attributes; with load=True the referenced file is read and its values are matched too).

find_values

find_values(element, instances=None)

Distinct values of element across the (given or all) instances.

add

add(ds_or_path) -> None

Stage an instance (a :class:Dataset or a file path) for :meth:write.

write

write(path) -> str

Write the staged instances + a conformant DICOMDIR under path; returns the DICOMDIR path. Instances staged from a file are copied byte-verbatim.

FileInstance

One referenced instance (a leaf DICOMDIR record), pydicom-compatible.

Attribute access falls back through the record hierarchy: the instance/IMAGE record first, then its SERIES, STUDY and PATIENT records — so inst.PatientID, inst.StudyInstanceUID etc. work without loading the file.

load

load() -> Dataset

Read and return the referenced :class:Dataset (reuses dcmread).

Volumes, geometry & ML

pydcm.volume

Directory of slices → one spatially-ordered 3D volume.

Thin orchestration over the native volume engine: IOP clustering, IPP-projection Z-sort, and N-D dimension discovery all happen in the compiled _core extension. Nothing about the geometry is reimplemented here — Python only enumerates the files, hands them to the engine, and wraps the result as NumPy.

Volume dataclass

An assembled 3D volume. pixels is float32 Hounsfield/real-world values from :func:load_series; from :func:from_nifti it keeps the file's dtype (e.g. an integer label mask).

to_nifti

to_nifti(path) -> str

Write this volume to NIfTI-1. .nii.gz extension → gzip, else .nii.

Thin call into the native dcm_nifti engine — the voxel→world affine (LPS) is flipped to RAS inside the writer; nothing is recomputed here. The NIfTI datatype follows pixels' dtype (float32 / uint8 / int16 / uint16, so integer label masks stay integer); other dtypes are cast to float32.

load_series

load_series(
    path,
    *,
    recursive: bool = True,
    pattern: str | None = None
) -> Volume

Assemble the DICOM slices under path into one ordered 3D HU volume.

path is a directory (or list of files). Files are grouped/sorted by the native engine; the largest coherent volume is returned (so a stray localizer or mixed series does not corrupt the stack). For a plain CT/MR series directory that is simply the volume.

from_nifti

from_nifti(path) -> Volume

Read a NIfTI-1 file (.nii/.nii.gz) back into a :class:Volume.

The file's RAS sform is flipped to our LPS convention by the native reader, so the returned affine matches what :func:load_series produces. Voxels keep the file's dtype (e.g. float32 image, integer label mask).

bids_sidecar

bids_sidecar(path) -> dict

Extract a BIDS JSON sidecar (the metadata dcm2niix writes next to a .nii) from one DICOM instance — timing (in seconds), sequence, and geometry fields.

Returns a dict of the present fields (e.g. RepetitionTime, EchoTime, FlipAngle, Manufacturer, ImageOrientationPatientDICOM).

PhaseEncodingDirection (BIDS i/i-/j/j-) is emitted when the vendor records the polarity (Siemens CSA / 0021,111C; GE 0018,9034; UIH 0065,1058); its sign follows this writer's no-row-flip storage (dcm2niix isFlipY=False), so the i sign equals dcm2niix's while the j sign is the negation — verified on the dcm2niix dcm_qa suite, where our volume equals dcm2niix's reference with the rows flipped, so each sidecar correctly describes its own array. Unknown polarity gives only the unsigned PhaseEncodingAxis; a 3-D non-EPI scan emits neither.

SliceTiming (seconds) is emitted for a Siemens mosaic from the CSA MosaicRefAcqTimes (the whole schedule lives in one instance). EffectiveEchoSpacing/TotalReadoutTime (seconds) are emitted for Siemens EPI (1/(BW·N) and ES·(N−1), N=NumberOfPhaseEncodingSteps); matches dcm2niix for full-resolution and phase-oversampled EPI.

pydcm.diffusion

DWI gradient table — a DICOM series → FSL .bval / .bvec.

Thin orchestration over the native read_diffusion (the STANDARD MR Diffusion sequence first — the modern enhanced-MF path already parsed by the native core — then the legacy Siemens CSA fallback). The collected (b-value, gradient) table is exactly the input the native dcm_dti tensor engine consumes.

diffusion_table

diffusion_table(files, *, output_prefix=None, rotate=True)

Collect DWI b-values + gradient directions into FSL .bval / .bvec.

a directory, a single file, or a list of instances. Multi-frame

(enhanced-MF) files contribute one entry per frame; single-frame series one per file. Entries are ordered by (InstanceNumber, frame).

rotate: rotate each gradient from the patient (LPS) frame into the image/voxel frame [row·g, col·g, normal·g] (the b0 zero vector is left as-is). output_prefix: also write <prefix>.bval and <prefix>.bvec.

Returns (bvals[N], bvecs[3, N]).

Note: bvecs are in the DICOM (LPS) image frame; FSL — relative to the NIfTI (RAS) axes — may need an axis sign flip. Validate against your pipeline.

load_dwi

load_dwi(series, *, recursive=True, order='gradient')

Load a single-frame DWI series as a 4-D volume + gradient table.

Groups the slices by their diffusion (b-value + gradient — the standard top-level tags 0018,9087 / 0018,9089, falling back to the Siemens CSA header; non-DWI frames are skipped), assembles each direction's 3-D volume, and stacks them. Returns (data[V, Z, Y, X], bvals[V], bvecs[3, V], affine) — bvecs rotated into the image/voxel frame, ready for dti_*.

"gradient" (default) sorts volumes by gradient, b0 first — fully

deterministic; "acquisition" orders them by each direction's earliest InstanceNumber, matching dcm2niix. The .bval/.bvec stay aligned either way.

Note: this covers single-frame (per-file) DWI. Enhanced-MF DWI needs the native per-frame MR Diffusion parse (a separate change).

save_dwi

save_dwi(
    series,
    output_prefix,
    *,
    recursive=True,
    order="acquisition"
)

Convert a single-frame DWI series to NIfTI + FSL .bval/.bvec (the dcm2niix DWI deliverable). Writes <prefix>.nii.gz (4-D), <prefix>.bval and <prefix>.bvec; returns their paths.

order defaults to "acquisition" so the volume order matches dcm2niix's output (pass "gradient" for deterministic b0-first ordering).

pydcm.transforms

Deterministic medical-image transforms — pydcm's "ITK".

Thin marshalling over the native transform engine (CPU C/C++). This is the same preprocessing whether you prepare training data here or deploy in the browser (where dcmmodel runs the identical ops as WGSL, CI-verified equal), so preprocessing cannot drift between train and serve.

Scope: the load-bearing deterministic ops — spatial (resample_to_spacing and resize with label-safe nearest; the exact index ops crop, pad, crop_foreground, flip), intensity (normalize_zscore, scale_intensity_range), post (argmax) — plus a minimal :class:Compose. Random augmentation is intentionally not here (use MONAI/torchio for training aug).

Spatial ops operate on a :class:~pydcm.volume.Volume (pixels[z,y,x] + LPS affine) and return a new Volume; intensity ops likewise. Geometry is never recomputed in Python — the native engine owns it.

Cross-framework conventions. Most ops are convention-free (identical across scipy/torch/MONAI). Two things genuinely diverge between frameworks: the resampling interpolation (scipy spline vs torch grid_sample) and the gaussian importance map. To get a self-consistent pipeline for one framework, import a preset instead of mixing primitives::

from pydcm.transforms import nnunet as T   # scipy spline + nnU-Net gaussian
from pydcm.transforms import monai  as T   # torch grid_sample + MONAI gaussian

The preset binds the divergent ops to that framework and passes the rest through. See docs/transforms_references.md for the per-op authoritative reference + precision.

Compose

Apply a sequence of transforms left-to-right (MONAI Compose).

Each entry is a callable Volume -> Volume; use functools.partial or a lambda to bind parameters, e.g. Compose([lambda v: resample_to_spacing(v, 1.0), normalize_zscore]).

resample_to_spacing

resample_to_spacing(
    vol: Volume,
    spacing,
    *,
    is_label: bool | None = None,
    interp: str = "linear"
) -> Volume

Resample vol to an axis-aligned LPS grid at spacing mm.

spacing is a scalar (isotropic) or (x, y, z). is_label defaults from dtype (integer → label → nearest); pass it to override. interp is "linear" / "cubic" / "nearest" (ignored for labels — always nearest).

resample_to_reference

resample_to_reference(
    moving: Volume,
    reference: Volume,
    *,
    is_label: bool | None = None,
    interp: str = "linear"
) -> Volume

Resample moving onto reference's grid (its shape + affine).

This is how you invert a preprocessing chain: a prediction computed in some processed space (resampled / reoriented / cropped) is mapped back onto the original :class:~pydcm.volume.Volume's grid by passing that original as reference. It works through any sequence of geometric transforms (it uses the affines, not a recorded op-stack) and handles an oblique reference grid. is_label defaults from dtype (integer → nearest).

affine

affine(
    vol: Volume,
    matrix,
    *,
    is_label: bool | None = None,
    interp: str = "linear"
) -> Volume

Apply a voxel-space affine to the image (MONAI Affine). matrix is a 4×4 array — the forward source-voxel → output-voxel transform (rotate/scale/shear/translate), in engine voxel order (x, y, z) i.e. (W, H, D). The output keeps the source's grid, so content moved outside the field of view is clipped. is_label defaults from dtype (integer → nearest).

resample_separate_z

resample_separate_z(vol: Volume, out_shape) -> Volume

nnU-Net anisotropic separate-z resample to out_shape (D, H, W): per-slice in-plane cubic B-spline + nearest through-plane (the low-res Z axis), with an fp64 prefilter — the precision-faithful path for thick-slice MR/CBCT (matches nnU-Net / scipy map_coordinates). For images (the in-plane spline blends labels).

resample_cubic

resample_cubic(vol: Volume, out_shape) -> Volume

Isotropic cubic B-spline resample to out_shape (D, H, W) — scipy order=3, fp64, bit-exact with skimage.resize(order=3, mode='edge', clip=True) (the nnU-Net image reference). For images.

resample_nearest

resample_nearest(vol: Volume, out_shape) -> Volume

Nearest-neighbour resample to out_shape (D, H, W) (scipy order=0, half-pixel) — the nnU-Net label path (no class blending).

resample_grid_sample

resample_grid_sample(vol: Volume, out_shape) -> Volume

Trilinear resample to out_shape (D, H, W) matching torch/MONAI grid_sample(align_corners=False, bilinear, padding_mode='border') — MONAI Spacing's resampling backend (for an axis-aligned grid). fp64 internally; same algorithm as torch, so the fp32 output agrees to ≤1 fp32 ULP (~all voxels equal). Not 100% bit-exact: torch's affine_grid builds the sampling grid with a SIMD linspace whose last bit isn't reproducible from a closed form. For images.

resize

resize(
    vol: Volume,
    out_shape,
    *,
    is_label: bool | None = None,
    interp: str = "linear"
) -> Volume

Resample vol to exactly out_shape (D, H, W) voxels over the same field of view (MONAI Resize). is_label defaults from dtype.

resize_with_pad_or_crop

resize_with_pad_or_crop(
    vol: Volume,
    size,
    *,
    mode: str = "constant",
    value: float = 0.0
) -> Volume

Force the volume to exactly size (z, y, x) by centre-cropping axes that are too large and centre-padding axes that are too small (MONAI ResizeWithPadOrCrop).

reorient

reorient(vol: Volume, axcodes: str = 'LPS') -> Volume

Reorient so increasing voxel index runs toward the target world directions (MONAI Orientation). axcodes is 3 letters from L/R, P/A, S/I — the world is LPS, so "LPS" is the engine-canonical orientation. Exact axis permutation + flips; world coordinates are unchanged. Raises on an oblique affine.

crop

crop(vol: Volume, start, size) -> Volume

Crop the [start, start+size) box; start/size are (z, y, x) (numpy axis order). Exact — no interpolation. The affine origin shifts to keep world coordinates.

pad

pad(
    vol: Volume,
    lo,
    hi,
    *,
    mode: str = "constant",
    value: float = 0.0
) -> Volume

Pad lo/hi voxels before/after each axis ((z, y, x)). mode is "constant" / "edge" / "reflect" (MONAI SpatialPad/BorderPad).

crop_foreground

crop_foreground(vol: Volume, *, margin: int = 0) -> Volume

Crop to the bounding box of non-zero voxels, expanded by margin (MONAI CropForeground). Returns vol unchanged if every voxel is zero.

flip

flip(vol: Volume, axis) -> Volume

Reverse voxel order along the flagged axes. axis is a (z, y, x) triple of bools (MONAI Flip). The affine updates so world coordinates are unchanged.

center_crop

center_crop(
    vol: Volume, size, *, is_label: bool | None = None
) -> Volume

Center-crop to size (z, y, x) (MONAI CenterSpatialCrop; start = dim//2 - size//2). A size larger than the source is clamped to the source (crop only, no pad). Exact; the affine origin shifts to keep world coords.

spatial_pad

spatial_pad(
    vol: Volume,
    size,
    *,
    mode: str = "constant",
    value: float = 0.0,
    is_label: bool | None = None
) -> Volume

Centered pad to at least size (z, y, x) (MONAI SpatialPad symmetric). Axes already ≥ size are untouched. modeconstant/edge/reflect.

divisible_pad

divisible_pad(
    vol: Volume,
    k,
    *,
    mode: str = "constant",
    value: float = 0.0,
    is_label: bool | None = None
) -> Volume

Centered pad so each axis becomes a multiple of k (MONAI DivisiblePad — e.g. make dims divisible by 2^depth for a U-Net). k is a scalar or (z, y, x).

rotate90

rotate90(
    vol: Volume,
    k: int = 1,
    axes=(1, 2),
    *,
    is_label: bool | None = None
) -> Volume

Rotate k*90° in the plane of axes (numpy (D, H, W) axis indices; default (1, 2) = the in-plane H–W axes) — MONAI Rotate90 / np.rot90. Exact (no interpolation); the affine updates so world coordinates are kept.

normalize_zscore

normalize_zscore(
    vol: Volume, *, nonzero: bool = False
) -> Volume

z-score normalize voxels (→ float32). nonzero=True ignores zero voxels (MONAI NormalizeIntensity(nonzero=True)).

scale_intensity_range

scale_intensity_range(
    vol: Volume,
    a_min: float,
    a_max: float,
    b_min: float,
    b_max: float,
    *,
    clip: bool = True
) -> Volume

Linearly remap [a_min, a_max] → [b_min, b_max] (→ float32). CT windowing (MONAI ScaleIntensityRange). clip bounds the output to [b_min, b_max].

normalize_ct

normalize_ct(
    vol: Volume,
    clip_lo: float,
    clip_hi: float,
    mean: float,
    std: float,
) -> Volume

Clip to [clip_lo, clip_hi] then z-score with fixed mean/std (→ float32). This is nnU-Net CTNormalization (clip to the dataset's [0.5, 99.5] percentiles, normalize by the dataset mean/std) and equivalently MONAI NormalizeIntensity(subtrahend=mean, divisor=std) preceded by a clip.

scale_intensity_range_percentiles

scale_intensity_range_percentiles(
    vol: Volume,
    lower: float,
    upper: float,
    b_min: float,
    b_max: float,
    *,
    clip: bool = True
) -> Volume

Like :func:scale_intensity_range, but a_min/a_max are the per-image lower/upper percentiles (0..100, np.percentile linear) — MONAI ScaleIntensityRangePercentiles. → float32.

adjust_contrast

adjust_contrast(vol: Volume, gamma: float) -> Volume

Gamma contrast ((x-min)/(range+1e-7))**gamma * range + min (MONAI AdjustContrast). → float32.

gaussian_smooth

gaussian_smooth(vol: Volume, sigma) -> Volume

Separable Gaussian smoothing (MONAI GaussianSmooth). sigma is a scalar (isotropic) or (z, y, x) in voxels; sigma<=0 on an axis skips it. → float32.

argmax

argmax(
    probs: ndarray, affine: ndarray, *, channel_dim: int = 0
) -> Volume

Channel-wise argmax of a probability/logit array → a label :class:Volume.

probs is channel-first [C, z, y, x] by default (torch/MONAI convention); set channel_dim for another layout. Output is uint8 (C ≤ 256) else uint16.

connected_components

connected_components(
    vol: Volume, *, connectivity: int = 6
) -> Volume

Label each connected component of the non-zero foreground with a distinct id (1..N) → uint16 (≈ scipy.ndimage.label). connectivity is 6/18/26.

keep_largest_connected_component

keep_largest_connected_component(
    vol: Volume,
    *,
    connectivity: int = 6,
    per_class: bool = True
) -> Volume

Keep only the largest connected component, zeroing the rest (MONAI KeepLargestConnectedComponent). per_class=True keeps each non-zero class's own largest CC; False treats all non-zero as one foreground.

fill_holes

fill_holes(vol: Volume, *, connectivity: int = 6) -> Volume

Fill holes — background regions fully enclosed by a label — by setting them to that label (MONAI FillHoles). Each non-zero class is filled independently. connectivity (6/18/26) is that of the background.

as_discrete

as_discrete(vol: Volume, *, threshold: float) -> Volume

Binarize at threshold (value > threshold → 1) → uint8 label (MONAI AsDiscrete(threshold=...); the sigmoid-output counterpart of :func:argmax).

remove_small_objects

remove_small_objects(
    vol: Volume,
    *,
    min_size: int,
    connectivity: int = 6,
    per_class: bool = True
) -> Volume

Zero connected components smaller than min_size voxels (MONAI RemoveSmallObjects). per_class=True prunes each non-zero class independently.

one_hot

one_hot(
    vol: Volume,
    num_classes: int,
    *,
    channel_first: bool = True
) -> np.ndarray

Integer-label Volume → one-hot float32 array (MONAI AsDiscrete(to_onehot=...)). channel_first=True[C, z, y, x] (torch); else [z, y, x, C]. Pure NumPy — a training-side helper, not a browser-inference op.

sliding_window_positions

sliding_window_positions(
    spatial_shape, roi_size, *, overlap: float = 0.25
) -> np.ndarray

Patch origins for sliding-window inference over a (D, H, W) volume with window roi_size and fractional overlap ∈ [0, 1) — MONAI dense_patch_slices / _get_scan_interval (fixed scan interval, last patch shifted inward). Returns an (n, 3) int array of (z, y, x) origins. Raises if roi exceeds spatial.

gaussian_importance_map

gaussian_importance_map(
    roi_size,
    *,
    sigma_scale: float = 0.125,
    convention: str = "nnunet"
) -> np.ndarray

Gaussian blend-weight window of shape roi_size (D, H, W) — separable product of 1D sampled gaussians, sigma = roi*sigma_scale. → float32.

convention picks the framework's map (the two are not interchangeable):

  • "nnunet" (default) — center roi//2, peak 1, min = natural corner. Bit-exact with nnU-Net V2 get_gaussian / the deployed ai_segmeation maps.
  • "monai" — center (roi-1)/2, unnormalized (peak ≈0.91), min clamped to max(min, 1e-3). Matches MONAI compute_importance_map(mode='gaussian').

sliding_window_inference

sliding_window_inference(
    image,
    roi_size,
    predictor,
    *,
    overlap: float = 0.25,
    mode: str = "gaussian",
    sigma_scale: float = 0.125,
    convention: str = "nnunet",
    padding_mode: str = "constant",
    cval: float = 0.0
) -> np.ndarray

Run predictor over sliding windows and blend the patch outputs — the deterministic reproduction of MONAI sliding_window_inference.

image is (D, H, W) or (C_in, D, H, W). predictor maps one patch (same leading shape as image, spatial roi_size) to logits/probs (C_out, *roi_size). Windows use :func:sliding_window_positions; overlaps are combined with a mode='gaussian' importance map (sigma_scale + convention, 'nnunet'/'monai' — see :func:gaussian_importance_map) or mode='constant' (uniform) weight, accumulated and normalized by the summed weight. If the volume is smaller than roi_size it is padded (padding_mode/cval) and the result is cropped back. Returns (C_out, D, H, W).

pydcm.torchdata

Directory → samples. A directory of DICOM files is, for PyTorch, just a list of instances; DICOMDataset walks it and decodes one image per __getitem__.

One sample = one file. Single-frame files yield [rows, cols(, samples)]; multi-frame files yield [frames, rows, cols(, samples)]. To instead collapse a directory into one spatially-ordered 3D volume, use :func:pydcm.load_series.

DICOMDataset

Map-style dataset over the DICOM files under root.

DataLoader-compatible via __len__ / __getitem__ WITHOUT importing torch, so torch stays optional. __getitem__ returns a NumPy array (or, with to_torch=True, a torch.Tensor); pass a transform to override that and shape each sample however your model wants. rescale=True yields HU.

scan

scan(
    root,
    *,
    recursive: bool = True,
    pattern: str | None = None
) -> list[Path]

Discover DICOM instance files under root (a directory or a single file).

pattern — a glob (e.g. "*.dcm") selects by name only. When None, files are detected by extension OR the DICM preamble (also catching the extension-less files clinical exports often produce). Returns a sorted list.

pydcm.radiomics

pydcm radiomics — IBSI features over an ROI, plus a pyradiomics-compatible extractor.

Two surfaces over the one native radiomics engine:

  • pydcm.radiomics(image, mask=..., roi=...) — pydcm's own one-call API.
  • from pydcm.radiomics import featureextractor — a drop-in for ported from radiomics import featureextractor code (pyradiomics' import name is radiomics); featureextractor.RadiomicsFeatureExtractor(...).execute(img, mask) returns a pyradiomics-shaped OrderedDict.

This file is named for the domain (radiomics), mirroring how the pydicom-compat shims are named for what they mirror (srpydicom.sr) — not for the vendor.

RadiomicsFeatureExtractor

pyradiomics-compatible extractor over pydcm's native IBSI engine.

execute

execute(image, mask, label=None, voxelBased=False)

Return an OrderedDict of features over the ROI (pyradiomics-shaped).

radiomics

radiomics(
    image,
    mask=None,
    *,
    roi=None,
    spacing=None,
    bins=32,
    value_range=(-1024.0, 3071.0),
    bin_width=0.0,
    resample=0.0,
    normalize=False,
    normalize_scale=1.0,
    log_sigma=None,
    wavelet=False,
    averaged=True,
    resegment=None,
    resegment_sigma=False,
    resample_bspline=False,
    voxel_array_shift=0.0,
    filters=None
)

IBSI radiomic features over an ROI — 7 classes (firstorder / glcm / glrlm / glszm / gldm / ngtdm / shape), pyradiomics-standard names.

Two call styles — the same surface as the dcmradiomics CLI:

  • From files (the convenient path) — image is a DICOM path: pixels are decoded to real-world values (HU) and spacing is read from the image geometry (PixelSpacing / SliceThickness). Give the ROI as either mask = a co-framed mask DICOM path (non-zero = inside) or roi = (min, max) real-world-value thresholds.
  • From arrays (the low-level primitive) — image is a real-world-valued array (e.g. decode(..., rescale=True)) and mask a non-zero-inside array of the same shape; pass spacing = (x, y, z) mm yourself.

Preprocessing (IBSI / pyradiomics, off by default): resample > 0 resamples the ROI to isotropic voxels of that size in mm (trilinear image / nearest mask); bin_width > 0 uses fixed-bin-width discretisation (vs the fixed bins count); normalize z-score-normalises intensities (× normalize_scale). Filters multiply the feature set under pyradiomics image-type prefixes: log_sigma = LoG sigma(s) in mm (log-sigma-<s>-mm-3D_…); wavelet=True = the coif1 SWT 8 sub-bands (wavelet-LLH_…). With any filter, every key (incl. the original) is prefixed.

Both 2D (H, W) or 3D (slices, H, W). Returns {feature_name: value}.

Derived objects — authoring & reading

Author and read the structured DICOM objects (see the how-to recipes).

Segmentations (SEG)

pydcm.seg

pydcm — DICOM Segmentation authoring + reading (pydcm.seg).

Author coded binary or fractional Segmentations from a labelmap / probability maps, and read a Segmentation back to a labelmap / per-segment masks — the dcmqi itkimage2segimage / segimage2itkimage pair, over the shared native SEG write/decode engines (the mkseg CLI capability). An interop path that replaces pydicom-seg / dcmqi for the common cases; pydicom has no SEG module, so this is pydcm-native value-add.

SegmentReader

Read a DICOM Segmentation into per-segment masks (pydicom-seg-compatible).

MultiClassReader

Read a (non-overlapping) Segmentation into one label-map volume (pydicom-seg).

AlgorithmIdentificationSequence

highdicom-compatible: identifies the algorithm that produced a segment.

SegmentDescription

highdicom.seg.SegmentDescription-compatible description of one segment.

write_seg

write_seg(reference, labelmap, segments, output=None)

Author a coded BINARY DICOM Segmentation from a labelmap + segment terminology.

a source-image path, or a list of the source series' instance paths

— geometry, demographics and source references are taken from it.

labelmap: a uint16 array (H, W) or (slices, H, W); value k marks the segment whose labelID is k. For a series the slices must be ordered by ascending position. segments: list of dicts, each with label, labelID, rgb = (r, g, b), category / type / anatomic = (CodeValue, CodingScheme, CodeMeaning), algorithm_type, algorithm_name. output: write the SEG there and return None; if omitted, return Part-10 bytes.

write_seg_fractional

write_seg_fractional(
    reference,
    maps,
    segments,
    *,
    type="probability",
    max_value=255,
    output=None
)

Author a FRACTIONAL DICOM Segmentation from per-segment probability/occupancy maps.

The natural output of a soft-prediction model — each segment keeps its 8-bit value map instead of a hard 1-bit mask.

reference / segments / output: as in :func:write_seg. maps: array [nseg, (slices,) H, W] (segment-major; maps[i] is segment i's map). Float input is treated as 0..1 and scaled to 0..max_value; integer input is used as-is. type: 'probability' or 'occupancy' (Segmentation Fractional Type).

seg_from_nifti

seg_from_nifti(reference, mask, segments, output=None)

Author a coded DICOM Segmentation from a NIfTI label volume + reference series.

The NIfTI / FSL / ANTs → DICOM-SEG return path — the leg dcm2niix + nibabel cannot produce. mask is a .nii/.nii.gz label volume co-framed with reference (the natural dcm2nii → segment → mask-back round-trip); the native reader flips its Z axis to the reference's ascending-position order via the affine, so the labels land on the right slices.

a source-image path, a directory of the series' instances, or a list

of instance paths — geometry / demographics / source references come from it.

mask: path to the NIfTI label volume. segments: as in :func:write_seg (labelID selects which label maps to each). output: write the SEG there and return None; if omitted, return Part-10 bytes.

read_seg

read_seg(path, *, masks=False)

Reconstruct a DICOM Segmentation — the dcmqi segimage2itkimage capability, over the shared native SEG decode engine (geometry-correct: frames are placed onto a slice grid built from the per-frame Image Position projected along the slice normal — unlike the pydicom-seg-compat :class:MultiClassReader).

masks=False (default): (labelmap, meta)labelmap is (slices, rows, cols) uint16, voxel value = DICOM Segment Number (0 = background). meta["overlapping"] flags overlapping segments (combined labelmap is lossy there — use masks=True). masks=True: (masks, meta)(nseg, slices, rows, cols) float32 occupancy in [0, 1] (binary → 0/1, fractional → value/max); lossless for overlapping / fractional. Plane k is segment meta["segment_numbers"][k].

meta carries per-segment terminology (segments: number / label / category / type / anatomic codes / rgb), geometry (image_orientation_patient, pixel_spacing, slice_thickness, slice_origins) and a 4×4 affine (voxel→world LPS mm). Returns None when path is not a Segmentation.

Segmentation

Segmentation(
    source_images,
    pixel_array,
    segmentation_type,
    segment_descriptions,
    series_instance_uid,
    series_number,
    sop_instance_uid,
    instance_number,
    manufacturer,
    manufacturer_model_name=None,
    software_versions=None,
    device_serial_number=None,
    *,
    fractional_type="PROBABILITY",
    max_fractional_value=255,
    content_description=None,
    content_label=None,
    content_creator_name=None,
    transfer_syntax_uid=None,
    **_kwargs
)

highdicom.seg.Segmentation-compatible constructor — returns a pydcm Dataset.

Built over the native write_seg / write_seg_fractional: source_images supply geometry/demographics, pixel_array is the labelmap (BINARY/LABELMAP) or per-segment maps (FRACTIONAL), segment_descriptions the coded terminology. highdicom-only kwargs are accepted for source compatibility.

Parametric maps

pydcm.paramap

pydcm — DICOM Parametric Map authoring + reading (pydcm.paramap).

The dcmqi itkimage2paramap / paramap2itkimage pair, over the native engine:

  • :func:write_paramap — author a float Parametric Map (SOP 1.2.840.10008.5.1.4.1.1.30) from a real-valued array + the source series' geometry + a Real World Value Mapping (units / quantity / slope / intercept), via the native parametric-map engine (the float-pixel counterpart of the SEG writer). pydicom has no Parametric Map authoring; this is pydcm-native value-add.
  • :func:read_paramap — read a Parametric Map back to a real-valued float32 array
  • metadata (the Real World Value Mapping). Float / double-float pixel data decode directly; integer-stored maps have their RWVM slope/intercept applied. Reads third-party maps (dcmqi / highdicom), not only pydcm's own output.

write_paramap

write_paramap(
    reference,
    values,
    *,
    units=None,
    quantity=None,
    slope=None,
    intercept=None,
    label=None,
    explanation=None,
    dtype=None,
    output=None
)

Author a DICOM Parametric Map from a real-valued array.

a source-image path, or the list of source-series instance paths —

geometry, demographics and Frame of Reference are taken from it (one slice per array plane, ordered by position).

values: a float array (H, W) or (slices, H, W) of real-world values (one plane per reference slice). units: the measurement units — (code, scheme, meaning) (UCUM by default), (code, meaning), a plain meaning string, or a dict. E.g. ("um2/s", "UCUM", "um2/s"). quantity: the measured quantity code (value, scheme, meaning) (DCM by default), e.g. ("113041", "DCM", "Apparent Diffusion Coefficient"). dtype: the stored pixel type. None (default) → 32-bit float (FloatingPointImagePixel; the values are stored verbatim). "uint16" / "int16" / "uint8" / "int8" → integer pixels quantized through the Real World Value Mapping (stored = round((value - intercept) / slope)), so a reader recovers value = stored * slope + intercept. slope / intercept: Real World Value Mapping slope / intercept. For float storage the default is identity (1 / 0 — values are already real-world). For an integer dtype left unset, they are auto-computed to span the value range across the integer range (lossy only by the quantization step); pass them to control the scaling explicitly. output: write the map there and return None; if omitted, return Part-10 bytes.

read_paramap

read_paramap(path)

Read a DICOM Parametric Map to (values, meta) — the dcmqi paramap2itkimage capability.

a float32 array (frames, rows, cols) of real-world values. Float /

double-float pixel data is returned directly; an integer-stored map has its Real World Value Mapping (slope/intercept) applied.

meta: the geometry sidecar (as :func:pydcm.decode) plus is_parametric_map and, when present, real_world_value_mapping = {slope, intercept, units, label, first_value_mapped, last_value_mapped, has_lut}.

Structured reports (SR / TID 1500)

pydcm.sr

pydcm Structured Reporting (pydcm.sr) — pydicom-compat surface + native engine.

  • pydicom-compatCode (pydicom's sr.coding.Code shape) and content_json, so code ported from from pydicom.sr import … keeps working.
  • pydcm-native (what pydicom lacks, over the shared native SR engines — the mksr / mkreport / dcmvalidate CLI capabilities): author any Comprehensive SR from a content-tree dict (write_sr); author / read a TID 1500 Measurement Report (write_report / read_report — the dcmqi tid1500writer / tid1500reader pair); validate an SR content tree (sr_validate); and look up the PS3.16 coded-concept table (sr_code_meaning / sr_validate_code / sr_cid_has).

TrackingIdentifier

highdicom.sr.TrackingIdentifier — a measurement group's tracking identity.

FindingSite

highdicom.sr.FindingSite — an anatomic location code.

Measurement

highdicom.sr.Measurement — one numeric measurement (name/value/unit + qualifiers).

QualitativeEvaluation

highdicom.sr.QualitativeEvaluation — a coded name/value evaluation.

SourceImageForRegion

highdicom.sr.SourceImageForRegion — the image a 2D region is drawn on.

ImageRegion

highdicom.sr.ImageRegion — a 2D ROI (SCOORD) on a source image.

ImageRegion3D

highdicom.sr.ImageRegion3D — a 3D ROI (SCOORD3D) in a Frame of Reference.

MeasurementsAndQualitativeEvaluations

highdicom.sr measurement group (no ROI).

PlanarROIMeasurementsAndQualitativeEvaluations

Bases: MeasurementsAndQualitativeEvaluations

highdicom.sr planar (2D) ROI measurement group.

VolumetricROIMeasurementsAndQualitativeEvaluations

Bases: PlanarROIMeasurementsAndQualitativeEvaluations

highdicom.sr volumetric (3D) ROI measurement group.

PersonObserverIdentifyingAttributes

highdicom.sr.PersonObserverIdentifyingAttributes.

DeviceObserverIdentifyingAttributes

highdicom.sr.DeviceObserverIdentifyingAttributes.

ObserverContext

highdicom.sr.ObserverContext — wraps a person/device observer.

ObservationContext

highdicom.sr.ObservationContext — observer (+ subject) context.

MeasurementReport

highdicom.sr.MeasurementReport — the TID 1500 root (observation context + procedure + measurement groups).

ContentItem

Base for the highdicom.sr content-item primitives — a typed SR tree node.

append

append(item)

Add a child content item (pydicom-/highdicom-compatible).

ContentSequence

Bases: list

highdicom.sr.ContentSequence — an ordered list of content items.

content_json

content_json(ds)

The semantic SR content tree (reuses the native content engine via :func:pydcm.content).

write_sr

write_sr(document, output=None)

Author a Comprehensive DICOM Structured Report from a content-tree dict.

The general SR writer (vs. a fixed template): build any tree of content items.

a dict with patient_name / patient_id / study_uid /

study_date / series_uid (+ optional sop_class_uid / completion_flag / verification_flag), a title code {value, scheme, meaning} (the root CONTAINER's Concept Name), and a content list. Each content item: relationship ("CONTAINS", …), value_type ("CODE"/"NUM"/"TEXT"/"CONTAINER"/"IMAGE"/"SCOORD"/…), concept code, and per-type fields — text; code ({value,scheme, meaning}); value + unit (NUM); datetime; ref_sop_class / ref_sop_instance (IMAGE); graphic_type + graphic_data (SCOORD) — plus a nested content list for children.

output: write the SR there and return None; if omitted, return Part-10 bytes.

write_report

write_report(
    measurements,
    *,
    reference=None,
    patient_name="",
    patient_id="",
    study_uid="",
    study_date="",
    series_uid="",
    output=None
)

Author a TID 1500 Measurement Report SR from a list of measurements — the dcmqi tid1500writer capability, over the same native SR-export engine the mkreport CLI uses (pydicom has no SR authoring; pydcm-native).

a list of measurement dicts, each with concept_value /

concept_scheme / concept_meaning (the measured quantity's code, e.g. "103355008", "SCT", "Width"), value (float), unit_code / unit_meaning (UCUM, e.g. "mm"), and optionally ref_sop_class_uid / ref_sop_instance_uid (the measured image), graphic_type ("POINT" / "POLYLINE" / "CIRCLE" / "ELLIPSE") and scoord ([col0, row0, col1, row1, …] pixel coordinates). scoord is recorded only when ref_sop_instance_uid is also given — spatial coordinates are stored relative to their referenced image. May instead be a full document dict ({patient_…, study_…, series_uid, measurements: […]}, the shape :func:read_report returns) so write_report(read_report(x)) round-trips.

reference: a DICOM path to inherit patient + study identity from (the report attaches to that study); explicit keyword args take precedence. Study/Series UIDs are content-derived when neither given nor inherited. output: write the SR there and return None; if omitted, return Part-10 bytes.

read_report

read_report(path)

The measurements of a TID 1500 Measurement Report SR — the dcmqi tid1500reader capability. Returns {patient_name, patient_id, study_uid, study_date, series_uid, measurements: [...]} round-tripping :func:write_report's input (measurements is empty when path carries no SR content).

write_measurement_report

write_measurement_report(document, output=None)

Author a TYPED TID 1500 Measurement Report — the dcmqi/DCMTK/highdicom measurement-report capability, over the native SR authoring engine (structure cross-validated against DCMTK dcmsr/libcmr). Unlike :func:write_report (a flat list of measurements) this builds the full TID 1500 structure: observation context, measurement groups (TID 1411/1501), each with tracking identity, finding + finding sites, an optional ROI region, NUM measurements (TID 300, with method / derivation / per-measurement finding sites) and qualitative evaluations.

a dict with patient_name / patient_id / study_uid /

study_date / series_uid; optional observer = {type: "device"|"person", name, uid}, procedure_reported (a code), language (default "en-US"); and groups — each {tracking_id, tracking_uid, finding?, finding_sites?[], roi?, measurements[], qualitative_evaluations?[]}. A code is {value, scheme, meaning}. A measurement is {name, value, unit, method?, derivation?, finding_sites?[]}. A roi is {graphic_type, scoord:[...], is_3d?, frame_of_reference_uid?, ref_sop_class_uid?, ref_sop_instance_uid?}. A qualitative evaluation is {name, value} (both codes).

output: write the SR there and return None; if omitted, return Part-10 bytes.

read_measurement_report

read_measurement_report(path)

The typed TID 1500 Measurement Report of an SR — {patient/study, observer, procedure_reported?, groups: [...]} round-tripping :func:write_measurement_report (empty groups when path is not a measurement report). Reads third-party reports (DCMTK / highdicom), not just pydcm's own output.

sr_code_meaning

sr_code_meaning(scheme, value)

Code Meaning for a coded concept (scheme, value) from the DICOM PS3.16 Content Mapping Resource (DCMTK ∪ pydicom — the most complete set), or None if the code is unknown. E.g. sr_code_meaning("DCM", "126000")"Imaging Measurement Report".

sr_validate_code

sr_validate_code(scheme, value, meaning=None)

True if (scheme, value) is a known coded concept and — when meaning is given — its Code Meaning matches it (a typo / wrong-meaning check).

sr_cid_has

sr_cid_has(cid, scheme, value)

True if the coded concept (scheme, value) is a member of Context Group cid (e.g. sr_cid_has(7469, "SCT", "103339001")).

sr_validate

sr_validate(path)

Validate an SR file's content tree — structural well-formedness (root is a CONTAINER, valid value types / relationships, NUM has units, CODE has a value, …) plus coded-concept conformance against the PS3.16 table — returning a list of {severity, location, message} findings (empty = a well-formed SR).

Comprehensive3DSR

Comprehensive3DSR(
    evidence,
    content,
    series_instance_uid,
    series_number,
    sop_instance_uid,
    instance_number,
    manufacturer=None,
    **_kw
)

highdicom.sr.Comprehensive3DSR-compatible constructor — a TID 1500 SR Dataset.

Key Object Selection

pydcm.ko

pydcm — DICOM Key Object Selection authoring + reading (pydcm.ko).

The highdicom ko capability: flag instances as "key objects" (a KOS document, PS3.3 KOS IOD / PS3.16 TID 2010) and read one back, over the native KOS engine (the mkkos CLI capability). pydicom has no KOS authoring; pydcm-native.

KeyObjectSelection

highdicom.ko.KeyObjectSelection-compatible content: a document-title code + the objects flagged as key.

write_ko

write_ko(
    references,
    *,
    patient_name="",
    patient_id="",
    study_uid="",
    study_date="",
    study_time="",
    study_id="",
    accession_number="",
    title=None,
    output=None
)

Author a Key Object Selection document flagging references as key objects.

a list whose items are either reference dicts (``{study_uid, series_uid,

sop_class_uid, sop_instance_uid}) or DICOM paths /Dataset`` objects (their identifiers are extracted automatically — the common "flag these images" case).

title: the Key Object Document Title — a {value, scheme, meaning} dict or a (value, scheme, meaning) tuple; defaults to (113000, DCM, "Of Interest"). patient_ / study_: identity for the KOS; when omitted they are inherited from the first path/Dataset reference (the study the KOS is filed under). output: write the KOS there and return None; if omitted, return Part-10 bytes.

read_ko

read_ko(path)

Read a Key Object Selection document -> {patient_name, patient_id, study_uid, series_uid, title, references: [{sop_class_uid, sop_instance_uid}, …]} (the IMAGE content items), or None when path is not a KOS.

KeyObjectSelectionDocument

KeyObjectSelectionDocument(
    evidence,
    content,
    series_instance_uid,
    series_number,
    sop_instance_uid,
    instance_number,
    manufacturer=None,
    institution_name=None,
    institutional_department_name=None,
    requested_procedures=None,
    transfer_syntax_uid=None,
    **_kwargs
)

highdicom.ko.KeyObjectSelectionDocument-compatible constructor — returns a pydcm Dataset over the native write_ko. content.referenced_objects are the flagged key objects, content.document_title the KOS title code.

Presentation State (GSPS)

pydcm.pr

pydcm — DICOM Grayscale Softcopy Presentation State authoring + reading (pydcm.pr).

The highdicom pr capability (GSPS): author a presentation state that records how to display referenced images — window/level, Presentation LUT shape, rotate/flip, displayed area, and graphic/text annotations on named layers — over the native presentation-state engine. Reading reuses the existing PS content reader (pydcm.content). pydicom has no PR authoring; pydcm-native.

write_pr

write_pr(
    references,
    *,
    kind="GSPS",
    patient_name="",
    patient_id="",
    study_uid="",
    study_date="",
    content_label="PS",
    content_description="",
    content_creator="",
    window=None,
    voi_luts=None,
    presentation_lut_shape="IDENTITY",
    rotation=0,
    h_flip=False,
    displayed_areas=None,
    graphic_layers=None,
    graphic_annotations=None,
    palette=None,
    icc_profile=None,
    color_space="",
    mask=None,
    blending=None,
    blending_display=None,
    output=None
)

Author a Softcopy Presentation State for references.

"GSPS" (Grayscale, default), "COLOR" (Color SC PS, for RGB images — adds an

ICC profile, drops the grayscale VOI/Presentation-LUT pipeline), "PSEUDO_COLOR" (Pseudo-Color SC PS, for grayscale images — adds a Palette Color LUT mapping stored values to RGB), "XAXRF" (XA/XRF Grayscale SC PS, the grayscale pipeline + Mask Subtraction), or "ADVANCED_BLENDING" (Advanced Blending SC PS, 11.8 — blend N pseudo-color/color inputs into true color).

mask: XA/XRF Mask Subtraction — {operation:"AVG_SUB"|"TID"|"REV_TID", mask_frames?:[...], applicable_range?:[start,end], sub_pixel_shift?:[row,col], tid_offset?}. blending: ADVANCED_BLENDING inputs — [{input_number, study_uid, series_uid, references:[{sop_class_uid, sop_instance_uid}], palette:{red,green,blue, first_mapped?}}] (each input is pseudo-colored via its palette). blending_display: how the inputs combine — [{mode:"EQUAL"|"FOREGROUND", inputs:[input_number,...], relative_opacity?}]. references: a list of reference dicts ({series_uid, sop_class_uid, sop_instance_uid, frame_numbers?}) or DICOM paths / Dataset objects (identifiers extracted). window: convenience for one Softcopy VOI LUT — (center, width) or a dict {window_center, window_width, function?, explanation?}. Use voi_luts for several. function: "LINEAR" (default) / "LINEAR_EXACT" / "SIGMOID". presentation_lut_shape: "IDENTITY" (default) or "INVERSE" (GSPS only). rotation / h_flip: spatial transform (0/90/180/270; flip horizontally). displayed_areas: list of {tlhc:[x,y], brhc:[x,y], size_mode?, magnification?, pixel_spacing?:[x,y]}. If omitted, a SCALE-TO-FIT area covering the first path/Dataset reference's full extent is added automatically. graphic_layers: [{name, order?, description?, cielab?:[L,a,b]}]. graphic_annotations: [{layer, texts?:[...], graphics?:[...]}]. palette: PSEUDO_COLOR Palette Color LUT — {red:[...], green:[...], blue:[...], first_mapped?}; each channel an equal-length list of 16-bit values (the entry count and 16-bit depth are taken from the data — there is nothing else to set). icc_profile: COLOR ICC profile bytes (0028,2000); color_space: defined term (0028,2002), e.g. "SRGB". output: write the PS there and return None; if omitted, return Part-10 bytes.

read_pr

read_pr(path)

Read a Presentation State's semantic content (referenced images, presentation LUT shape, displayed areas, graphic layers, annotations, …) as a dict, or None when path is not a presentation state. Reuses the shared PS content reader.

GrayscaleSoftcopyPresentationState

GrayscaleSoftcopyPresentationState(
    referenced_images,
    series_instance_uid,
    series_number,
    sop_instance_uid,
    instance_number,
    manufacturer,
    manufacturer_model_name,
    software_versions,
    device_serial_number,
    content_label,
    **kwargs
)

highdicom.pr.GrayscaleSoftcopyPresentationState-compatible constructor (GSPS).

ColorSoftcopyPresentationState

ColorSoftcopyPresentationState(
    referenced_images,
    series_instance_uid,
    series_number,
    sop_instance_uid,
    instance_number,
    manufacturer,
    manufacturer_model_name,
    software_versions,
    device_serial_number,
    content_label,
    **kwargs
)

highdicom.pr.ColorSoftcopyPresentationState-compatible constructor (Color SC PS).

PseudoColorSoftcopyPresentationState

PseudoColorSoftcopyPresentationState(
    referenced_images,
    series_instance_uid,
    series_number,
    sop_instance_uid,
    instance_number,
    manufacturer,
    manufacturer_model_name,
    software_versions,
    device_serial_number,
    content_label,
    **kwargs
)

highdicom.pr.PseudoColorSoftcopyPresentationState-compatible constructor.

Bulk annotations (microscopy)

pydcm.ann

pydcm — Microscopy Bulk Simple Annotations reading + authoring (pydcm.ann).

The highdicom ann capability: read AND write a Microscopy Bulk Simple Annotations object (SOP 1.2.840.10008.5.1.4.1.1.91.1) — the compact format for huge numbers of whole-slide annotations (cells / nuclei / regions). Both directions live in the native engine (read + build over the shared Part-10 emit/parse primitives); these are the thin marshalling wrappers.

Measurements

highdicom.ann.Measurements-compatible measured quantity over a group.

AnnotationGroup

highdicom.ann.AnnotationGroup-compatible annotation group.

read_ann

read_ann(path)

Read a Microscopy Bulk Simple Annotations file.

Returns a dict {coordinate_type, groups:[...]} or None if path is not a Bulk Annotations object. Each group carries its identity (number/uid/label/ generation_type), coded property_category / property_type, graphic_type, num_annotations, and annotations: a list of (n_points, dim) float64 arrays decoded from the bulk coordinates (dim is 2 for a "2D" coordinate type, 3 for "3D"). Each group's measurements is a list of {name, unit, values, annotation_index}values a float64 array (one per annotation, or per annotation_index when sparse).

write_ann

write_ann(
    source,
    groups,
    *,
    coordinate_type="2D",
    series_instance_uid=None,
    series_number=1,
    sop_instance_uid=None,
    instance_number=1,
    manufacturer="pydcm",
    manufacturer_model_name=None,
    software_versions=None,
    device_serial_number=None,
    output=None
)

Author a Microscopy Bulk Simple Annotations object (native annotation engine).

a source-image path / Dataset (or a list) — identity, Frame of Reference and

referenced-image links are taken from it.

groups: list of dicts {number, label, generation_type, property_category, property_type, graphic_type, annotations, measurements?} where annotations is a list of (n_points, dim) arrays and the codes are (value, scheme, meaning) tuples or :class:~pydcm.sr.Code.

MicroscopyBulkSimpleAnnotations

MicroscopyBulkSimpleAnnotations(
    source_images,
    annotation_coordinate_type,
    annotation_groups,
    series_instance_uid,
    series_number,
    sop_instance_uid,
    instance_number,
    manufacturer,
    manufacturer_model_name=None,
    software_versions=None,
    device_serial_number=None,
    **_kwargs
)

highdicom.ann.MicroscopyBulkSimpleAnnotations-compatible constructor — returns a pydcm Dataset built over the native write_ann.

Secondary Capture

pydcm.sc

Secondary Capture images (pydcm.sc) — write an ndarray as an SC DICOM object.

Functional API write_sc plus a highdicom.sc.SCImage-compatible constructor (returns a :class:pydcm.Dataset built over the native set_pixel_data).

write_sc

write_sc(
    pixel_array,
    photometric_interpretation="MONOCHROME2",
    *,
    bits_stored=None,
    study_instance_uid=None,
    series_instance_uid=None,
    sop_instance_uid=None,
    series_number=1,
    instance_number=1,
    manufacturer="pydcm",
    patient_id="",
    patient_name="",
    patient_birth_date="",
    patient_sex="",
    accession_number="",
    study_id="",
    study_date="",
    study_time="",
    referring_physician_name="",
    conversion_type="WSD",
    pixel_spacing=None,
    output=None
)

Author a Secondary Capture image from pixel_array.

pixel_array is uint8/uint16 shaped (rows, cols), (frames, rows, cols), (rows, cols, 3) or (frames, rows, cols, 3). Returns a :class:Dataset (or writes to output and returns None).

SCImage

SCImage(
    pixel_array,
    photometric_interpretation,
    bits_allocated,
    coordinate_system,
    study_instance_uid,
    series_instance_uid,
    series_number,
    sop_instance_uid,
    instance_number,
    manufacturer,
    *,
    patient_id=None,
    patient_name=None,
    patient_birth_date=None,
    patient_sex=None,
    accession_number=None,
    study_id=None,
    study_date=None,
    study_time=None,
    referring_physician_name=None,
    pixel_spacing=None,
    **_kwargs
) -> Dataset

highdicom.sc.SCImage-compatible constructor — returns a :class:Dataset.

pydcm builds the object over the native set_pixel_data (a thin Dataset, not a bespoke class); coordinate_system and other highdicom-only kwargs are accepted for source compatibility.

Parametric Map classes

pydcm.pm

Parametric Maps (pydcm.pm) — highdicom.pm-compatible class API over the native write_paramap writer. Re-exports the functional write_paramap / read_paramap.

RealWorldValueMapping

highdicom.pm.RealWorldValueMapping-compatible real-world value mapping.

write_paramap

write_paramap(
    reference,
    values,
    *,
    units=None,
    quantity=None,
    slope=None,
    intercept=None,
    label=None,
    explanation=None,
    dtype=None,
    output=None
)

Author a DICOM Parametric Map from a real-valued array.

a source-image path, or the list of source-series instance paths —

geometry, demographics and Frame of Reference are taken from it (one slice per array plane, ordered by position).

values: a float array (H, W) or (slices, H, W) of real-world values (one plane per reference slice). units: the measurement units — (code, scheme, meaning) (UCUM by default), (code, meaning), a plain meaning string, or a dict. E.g. ("um2/s", "UCUM", "um2/s"). quantity: the measured quantity code (value, scheme, meaning) (DCM by default), e.g. ("113041", "DCM", "Apparent Diffusion Coefficient"). dtype: the stored pixel type. None (default) → 32-bit float (FloatingPointImagePixel; the values are stored verbatim). "uint16" / "int16" / "uint8" / "int8" → integer pixels quantized through the Real World Value Mapping (stored = round((value - intercept) / slope)), so a reader recovers value = stored * slope + intercept. slope / intercept: Real World Value Mapping slope / intercept. For float storage the default is identity (1 / 0 — values are already real-world). For an integer dtype left unset, they are auto-computed to span the value range across the integer range (lossy only by the quantization step); pass them to control the scaling explicitly. output: write the map there and return None; if omitted, return Part-10 bytes.

read_paramap

read_paramap(path)

Read a DICOM Parametric Map to (values, meta) — the dcmqi paramap2itkimage capability.

a float32 array (frames, rows, cols) of real-world values. Float /

double-float pixel data is returned directly; an integer-stored map has its Real World Value Mapping (slope/intercept) applied.

meta: the geometry sidecar (as :func:pydcm.decode) plus is_parametric_map and, when present, real_world_value_mapping = {slope, intercept, units, label, first_value_mapped, last_value_mapped, has_lut}.

ParametricMap

ParametricMap(
    source_images,
    pixel_array,
    series_instance_uid,
    series_number,
    sop_instance_uid,
    instance_number,
    manufacturer,
    manufacturer_model_name,
    software_versions,
    device_serial_number,
    contains_recognizable_visual_features,
    real_world_value_mappings,
    window_center,
    window_width,
    *,
    content_description=None,
    content_label=None,
    content_creator_name=None,
    transfer_syntax_uid=None,
    **_kwargs
)

highdicom.pm.ParametricMap-compatible constructor — returns a pydcm Dataset.

Built over the native write_paramap: source_images supply geometry/demographics, pixel_array the real-valued (or stored) planes, and the first real_world_value_mappings entry the units / quantity / slope / intercept. highdicom-only kwargs are accepted for source compatibility.

Legacy Converted Enhanced

pydcm.legacy_converted

pydcm — Legacy Converted Enhanced CT/MR/PET authoring (pydcm.legacy_converted).

The highdicom legacy capability: fold a set of classic single-frame CT/MR/PET instances (one series) into ONE enhanced multi-frame object (LegacyConvertedEnhanced{CT,MR,PET}Image) over the native legacy-conversion engine. A faithful, reversible re-encapsulation — identity is inherited from the source series, geometry / rescale / window / frame-type are mapped into the Shared / Per-Frame Functional Groups, every frame is linked back to its origin, and leftover source attributes are preserved verbatim. pydicom has no legacy converter; pydcm-native.

write_legacy_converted

write_legacy_converted(
    series,
    *,
    series_instance_uid="",
    sop_instance_uid="",
    series_number=0,
    instance_number=1,
    manufacturer="",
    model_name="",
    device_serial="",
    software_versions="",
    output=None
)

Convert a classic single-frame CT/MR/PET series into one Legacy Converted Enhanced multi-frame object.

a list of DICOM file paths (or pydicom Datasets read from disk) for the

classic single-frame instances of ONE series, in any order — frames are sorted into geometric slice order. The target SOP Class (CT/MR/PET) is chosen from the shared source Modality.

series_instance_uid / sop_instance_uid: identity for the new object; minted deterministically when omitted. series_number / instance_number: new series / instance numbers. manufacturer / model_name / device_serial / software_versions: Enhanced General Equipment (Type 1). Inherited from the source when omitted, else a default. output: write the object there and return None; if omitted, return Part-10 bytes.

Encapsulated documents (PDF / CDA / STL / OBJ / MTL)

pydcm.encapdoc

pydcm — Encapsulated Documents (pydcm.encapdoc).

Wrap a PDF / CDA / STL / OBJ / MTL document into its Encapsulated Document Storage instance (PS3.3 A.45/A.85), or extract one — over the shared native encapsulation engine (the same capability behind dcmencap/dcmdecap and pdf2dcm/dcm2pdf). The assembly, per-type module sets, detection and MIME-aware extraction all run in C++; this wrapper only shuttles bytes and copies identity from a reference dataset.

EncapsulatedDocument

An extracted Encapsulated Document.

Attributes:

Name Type Description
payload

the document bytes. Trailing OB pad NULs are stripped for pdf/text MIME types; model/* (binary STL legitimately ends in 0x00) is verbatim, possibly with the single even-length pad byte.

mime / title / sop_class_uid / sop_instance_uid

as recorded.

type

"pdf" | "cda" | "stl" | "obj" | "mtl" when the SOP class is one of the five Encapsulated …Storage classes, else None.

write_encapsulated

write_encapsulated(
    src,
    *,
    type="auto",
    output=None,
    title=None,
    mime=None,
    units=None,
    reference=None,
    **ids
)

Wrap a document into its Encapsulated Document DICOM instance.

src: a file path, or raw bytes (then type must be explicit unless content magic identifies it). type: auto (file extension, then content magic) or pdf|cda|stl|obj|mtl. title defaults to the file stem. units: 3D-model Measurement Units UCUM code (default um, DCMTK-compatible). reference: a DICOM file whose Patient/Study identity is copied (the document joins that study). Extra keyword ids: patient_name, patient_id, birth_date, sex, study_uid, study_date, study_time, study_id, accession, referring, series_uid, frame_of_reference_uid, charset.

Returns the Part-10 bytes, or writes output and returns its path.

read_encapsulated

read_encapsulated(path)

Extract an Encapsulated Document instance → :class:EncapsulatedDocument.

Semantic content (auto-detect by SOP class)

pydcm.content

pydcm — structured-object content reader (pydcm.content).

The interpreted (semantic) view of a derived DICOM object — Segmentation, RT Structure Set, RT Plan (photon + ion), RT Dose, Presentation State, or Waveform — over the shared native content engine (the same capability as the dcm2content CLI). One unified reader that auto-detects the SOP class, matching dcmclient's "by operation, not object" organization; the interpreted counterpart to the raw element model.

content

content(path, contours=False, control_points=False)

Semantic content of a structured DICOM object — Segmentation, RT Structure Set, RT Plan, RT Dose, Presentation State, or Waveform — as a dict (coded concepts resolved), or None if path is not one of those.

Raises RuntimeError when path is not decodable DICOM at all.

contours: RT Structure Set only — include each contour's xyz point list. control_points: RT Plan only — include every control point (angles, meterset, leaf/jaw positions) instead of the first-CP summary.

RT dosimetry

pydcm.rt

pydcm — RT dosimetry reader (pydcm.rt).

The dose-side counterpart to the RT Structure Set support: read_rtdose returns the scaled dose grid (pixel × DoseGridScaling, computed in C++ by the shared native RT engine — the same capability behind dcm2content) as a NumPy volume plus its geometry and any stored DVH curves. The semantic metadata view of RT Plan / RT Dose lives in :func:pydcm.content.

DoseGrid

A scaled RT Dose grid.

Attributes:

Name Type Description
dose

ndarray[depth, rows, cols] float32 — pixel × DoseGridScaling (Gy when dose_units == "GY").

dose_grid_scaling

the scale that was APPLIED — equals the file's DoseGridScaling, or 1.0 when that tag is absent/zero (malformed), so dose == pixel × dose_grid_scaling holds unconditionally.

max_dose

grid maximum, computed in double precision — may differ from dose.max() in the last float32 ulp.

affine

4×4 voxel→world (LPS) matrix, column-major flat list — same convention as :func:pydcm.load_series.

spacing

(z, y, x) mm; z is the |inter-frame step|.

grid_frame_offsets

raw GridFrameOffsetVector (mm relative to frame 0).

uniform_offsets

False when frame steps vary — the affine then uses the first step; resample against grid_frame_offsets in that case.

dvhs

list of stored DVH curves (dicts with bin_widths/volumes and the dose scaling/min/max/mean as recorded in the file).

ComputedDVH

A DVH computed from RTSTRUCT + RTDOSE (dicompyler-core dvhcalc parity).

Attributes:

Name Type Description
counts

differential histogram, cm³ per 1-cGy bin (float64 ndarray, trailing zeros trimmed — dicompyler shape).

cumulative

suffix-sum of counts (what dicompyler's get_dvh returns).

bins

bin edges in Gy (len(counts) + 1 values, 0.01 Gy wide).

volume

structure volume in cm³; min/max/mean: dose statistics in Gy, defined exactly as dicompyler-core's dvh.DVH properties.

notes

dose-grid coverage notes ('' when the grid covers the structure).

read_rtdose

read_rtdose(path)

Read an RT Dose file (SOP Class …481.2) into a :class:DoseGrid.

All computation (scaling in double precision, geometry, DVH decode) runs in the native RT engine; this wrapper only shapes the result.

dvhcalc

dvhcalc(
    structure,
    dose,
    roi,
    limit=None,
    calculate_full_volume=True,
    thickness=None,
)

Compute the DVH of roi from an RT Structure Set + RT Dose file pair.

The rasterisation, dose-plane interpolation, histogram and statistics all run in the native RT engine; results are validated against dicompylercore.dvhcalc.get_dvh (base path: no in-plane interpolation / structure-extents options). limit caps the histogram in cGy.

write_rtdose

write_rtdose(
    dose,
    *,
    affine=None,
    origin=None,
    orientation=(1, 0, 0, 0, 1, 0),
    spacing=None,
    grid_frame_offsets=None,
    dose_units="GY",
    dose_type="PHYSICAL",
    dose_summation_type="PLAN",
    ref_plan_uid=None,
    reference=None,
    patient_name=None,
    patient_id=None,
    study_uid=None,
    study_date=None,
    series_uid=None,
    frame_of_reference_uid=None,
    scaling=None,
    bits=32,
    output=None
)

Author an RT Dose file (SOP Class …481.2) from a dose grid.

The write side of :func:read_rtdose — the export (quantisation to unsigned integers with a self-consistent DoseGridScaling, Part-10 emit) runs in the native engine. Typical AI-workflow use: a predicted or accumulated grid → a file a TPS/viewer imports.

Geometry: pass affine (column-major 4×4 voxel→world, the :func:read_rtdose/:func:pydcm.load_series convention) OR origin+orientation+spacing (row, col) mm (+ optional grid_frame_offsets, default derived from the affine's frame step / uniform z spacing).

reference: a DICOM file (the RT Plan, planning CT, …) whose Patient/Study/FrameOfReference identity is copied; when it IS an RT Plan, ref_plan_uid defaults to its SOP Instance UID.

scaling: explicit DoseGridScaling; default = max dose / integer max (full dynamic range of bits, 32 or 16). Returns the Part-10 bytes, or writes output and returns its path.

Note: PS3.3 requires a ReferencedRTPlanSequence (Type 1C) for the PLAN/BEAM/… summation types — pass ref_plan_uid or an RT Plan as reference for fully conformant output; research/AI grids without a plan are written as-is.

Whole-slide imaging

pydcm.wsi

Whole-slide imaging (DICOM VL Whole Slide Microscopy) reader — an OpenSlide-shaped surface over pydcm's native pyramid engine.

from pydcm.wsi import open_slide
s = open_slide("/path/to/slide_dir")           # a dir of the slide's .dcm levels
s.level_count, s.level_dimensions, s.level_downsamples
rgba = s.read_region((x, y), level, (w, h))     # (x,y) in LEVEL-0 coords → numpy

Mirrors OpenSlide (read_region location is in level-0 reference coords, size in the requested level's coords; default returns RGBA, edge/sparse-missing pixels are transparent). rgba=False returns RGB. associated_images exposes DICOM label / overview / thumbnail / localizer images as a lazy mapping. The decode / tile assembly / pyramid all run in the shared native core — analysis (tiling for ML, stain normalisation) stays interop: feed the returned NumPy arrays to your pipeline.

Slide

A whole-slide pyramid (OpenSlide-compatible subset).

level_dimensions property

level_dimensions

((cols, rows), ...) per level, largest (level 0) first.

level_tile_dimensions property

level_tile_dimensions

((tile_cols, tile_rows), ...) per level.

level_tile_counts property

level_tile_counts

((tiles_x, tiles_y), ...) per level.

level_frame_counts property

level_frame_counts

Stored frame count per level in source instance frame order.

properties property

properties: dict[str, str]

OpenSlide-style and DICOM-derived slide metadata as string properties.

associated_image_names property

associated_image_names

Names of associated non-pyramid images, e.g. label or overview.

associated_images property

associated_images

Lazy OpenSlide-style mapping: name -> RGBA uint8 NumPy image.

icc_profile property

icc_profile: bytes | None

Raw DICOM ICC Profile bytes for the base pyramid level, if present.

icc_transform_available property

icc_transform_available: bool

Whether this build can apply WSI ICC profiles to sRGB via LCMS2.

dimensions property

dimensions

Level-0 (cols, rows).

tile_cache_capacity property

tile_cache_capacity: int

Decoded tile cache capacity in bytes. 0 disables retaining decoded tiles.

level_descriptor

level_descriptor(level: int) -> dict

Viewer-oriented metadata for one level without decoding any tile.

viewer_level

viewer_level(level: int, *, include_ranges=False) -> dict

Return the level descriptor plus source paths and optional range table.

The level entry a tiled WSI viewer consumes directly: metadata is cheap, and the dense encoded tile range grid can be handed to a range loader / tile scheduler without decoding pixels in this call.

viewer_levels

viewer_levels(*, include_ranges=False) -> tuple[dict, ...]

Return viewer-oriented descriptors for all pyramid levels.

level_concatenation

level_concatenation(level: int) -> dict | None

DICOM Concatenation metadata for a pyramid level, if present.

associated_image_dimensions

associated_image_dimensions(name: str)

(cols, rows) for an associated image, or (0, 0) if absent.

read_associated_image

read_associated_image(
    name: str, *, rgba=True, apply_icc_profile=False
)

Return an associated image as (rows, cols, 4) RGBA or (rows, cols, 3) RGB.

level_icc_profile

level_icc_profile(level: int) -> bytes | None

Raw DICOM ICC Profile bytes for one pyramid level, if present.

associated_image_icc_profile

associated_image_icc_profile(name: str) -> bytes | None

Raw DICOM ICC Profile bytes for an associated image, if present.

get_best_level_for_downsample

get_best_level_for_downsample(downsample: float) -> int

Return the OpenSlide-style best pyramid level for downsample.

set_tile_cache_capacity

set_tile_cache_capacity(capacity: int) -> None

Set decoded tile cache capacity in bytes for this slide.

read_region

read_region(location, level, size, *, rgba=True)

location = (x, y) top-left in LEVEL-0 coords; size = (w, h) in level coords. Returns a (h, w, 4) RGBA (or (h, w, 3) RGB) uint8 NumPy array.

read_tile

read_tile(level, tile, *, rgba=True, fill_missing=False)

Return one stored tile by zero-based tile = (tile_x, tile_y).

Sparse-missing tiles return an empty array by default. With fill_missing=True, an in-grid sparse-missing tile returns an all-zero tile instead (transparent in RGBA).

read_tiles

read_tiles(level, tiles, *, rgba=True, fill_missing=False)

Return multiple stored tiles in input order.

This is equivalent to repeated read_tile() calls, but crosses the Python/native boundary once for the whole batch.

read_tile_stack

read_tile_stack(
    level, tiles, *, rgba=True, fill_missing=False
)

Return multiple full-size tiles as (n, tile_h, tile_w, channels).

Unlike read_tiles(), this is strict: every requested tile must produce a full tile. Sparse-missing tiles require fill_missing=True.

read_tile_grid

read_tile_grid(
    level,
    origin,
    shape,
    *,
    rgba=True,
    require_existing=False
)

Read a rectangular tile grid as (tile_rows, tile_cols, tile_h, tile_w, channels).

This uses one native read_region() call and returns a NumPy view over the region buffer. Sparse-missing tiles are transparent by default; set require_existing=True to reject grids containing missing sparse tiles.

level_frame_tile

level_frame_tile(level, frame_number, *, as_index=False)

Return (tile_x, tile_y) for a stored frame in source frame order.

frame_number is DICOM 1-based by default. Set as_index=True for Python 0-based indexing.

level_source_paths

level_source_paths(level: int) -> tuple[str, ...]

File-backed source path(s) for a level; memory-backed slides return ().

level_tile_ranges

level_tile_ranges(level: int)

Return encoded tile byte ranges for a level as uint64[n, 6].

Columns are source_index, frame_index, tile_x, tile_y, offset, length. The ranges point into the source Part-10 file and are intended for viewer-style range loading; this method does not decode pixels.

level_tile_range_grid

level_tile_range_grid(level: int)

Return dense row-major encoded tile ranges as uint64[n, 6].

The row-major index is tile_y * tile_count_x + tile_x. Sparse-missing tiles have length == 0 and frame_index == MISSING_FRAME_INDEX. Columns are source_index, frame_index, tile_x, tile_y, offset, length.

tile_range

tile_range(
    level, tile
) -> tuple[int, int, int, int, int, int] | None

Return one encoded tile range or None for an absent/out-of-grid tile.

level_frame_range

level_frame_range(
    level, frame_number, *, as_index=False
) -> tuple[int, int, int, int, int, int]

Return encoded byte range for a stored frame in source frame order.

get_stored_frame

get_stored_frame(
    frame_number,
    *,
    level=0,
    as_index=False,
    dtype=None,
    rgba=False,
    fill_missing=False,
    apply_icc_profile=None
)

Highdicom-style stored frame access for one WSI level.

The frame number follows DICOM 1-based numbering unless as_index=True. The returned array is a full stored tile in source frame order.

get_stored_frames

get_stored_frames(
    frame_numbers=None,
    *,
    level=0,
    as_indices=False,
    dtype=None,
    rgba=False,
    fill_missing=False,
    apply_icc_profile=None
)

Highdicom-style stored frame batch access.

frame_numbers are DICOM 1-based by default. Set as_indices=True for Python 0-based indexing. None reads all stored frames for the level.

get_frame

get_frame(
    frame_number,
    *,
    level=0,
    as_index=False,
    dtype=None,
    rgba=False,
    fill_missing=False,
    apply_real_world_transform=None,
    apply_modality_transform=None,
    apply_voi_transform=False,
    apply_palette_color_lut=None,
    apply_icc_profile=None,
    **_kwargs
)

Highdicom-style frame access.

For WSI this currently aliases stored-frame access. Pixel-transform keyword arguments are accepted for source compatibility; unsupported requested transforms raise NotImplementedError.

get_frames

get_frames(
    frame_numbers=None,
    *,
    level=0,
    as_indices=False,
    dtype=None,
    rgba=False,
    fill_missing=False,
    apply_real_world_transform=None,
    apply_modality_transform=None,
    apply_voi_transform=False,
    apply_palette_color_lut=None,
    apply_icc_profile=None,
    **_kwargs
)

Highdicom-style batch frame access for WSI stored frames.

get_total_pixel_matrix

get_total_pixel_matrix(
    *,
    row_start=None,
    row_end=None,
    column_start=None,
    column_end=None,
    level=0,
    as_indices=False,
    dtype=None,
    rgba=False,
    apply_real_world_transform=None,
    apply_modality_transform=None,
    apply_voi_transform=False,
    apply_palette_color_lut=None,
    apply_icc_profile=None,
    **_kwargs
)

Highdicom-style total pixel matrix access.

Row/column positions are DICOM 1-based by default, with row_end and column_end denoting the first position beyond the returned matrix. Set as_indices=True for Python 0-based intervals.

tile_exists

tile_exists(level, tile) -> bool

Return true when zero-based tile = (tile_x, tile_y) has stored pixel data.

get_thumbnail

get_thumbnail(size)

A downscaled RGB overview of the whole slide fitting within size = (w, h).

open_slide

open_slide(path) -> Slide

Open a slide from a directory of its .dcm instances, a list of paths, or a single multi-level file. Drop-in for openslide.open_slide.

open_slides

open_slides(path) -> dict[str, Slide]

Open every WSI slide found under a directory or path list.

Returns {slide_key: Slide}, where slide_key is usually the FrameOfReferenceUID. Non-WSI files and WSI groups without a VOLUME instance are skipped.

Waveforms (ECG / EEG)

pydcm.waveforms

DICOM waveform I/O for ECG / EEG / hemodynamic / audio (pydcm.waveforms).

Three layers, all over the one DICOM model (no signal-analysis reimplemented — that is neurokit2 / MNE territory; feed them the arrays this module returns):

  • multiplex_array / generate_multiplex — drop-in for pydicom.waveforms.
  • read_waveform — rich read: every multiplex group as physical-unit signals plus per-channel metadata (lead/electrode source, units, sensitivity, filters) and the waveform annotations — the dcm2content JSON, as NumPy + dicts.
  • write_waveform — author a Waveform SOP instance (12-lead/General ECG, scalp/sleep EEG, EMG/EOG, hemodynamic, respiratory, audio …) from per-channel arrays + metadata.
  • to_mne — hand a group straight to MNE-Python (EEG/MEG) as a RawArray.

multiplex_array

multiplex_array(ds, index: int = 0, as_raw: bool = True)

The (samples × channels) array of multiplex group index (PS3.3 C.10.9), pydicom.

With as_raw=False the per-channel Sensitivity / baseline correction from ChannelDefinitionSequence is applied (real-world units).

generate_multiplex

generate_multiplex(ds, as_raw: bool = True)

Yield each multiplex group's array (pydicom).

read_waveform

read_waveform(src) -> dict

Read every multiplex group of a waveform SOP instance into physical-unit signals plus metadata. src is a path or a parsed dataset. Returns::

{modality, sop_class_uid, groups: [{sampling_frequency, num_samples,
  duration_s, channels: [{label, source, units, sensitivity, baseline,
  sensitivity_correction, filter_low, filter_high, notch}], signals (n×ch
  physical units), raw (n×ch), annotations: [...]}], annotations: [...]}

Hand signals (and a channel's source/units) straight to neurokit2 (ECG) or MNE (EEG) — see to_mne.

write_waveform

write_waveform(
    out,
    signals,
    *,
    sampling_frequency,
    kind="ecg12",
    labels=None,
    sources=None,
    units="mV",
    sensitivity=None,
    sample_bits=16,
    patient_id="",
    patient_name="Anonymous^",
    patient_birth_date="",
    patient_sex="",
    study_uid=None,
    series_uid=None,
    sop_uid=None,
    series_number=1,
    instance_number=1
)

Author a DICOM Waveform SOP instance from per-channel signals.

signals: an (n_samples, n_channels) array (or a list of 1-D channel arrays) in physical units. Each channel is quantised to a sample_bits-bit integer via its sensitivity (physical units per LSB); ChannelSensitivity is stored so :func:read_waveform reconstructs the physical values. sensitivity may be a scalar, a per-channel list, or None (auto: max-fit per channel).

kind selects the IOD: ecg12 / ecg / ecg32 / ambulatory_ecg / hemodynamic / eps / eeg / sleep_eeg / emg / eog / arterial_pulse / respiratory / audio. labels / sources give the per-channel ChannelLabel and source meaning (e.g. "Lead I" / "Fp1"). Returns the SOP Instance UID.

to_mne

to_mne(group, ch_types='eeg')

A multiplex group (from :func:read_waveform) → an mne.io.RawArray.

MNE expects volts; channels in µV/mV are scaled accordingly from their units. Requires MNE (pip install mne). For ECG, prefer neurokit2 on group['signals'].

Networking

DICOMweb

pydcm.dicomweb

DICOMweb client (QIDO-RS) — native request builders + native HTTP transport.

Query a DICOMweb server for studies/series/instances and get DICOM-JSON back::

studies = pydcm.dicomweb.search_studies("http://pacs:8042", base_path="/dicom-web",
                                        matches={"00100020": "PAT001"}, limit=10)

Self-contained (no Python HTTP dependency): the request is built by the native zero-alloc DICOMweb builders — the same ones the dcmclient CLI uses — and executed through the native async HTTP transport driven synchronously, so this is the conformance-tested path.

Covers the three core transactions: QIDO-RS search (search_studies/_series/ _instances), WADO-RS retrieve (retrieve_study/_series/_instance → Part-10 bytes), and STOW-RS store (store_instances). Requires the optional _dicomweb extension.

search_studies

search_studies(
    server,
    *,
    base_path="",
    matches=None,
    includefields=None,
    limit=0,
    offset=0,
    auth=""
) -> list[dict]

QIDO-RS study search → list of DICOM-JSON study records.

matches is {tag_or_keyword: value} (e.g. {"00100020": "PAT001"}); includefields is a list of tags/keywords (or ["all"]); auth is an Authorization header value (e.g. "Bearer …"/"Basic …"). Returns [] on 204.

search_series

search_series(
    server,
    study_uid="",
    *,
    base_path="",
    matches=None,
    includefields=None,
    limit=0,
    offset=0,
    auth=""
) -> list[dict]

QIDO-RS series search (all series, or within study_uid).

search_instances

search_instances(
    server,
    study_uid="",
    series_uid="",
    *,
    base_path="",
    matches=None,
    includefields=None,
    limit=0,
    offset=0,
    auth=""
) -> list[dict]

QIDO-RS instance search (optionally scoped to a study/series).

retrieve_study

retrieve_study(
    server,
    study_uid,
    *,
    base_path="",
    transfer_syntax="",
    auth=""
) -> list[bytes]

WADO-RS: retrieve every Part-10 instance of a study → list of bytes blobs.

transfer_syntax optionally negotiates the wire encoding (a TS UID, e.g. "1.2.840.10008.1.2.4.50" for JPEG baseline, or "*" for any) — the server falls back to the default if it cannot honour it.

retrieve_series

retrieve_series(
    server,
    study_uid,
    series_uid,
    *,
    base_path="",
    transfer_syntax="",
    auth=""
) -> list[bytes]

WADO-RS: retrieve every Part-10 instance of a series → list of bytes blobs.

retrieve_instance

retrieve_instance(
    server,
    study_uid,
    series_uid,
    instance_uid,
    *,
    base_path="",
    transfer_syntax="",
    auth=""
) -> bytes

WADO-RS: retrieve one Part-10 instance → bytes (raises if the server returns none).

retrieve_study_metadata

retrieve_study_metadata(
    server, study_uid, *, base_path="", auth=""
) -> list[dict]

WADO-RS: study metadata → list of per-instance DICOM-JSON records (no pixel data).

retrieve_series_metadata

retrieve_series_metadata(
    server, study_uid, series_uid, *, base_path="", auth=""
) -> list[dict]

WADO-RS: series metadata → list of per-instance DICOM-JSON records.

retrieve_instance_metadata

retrieve_instance_metadata(
    server,
    study_uid,
    series_uid,
    instance_uid,
    *,
    base_path="",
    auth=""
) -> dict

WADO-RS: one instance's metadata → a single DICOM-JSON record.

retrieve_frames

retrieve_frames(
    server,
    study_uid,
    series_uid,
    instance_uid,
    frames,
    *,
    base_path="",
    transfer_syntax="",
    auth=""
) -> list[bytes]

WADO-RS: retrieve specific frames of an instance → list of raw frame bytes.

frames is a 1-based frame number, an iterable of them, or a spec string ("1", "1,3-5,7"). transfer_syntax optionally negotiates the frame pixel encoding.

iter_study

iter_study(
    server,
    study_uid,
    *,
    base_path="",
    transfer_syntax="",
    auth=""
)

WADO-RS: yield each Part-10 instance of a study as bytes (memory-efficient stream).

iter_series

iter_series(
    server,
    study_uid,
    series_uid,
    *,
    base_path="",
    transfer_syntax="",
    auth=""
)

WADO-RS: yield each Part-10 instance of a series as bytes (memory-efficient stream).

retrieve_bulkdata

retrieve_bulkdata(uri, *, auth='') -> list[bytes]

WADO-RS: follow a server-issued BulkDataURI → list of bytes.

uri is the absolute URL the server handed out (e.g. a metadata record's BulkDataURI for PixelData); it is fetched verbatim, since its path layout is server-specific.

retrieve_rendered

retrieve_rendered(
    server,
    study_uid,
    series_uid="",
    instance_uid="",
    *,
    base_path="",
    level=None,
    quality=0,
    window=None,
    viewport=None,
    auth=""
) -> bytes

WADO-RS: a server-rendered image (default image/jpeg) → bytes.

level defaults to the deepest UID supplied (instance > series > study). window is an optional (center, width) tuple; quality an optional JPEG quality (1–100); viewport an optional (width, height) output size.

delete_study

delete_study(
    server, study_uid, *, base_path="", auth=""
) -> int

DICOMweb DELETE a whole study → HTTP status (200/204). Server must support the (non-core) delete transaction.

delete_series

delete_series(
    server, study_uid, series_uid, *, base_path="", auth=""
) -> int

DICOMweb DELETE a series → HTTP status.

delete_instance

delete_instance(
    server,
    study_uid,
    series_uid,
    instance_uid,
    *,
    base_path="",
    auth=""
) -> int

DICOMweb DELETE a single instance → HTTP status.

store_instances

store_instances(
    server,
    instances,
    *,
    study_uid="",
    base_path="",
    auth=""
) -> dict

STOW-RS: store Part-10 instances on the server.

instances is an iterable of bytes (path-like / file objects are NOT accepted — pass raw DICOM bytes, e.g. open(p, "rb").read() or ds.to_bytes()). If study_uid is given, the POST is bound to that study (server rejects mismatched StudyInstanceUID).

Returns {"status", "stored", "failed"} — status 200 (all stored) / 202 (partial) / 409 (none); stored and failed are lists of {sop_class_uid, sop_instance_uid, ...} parsed natively from the ReferencedSOPSequence (00081199) / FailedSOPSequence (00081198) by the native store-response parser, so a 202 partial-success is directly inspectable (failed[i]["failure_reason"] holds the PS3.4 code).

DIMSE

pydcm.dimse

pynetdicom-compatible DIMSE networking (pydcm.dimse).

A thin, same-interface layer over pydcm's native C++ DIMSE engine, via the pydcm._dimse binding. The interface matches pynetdicom, so ported code works after one alias::

import pydcm.dimse as pynetdicom          # the drop-in, like `import pydcm as pydicom`
from pydcm.dimse import AE, evt

ae = AE(ae_title="MY_SCU")
ae.add_requested_context(Verification)
assoc = ae.associate("127.0.0.1", 11112)
if assoc.is_established:
    status = assoc.send_c_echo()          # Dataset with .Status
    assoc.release()

# SCP:
ae.add_supported_context(CTImageStorage)
server = ae.start_server(("0.0.0.0", 11112), block=False,
                         evt_handlers=[(evt.EVT_C_STORE, handle_store)])

ae.associate() opens ONE association (a persistent native client) that send_c_echo / send_c_store / send_c_find / send_c_move reuse, exactly like pynetdicom — one negotiate, many ops, one release(). The contexts negotiated are the ones you add_requested_context'd (plus a broad default set so basic flows work out of the box). send_c_get also reuses the persistent association when you add_supported_context the storage classes to receive — they are negotiated with the PS3.7 §D.3.3.4 role flip (scp_role) at associate() so the matched instances arrive as inbound C-STORE on the same connection. Without any supported context it falls back to a one-shot association (CT/MR defaults). TLS is opt-in via ae.tls = {...} (ca_file/cert_file/key_file/verify_peer/server_name).

AE

A pynetdicom-compatible Application Entity over the native DIMSE engine.

active_associations property

active_associations

The associations opened by this AE that are still established.

remove_requested_context

remove_requested_context(
    abstract_syntax, transfer_syntax=None
)

Remove a requested presentation context (pynetdicom-compatible).

remove_supported_context

remove_supported_context(
    abstract_syntax, transfer_syntax=None
)

Remove a supported presentation context (pynetdicom-compatible).

make_server

make_server(
    address,
    ae_title=None,
    contexts=None,
    evt_handlers=None,
    ssl_context=None,
    **kw
)

Return a non-blocking SCP server handle (pynetdicom-compatible); call .serve_forever() / .shutdown() on it. == start_server(block=False).

shutdown

shutdown()

Stop all servers started by this AE (pynetdicom-compatible).

Association

A pydcm DIMSE association, persistent like pynetdicom's: ae.associate() opens it, every send_c_* reuses it, release() tears it down. C-GET is the lone exception (one-shot, for SCU-role storage negotiation — see the module docstring).

local property

local

The local (requestor) AE — {ae_title, address, port} (pynetdicom-compatible).

remote property

remote

The remote (acceptor) AE — {ae_title, address, port}.

accepted_contexts property

accepted_contexts

Presentation contexts the peer accepted (pynetdicom-shaped) — each carries the negotiated transfer_syntax. Built from the proposals + the engine's per-context negotiation result.

rejected_contexts property

rejected_contexts

Presentation contexts the peer did not accept.

kill

kill()

Forcibly terminate the association (pynetdicom-compatible) — same as abort().

bind

bind(event, handler, args=None)

Bind a handler to a lifecycle event (EVT_ESTABLISHED/RELEASED/ABORTED) on this association (pynetdicom-compatible).

unbind

unbind(event, handler)

Unbind a lifecycle event handler (pynetdicom-compatible).

send_c_cancel

send_c_cancel(msg_id, context_id=None)

Accepted for pynetdicom compatibility. The native engine runs each C-FIND/GET/ MOVE to completion (no per-operation C-CANCEL), so this is a no-op; use abort() to tear the association down.

send_c_get

send_c_get(dataset, query_model, msg_id=1, priority=2)

C-GET. Matched instances are delivered to the EVT_C_STORE handler registered on this AE (pynetdicom routes the same way). When the AE has add_supported_context'd the storage classes (negotiated with SCP role at associate()), it runs on the PERSISTENT association; otherwise it falls back to a one-shot association that negotiates a CT/MR default set to receive the inbound C-STORE-RQs.

EHR bridges

FHIR

pydcm.fhir

FHIR R4 bridge — DICOM → FHIR resources, over the native FHIR engine.

Turns a DICOM instance into a FHIR ImagingStudy resource (the imaging↔EHR seam), so an agent or app can hand a study to a FHIR consumer without a separate mapping layer::

study = pydcm.fhir.imaging_study("CT0001.dcm")   # -> dict (FHIR R4 ImagingStudy)

The field mapping (study/series/instance + a contained Patient) lives in the native engine; this module is the thin marshaller. Requires the optional _fhir extension (like _dimse for networking).

imaging_study

imaging_study(source) -> dict

Build a FHIR R4 ImagingStudy (as a dict) from one DICOM instance.

source is a filesystem path or raw Part-10 bytes. The study/series/instance identifiers, modality, and patient demographics are mapped into the FHIR ImagingStudy with a contained Patient (subject.reference = "#patient-…"). Single-instance for now — one series / one instance per call. Raises if StudyInstanceUID is absent.

HL7 v2

pydcm.hl7

HL7 v2.5 — parse messages + build ORU^R01 results, over the native HL7 engine.

The HL7 side of the imaging↔EHR seam: read an inbound order/result and emit a radiology result back to the HIS::

segs = pydcm.hl7.parse(open("oru.hl7").read())   # -> [{"id": "MSH", "fields": [...]}, ...]
msg  = pydcm.hl7.build_oru(config, context, observations)   # -> ER7 string

Parsing returns each segment as {id, fields} (fields split on the field separator — the universal ER7 shape). build_oru takes plain dicts. MLLP networking (send/listen) is not bound here yet — this is the message layer. Requires the optional _hl7 extension.

parse

parse(text: str) -> list[dict]

Parse an HL7 v2 message into a list of segments.

Each segment is {"id": "MSH"|"PID"|…, "fields": [str, …]} where fields is the segment split on its field separator (ER7). Raises on a malformed message.

build_oru

build_oru(
    config: dict, context: dict, observations: list[dict]
) -> str

Build an HL7 v2.5 ORU^R01 result message (ER7 string).

config → MSH (our_app/our_facility/their_app/their_facility/ control_id/timestamp/version/specific_character_set); context → PID + ORC/OBR order identifiers (patient_, order_number, accession_number, procedure*, modality, ordering_provider, observation_datetime); observations → OBX rows (observation_id, value, value_type def "TX", status def "F"). Absent keys keep the native defaults.

Agent / MCP server

pydcm.mcp

pydcm — agent-facing MCP server (pydcm.mcp).

The Model Context Protocol projection of pydcm, over the shared dcmagent engine (the same protocol engine behind dcmclient mcp). Where dcmclient mcp re-execs the CLI binary, this dispatches in-process to Python — so it exposes pydcm's live capabilities that the CLI structurally cannot: 3D volume assembly, the deterministic transform suite, DWI, WSI, FHIR/HL7, RT dosimetry, and the structured-content reader.

Run it as an MCP stdio server (what an agent / Claude Desktop / mcp client connects to)::

python -m pydcm.mcp

Each tool's handler returns a string: a JSON object (surfaced as structuredContent), plain text, or a data:image/*;base64,… data URI (surfaced as an image block). Register more tools with the :func:tool decorator. Requires the _agent extension (built when dcmagent is installed).

serve

serve(verbose: bool = False) -> int

Run the MCP stdio server until stdin EOF. Returns the engine exit code.

The rest of the DICOM API

The standard DICOM data model is all here, so existing code runs unchanged.

TypesDataset, FileDataset, FileMetaDataset, DataElement, Sequence, PersonName, Tag, BaseTag, MultiValue, DSfloat, IS, UID, InvalidDicomError.

Submodulescharset, config, datadict, dataelem, dataset, dicomio, encaps, encoders, env_info, errors, examples, filereader, filewriter, jsonrep, multival, overlays, pixel_data_handlers, sequence, tag, uid, valuerep, values.

See Behaviour notes for the deliberate behaviours worth knowing.