JPEG-LS Encoding¶
The requirements for JPEG-LS encoding are defined in Section 8.2.3 and Annex A.4.3 of Part 5 of the DICOM Standard. The JPEG-LS compression scheme is defined by ISO/IEC 14495-1/ITU T.87 (the second link has free access).
Valid Image Pixel Parameters¶
The table below lists the valid Image Pixel module parameters for Pixel Data encoded using the JPEG-LS Lossless or JPEG-LS Near-lossless transfer syntaxes. For an explanation of each parameter and its relationship with the Pixel Data see the glossary of Image Pixel elements.
Samples per Pixel |
Photometric Interpretation |
Pixel Representation |
Planar Configuration |
Bits Allocated |
Bits Stored |
---|---|---|---|---|---|
1 |
MONOCHROME1 |
0 or 1 |
(absent) |
8 or 16 |
2 to 16 |
MONOCHROME2 |
|||||
PALETTE COLOR 1 |
0 |
||||
3 |
RGB |
0 |
0 |
8 or 16 |
2 to 16 |
YBR_FULL |
8 |
2 to 8 |
Pixel Representation¶
The DICOM Standard allows the use of the JPEG-LS Near Lossless transfer
syntax with signed pixel data as long as the Photometric Interpretation
is MONOCHROME1
or MONOCHROME2
. In practice, however, this is complicated
by the way lossy JPEG-LS encoding works:
JPEG-LS does not track the signedness of the pixel data, so all data is assumed to be unsigned during compression
JPEG-LS uses the specified absolute pixel value error as the constraint when performing lossy encoding (the NEAR parameter - in pydicom this is the jls_error parameter passed to the
encoding functions
)
Because of this, even though a NEAR value of 1
should limit the absolute
pixel value error to 1 intensity unit, it’s possible to have pixels with an
absolute error up to the sample bit-depth of the data:
Raw 8-bit value:
-128: 0b10000000 (as signed integer)
128: 0b10000000 (as unsigned integer)
Possible value after lossy encoding with a NEAR value of 1:
127: 0b01111111 (as signed/unsigned integer)
Total error as unsigned: 1 - OK
Total error as signed: 255 - Very much not OK
Signed pixel data values should therefore be limited to the range (MIN + NEAR,
MAX - NEAR), where MIN and MAX are the minimum and maximum values allowed for
the given Bits Stored value: -2**(Bits Stored - 1)
and
2**(Bits Stored - 1) - 1
. For example, when performing lossy encoding of
8-bit signed data and a NEAR value of 3 you should limit the pixel data values
to the range (-128 + 3, 127 - 3).
Lossless JPEG-LS encoding has no such restriction and the full value range for the given Bits Stored can be used with both signed and unsigned pixel data.
Photometric Interpretation¶
To ensure you have the correct Photometric Interpretation the uncompressed pixel data should already be in the corresponding color space:
If your uncompressed pixel data is grayscale (intensity) based:
Use
MONOCHROME1
if the minimum intensity value should be displayed as white.Use
MONOCHROME2
if the minimum intensity value should be displayed as black.
If your uncompressed pixel data uses a single sample per pixel and is an index to the Red, Green and Blue Palette Color Lookup Tables:
Use
PALETTE COLOR
.
If your uncompressed pixel data is in RGB color space:
For Photometric Interpretation
RGB
nothing else is required.For Photometric Interpretation
YBR_FULL
For Bits Allocated and Bits Stored less than or equal to 8: pixel data must be
converted into YCbCr color space
. However you should keep in mind that the conversion operation is lossy.For Bits Allocated and Bits Stored between 9 and 16 (inclusive): pixel data should be downscaled to 8-bit (with Bits Stored, Bits Allocated and High Bit updated accordingly) and converted to YCbCr color space. Both of these operations are lossy.
If your uncompressed pixel data is in YCbCr color space:
For Photometric Interpretation
RGB
the pixel data must first beconverted into RGB color space
. However the conversion operation is lossy.For Photometric Interpretation
YBR_FULL
nothing else is required.
Planar Configuration¶
If your uncompressed pixel data is in RGB
or YBR_FULL
color space then
you may use a Planar Configuration of either 0
or 1
as JPEG-LS allows
the use of different interleave modes. While a Planar Configuration of
1
(interleave mode 0) may result in better compression ratios, its also
more likely to result in downstream issues with decoders that expect the more
common Planar Configuration 0
(interleave mode 2) pixel ordering.
For either case, if the pixel data being encoded is in an ndarray
then each frame should be shaped as (rows, columns, samples). If the pixel data
being encoded is bytes
then with Planar Configuration 0
the data
is ordered as color-by-pixel:
# Three 8-bit RGB pixels: (255, 255, 0), (0, 255, 0), (0, 255, 255)
# Each pixel is encoded separately the concatenated
# first pixel | second px | third px |
src = b"\xFF\xFF\x00\x00\xFF\x00\x00\xFF\xFF"
With Planar Configuration 1
the data is ordered as color-by-plane:
# Three 8-bit RGB pixels: (255, 255, 0), (0, 255, 0), (0, 255, 255)
# Each color channel is encoded separately then concatenated
# red channel | green ch. | blue ch. |
src = b"\xFF\x00\x00\xFF\xFF\xFF\x00\x00\xFF"
Examples¶
JPEG-LS Lossless¶
Losslessly compress unsigned RGB pixel data in-place:
from pydicom import examples
from pydicom.uid import JPEGLSLossless
ds = examples.rgb_color
assert ds.SamplesPerPixel == 1
assert ds.PhotometricInterpretation == 'RGB'
assert ds.BitsAllocated == 8
assert ds.BitsStored == 8
assert ds.PixelRepresentation == 0
assert len(ds.PixelData) == 921600
ds.compress(JPEGLSLossless)
print(len(ds.PixelData)) # ~261792
Losslessly compress signed greyscale pixel data in-place:
from pydicom import examples
from pydicom.uid import JPEGLSLossless
ds = examples.ct
assert ds.SamplesPerPixel == 1
assert ds.PhotometricInterpretation == 'MONOCHROME2'
assert ds.BitsAllocated == 16
assert ds.BitsStored == 16
assert ds.PixelRepresentation == 1
assert len(ds.PixelData) == 32768
ds.compress(JPEGLSLossless)
print(len(ds.PixelData)) # ~14180
JPEG-LS Near-lossless¶
Warning
pydicom makes no recommendations for specifying image quality for lossy encoding methods. Any examples of lossy encoding are for illustration purposes only.
When using the JPEG-LS Near-lossless transfer syntax, image quality is
controlled by passing the jls_error parameter to the encoding function
. jls_error is directly related to the JPEG-LS
NEAR parameter, which is the allowed absolute error in pixel intensity units from
the compression process and should be in the range (0, 2**BitsStored - 1)
.
Lossy compression of unsigned pixel data with a maximum error of 2 pixel intensity units:
from pydicom import examples
from pydicom.uid import JPEGLSNearLossless
ds = examples.rgb_color
assert ds.SamplesPerPixel == 1
assert ds.PhotometricInterpretation == 'RGB'
assert ds.BitsAllocated == 8
assert ds.BitsStored == 8
assert ds.PixelRepresentation == 0
assert len(ds.PixelData) == 921600
ds.compress(JPEGLSNearLossless, jls_error=2)
print(len(ds.PixelData)) # ~149188
Lossy compression of signed pixel data with a maximum error of 3 pixel intensity units:
from pydicom import examples
from pydicom.uid import JPEGLSNearLossless
ds = examples.ct
assert ds.SamplesPerPixel == 1
assert ds.PhotometricInterpretation == 'MONOCHROME2'
assert ds.BitsAllocated == 16
assert ds.BitsStored == 16
assert ds.PixelRepresentation == 1
assert len(ds.PixelData) == 32768
# Our pixel data therefore uses signed 16-bit integers with a single channel
# We need to make sure the maximum and minimum values are within the allowed
# range (see the section on Pixel Representation near the start of this page)
jls_error = 3
# The minimum and maximum sample values for the given *Bits Stored*
minimum = -2**(ds.BitsStored - 1)
maximum = 2**(ds.BitsStored - 1) - 1
arr = ds.pixel_array
# Clip the array so all values are within the limits, you may want to
# rescale instead of clipping. For this dataset this isn't actually
# necessary as the pixel data is already within the limits
arr = np.clip(minimum + jls_error, maximum - jls_error)
ds.compress(JPEGLSNearLossless, arr, jls_error=jls_error)
print(ds.PixelData) # ~8508
Available Plugins¶
Encoder |
Plugins |
||
---|---|---|---|
Name |
Requires |
Added |
|
pyjpegls |
v3.0 |
||