imageio.plugins.pydicom#

Read/Write single-file DICOM instances using pydicom.

Note

To use this plugin you need to have pydicom installed:

pip install pydicom

Backend Library: pydicom

This plugin reads and writes DICOM files using pydicom. It operates in individual files, meaning while it supports multi-frame files natively, it does not assemble data across files in a directory.

Methods#

Note

Check the respective function for supported kwargs and detailed documentation.

PydicomPlugin.read(*[, index, raw])

Read pixel data from the DICOM instance.

PydicomPlugin.iter(*[, raw])

Yield decoded frames one at a time (frame-wise decode).

PydicomPlugin.write(ndimage, *[, metadata, ...])

Write an image stack to DICOM.

PydicomPlugin.properties(*[, index, raw])

Standardized ndimage metadata from Dataset tags (no PixelData decode).

PydicomPlugin.metadata(*[, index, ...])

Read (non-data) DICOM tags.

Additional methods available inside the imopen context:

PydicomPlugin.write_frame(frame, *[, metadata])

Add the given ndimage to the frames in this image.

PydicomPlugin.lut_dense(colormap[, first_mapped])

Set the given colormap as the image's LUT.

PydicomPlugin.lut_linear(colors, indices[, ...])

Set a piecewise linear gradient as the image's LUT.

PydicomPlugin.instance_metadata

Instance-level (global) metadata of the file.

PydicomPlugin.shared_frame_metadata

Metadata in the shared functional group.

PydicomPlugin.compression

The pixel compression to use when writing.

Advanced API#

In addition to the default ImageIO v3 API this plugin exposes custom functions and attributes for writing DICOM. These are available inside the imopen context and allow fine-grained control over tags, functional groups, palettes, and compression. The callables are documented above; below is a usage example:

import imageio.v3 as iio

frames = [...]  # list of (rows, cols) arrays, e.g. uint16
with iio.imopen("out.dcm", "w", plugin="pydicom") as f:
    # global metadata
    f.instance_metadata["PatientName"] = "Anonymous"
    f.instance_metadata["Modality"] = "OT"

    # bit depth
    f.instance_metadata["BitsStored"] = 12

    # pixel compression
    f.compression = "rle"  # or "jpeg-ls", "jpeg2000-lossless", ...

    # shared FG (written once, applied to each frame)
    f.shared_frame_metadata["PixelMeasuresSequence"] = [
        {"PixelSpacing": [1.0, 1.0]}
    ]

    # write each frame
    for i, frame in enumerate(frames):
        f.write_frame(
            frame,
            # set frame-level metadata
            metadata={"FrameContentSequence": [{"FrameAcquisitionNumber": i}]},
        )

meta = iio.immeta("out.dcm", plugin="pydicom")
assert meta["PatientName"] == "Anonymous"

Frames are buffered until flush (close() / context exit).

Compression#

Compression is imopen-only. imwrite / write() always produce an uncompressed Dataset; set f.compression before close to run pydicom Dataset.compress() once at flush. Changing the value after some write_frame calls is fine — only the value at flush matters.

Accepted values are short names (resolved to transfer syntax UIDs) or a raw UID string:

  • "rle" — RLE Lossless

  • "jpeg-ls" — JPEG-LS Lossless

  • "jpeg-ls-near" — JPEG-LS Near-Lossless

  • "jpeg2000" — JPEG 2000

  • "jpeg2000-lossless" — JPEG 2000 Lossless

Availability depends on pydicom’s installed encoders (RLE is built-in; others may need extra packages). Unknown names raise immediately; unsupported UIDs fail at flush.

Example:

import imageio.v3 as iio
import numpy as np

img = np.arange(64, dtype=np.uint8).reshape(8, 8)
with iio.imopen("compressed.dcm", "w", plugin="pydicom") as f:
    f.compression = "rle"
    f.write(img)

Palette LUTs#

The plugin offers helpers to build color palettes when writing palettized DICOM images. Supported palettes are either dense or linear:

  • A dense palette maps each pixel index to a color from a lookup table.

  • A linear palette maps each pixel index via piecewise-linear interpolation between the provided colors at the provided indices.

Dense color maps use the ordinary LookupTableData tags, linear maps use SegmentedLookupTableData tags. Please ensure your reader supports these. Optional alpha is supported as a 4th LUT channel. Pixel frames must be index arrays into the LUT (not already-colored RGB).

When using a LUT, do not set BitsStored in instance_metadata.

Example using a dense palette:

import imageio.v3 as iio
import numpy as np

frame = ...  # (rows, cols) index array, with values 0..3
colormap = np.array(
    [[0, 0, 0], [255, 0, 0], [0, 255, 0], [0, 0, 255]],
    dtype=np.uint8,
)
with iio.imopen("palette_dense.dcm", "w", plugin="pydicom") as f:
    f.lut_dense(colormap)
    f.write(frame)

Example using a linear palette:

import imageio.v3 as iio

frame = ...  # (rows, cols) index array, uint8
with iio.imopen("palette_linear.dcm", "w", plugin="pydicom") as f:
    f.lut_linear(
        colors=[(0, 0, 0), (65535, 65535, 65535)],
        indices=[0, 255],
    )
    f.write(frame)

Calling either helper replaces any previous palette state and clears the other encoding’s data tags (explicit and segmented are mutually exclusive). While we offer helpers for dense and linear, you can still manually build and assign other tables directly to the respective metadata tags.

Pixel reconstruction#

By default read / iter / imread / imiter reconstruct the image. This means any LUT (lookup table), grayscale correction, or ROI (region of interest) windowing is applied before the image is returned.

Pass raw=True to skip that pipeline and get stored values (pixel_array(..., raw=True)). improps / properties take the same raw flag so reported shape and dtype match read.

Notes#

Be aware that .write() on "<bytes>" needs to flush immediately so imwrite can return encoded bytes. This may cause surprising behavior when used inside an explicit imopen context.