Pixel Data Decoder Plugins¶
Note
This guide is intended for advanced users who need support for something not provided by the existing decoder plugins.
Pixel Data decoding in pydicom uses a Decoder
instance to manage plugins that perform the actual decoding work. This guide covers
the requirements for those plugins and how to add them to pydicom.
Plugin Requirements¶
Each available pixel data decoder in pydicom corresponds directly to a single DICOM Transfer Syntax UID, and is intended to provide a mechanism for converting raw encoded source data to unencoded pixel values. In order to do so, each decoder for compressed transfer syntaxes has at least one decoding plugin which performs the actual conversion.
An decoding plugin must implement three objects within the same module:
A function named
is_available
with the following signature:def is_available(uid: pydicom.uid.UID) -> bool:
Where uid is the Transfer Syntax UID for the corresponding decoder as a
UID
. If the plugin supports the uid and has its dependencies met then it should returnTrue
, otherwise it should returnFalse
.A
dict
namedDECODER_DEPENDENCIES
with the typedict[pydicom.uid.UID, tuple[str, ...]
, such as:from pydicom.uid import RLELossless, JPEG2000 DECODER_DEPENDENCIES = { RLELossless: ('numpy', 'pillow', 'imagecodecs'), JPEG2000: ('numpy', 'gdcm'), }
This will be used to provide the user with a list of dependencies required by the plugin.
A function that performs the decoding with the following function signature:
def decoder(src: bytes, runner: DecodeRunner) -> bytearray | bytes:
Where
src is a single frame’s worth of raw compressed data to be decoded.
runner is a
DecodeRunner
instance that manages the decoding process and has access to the decoding options, either directly through the class properties or indirectly with theget_option()
method.
At a minimum the following decoding options should be available:
transfer_syntax_uid
:UID
- the Transfer Syntax UID of the encoded data.rows
:int
- the number of rows of pixels in decoded data.columns
:int
- the number of columns of pixels in the decoded data.samples_per_pixel
:int
- the number of samples used per pixel, e.g. 1 for grayscale images or 3 for RGB.number_of_frames
:int
- the number of image frames contained in srcbits_allocated
:int
- the number of bits used to contain each pixel in src, should be a multiple of 8.bits_stored
:int
- the number of bits actually used by each pixel in src, e.g. 12-bit pixel data (range 0 to 4095) will be contained by 16-bits (range 0 to 65535).photometric_interpretation
:str
- the color space of the encoded data, such as'YBR_FULL'
pixel_keyword
:str
- one of"PixelData"
,"FloatPixelData"
,"DoubleFloatPixelData"
.
And conditionally:
pixel_representation
:int
- required when pixel_keyword is"PixelData"
,0
for unsigned integers,1
for signed.planar_configuration
:int
- required whensamples_per_pixel
> 1,0
for color-by-pixel,1
for color-by-plane.
If your decoder needs to signal that one of the decoding option values needs to be modified then this can be done with the
set_option()
method. This should only be done after successfully decoding the frame, as if the decoding fails changing the option value may cause issues with other decoding plugins that may also attempt to decode the same frame. It’s also important to be aware that any changes you make will also affect following frames (if any).When possible it’s recommended that the decoding function return the decoded pixel data as a
bytearray
to minimize later memory usage.
An example of the requirements of a plugin is available here.
Adding Plugins to a Decoder¶
Additional plugins can be added to an existing decoder with the
add_plugin()
method, which takes the
a unique str
plugin_label, and a tuple
of ('the import
path to the decoder function's module', 'decoder function name')
. For
example, if you’d import your decoder function my_decoder_func with
from my_package.decoders import my_decoder_func
, then you’d do the
following:
from pydicom.pixels.decoders import RLELosslessDecoder
RLELosslessDecoder.add_plugin(
'my_decoder', # the plugin's label
('my_package.decoders', 'my_decoder_func') # the import paths
)
The my_package.decoders
module must contain the encoding function and the
DECODER_DEPENDENCIES
and is_available
objects.