Back to Claude Scientific Skills

Transfer syntaxes, pixel plugins, and encapsulation

skills/pydicom/references/transfer_syntaxes.md

2.55.012.2 KB
Original Source

Transfer syntaxes, pixel plugins, and encapsulation

Transfer Syntax UID (0002,0010) identifies the encoding rules for the dataset, including VR encoding, byte order, and pixel compression. This guide targets stable pydicom 3.0.2. Always use the applicable DICOM PS3.5/PS3.6 and the deployment's conformance statements for interoperability decisions.

Inspect before decoding

python
from pydicom import dcmread

ds = dcmread(
    "authorized/image.dcm",
    stop_before_pixels=True,
    specific_tags=[
        "Rows",
        "Columns",
        "NumberOfFrames",
        "SamplesPerPixel",
        "BitsAllocated",
        "BitsStored",
        "PhotometricInterpretation",
    ],
)
ts = ds.file_meta.TransferSyntaxUID
technical = {
    "uid": str(ts),
    "name": ts.name,
    "compressed": ts.is_compressed,
    "implicit_vr": ts.is_implicit_VR,
    "little_endian": ts.is_little_endian,
}

Do not infer decoder support from the UID name. Run:

bash
python scripts/transfer_syntax_inspector.py --input authorized/image.dcm
python scripts/pixel_frame_planner.py authorized/image.dcm --frames 0

Plugin availability is not proof that a particular codestream, bit depth, color representation, or platform is handled correctly.

Native and dataset-compressed transfer syntaxes

NameUIDEncodingpydicom constant
Implicit VR Little Endian1.2.840.10008.1.2implicit VR, little endianImplicitVRLittleEndian
Explicit VR Little Endian1.2.840.10008.1.2.1explicit VR, little endianExplicitVRLittleEndian
Deflated Explicit VR Little Endian1.2.840.10008.1.2.1.99deflated datasetDeflatedExplicitVRLittleEndian
Explicit VR Big Endian1.2.840.10008.1.2.2explicit VR, big endian; retiredExplicitVRBigEndian

Explicit VR Big Endian was retired in 2006 and should not be selected for new objects. pydicom can read it, but endianness conversion when writing is not an automatic Dataset.save_as() operation.

The default DICOM network Transfer Syntax is Implicit VR Little Endian. This is not a recommendation to omit File Meta Information from files.

Encapsulated image transfer syntaxes

FamilyNameUIDLoss
JPEGJPEG Baseline 8-bit1.2.840.10008.1.2.4.50lossy
JPEGJPEG Extended 12-bit1.2.840.10008.1.2.4.51lossy
JPEGJPEG Lossless Process 141.2.840.10008.1.2.4.57lossless
JPEGJPEG Lossless Process 14 SV11.2.840.10008.1.2.4.70lossless
JPEG-LSJPEG-LS Lossless1.2.840.10008.1.2.4.80lossless
JPEG-LSJPEG-LS Near-Lossless1.2.840.10008.1.2.4.81near-lossless
JPEG 2000JPEG 2000 Lossless Only1.2.840.10008.1.2.4.90lossless
JPEG 2000JPEG 20001.2.840.10008.1.2.4.91lossless or lossy in DICOM; pydicom encoding treats it as lossy
HTJ2KHTJ2K Lossless1.2.840.10008.1.2.4.201lossless
HTJ2KHTJ2K RPCL Lossless1.2.840.10008.1.2.4.202lossless
HTJ2KHTJ2K1.2.840.10008.1.2.4.203lossy/lossless by syntax rules
RLERLE Lossless1.2.840.10008.1.2.5lossless

In pydicom 3.0, JPEGLossless is .57; use JPEGLosslessSV1 for .70.

Video, JPIP-referenced, encapsulated uncompressed, JPEG XL, and other current DICOM transfer syntaxes exist but are not all decoded by pydicom's pixel API. Consult PS3.6 and the installed get_decoder() result instead of assuming that all registered UIDs are supported.

Stable 3.0.2 decompression plugins

The stable pydicom matrix reports these main choices:

Transfer-syntax familyTypical pydicom plugin dependencies
Native/deflatedpydicom + NumPy
RLE Losslessbuilt-in pydicom; pylibjpeg-rle; GDCM
JPEG Baseline/Extendedpylibjpeg-libjpeg; GDCM; Pillow with JPEG support
JPEG Losslesspylibjpeg-libjpeg; GDCM
JPEG-LSpyjpegls; pylibjpeg-libjpeg; GDCM
JPEG 2000pylibjpeg-openjpeg; GDCM; Pillow with OpenJPEG
HTJ2Kpylibjpeg-openjpeg

Pinned reviewed installations:

bash
uv pip install "pydicom==3.0.2" "numpy==2.5.1"

uv pip install "pylibjpeg==2.1.0" \
  "pylibjpeg-libjpeg==2.4.0" \
  "pylibjpeg-openjpeg==2.5.0" \
  "pylibjpeg-rle==2.2.0"

uv pip install "pyjpegls==1.5.1"
uv pip install "Pillow==12.3.0"
uv pip install "python-gdcm==3.2.6"

Install only what is required. Review transitive/package licensing: pylibjpeg-libjpeg has different licensing from MIT pydicom.

Important stable documentation limitations include:

  • Pillow performs transformations that pydicom describes as not always reversible and is not the preferred general decoder.
  • Pillow JPEG Extended support requires 8 Bits Allocated.
  • Pillow JPEG 2000 multi-sample support is constrained by bit depth.
  • GDCM has syntax/bit-depth limits; pydicom rejects known incorrect JPEG-LS combinations for older GDCM releases.
  • pylibjpeg-openjpeg and other plugins have their own maximum bit depths.
  • pydicom's built-in RLE implementation is slower than compiled alternatives.

Never silently fall back in a validated workflow. Pin a plugin explicitly with decoding_plugin=..., record versions, and compare results against independent test vectors.

Frame-specific decoding

Stable pydicom 3.0 adds path-based APIs that can reduce memory use:

python
from pydicom.pixels import iter_pixels, pixel_array

first = pixel_array("authorized/multiframe.dcm", index=0)

for frame in iter_pixels(
    "authorized/multiframe.dcm",
    indices=[0, 2, 4],
):
    process_bounded_frame(frame)

Always calculate limits from:

  • Rows and Columns
  • Samples per Pixel
  • Bits Allocated and decoded NumPy item size
  • Number of Frames
  • expected intermediate arrays for rescale/window/color conversion

The compressed file size is not a safe proxy for decoded memory. Metadata can also disagree with the codestream.

Default decoding performs mandatory pixel unpacking and may convert YCbCr to RGB. raw=True suppresses optional color conversion, not mandatory processing such as bit unpacking.

Decoder and encoder introspection

python
from pydicom.pixels import get_decoder, get_encoder
from pydicom.uid import JPEG2000Lossless

decoder = get_decoder(JPEG2000Lossless)
decoder_report = {
    "available": decoder.is_available,
    "plugins": decoder.available_plugins,
    "missing": decoder.missing_dependencies,
}

try:
    encoder = get_encoder(JPEG2000Lossless)
except NotImplementedError:
    encoder = None

is_available means at least one implementation is importable. It does not guarantee support for every image or correctness of output.

In-place decompression behavior

python
from pydicom import dcmread

ds = dcmread("compressed.dcm")
ds.decompress(
    decoding_plugin="pylibjpeg",
    generate_instance_uid=True,
)

Dataset.decompress():

  • decodes and replaces Pixel Data in the dataset;
  • updates image-pixel metadata as needed;
  • sets Transfer Syntax UID to Explicit VR Little Endian;
  • generates a new SOP Instance UID by default;
  • may convert YCbCr to RGB by default (as_rgb=False controls this).

This is a semantic modification. Write to a new file, keep source provenance, and use enforce_file_format=True, overwrite=False.

Compression behavior

pydicom 3.0.2 directly exposes dataset compression for:

  • RLE Lossless (built-in pydicom and optional plugins)
  • JPEG-LS Lossless/Near-Lossless (pyjpegls)
  • JPEG 2000 Lossless/JPEG 2000 (pylibjpeg-openjpeg)
python
from pydicom import dcmread, dcmwrite
from pydicom.uid import RLELossless

ds = dcmread("uncompressed.dcm")
ds.compress(
    RLELossless,
    encoding_plugin="pydicom",
    generate_instance_uid=True,
)
dcmwrite("rle-derived.dcm", ds, enforce_file_format=True, overwrite=False)

Compression:

  • replaces Pixel Data with an encapsulated codestream;
  • updates Transfer Syntax UID;
  • generates a new SOP Instance UID by default;
  • requires Image Pixel attributes consistent with the encoded stream.

Lossy compression decisions and clinical acceptability are outside pydicom and PS3.5. Record method, ratio, derivation, and quality effects according to the applicable IOD/workflow.

Encapsulation rules

For encapsulated Pixel Data:

  • each frame is compressed separately;
  • frame codestreams are encapsulated into fragments;
  • Pixel Data VR is OB;
  • the dataset is explicit VR little endian at the dataset-structure level;
  • a Basic Offset Table may be empty;
  • Extended Offset Table/Lengths can locate large/multi-fragment frames.

Access existing encapsulated data:

python
from pydicom.encaps import generate_frames, get_frame

frame0 = get_frame(
    ds.PixelData,
    0,
    number_of_frames=int(ds.get("NumberOfFrames", 1)),
)

for encoded_frame in generate_frames(
    ds.PixelData,
    number_of_frames=int(ds.get("NumberOfFrames", 1)),
):
    inspect_bounded_codestream(encoded_frame)

Create encapsulated Pixel Data from externally encoded frame bytes:

python
from pydicom.encaps import encapsulate_extended

pixel_data, offsets, lengths = encapsulate_extended(encoded_frames)
ds.PixelData = pixel_data
ds.ExtendedOffsetTable = offsets
ds.ExtendedOffsetTableLengths = lengths
ds["PixelData"].VR = "OB"

Set a matching Transfer Syntax UID and consistent Image Pixel metadata. get_frame_offsets(), generate_pixel_data_frame(), and other legacy encapsulation helpers are deprecated for removal in pydicom 4; use parse_basic_offsets(), generate_fragments(), generate_fragmented_frames(), and generate_frames().

Writing and transfer-syntax conversion

pydicom 3.0 resolves encoding in this priority:

  1. File Meta Information Transfer Syntax UID
  2. explicit implicit_vr/little_endian arguments
  3. deprecated dataset encoding flags
  4. original encoding
python
from pydicom import dcmwrite

dcmwrite(
    "derived.dcm",
    ds,
    enforce_file_format=True,
    overwrite=False,
)

Changing only TransferSyntaxUID does not compress/decompress Pixel Data. Likewise, Dataset.save_as() does not automatically convert between little and big endian. Use the documented pixel and writer APIs, then validate the derived instance.

Validation checklist

  • Transfer Syntax UID is present, valid, and matches the encoded dataset.
  • SOP Class/Instance UIDs match File Meta Information.
  • Rows, Columns, Samples per Pixel, Bits Allocated/Stored, High Bit, Pixel Representation, Photometric Interpretation, Planar Configuration, and Number of Frames match the codestream.
  • Decoder/encoder plugin and version are recorded.
  • Frame count and decompressed memory are bounded before decode.
  • Lossy/lossless status and derivation attributes are correct.
  • Derived SOP Instance UID/provenance behavior is intentional.
  • Pixel values, frame order, color, signedness, modality transform, and VOI are independently verified.
  • No diagnostic or conformance conclusion is based only on pydicom success.

Sources (verified 2026-07-23)