skills/pydicom/references/transfer_syntaxes.md
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.
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:
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.
| Name | UID | Encoding | pydicom constant |
|---|---|---|---|
| Implicit VR Little Endian | 1.2.840.10008.1.2 | implicit VR, little endian | ImplicitVRLittleEndian |
| Explicit VR Little Endian | 1.2.840.10008.1.2.1 | explicit VR, little endian | ExplicitVRLittleEndian |
| Deflated Explicit VR Little Endian | 1.2.840.10008.1.2.1.99 | deflated dataset | DeflatedExplicitVRLittleEndian |
| Explicit VR Big Endian | 1.2.840.10008.1.2.2 | explicit VR, big endian; retired | ExplicitVRBigEndian |
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.
| Family | Name | UID | Loss |
|---|---|---|---|
| JPEG | JPEG Baseline 8-bit | 1.2.840.10008.1.2.4.50 | lossy |
| JPEG | JPEG Extended 12-bit | 1.2.840.10008.1.2.4.51 | lossy |
| JPEG | JPEG Lossless Process 14 | 1.2.840.10008.1.2.4.57 | lossless |
| JPEG | JPEG Lossless Process 14 SV1 | 1.2.840.10008.1.2.4.70 | lossless |
| JPEG-LS | JPEG-LS Lossless | 1.2.840.10008.1.2.4.80 | lossless |
| JPEG-LS | JPEG-LS Near-Lossless | 1.2.840.10008.1.2.4.81 | near-lossless |
| JPEG 2000 | JPEG 2000 Lossless Only | 1.2.840.10008.1.2.4.90 | lossless |
| JPEG 2000 | JPEG 2000 | 1.2.840.10008.1.2.4.91 | lossless or lossy in DICOM; pydicom encoding treats it as lossy |
| HTJ2K | HTJ2K Lossless | 1.2.840.10008.1.2.4.201 | lossless |
| HTJ2K | HTJ2K RPCL Lossless | 1.2.840.10008.1.2.4.202 | lossless |
| HTJ2K | HTJ2K | 1.2.840.10008.1.2.4.203 | lossy/lossless by syntax rules |
| RLE | RLE Lossless | 1.2.840.10008.1.2.5 | lossless |
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.
The stable pydicom matrix reports these main choices:
| Transfer-syntax family | Typical pydicom plugin dependencies |
|---|---|
| Native/deflated | pydicom + NumPy |
| RLE Lossless | built-in pydicom; pylibjpeg-rle; GDCM |
| JPEG Baseline/Extended | pylibjpeg-libjpeg; GDCM; Pillow with JPEG support |
| JPEG Lossless | pylibjpeg-libjpeg; GDCM |
| JPEG-LS | pyjpegls; pylibjpeg-libjpeg; GDCM |
| JPEG 2000 | pylibjpeg-openjpeg; GDCM; Pillow with OpenJPEG |
| HTJ2K | pylibjpeg-openjpeg |
Pinned reviewed installations:
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:
pylibjpeg-openjpeg and other plugins have their own maximum bit depths.Never silently fall back in a validated workflow. Pin a plugin explicitly with
decoding_plugin=..., record versions, and compare results against independent
test vectors.
Stable pydicom 3.0 adds path-based APIs that can reduce memory use:
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:
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.
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.
from pydicom import dcmread
ds = dcmread("compressed.dcm")
ds.decompress(
decoding_plugin="pylibjpeg",
generate_instance_uid=True,
)
Dataset.decompress():
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.
pydicom 3.0.2 directly exposes dataset compression for:
pyjpegls)pylibjpeg-openjpeg)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:
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.
For encapsulated Pixel Data:
OB;Access existing encapsulated data:
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:
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().
pydicom 3.0 resolves encoding in this priority:
implicit_vr/little_endian argumentsfrom 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.