pydicom
How to compress Pixel Data
Pixel Data can be compressed natively using pydicom for the following transfer syntaxes:
+------------------------------------------------+------------------+-----------------------------+ | Transfer Syntax | Plugin names | Dependencies | +-----------------------+------------------------+ | | | Name | UID | | | +=======================+========================+==================+=============================+ | JPEG-LS Lossless | 1.2.840.10008.1.2.4.80 | pyjpegls | numpy, | +-----------------------+------------------------+ | pyjpegls | | JPEG-LS Near Lossless | 1.2.840.10008.1.2.4.81 | | | +-----------------------+------------------------+------------------+-----------------------------+ | JPEG 2000 Lossless | 1.2.840.10008.1.2.4.90 | pylibjpeg | numpy, | +-----------------------+------------------------+ | pylibjpeg, | | JPEG 2000 | 1.2.840.10008.1.2.4.91 | | pylibjpeg-openjpeg | +-----------------------+------------------------+------------------+-----------------------------+ | RLE Lossless | 1.2.840.10008.1.2.5 | pydicom 1 | | | | +------------------+-----------------------------+ | | | pylibjpeg | numpy, | | | | | pylibjpeg, | | | | | pylibjpeg-rle | | | +------------------+-----------------------------+ | | | gdcm | gdcm | +-----------------------+------------------------+------------------+-----------------------------+
Each of the supported transfer syntaxes has a corresponding encoding guide to help you with the specific requirements of the encoding method.
+-------------------------+-----------------------------------------------------+ | Transfer Syntax | Encoding guide | +=========================+=====================================================+ | JPEG-LS Lossless | JPEG-LS Encoding</guides/encoding/jpeg_ls>
| +-------------------------+ | | JPEG-LS Near Lossless | | +-------------------------+-----------------------------------------------------+ | JPEG 2000 Lossless | JPEG 2000 Encoding</guides/encoding/jpeg_2k>
| +-------------------------+ | | JPEG 2000 | | +-------------------------+-----------------------------------------------------+ | RLE Lossless | RLE Encoding</guides/encoding/rle_lossless>
| +-------------------------+-----------------------------------------------------+
The Dataset.compress()<pydicom.dataset.Dataset.compress>
method can be used to compress an uncompressed dataset in-place:
from pydicom import examples
from pydicom.uid import RLELossless
ds = examples.ct
ds.compress(RLELossless)
ds.save_as("ct_rle_lossless.dcm")
A specific encoding plugin can be used by passing the plugin name via the encoding_plugin argument:
# Will set `ds.is_little_endian` and `ds.is_implicit_VR` automatically
ds.compress(RLELossless, encoding_plugin='pylibjpeg')
ds.save_as("ct_rle_lossless.dcm")
Implicitly changing the compression on an already compressed dataset is not currently supported, however it can still be done explicitly by decompressing prior to calling ~pydicom.dataset.Dataset.compress
. In the example below, a matching image data handler<image_data_handlers>
for the original transfer syntax - JPEG 2000 Lossless - is required.
# Requires a JPEG 2000 compatible image data handler
ds = examples.jpeg2k
arr = ds.pixel_array
ds.PhotometricInterpretation = 'RGB'
ds.compress(RLELossless, arr)
ds.save_as("US1_RLE.dcm")
Note that the Photometric Interpretation in this case has been changed from 'YBR_RCT'
, which is the value for when it's J2K compressed, to 'RGB'
which is the correct value for this particular dataset once the Pixel Data is RLE compressed. It's up to you to ensure that the the correct Photometric Interpretation has been set and that the decompressed pixel data is in the correct color space prior to actually calling ~pydicom.dataset.Dataset.compress
.
If you need to perform pixel data compression using an encoding method not supported by pydicom - such as ISO/IEC 10918-1 JPEG
<part05/sect_8.2.html#sect_8.2.1>
- then you'll need to find a third-party package or application to do so. Once you've done that you have to follow the requirements for compressed Pixel Data in the DICOM Standard:
- Each frame of pixel data must be encoded separately
- All the encoded frames must then be
encapsulated <pydicom.encaps.encapsulate>
using a basic offset table. When the amount of encoded data is too large for the basic offset table then the use of theextended offset table <pydicom.encaps.encapsulate_extended>
is recommended. - A dataset with encapsulated pixel data must use explicit VR little endian encoding
See the relevant sections of the DICOM Standard<part05/sect_8.2.html>
for more information.
from typing import List, Tuple
from pydicom import examples
from pydicom.encaps import encapsulate, encapsulate_extended
from pydicom.uid import JPEGBaseline8Bit
# Fetch an example dataset
ds = examples.ct
# Use third-party package to compress
# Let's assume it compresses to JPEG Baseline (lossy)
frames: List[bytes] = third_party_compression_func(...)
# Set the *Transfer Syntax UID* appropriately
ds.file_meta.TransferSyntaxUID = JPEGBaseline8Bit
# Basic encapsulation
ds.PixelData = encapsulate(frames)
# Set the element's VR and use an undefined length
ds["PixelData"].is_undefined_length = True
ds["PixelData"].VR = "OB"
# Save!
ds.save_as("ct_compressed_basic.dcm")
# Extended encapsulation
result: Tuple[bytes, bytes, bytes] = encapsulate_extended(frames)
ds.PixelData = result[0]
ds.ExtendedOffsetTable = result[1]
ds.ExtendedOffsetTableLength = result[2]
ds.save_as("ct_compressed_ext.dcm")