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 separate codec plugins to install, 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.
fp may be a path, an os.PathLike, raw bytes, or a readable binary
file-like object (e.g. io.BytesIO). 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 ¶
Write dataset to path as Part-10 (name).
pixel_array ¶
Decode pixels from a Dataset / path / binary file-like.
generate_uid ¶
Return a unique :class:UID.
With entropy_srcs the result is deterministic for that input; otherwise it is
random. Defaults to pydcm's registered root (PYDCM_ROOT_UID); pass an explicit
prefix for a different root, or prefix=None for a 2.25. UUID-derived
UID. Reuses the native canonical generator (_native.mint_uid).
Pixels¶
pydcm.pixels ¶
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 ¶
Decode 1-bit packed PixelData (BitsAllocated=1) to a {0,1} uint8 array
[frames, rows, cols] (PS3.5 §8.1.1, little bit order).
decode_uncompressed ¶
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). The native decoder stays
authoritative for files and for compressed / Big-Endian / Deflated syntaxes.
pack_bits ¶
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).
apply_rescale ¶
Apply the linear Modality LUT (arr * RescaleSlope + RescaleIntercept).
Use :func:apply_modality_lut when a Modality LUT Sequence
may be present; this is the rescale-only path.
pixel_array ¶
Decode pixels from a Dataset / path / binary file-like.
apply_modality_lut ¶
Modality LUT (Rescale Slope/Intercept → e.g. Hounsfield units), PS3.3 C.11.1.
apply_voi_lut ¶
VOI LUT / windowing (PS3.3 C.11.2).
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] (NOT [0,1]).
apply_voi ¶
Apply a VOI LUT Sequence (0028,3010) if present; else return arr unchanged.
apply_windowing ¶
Linear/sigmoid Window Center/Width (PS3.3 C.11.2.1.2).
convert_color_space ¶
Convert between RGB and YBR_FULL/YBR_FULL_422 (PS3.3 C.7.6.3.1.2).
apply_color_lut ¶
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 a Presentation LUT (Sequence or INVERSE shape) to arr.
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 ¶
Return the Image Pixel module element values from ds as a dict.
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's Pixel Data in place to native encoding (delegates to native).
set_pixel_data ¶
Set ds's Pixel Data + Image Pixel module elements from arr.
File-sets (DICOMDIR)¶
pydcm.fileset ¶
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 ¶
Return instances matching filters (record attributes; with load=True
the referenced file is read and its values are matched too).
find_values ¶
Distinct values of element across the (given or all) instances.
write ¶
Write the staged instances + a conformant DICOMDIR under path; returns
the DICOMDIR path. Instances staged from a file are copied byte-verbatim.
The DICOMDIR itself is built by the native engine: it reads each instance's key attributes, groups them PATIENT→STUDY→SERIES→leaf, and emits a conformant Explicit-VR-LE directory with correct inter-record byte offsets. This wrapper only does the filesystem side — assign each instance a media File ID, place the file, and write the returned DICOMDIR.
FileInstance ¶
One referenced instance (a leaf DICOMDIR record).
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.
Conformance validation¶
Validate a file against the DICOM standard. validate is the full report
(element-level layers — VR, value multiplicity, enumerated values, value format,
SpecificCharacterSet, pixel geometry, LUT — plus IOD / module / conditional and the
SR content tree); iod_validate is the narrower IOD-only view. Both return a list
of findings ([] = conformant).
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 separate codec plugins to install, 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.
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 ¶
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.
to_nrrd ¶
Write this volume to single-file NRRD (.nrrd) — 3D Slicer's native
format. LPS-native, so the voxel→world affine maps straight to NRRD
space directions/space origin (no RAS flip). gzip=True →
encoding: gzip. Datatype follows pixels (label masks stay integer).
to_metaimage ¶
Write this volume to single-file MetaImage (.mha) — the ITK / nnU-Net /
MONAI interchange format. LPS-native (direction cosines → TransformMatrix,
origin → Offset). compress=True → CompressedData=True.
Axis
dataclass
¶
One non-spatial axis of a :class:Volume4D — what the 4th dimension means.
kind is the semantic label ("temporal", "bvalue", "direction",
"echo", "cardiac", "stack", "frametype", "velocity",
"energy"); values holds one sorted value per step (e.g. trigger times,
b-values, echo times).
Volume4D
dataclass
¶
A 4-D stack: n_volumes co-registered 3-D volumes sharing one world grid.
The 4th dimension is the non-spatial axis (or axes) that varies across the
series — time (cardiac cine, perfusion, fMRI), b-value/direction (DWI), echo
time (multi-echo), cardiac phase, stack, spectral energy. :attr:dimensions
labels it; when more than one axis varies the volume index decomposes
row-major (slowest-first) across them — see :meth:coords.
coords ¶
The non-spatial coordinate of volume i as {kind: value} (e.g.
{"bvalue": 1000.0, "direction": 3.0} or {"temporal": 7.0}).
to_nifti ¶
Write the 4-D stack to NIfTI-1 (.nii.gz → gzip). Thin call into the
native dcm_nifti N-D writer — the LPS affine is flipped to RAS inside;
the 4th axis becomes NIfTI's time/volume dimension.
to_nrrd ¶
Write the 4-D stack to single-file NRRD (.nrrd). The 4th axis becomes
a non-spatial NRRD axis (kind: list, no space direction). gzip=True →
encoding: gzip.
to_metaimage ¶
Write the 4-D stack to single-file MetaImage (.mha); the 4th axis is the
slowest MetaImage dimension. compress=True → CompressedData=True.
load_series ¶
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.
load_4d ¶
Assemble the DICOM slices under path into one 4-D stack [T, Z, Y, X].
The native engine clusters by orientation/grid, Z-sorts by IPP projection, and
discovers the varying non-spatial axes (time / b-value / direction / echo /
cardiac phase / stack / energy) — each becomes the 4th dimension and is
labelled in :attr:Volume4D.dimensions. path may be a directory, a single
enhanced multi-frame file, or a list of files. A plain 3-D series yields
T == 1 with an empty dimensions list.
For a DWI series where you also need the FSL .bval/.bvec gradient table,
use :func:pydcm.load_dwi; this returns the geometry + dimension semantics for
any 4-D organisation, not just diffusion.
from_nifti ¶
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 ¶
Extract a BIDS JSON sidecar (the standard BIDS metadata written 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 (no row flip), so
the i sign equals the reference while the j sign is the negation — verified on
the dcm_qa suite, where our volume equals the 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)
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 ¶
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 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. 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 ¶
Convert a single-frame DWI series to NIfTI + FSL .bval/.bvec (
DWI deliverable). Writes <prefix>.nii.gz (4-D), <prefix>.bval and
<prefix>.bvec; returns their paths.
order defaults to "acquisition" so the volume order is deterministic (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
skimage/torch/ITK). Two things genuinely diverge between frameworks: the resampling
interpolation (skimage.resize spline vs torch grid_sample vs SimpleITK) 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 # skimage.resize 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",
fill: float = 0.0
) -> 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).
Bit-exact with SimpleITK's sitk.Resample(moving, reference, sitkLinear) /
sitkNearestNeighbor — the resample-to-reference pattern medical models use —
both INSIDE and OUTSIDE the moving extent: reference voxels whose moving index leaves
[-0.5, dim-0.5) on any axis are set to fill (SimpleITK's defaultPixelValue),
not edge-extrapolated. fill=0 is background — correct for a label map or a
prediction mapped onto a larger grid; pass e.g. -1024 for CT air.
affine ¶
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 ¶
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 ¶
Isotropic cubic B-spline resample to out_shape (D, H, W) — order=3 B-spline,
fp64, bit-exact with skimage.resize(order=3, mode='edge', clip=True) (the
nnU-Net image reference — note this is skimage.resize, NOT scipy.ndimage.zoom). For images.
resample_nearest ¶
Nearest-neighbour resample to out_shape (D, H, W) (order=0, half-pixel — matches
skimage.resize(order=0)) — the nnU-Net label path (no class blending).
resample_grid_sample ¶
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_2d ¶
2-D resize, applied per slice in-plane to out_hw (out_H, out_W) (each (H, W)
slice → (out_H, out_W); depth/slices preserved). All paths are deterministic — the CPU
result and a GPU/WGSL port agree bit-for-bit (train/serve parity). filt:
"bilinear"(default): plain bilinear, no anti-aliasing, half-pixel ((o+0.5)*scale-0.5), clamp, double. This is the convention the deployment actually uses (ai_segmeation'sresizeBilinearU8/zoom2d.wgsl) and the same kernel ascv2.INTER_LINEAR/F.interpolate(bilinear, align_corners=False). Bit-exact with resizeBilinearU8; ≤1 fp32 ULP vs cv2/torch (those use SIMD/FMA and disagree ≤1 ULP with each other — no single bit-exact target exists, so this clean form is the reproducible spec)."bicubic":PIL.Image.resizebicubic (anti-aliased on downscale), bit-exact with PIL — MedSAM2's Python preprocessing (each windowed CT slice → 512²). uint8 → PIL fixed-point, float → PIL float convolution."pil-bilinear": PIL bilinear (anti-aliased), bit-exact with PIL.
A uint8 Volume gives integer output (resizeBilinearU8 truncates; PIL rounds); window/cast
beforehand as the model does.
resample ¶
resample(
vol: Volume,
out_shape,
*,
backend: str = "skimage",
is_label: bool | None = None
) -> Volume
Resample to out_shape (D, H, W) under a framework's interpolation convention:
backend="skimage" (cubic B-spline, Tier-1 bit-exact — bit-identical to
skimage.transform.resize(order=3, mode='edge', anti_aliasing=False), which is what
nnU-Net's default_resampling uses; NOT scipy.ndimage.zoom, whose half-pixel
coordinate convention differs — hence the name is skimage, not scipy) / "torch"
(grid_sample, Tier-2 ≤1 fp32 ULP — also matches F.interpolate(mode='trilinear',
align_corners=False) to ≤1 ULP, nnU-Net's opt-in torch resampling backend) / "itk"
(SimpleITK linear/nearest in double — bit-exact vs SimpleITK on realistic grids; ITK
B-spline is deferred). Labels — or is_label=True — force nearest, which is convention-free.
The single entry for choosing the lineage;
:func:resample_cubic / :func:resample_grid_sample are its skimage / torch backends.
resize ¶
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 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 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 lo/hi voxels before/after each axis ((z, y, x)). mode is
"constant" / "edge" / "reflect" (MONAI SpatialPad/BorderPad).
crop_foreground ¶
Crop to the bounding box of non-zero voxels, expanded by margin (MONAI
CropForeground). Returns vol unchanged if every voxel is zero.
flip ¶
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.
transpose ¶
Permute the voxel axes (numpy/MONAI transpose); axes is a permutation of
(0, 1, 2) in (z, y, x) order — output axis i is input axis axes[i].
Exact (no interpolation); the affine's axis columns permute so world coordinates are
unchanged. This is nnU-Net's transpose_forward; the inverse (transpose_backward)
is transpose(v, np.argsort(axes)).
center_crop ¶
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. mode ∈ constant/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 ¶
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 ¶
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 ¶
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.
rescale_robust ¶
rescale_robust(
vol: Volume,
*,
dst_min: float = 0.0,
dst_max: float = 255.0,
f_low: float = 0.0,
f_high: float = 0.999
) -> Volume
FreeSurfer/FastSurfer "conform" ROBUST intensity rescale to [dst_min, dst_max]
(→ float32). A 1000-bin histogram picks a robust source range, ignoring the f_low
fraction of all voxels at the bottom and (1 - f_high) of the non-zero voxels at
the top (mri_convert defaults f_low=0, f_high=0.999), then
x → clip(dst_min + scale*(x - src_min)). This is the intensity step of the
FastSurfer / SynthSeg / DL-DiReCT brain "conform" pipeline — the orientation + 1 mm
resample steps are :func:reorient + :func:resample_to_spacing; cast the result to
uint8 for those models. Bit-faithful to conform.py getscale()+scalecrop().
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 ¶
Gamma contrast ((x-min)/(range+1e-7))**gamma * range + min (MONAI
AdjustContrast). → float32.
gaussian_smooth ¶
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 ¶
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 ¶
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 — 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 ¶
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 ¶
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 ¶
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) — centerroi//2, peak 1, min = natural corner. Bit-exact with nnU-Net V2get_gaussian/ the deployed ai_segmeation maps."monai"— center(roi-1)/2, unnormalized (peak ≈0.91), min clamped tomax(min, 1e-3). Matches MONAIcompute_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 ¶
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 feature extractor.
Three surfaces over the one native radiomics engine:
pydcm.radiomics(image, mask=..., roi=...)— pydcm's own one-call API.from pydcm.radiomics import featureextractor— the conventionalRadiomicsFeatureExtractor(...).execute(img, mask)extractor API, so a pipeline written against that interface needs only change its import path; returns anOrderedDict.@pydcm.radiomics.feature(...)— register a custom feature in Python; it runs over the SAME preprocessed + discretised grid the native engine used, so a researcher can add a feature or override a formula without recompiling. Applies to array inputs and toRadiomicsFeatureExtractor.execute.
ROI ¶
The preprocessed + discretised ROI handed to a custom feature: the exact grid
the native IBSI extractor ran over (same resample / normalise / resegmentation and
the same gray-level discretisation). Arrays are (nz, h, w); ROI voxels have
mask 1 and level [0, nb), the rest mask 0 and level -1.
RadiomicsFeatureExtractor ¶
extractor over pydcm's native IBSI engine.
execute ¶
Return an OrderedDict of features over the ROI.
feature ¶
Register a custom radiomic feature (decorator).
The decorated function receives an :class:ROI and returns a scalar; the value
joins every result dict as "<class_name>_<name>" (name defaults to the
function's name). Use as @feature, @feature("glcm") or
@feature("firstorder", name="my_stat"). Applies to array inputs and to
RadiomicsFeatureExtractor.execute (the compatibility extractor).
Naming a custom feature after a standard one — @feature("firstorder",
name="Mean") — OVERRIDES that feature's value in the result, which is how you
change a built-in formula without recompiling.
registered_features ¶
The keys ("<class>_<name>") of the currently registered custom features.
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,
distances=None
)
The IBSI radiomic feature set over an ROI — 10 classes (firstorder / glcm / glrlm / glszm / gldm / gldzm / ngtdm / shape / ivh [intensity-volume histogram] / local intensity), with the standard radiomics feature names.
Two call styles:
- From files (the convenient path) —
imageis a DICOM path: pixels are decoded to real-world values (HU) andspacingis read from the image geometry (PixelSpacing / SliceThickness). Give the ROI as eithermask= a co-framed mask DICOM path (non-zero = inside) orroi=(min, max)real-world-value thresholds. - From arrays (the low-level primitive) —
imageis a real-world-valued array (e.g.decode(..., rescale=True)) andmaska non-zero-inside array of the same shape; passspacing=(x, y, z)mm yourself.
Preprocessing (IBSI, 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 standard 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}.
pydcm.dce ¶
pydcm DCE-MRI — dynamic contrast-enhanced pharmacokinetic modelling.
A thin NumPy surface over the native DCE engine. Scope is the validated slice: the Parker population AIF, spoiled-GRE signal→concentration conversion, and the Tofts / Extended-Tofts / Patlak tissue models, fitted per voxel.
Typical use (concentration already computed)::
import numpy as np, pydcm.dce as dce
t = np.arange(0, 6, 0.025) # minutes, injection at t=0
cp = dce.parker_aif(t) # population plasma AIF (mM)
maps = dce.fit(conc_4d, t, model="ext_tofts") # conc_4d: (T, H, W)
ktrans = maps["ktrans"] # (H, W) float32, 1/min
From spoiled-GRE signal instead of concentration::
maps = dce.fit(signal_4d, t, input="spgr",
t1_0_s=1.4, tr_s=0.005, fa_deg=25.0, r1=4.5)
Units: time in minutes (t=0 = injection; negative times = pre-contrast), Ktrans in 1/min, ve/vp dimensionless fractions, concentration in mM.
population_aif ¶
Population arterial input function (plasma, mM) by name.
model is one of :data:AIFS — parker (2006), georgiou (2019),
fritz_hansen (1996), weinmann (dose-scaled bi-exp), mcgrath (2009,
preclinical). times_min in minutes (t=0 = injection). The published forms
are already plasma, so hct defaults to 0 (verbatim); set it only to convert
a measured whole-blood curve. Returns a 1-D float64 array, length of times.
parker_aif ¶
Parker (2006) population AIF (plasma, mM) — population_aif(..., 'parker').
hct defaults to 0 (the Parker curve is already plasma); set it only to
convert a measured whole-blood curve. Returns a 1-D float64 array.
forward ¶
Synthesise a tissue concentration curve Ct(t) from known PK parameters.
Uses the exact piecewise-linear-AIF convolution. cp is the arterial plasma
curve at times_min. Returns Ct (mM), same length as times_min.
measure_aif ¶
Extract a measured plasma AIF from an arterial ROI in a 4-D signal series.
The clinical alternative to a population (Parker) AIF: average the ROI's
spoiled-GRE signal per time frame, invert to blood concentration with the
blood baseline T1, and convert whole-blood → plasma via /(1 - hct).
series is (T, H, W) or (T, Z, Y, X) signal; mask is a non-zero ROI
over the spatial dims (broadcast to the series' spatial shape). Returns the
plasma AIF Cp(t) (mM, length T) — pass it to :func:fit / :func:fit_series
as aif=. n_baseline (pre-contrast frames for S0) is auto-detected from
the bolus rise when None.
t1_map_vfa ¶
VFA / DESPOT1 baseline-T1 map from a multi-flip-angle SPGR acquisition.
volumes is (F, H, W) — the spoiled-GRE signal at each of the F flip
angles (same TR); flip_angles_deg is (F,). Returns {"t1", "m0",
"fitted"} with t1 an (H, W) map in seconds — feed it to
:func:fit / :func:fit_series as t1_map= for the SPGR path, instead of
assuming a single baseline T1.
signal_to_conc ¶
Invert the spoiled-GRE steady-state signal → tracer concentration (mM).
signal is a 1-D series; the first n_baseline samples form the
pre-contrast S0. t1_0_s/tr_s in seconds, fa_deg in degrees, r1 the
relaxivity (L·mmol⁻¹·s⁻¹).
fit_curve ¶
Fit a single tissue curve. Returns {ktrans, ve, vp, rmse, iters, ok, delay}.
ct measured tissue concentration, cp arterial plasma — both at
times_min (mM). Patlak is solved in closed form; Tofts / Extended-Tofts
use Levenberg–Marquardt. With fit_delay=True, a bolus-arrival delay (min)
is jointly estimated over delay_bounds (the AIF is time-shifted), which
removes the need for t=0 to be the exact injection instant.
fit ¶
fit(
series,
times_min,
model="ext_tofts",
*,
input="concentration",
aif=None,
hct=0.0,
mask=None,
enhance_thresh=0.0,
t1_0_s=1.4,
tr_s=0.005,
fa_deg=25.0,
r1=4.5,
n_baseline=0,
t1_map=None,
fit_delay=False,
delay_bounds=(0.0, 0.5)
)
Voxel-wise PK fit over a 4-D (T, H, W) series → parameter maps.
Returns {"ktrans", "ve", "vp", "rmse"} — each an (H, W) float32 map —
plus "fitted" (the count of voxels actually fitted), and "delay" (min)
when fit_delay=True. Maps a model does not estimate (ve for Patlak, vp for
Tofts) are zero.
Parameters¶
series : (T, H, W) array — concentration, or raw signal when input='spgr'.
times_min : (T,) acquisition times in minutes (t=0 = injection); frames must be
time-ordered with the pre-contrast baseline first.
model : 'tofts' | 'ext_tofts' | 'patlak'.
input : 'concentration' (default) or 'spgr' (convert signal→conc per voxel).
aif : optional measured plasma AIF (T,) in mM; default uses the Parker curve.
hct : haematocrit for the Parker→plasma path (default 0 = use Parker verbatim).
mask : optional (H, W) array; non-zero voxels are fitted.
enhance_thresh : skip voxels whose peak enhancement is below this (noise gate).
t1_0_s, tr_s, fa_deg, r1, n_baseline : SPGR conversion params (input='spgr').
t1_map : optional (H, W) per-voxel baseline T1 (s) overriding t1_0_s.
fit_series ¶
fit_series(
source,
times_min=None,
model="ext_tofts",
*,
input="concentration",
aif=None,
hct=0.0,
mask=None,
enhance_thresh=0.0,
t1_0_s=1.4,
tr_s=None,
fa_deg=None,
r1=4.5,
n_baseline=0,
t1_map=None,
fit_delay=False,
delay_bounds=(0.0, 0.5)
)
Fit a whole DCE series → parameter-map volumes.
source may be a DICOM directory / file-list / single enhanced-multiframe
file (assembled via :func:pydcm.load_4d), a loaded :class:pydcm.Volume4D,
or a raw array shaped (T, Z, Y, X) or (T, H, W). Each slice's
(T, H, W) time course is fitted independently with :func:fit.
Returns {"ktrans", "ve", "vp", "rmse"} — each a (Z, H, W) float32
volume — plus "fitted" (total voxels fitted), "times_min" (the time
grid used), and "delay" (min) when fit_delay=True. With fit_delay
the per-voxel bolus-arrival delay is jointly fitted over delay_bounds,
which absorbs the offset between t=0 and the true injection — the robust way
to use auto-derived (frame-relative) times.
Timing & sequence params: pass times_min explicitly (minutes; t=0 =
injection), else they are read from the DICOM tags — the dynamic-time grid
from AcquisitionDateTime → AcquisitionTime/ContentTime → TriggerTime
(acquisition-clock preference order; whichever varies), or, for an enhanced-multiframe
file, the per-frame FrameAcquisitionDateTime; RepetitionTime / FlipAngle
(via :func:pydcm.bids_sidecar, already in seconds) for the SPGR conversion. input='spgr' still needs a baseline t1_0_s / t1_map and r1
(T1 is not a stored tag).
IMPORTANT — injection alignment: auto-derived times are relative to the FIRST
frame (t[0]=0), but the population (Parker) AIF assumes t=0 at the contrast
injection. If the series has pre-contrast baseline frames, pass times_min
with the baseline frames at negative t (injection at 0), or supply a measured
aif; otherwise the AIF is mis-aligned and Ktrans/vp are biased. Automatic
bolus-arrival/delay estimation is not yet implemented (deferred).
write_param_maps ¶
write_param_maps(
reference,
result,
params=("ktrans", "ve", "vp"),
*,
dtype=None,
output_dir=None
)
Emit DCE parameter VOLUMES as DICOM Parametric Maps (multi-frame when 3-D).
Thin convenience over :func:pydcm.write_paramap that supplies the correct
per-parameter units (Ktrans 1/min, ve/vp dimensionless, delay min): each
requested map in result (the dict from :func:fit / :func:fit_series) is
written against reference — the source DCE slices, one per Z plane — so a
(Z, H, W) volume becomes one multi-frame Parametric Map.
Returns {param: Part-10 bytes}, or {param: written path} when
output_dir is given. dtype (e.g. "uint16") is forwarded to write_paramap
for integer-quantised storage.
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, over the shared native SEG write/decode engines — a native interop path for the common cases.
SegmentReader ¶
Read a DICOM Segmentation into per-segment masks.
MultiClassReader ¶
Read a (non-overlapping) Segmentation into one label-map volume.
AlgorithmIdentificationSequence ¶
Identifies the algorithm that produced a segment.
SegmentDescription ¶
Description of one segment.
write_seg ¶
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).
write_seg_from_prediction ¶
Map a model prediction back onto the original DICOM grid and write a coded SEG.
Closes the inference loop: preprocess a series (resample / crop / reorient /
transpose), run a model, then call this to put the label map back where it came
from. prediction is resampled onto the reference series' grid by AFFINE
(label-safe nearest), so whatever spatial preprocessing produced it is inverted
geometrically — no recorded op-stack needed.
a label-map :class:~pydcm.volume.Volume — integer voxels, value k
marks the segment with labelID k — carrying its (processed-space) affine.
For soft probabilities [C, Z, Y, X] take :func:pydcm.transforms.argmax first.
reference: the original series — a directory or list of instance paths. Its grid,
demographics and per-slice references define the SEG. Precondition: a single
coherent 3-D series (one orientation, one stack). The resample target comes from
:func:load_series (which IOP-clusters + splits temporal/echo/b-value onto a 4th
axis), while :func:write_seg keeps every dims-matching slice — so a 4-D / multi-echo
/ perfusion / multi-orientation reference makes the two grids disagree and raises a
clear error; pass the coherent sub-series in that case.
segments / output: as in :func:write_seg.
seg_from_nifti ¶
Author a coded DICOM Segmentation from a NIfTI label volume + reference series.
The NIfTI / FSL / ANTs → DICOM-SEG return path — the converter
cannot produce. mask is a .nii/.nii.gz label volume co-framed with
reference (the natural NIfTI → 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 ¶
Reconstruct a DICOM Segmentation, 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 simpler
: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
)
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.
extra kwargs are accepted for source compatibility.
Parametric maps¶
pydcm.paramap ¶
pydcm — DICOM Parametric Map authoring + reading (pydcm.paramap).
Parametric Map conversion (image ↔ paramap), 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). a native capability authoring; this is pydcm-native value-add. - :func:
read_paramap— read a Parametric Map back to a real-valuedfloat32array - 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, 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 a DICOM Parametric Map to (values, meta).
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) — compat surface + native engine.
- compat —
Code(a coded-concept shape) andcontent_json, forfrom pydcm.sr import …imports. - pydcm-native (over the shared native SR engines): author any Comprehensive SR
from a content-tree dict (
write_sr); author / read a TID 1500 Measurement Report (write_report/read_report); 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 ¶
A measurement group's tracking identity.
FindingSite ¶
An anatomic location code.
Measurement ¶
One numeric measurement (name/value/unit + qualifiers).
QualitativeEvaluation ¶
A coded name/value evaluation.
SourceImageForRegion ¶
The image a 2D region is drawn on.
ImageRegion ¶
A 2D ROI (SCOORD) on a source image.
ImageRegion3D ¶
A 3D ROI (SCOORD3D) in a Frame of Reference.
MeasurementsAndQualitativeEvaluations ¶
Measurement group (no ROI).
PlanarROIMeasurementsAndQualitativeEvaluations ¶
VolumetricROIMeasurementsAndQualitativeEvaluations ¶
PersonObserverIdentifyingAttributes ¶
Person observer identifying attributes.
DeviceObserverIdentifyingAttributes ¶
Device observer identifying attributes.
ObserverContext ¶
Wraps a person/device observer.
ObservationContext ¶
Observer (+ subject) context.
MeasurementReport ¶
The TID 1500 root (observation context + procedure + measurement groups).
ContentItem ¶
Base for the SR content-item primitives — a typed SR tree node.
ContentSequence ¶
Bases: list
An ordered list of content items.
content_json ¶
The semantic SR content tree (reuses the native content engine via
:func:pydcm.content).
write_sr ¶
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, over the same native SR-export engine.
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 ¶
The measurements of a TID 1500 Measurement Report SR. 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).
sr_to_html ¶
Render a DICOM Structured Report to clinical-readable HTML (a str).
Renders any SR to standalone, clinical-readable markup — not only the TID 1500 measurement-report shape.
write_measurement_report ¶
Author a TYPED TID 1500 Measurement Report — the standard TID 1500
measurement-report capability, over the native SR authoring engine (structure
cross-validated against reference SR implementations). 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 ¶
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
SR reports, not just pydcm's own output.
sr_code_meaning ¶
Code Meaning for a coded concept (scheme, value) from the DICOM PS3.16
Content Mapping Resource (the most complete public set), or None
if the code is unknown. E.g. sr_code_meaning("DCM", "126000") →
"Imaging Measurement Report".
sr_validate_code ¶
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 ¶
True if the coded concept (scheme, value) is a member of Context Group
cid (e.g. sr_cid_has(7469, "SCT", "103339001")).
sr_validate ¶
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,
…), coded-concept conformance against the PS3.16 table, AND TID content-template
conformance (measurement-group mandatory rows + cardinality, value-type per row,
value-set-per-concept, container nesting, and conditional Observer / Subject /
Algorithm-Identification rows) — returning a list of {severity, location,
message} findings (empty = a conformant SR).
Comprehensive3DSR ¶
Comprehensive3DSR(
evidence,
content,
series_instance_uid,
series_number,
sop_instance_uid,
instance_number,
manufacturer=None,
**_kw
)
Constructor — a TID 1500 SR Dataset.
Key Object Selection¶
pydcm.ko ¶
pydcm — DICOM Key Object Selection authoring + reading (pydcm.ko).
The 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
— a native authoring capability.
KeyObjectSelection ¶
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 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
)
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 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) — a native authoring capability.
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 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
)
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
)
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
)
Constructor.
Bulk annotations (microscopy)¶
pydcm.ann ¶
pydcm — Microscopy Bulk Simple Annotations reading + authoring (pydcm.ann).
The 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 ¶
Measured quantity over a group.
AnnotationGroup ¶
Annotation group.
read_ann ¶
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
)
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 an SCImage 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
SCImage 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 extra kwargs are accepted
for source compatibility.
Parametric Map classes¶
pydcm.pm ¶
Parametric Maps (pydcm.pm) — class API over the native
write_paramap writer. Re-exports the functional write_paramap / read_paramap.
RealWorldValueMapping ¶
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 a DICOM Parametric Map to (values, meta).
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
)
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.
extra kwargs are accepted for source compatibility.
Legacy Converted Enhanced¶
pydcm.legacy_converted ¶
pydcm — Legacy Converted Enhanced CT/MR/PET authoring (pydcm.legacy_converted).
The 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.
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 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 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 |
|
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).
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 ¶
Extract an Encapsulated Document instance → :class:EncapsulatedDocument.
Surface segmentation meshes¶
pydcm.surface ¶
pydcm — Surface Segmentation reading (pydcm.surface).
Read a Surface Segmentation object (SOP 1.2.840.10008.5.1.4.1.1.66.5) — surface
meshes stored as native DICOM (points + mesh primitives) rather than an
encapsulated STL blob. Every primitive type is decoded by the native engine and
triangulated into one flat triangle list, so each surface comes back as a plain
(points, triangles) pair ready for pydcm.mesh / rendering. This is the
thin marshalling wrapper; the parse lives in the native engine.
Authoring (the inverse write_surface) is deliberately not implemented yet —
surface producers are rare, and STL encapsulation covers most mesh-export needs.
read_surface ¶
Read a Surface Segmentation file.
Returns a dict {surfaces:[...], segments:[...]} or None if path is
not a Surface Segmentation object. Each surface carries its identity
(number) and display hints (finite_volume / manifold /
recommended_type / recommended_opacity / cielab) plus geometry:
points—(N, 3)float64 vertex coordinates (mm, patient space)normals—(N, 3)float64 per-vertex normals, orNonetriangles—(M, 3)uint32 vertex indices (0-based; triangle / strip / fan / facet primitives are all expanded into this single list)lines/vertices—(L, 2)/(V,)uint32 indices for edge / line / vertex primitives (present only when the surface uses them)
Each segment carries number, label, algorithm_type, coded
property_category / property_type, the surface-generation
algorithm_family / algorithm_name / algorithm_version, and the
referenced_surface_numbers linking it to its surface(s).
Tractography results¶
pydcm.tract ¶
pydcm — DICOM Tractography Results authoring (pydcm.tract).
- :func:
write_mktract— author a Tractography Results Storage (SOP 1.2.840.10008.5.1.4.1.1.66.6) from track sets of polylines (streamlines) in Frame-of-Reference world coordinates, via the nativedcm_tract_exportengine. The write counterpart of pydcm's tractography reader and the natural sink fordipy/MRtrixstreamlines.
write_mktract ¶
Author a DICOM Tractography Results Storage from track sets.
source-series path / list of instance paths (demographics + Frame
of Reference UID), or None to mint fresh identifiers. Track point
coordinates are in that Frame of Reference (patient world mm).
track_sets: a single track-set dict, or a list of them. Each::
{
"label": str, "description": str, "algorithm_name": str,
"anatomy": coded concept, # default SCT 389080008 "White Matter"
"diffusion": coded concept, # default DCM 113231 "Single Tensor"
"line_thickness": float, "rgb": (r, g, b),
"tracks": [ (n_i, 3) array of xyz, ... ], # streamlines, world mm
# optional per-track measurements + statistics (e.g. FA / ADC):
"measurements": [
{"concept": code, "units": code, # what is measured + units
"values": [ arr_track0, arr_track1, … ]} # one value array per track
],
"track_statistics": [
{"concept": code, "modifier": code, "units": code,
"values": arr} # one scalar per track
],
"set_statistics": [
{"concept": code, "modifier": code, "units": code, "value": float}
],
}
Coded concepts accept ``(value, scheme, meaning)``, ``(value, meaning)``,
a plain meaning string, a dict, or ``None``. A measurement's ``concept``
is required; ``units`` defaults to unitless; ``modifier`` is optional.
output: write the file there and return None; if omitted, return the
Part-10 bytes.
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, Waveform, Ophthalmic Visual Field, or Structured Report (the full content tree) — over the shared native content engine. One unified reader that auto-detects the SOP class, organized by operation rather than object; the interpreted counterpart to the raw element model.
content ¶
Semantic content of a structured DICOM object — Segmentation, RT Structure
Set, RT Plan, RT Dose, Presentation State, Waveform, Ophthalmic Visual Field,
Surface Segmentation, or Structured Report (the full content tree) — 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.
meshes: Surface Segmentation only — include each surface's full points /
normals / triangles arrays instead of just counts (use pydcm.read_surface
for arrays as NumPy).
RT dosimetry & structures¶
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) 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.
ComputedDVH ¶
A DVH computed from RTSTRUCT + RTDOSE.
Attributes:
| Name | Type | Description |
|---|---|---|
counts |
differential histogram, cm³ per 1-cGy bin (float64 ndarray, trailing zeros trimmed). |
|
cumulative |
suffix-sum of |
|
bins |
bin edges in Gy ( |
|
volume |
structure volume in cm³; min/max/mean: dose statistics in Gy, the standard cumulative-DVH dose metrics. |
|
notes |
dose-grid coverage notes ('' when the grid covers the structure). |
DoseGrid ¶
A scaled RT Dose grid.
Attributes:
| Name | Type | Description |
|---|---|---|
dose |
|
|
dose_grid_scaling |
the scale that was APPLIED — equals the file's
DoseGridScaling, or 1.0 when that tag is absent/zero (malformed),
so |
|
max_dose |
grid maximum, computed in double precision — may differ from
|
|
affine |
4×4 voxel→world (LPS) matrix, column-major flat list — same
convention as :func: |
|
spacing |
|
|
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 |
|
dvhs |
list of stored DVH curves (dicts with |
read_rtdose ¶
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.
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.
write_rtstruct ¶
Author a DICOM RT Structure Set from ROI contours over a reference series.
the source-series instance paths (a path, a directory, or a list) — geometry,
demographics and per-contour source-image references are taken from it.
rois: a list of ROI dicts, each with name, optional rgb (r, g, b), optional
interpreted_type (RT ROI Interpreted Type code: 0=UNKNOWN, 4=ORGAN, …), and
contours: a list of (n, 3) float64 arrays of LPS patient-mm points — one planar
contour each, matched to its source slice by position along the slice normal.
label: Structure Set Label. output: write there and return None, else return Part-10 bytes.
dvhcalc ¶
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 follow the standard cumulative-DVH definition
(base path: no in-plane interpolation / structure-extents options). limit caps the histogram in cGy.
Rendering & overlays¶
Render a single frame to 8-bit RGB and burn presentation markup (GSPS graphics, SR SCOORD regions, SEG masks, RTSTRUCT contours) onto it — the frame an agent "sees".
pydcm.overlay ¶
pydcm — render a DICOM frame and burn structured markup onto it (pydcm.overlay).
The headless, agent-vision rendering path: render one frame to an 8-bit display image (window/level), then burn a presentation state's graphic annotations (GSPS) and/or a structured report's SCOORD measurement regions (SR) onto the pixels — so a vision model SEES the radiologist's markup. Only markup that references the rendered image is drawn. All compute is native; this is a thin wrapper.
render_overlay ¶
Render image (one frame) to 8-bit RGB and burn overlays' markup onto it.
image: the DICOM image — path | bytes | Dataset.
overlays: a GSPS/SR object or an iterable of them (path | bytes | Dataset).
Each is auto-detected; only markup referencing image's SOP Instance
is drawn (GSPS annotations in their layer colour, SR SCOORDs in green).
frame: 1-based frame number (default 1).
window: (center, width) window/level, or None for the per-frame default.
max_dim: aspect-preserving downscale so the largest side fits (0 = native).
Ignored when overlays are given — markup is in native pixels, so the
frame is rendered at native resolution to keep it aligned.
with_overlays: also return a structured description of the markup. The shapes
(and the image's own 60xx overlay planes) are projected to pixel space with
UTF-8 text / measurement values / labels — the "image + overlay JSON" form,
so values/text need no in-pixel font.
The result is a numpy.ndarray [H, W, 3] uint8 (RGB) — or, with
with_overlays=True, a tuple (ndarray, overlays) where overlays is a
list of dicts (source, kind, points, color, and optional
text / label / value / unit / number / filled).
Whole-slide imaging¶
pydcm.wsi ¶
Whole-slide imaging (DICOM VL Whole Slide Microscopy) reader — an 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
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 (subset).
level_dimensions
property
¶
((cols, rows), ...) per level, largest (level 0) first.
level_frame_counts
property
¶
Stored frame count per level in source instance frame order.
properties
property
¶
and DICOM-derived slide metadata as string properties.
associated_image_names
property
¶
Names of associated non-pyramid images, e.g. label or overview.
icc_profile
property
¶
Raw DICOM ICC Profile bytes for the base pyramid level, if present.
icc_transform_available
property
¶
Whether this build can apply WSI ICC profiles to sRGB via LCMS2.
tile_cache_capacity
property
¶
Decoded tile cache capacity in bytes. 0 disables retaining decoded tiles.
level_descriptor ¶
Viewer-oriented metadata for one level without decoding any tile.
viewer_level ¶
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 ¶
Return viewer-oriented descriptors for all pyramid levels.
level_concatenation ¶
DICOM Concatenation metadata for a pyramid level, if present.
associated_image_dimensions ¶
(cols, rows) for an associated image, or (0, 0) if absent.
read_associated_image ¶
Return an associated image as (rows, cols, 4) RGBA or (rows, cols, 3) RGB.
level_icc_profile ¶
Raw DICOM ICC Profile bytes for one pyramid level, if present.
associated_image_icc_profile ¶
Raw DICOM ICC Profile bytes for an associated image, if present.
get_best_level_for_downsample ¶
Return the best pyramid level for downsample.
set_tile_cache_capacity ¶
Set decoded tile cache capacity in bytes for this slide.
read_region ¶
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 ¶
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 ¶
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 ¶
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 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 ¶
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 ¶
File-backed source path(s) for a level; memory-backed slides return ().
level_tile_ranges ¶
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 ¶
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 ¶
Return one encoded tile range or None for an absent/out-of-grid tile.
level_frame_range ¶
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
)
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
)
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
)
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
)
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
)
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 ¶
Return true when zero-based tile = (tile_x, tile_y) has stored pixel data.
get_thumbnail ¶
A downscaled RGB overview of the whole slide fitting within size = (w, h).
open_slide ¶
Open a slide from a directory of its .dcm instances, a list of paths, or a
single multi-level file.
open_slides ¶
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.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 — 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 aRawArray.
multiplex_array ¶
The (samples × channels) array of multiplex group index (PS3.3 C.10.9).
With as_raw=False the per-channel Sensitivity / baseline correction from
ChannelDefinitionSequence is applied (real-world units).
generate_multiplex ¶
Yield each multiplex group's array.
read_waveform ¶
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 ¶
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 — 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 ¶
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.
start_retrieve ¶
start_retrieve(
server,
study_uid,
series_uid="",
*,
store_dir,
base_path="",
transfer_syntax="",
auth=""
)
Start a BACKGROUND WADO-RS retrieve on an engine-owned C++ thread that streams each instance
of the study (or one series_uid) to store_dir as a Part-10 file. No Python thread drives
it, so it progresses even while the host's main thread holds the GIL — the basis for a truly
non-blocking background retrieve (the WADO twin of pydcm.dimse.AE.start_get_to_dir). Returns a
handle: poll handle.done (bool), then read handle.result() (dict: done/ok/count/error).
Drop the handle only once done (its destructor joins the worker).
transfer_syntax sets the WADO-RS transfer-syntax Accept parameter: empty = the server's
default (PS3.18: Explicit VR Little Endian, possibly transcoded), "*" = the stored encoding
verbatim (no transcoding), or a TS UID for a specific encoding.
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_instance_wado_uri ¶
retrieve_instance_wado_uri(
server,
study_uid,
series_uid,
instance_uid,
*,
base_path="/wado",
content_type="application/dicom",
transfer_syntax="",
frame_number=0,
rows=0,
columns=0,
image_quality=0,
anonymize=False,
auth=""
) -> bytes
WADO-URI: retrieve one object via the classic query-parameter GET (PS3.18 §9) → bytes.
WADO-URI predates WADO-RS and is the only retrieval protocol on many older PACS; for modern
servers prefer :func:retrieve_instance (WADO-RS), which this mirrors. Returns the object
bytes — a Part-10 instance when content_type="application/dicom" (the default), or the
server-rendered image bytes for an image MIME (e.g. "image/jpeg"), in which case
rows/columns/image_quality apply.
base_path is the WADO-URI endpoint (default "/wado" — a standalone endpoint, not
under the DICOMweb /dicom-web root); frame_number is 1-based (0 = unset);
transfer_syntax optionally negotiates the wire encoding; anonymize=True asks the
server for a de-identified object.
retrieve_study_metadata ¶
WADO-RS: study metadata → list of per-instance DICOM-JSON records (no pixel data).
retrieve_series_metadata ¶
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 ¶
WADO-RS: yield each Part-10 instance of a study as bytes (memory-efficient stream).
iter_series ¶
WADO-RS: yield each Part-10 instance of a series as bytes (memory-efficient stream).
retrieve_bulkdata ¶
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 ¶
DICOMweb DELETE a whole study → HTTP status (200/204). Server must support the (non-core) delete transaction.
delete_series ¶
DICOMweb DELETE a series → HTTP status.
delete_instance ¶
DICOMweb DELETE a single instance → HTTP status.
store_instances ¶
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).
create_workitem ¶
UPS-RS create (POST /workitems). workitem is a Dataset/dict of attributes;
pass workitem_uid to propose the SOP Instance UID, else the server assigns one
(returned in the result's location). Returns {status, location, body}.
search_workitems ¶
search_workitems(
server,
*,
base_path="",
matches=None,
includefields=None,
limit=0,
offset=0,
auth=""
)
UPS-RS search (GET /workitems) → list of DICOM-JSON workitem records. [] on 204.
retrieve_workitem ¶
UPS-RS retrieve (GET /workitems/{w}) → the workitem as DICOM JSON (dict).
update_workitem ¶
UPS-RS update (POST /workitems/{w}?transaction=…). changes = Dataset/dict of
attributes to merge; transaction_uid is the one obtained when the workitem was claimed.
change_workitem_state ¶
UPS-RS change state (PUT /workitems/{w}/state). state is the CS value — e.g.
"IN PROGRESS" / "COMPLETED" / "CANCELED" (use pydcm.dimse.UPS.IN_PROGRESS etc.);
the body is built natively from (state, transaction_uid).
request_cancel_workitem ¶
UPS-RS cancel request (POST /workitems/{w}/cancelrequest). reason = Dataset/dict/None.
subscribe_workitem ¶
UPS-RS subscribe (POST /workitems/{w}/subscribers/{ae}). Pass the global subscription
instance UID as workitem_uid to watch all workitems.
unsubscribe_workitem ¶
UPS-RS unsubscribe (DELETE /workitems/{w}/subscribers/{ae}).
DIMSE¶
pydcm.dimse ¶
DIMSE networking (pydcm.dimse).
A thin, same-interface layer over pydcm's native C++ DIMSE engine, via the
pydcm._dimse binding.
Ported code works after one alias::
import pydcm.dimse as pynetdicom # the drop-in for pynetdicom
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
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/ciphers/check_hostname).
ciphers takes "bcp195" for the curated RFC 9325 AEAD+PFS allowlist, "" for the provider
default, or a raw OpenSSL cipher string; the server side also wins cipher selection. SCU TLS always
verifies the certificate chain (CA trust) when verify_peer is on; hostname/SAN matching is done
only when server_name is set (or check_hostname=True) — the pynetdicom idiom, since DICOM
peers are dialled by IP/AE, not DNS. verify_peer=False drops all verification (dev only).
AE ¶
An Application Entity over the native DIMSE engine.
active_associations
property
¶
The associations opened by this AE that are still established.
remove_requested_context ¶
Remove a requested presentation context.
remove_supported_context ¶
Remove a supported presentation context.
make_server ¶
Return a non-blocking SCP server handle; call
.serve_forever() / .shutdown() on it. == start_server(block=False).
get_to_dir ¶
get_to_dir(
addr,
port,
dataset,
query_model,
store_dir,
ae_title="ANY-SCP",
tls_args=None,
accept_storage_classes=None,
)
Native C-GET that writes each matched instance straight to store_dir as a Part-10
file IN C++ (via the shared store_sink) — no per-instance Python callback, so the GIL is
released for the WHOLE transfer. Safe to call from a background/worker thread without
starving the host's event loop (unlike send_c_get, whose EVT_C_STORE handler re-acquires
the GIL per instance). One-shot helper: it opens a persistent association negotiating the
storage SCP role (so the SCP can push instances back), retrieves, then releases. Returns the
result dict (status / completed / failed / received / remaining).
start_get_to_dir ¶
start_get_to_dir(
addr,
port,
dataset,
query_model,
store_dir,
ae_title="ANY-SCP",
tls_args=None,
accept_storage_classes=None,
)
Start a NATIVE C-GET on an engine-owned C++ thread (std::jthread) that writes each matched
instance to store_dir via the shared store_sink. No Python thread drives it, so it keeps
running even while the host's main thread holds the GIL (idle in a Qt/event loop) — the basis
for a truly non-blocking background retrieve. Returns a handle: poll handle.done (a bool
property), then read handle.result() (a dict with status / completed / failed / received /
error). Drop the handle only once done (its destructor joins the worker).
start_server ¶
store_dir ⇒ native store-to-dir listener: received instances are
wrapped + written to that directory entirely in C++ (no per-instance
Python C-STORE callback, no GIL), the path that holds up under burst.
When set, any EVT_C_STORE handler is bypassed (the native sink wins).
Association ¶
A pydcm DIMSE association, persistent: 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).
accepted_contexts
property
¶
Presentation contexts the peer accepted — each carries the
negotiated transfer_syntax. Built from the proposals + the engine's per-context
negotiation result.
bind ¶
Bind a handler to a lifecycle event (EVT_ESTABLISHED/RELEASED/ABORTED) on this association.
send_c_cancel ¶
Accepted for source 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 ¶
C-GET. Matched instances are delivered to the EVT_C_STORE handler registered on this AE. 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.
store_dir ⇒ NATIVE retrieve-to-dir: the engine writes each matched instance to that
directory in C++ (shared store_sink), with NO per-instance Python callback, so the GIL is
released for the whole transfer — safe from a background thread. Requires the persistent
path (call add_supported_context() for the storage classes first).
ups_push ¶
UPS Push: create a workitem (N-CREATE). instance_uid proposes the
new SOP Instance UID, else the SCP mints one.
ups_get ¶
UPS Pull: read a workitem (N-GET); attributes empty => whole workitem.
ups_set ¶
UPS Pull: merge a modification list into a claimed workitem (N-SET). Stamps the claiming Transaction UID.
ups_change_state ¶
UPS Pull: change Procedure Step State (N-ACTION type 1).
ups_claim ¶
Claim a SCHEDULED workitem (-> IN PROGRESS). Mints a Transaction UID if
not given; returns (status, transaction_uid).
ups_complete ¶
Transition a claimed workitem to COMPLETED.
ups_cancel ¶
Transition a claimed workitem to CANCELED (owner-side; use
:meth:ups_request_cancel to ask another performer to cancel).
ups_request_cancel ¶
UPS Pull: request cancellation of a workitem you don't own (N-ACTION type 2); optional (0074,1238) Reason For Cancellation.
ups_subscribe ¶
UPS Watch: subscribe to event reports (N-ACTION type 3). target
defaults to the global subscription instance (watch all workitems).
ups_unsubscribe ¶
UPS Watch: cancel a subscription (N-ACTION type 4).
mpps_create ¶
MPPS N-CREATE — start a Performed Procedure Step (the dataset should
carry PerformedProcedureStepStatus == MPPS.IN_PROGRESS plus the
Scheduled/Performed step attributes). Mints a SOP Instance UID when not
given; returns (status, sop_instance_uid).
mpps_set ¶
MPPS N-SET — update a step (typically transition PerformedProcedureStepStatus to COMPLETED / DISCONTINUED plus the Performed Series Sequence, end date/time).
mpps_complete ¶
MPPS N-SET transitioning the step to COMPLETED (stamps the status;
merge any final Performed* attributes via modifications).
mpps_discontinue ¶
MPPS N-SET transitioning the step to DISCONTINUED (operator cancelled the exam mid-procedure).
request_storage_commitment ¶
Storage Commitment N-ACTION (Request Storage). referenced_sop_instances
is an iterable of (sop_class_uid, sop_instance_uid) pairs to commit.
transaction_uid (0008,1195) is PS3.4 Table J.3-1 Type 1 — minted here
if not given. Returns (status, transaction_uid): keep the Transaction
UID to correlate the commitment RESULT, which arrives asynchronously as an
N-EVENT-REPORT (EVENT_SUCCESS / EVENT_FAILURES).
notify_instances_available ¶
notify_instances_available(
study_uid,
instances,
*,
retrieve_ae=None,
availability=None,
instance_uid=None
)
IAN N-CREATE (PS3.4 §R): announce that instances of study_uid are
available. instances is an iterable of (series_uid, sop_class_uid,
sop_instance_uid) tuples (grouped by series here). availability is an
IAN.* value (default IAN.ONLINE); retrieve_ae is the (0008,0054)
Retrieve AE Title applied to every instance. Mints the IAN SOP Instance UID
if not given. Returns (status, sop_instance_uid). For per-instance
availability/retrieve, build the Attribute List Dataset yourself and call
:meth:send_n_create on IAN.SOP_CLASS.
alias_presentation_context ¶
Route N-services for sop_class_uid over the presentation context
negotiated for negotiated_as (PS3.4 §H Meta SOP-class alias).
Call after :meth:associate when you negotiated the §H Grayscale or
Color Print Management Meta SOP class, then alias each member class
(Film Session, Film Box, Image Box) onto the Meta so the engine selects
the right presentation context for every N-* command::
assoc.alias_presentation_context(Print.FILM_SESSION, Print.GRAYSCALE_META)
assoc.alias_presentation_context(Print.FILM_BOX, Print.GRAYSCALE_META)
assoc.alias_presentation_context(Print.GRAYSCALE_IMAGE_BOX, Print.GRAYSCALE_META)
No-op if the association is not established.
print_film_session ¶
§H Film Session + Film Box N-CREATE workflow (steps 1–2).
Opens a Film Session, creates a Film Box with the given
image_display_format, and returns
(status, film_session_uid, film_box_uid, image_box_uid) — the
Image Box UID is what you pass to :meth:print_set_image_box.
Returns (status, None, None, None) on first N-CREATE failure.
print_set_image_box ¶
§H Image Box N-SET (step 2b): load rendered pixel data into the Image Box the SCP assigned.
pixel_bytes must be raw 8-bit display pixels in row-major order:
rows × cols × samples_per_pixel bytes, where samples_per_pixel
is 1 for MONOCHROME2 (grayscale) or 3 for RGB (color, interleaved).
Uses :func:build_image_box_mods to build the validated wire
bytes so the caller doesn't need to know the DICOM attribute layout.
print_action ¶
§H N-ACTION Print (step 3): ask the SCP to print the Film Box.
print_cleanup ¶
§H N-DELETE cleanup (step 4): best-effort; ignores failures.
Ophthalmic visual field¶
pydcm.opv ¶
Ophthalmic Visual Field static perimetry (PS3.3 Supplement 146, SOP Class
1.2.840.10008.5.1.4.1.1.80.1).
Manage OPV DICOM: parse a study, flatten it to pandas / JSON, and check standard conformance.
No DICOM logic lives here. The semantic extraction is the native content engine (exposed as
:func:pydcm.content); compliance is the native IOD/module conformance judge
(exposed as :func:pydcm.iod_validate). This module is only the pandas/JSON
ergonomics on top.
Example¶
import pydcm vf = pydcm.opv.read_dicom("vf.dcm") vf.pointwise_to_pandas() # one row per stimulus location vf.to_pandas() # one row of study-level fields vf.check_dicom_compliance() # Supplement 146 IOD Type-1/2 findings opvset, errors = pydcm.opv.read_dicom_directory("study_dir/")
OPVDicom ¶
A single Ophthalmic Visual Field DICOM file.
pointwise_to_pandas ¶
One row per test point, each tagged with the study identifiers.
pointwise_to_nested_json ¶
Study identifiers + the nested per-point records (JSON-ready dict).
check_dicom_compliance ¶
Sup-146 IOD / module conformance findings (list of dicts).
Reuses the native IOD conformance judge — every mandatory module's Type-1 / Type-2 attribute must be present (Type-1 also non-empty), descending into present sequences. An empty list means conformant at the IOD level. Stricter than a flat tag checklist: it is per-SOP-Class and nested-sequence aware.
OPVDicomSet ¶
A batch of :class:OPVDicom files with set-level aggregation.
read_dicom_directory ¶
Load every *.<file_extension> OPV file in a directory.
Returns (OPVDicomSet, errors) where errors is a list of
(path, message) for files that were not OPV objects or failed to parse.
read_visual_field ¶
The native semantic content (nested dict) of an OPV file, or None.
The low-level reader behind :class:OPVDicom; mirrors pydcm.read_seg /
pydcm.read_report in returning the raw structured content.
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 ¶
Build a FHIR R4 ImagingStudy (as a dict) from one DICOM instance, or a whole
study of many instances.
source may be:
- raw Part-10
bytes— a single instance; - a path to a
.dcmfile — a single instance; - a path to a directory — every readable instance under it is aggregated into one
ImagingStudy, grouped by
SeriesInstanceUID(the multi-series / multi-instance study form a FHIR consumer actually expects); - an iterable of paths /
bytes— likewise aggregated.
Study/series/instance identifiers, the modality set, and patient demographics are mapped
into the FHIR ImagingStudy with a contained Patient (subject.reference = "#patient-…").
Raises if no readable instance is found or StudyInstanceUID is absent.
HL7 v2¶
pydcm.hl7 ¶
HL7 v2 — parse messages (MSH-12 2.3 – 2.8) + 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 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 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 native agent engine. A self-contained agent surface: it dispatches in-process to Python and covers pydcm end to end — analysis (radiomics / DVH / validation), perfusion (DCE), volume / 4-D / DWI assembly, WSI, waveforms, the structured-content reader, authoring, de-identification, and DIMSE / DICOMweb networking.
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 (included in builds with agent support).
serve ¶
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.
Types — Dataset, FileDataset, FileMetaDataset, DataElement,
Sequence, PersonName, Tag, BaseTag, MultiValue, DSfloat, IS,
UID, InvalidDicomError.
Submodules — charset, 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.