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

1 JPEG-LS Lossless only

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 be converted 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

JPEGLSLosslessEncoder

pyjpegls

numpy, pyjpegls

v3.0

JPEGLSNearLosslessEncoder