Initial commit
This commit is contained in:
20
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/__init__.py
vendored
Executable file
20
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/__init__.py
vendored
Executable file
@@ -0,0 +1,20 @@
|
||||
# Copyright 2008-2024 pydicom authors. See LICENSE file for details.
|
||||
|
||||
from pydicom.pixels.decoders.base import (
|
||||
ImplicitVRLittleEndianDecoder,
|
||||
ExplicitVRLittleEndianDecoder,
|
||||
ExplicitVRBigEndianDecoder,
|
||||
DeflatedExplicitVRLittleEndianDecoder,
|
||||
JPEGBaseline8BitDecoder,
|
||||
JPEGExtended12BitDecoder,
|
||||
JPEGLosslessDecoder,
|
||||
JPEGLosslessSV1Decoder,
|
||||
JPEGLSLosslessDecoder,
|
||||
JPEGLSNearLosslessDecoder,
|
||||
JPEG2000LosslessDecoder,
|
||||
JPEG2000Decoder,
|
||||
HTJ2KLosslessDecoder,
|
||||
HTJ2KLosslessRPCLDecoder,
|
||||
HTJ2KDecoder,
|
||||
RLELosslessDecoder,
|
||||
)
|
||||
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/__pycache__/__init__.cpython-313.pyc
vendored
Executable file
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/__pycache__/__init__.cpython-313.pyc
vendored
Executable file
Binary file not shown.
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/__pycache__/base.cpython-313.pyc
vendored
Executable file
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/__pycache__/base.cpython-313.pyc
vendored
Executable file
Binary file not shown.
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/__pycache__/gdcm.cpython-313.pyc
vendored
Executable file
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/__pycache__/gdcm.cpython-313.pyc
vendored
Executable file
Binary file not shown.
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/__pycache__/pillow.cpython-313.pyc
vendored
Executable file
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/__pycache__/pillow.cpython-313.pyc
vendored
Executable file
Binary file not shown.
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/__pycache__/pyjpegls.cpython-313.pyc
vendored
Executable file
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/__pycache__/pyjpegls.cpython-313.pyc
vendored
Executable file
Binary file not shown.
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/__pycache__/pylibjpeg.cpython-313.pyc
vendored
Executable file
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/__pycache__/pylibjpeg.cpython-313.pyc
vendored
Executable file
Binary file not shown.
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/__pycache__/rle.cpython-313.pyc
vendored
Executable file
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/__pycache__/rle.cpython-313.pyc
vendored
Executable file
Binary file not shown.
2039
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/base.py
vendored
Executable file
2039
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/base.py
vendored
Executable file
File diff suppressed because it is too large
Load Diff
144
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/gdcm.py
vendored
Executable file
144
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/gdcm.py
vendored
Executable file
@@ -0,0 +1,144 @@
|
||||
# Copyright 2024 pydicom authors. See LICENSE file for details.
|
||||
"""Use GDCM <https://github.com/malaterre/GDCM> to decompress encoded
|
||||
*Pixel Data*.
|
||||
|
||||
This module is not intended to be used directly.
|
||||
"""
|
||||
|
||||
from typing import cast
|
||||
|
||||
from pydicom import uid
|
||||
from pydicom.pixels.decoders.base import DecodeRunner
|
||||
from pydicom.pixels.common import PhotometricInterpretation as PI
|
||||
|
||||
try:
|
||||
import gdcm
|
||||
|
||||
GDCM_VERSION = tuple(int(x) for x in gdcm.Version.GetVersion().split("."))
|
||||
HAVE_GDCM = True
|
||||
except ImportError:
|
||||
HAVE_GDCM = False
|
||||
|
||||
|
||||
DECODER_DEPENDENCIES = {
|
||||
uid.JPEGBaseline8Bit: ("gdcm>=3.0.10",),
|
||||
uid.JPEGExtended12Bit: ("gdcm>=3.0.10",),
|
||||
uid.JPEGLossless: ("gdcm>=3.0.10",),
|
||||
uid.JPEGLosslessSV1: ("gdcm>=3.0.10",),
|
||||
uid.JPEGLSLossless: ("gdcm>=3.0.10",),
|
||||
uid.JPEGLSNearLossless: ("gdcm>=3.0.10",),
|
||||
uid.JPEG2000Lossless: ("gdcm>=3.0.10",),
|
||||
uid.JPEG2000: ("gdcm>=3.0.10",),
|
||||
}
|
||||
|
||||
|
||||
def is_available(uid: str) -> bool:
|
||||
"""Return ``True`` if a pixel data decoder for `uid` is available for use,
|
||||
``False`` otherwise.
|
||||
"""
|
||||
if not HAVE_GDCM or GDCM_VERSION < (3, 0):
|
||||
return False
|
||||
|
||||
return uid in DECODER_DEPENDENCIES
|
||||
|
||||
|
||||
def _decode_frame(src: bytes, runner: DecodeRunner) -> bytes:
|
||||
"""Return the decoded `src` as :class:`bytes`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
src : bytes
|
||||
An encoded pixel data frame.
|
||||
runner : pydicom.pixels.decoders.base.DecodeRunner
|
||||
The runner managing the decoding.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bytes
|
||||
The decoded pixel data frame.
|
||||
"""
|
||||
tsyntax = runner.transfer_syntax
|
||||
photometric_interpretation = runner.photometric_interpretation
|
||||
bits_stored = runner.bits_stored
|
||||
if tsyntax == uid.JPEGExtended12Bit and bits_stored != 8:
|
||||
raise NotImplementedError(
|
||||
"GDCM does not support 'JPEG Extended' for samples with 12-bit precision"
|
||||
)
|
||||
|
||||
if (
|
||||
tsyntax == uid.JPEGLSNearLossless
|
||||
and runner.pixel_representation == 1
|
||||
and bits_stored < 8
|
||||
):
|
||||
raise ValueError(
|
||||
"Unable to decode signed lossy JPEG-LS pixel data with a sample "
|
||||
"precision less than 8 bits"
|
||||
)
|
||||
|
||||
if tsyntax in uid.JPEGLSTransferSyntaxes and bits_stored in (6, 7):
|
||||
raise ValueError(
|
||||
"Unable to decode unsigned JPEG-LS pixel data with a sample "
|
||||
"precision of 6 or 7 bits"
|
||||
)
|
||||
|
||||
fragment = gdcm.Fragment()
|
||||
fragment.SetByteStringValue(src)
|
||||
|
||||
fragments = gdcm.SequenceOfFragments.New()
|
||||
fragments.AddFragment(fragment)
|
||||
|
||||
elem = gdcm.DataElement(gdcm.Tag(0x7FE0, 0x0010))
|
||||
elem.SetValue(fragments.__ref__())
|
||||
|
||||
img = gdcm.Image()
|
||||
img.SetNumberOfDimensions(2)
|
||||
img.SetDimensions((runner.columns, runner.rows, 1))
|
||||
img.SetDataElement(elem)
|
||||
|
||||
pi_type = gdcm.PhotometricInterpretation.GetPIType(photometric_interpretation)
|
||||
img.SetPhotometricInterpretation(gdcm.PhotometricInterpretation(pi_type))
|
||||
if runner.samples_per_pixel > 1:
|
||||
img.SetPlanarConfiguration(runner.planar_configuration)
|
||||
|
||||
ts_type = gdcm.TransferSyntax.GetTSType(str.__str__(tsyntax))
|
||||
img.SetTransferSyntax(gdcm.TransferSyntax(ts_type))
|
||||
|
||||
if tsyntax in uid.JPEGLSTransferSyntaxes:
|
||||
# GDCM always returns JPEG-LS data as color-by-pixel
|
||||
runner.set_option("planar_configuration", 0)
|
||||
bits_stored = runner.get_option("jls_precision", bits_stored)
|
||||
if 0 < bits_stored <= 8:
|
||||
runner.set_option("bits_allocated", 8)
|
||||
elif 8 < bits_stored <= 16:
|
||||
runner.set_option("bits_allocated", 16)
|
||||
|
||||
if tsyntax in uid.JPEG2000TransferSyntaxes:
|
||||
# GDCM pixel container size is based on precision
|
||||
bits_stored = runner.get_option("j2k_precision", bits_stored)
|
||||
if 0 < bits_stored <= 8:
|
||||
runner.set_option("bits_allocated", 8)
|
||||
elif 8 < bits_stored <= 16:
|
||||
runner.set_option("bits_allocated", 16)
|
||||
elif 16 < bits_stored <= 32:
|
||||
runner.set_option("bits_allocated", 32)
|
||||
|
||||
pixel_format = gdcm.PixelFormat(
|
||||
runner.samples_per_pixel,
|
||||
runner.bits_allocated,
|
||||
bits_stored,
|
||||
bits_stored - 1,
|
||||
runner.pixel_representation,
|
||||
)
|
||||
img.SetPixelFormat(pixel_format)
|
||||
|
||||
# GDCM returns char* as str, so re-encode it to bytes
|
||||
frame = img.GetBuffer().encode("utf-8", "surrogateescape")
|
||||
|
||||
# GDCM returns YBR_ICT and YBR_RCT as RGB
|
||||
if tsyntax in uid.JPEG2000TransferSyntaxes and photometric_interpretation in (
|
||||
PI.YBR_ICT,
|
||||
PI.YBR_RCT,
|
||||
):
|
||||
runner.set_option("photometric_interpretation", PI.RGB)
|
||||
|
||||
return cast(bytes, frame)
|
||||
127
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/pillow.py
vendored
Executable file
127
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/pillow.py
vendored
Executable file
@@ -0,0 +1,127 @@
|
||||
# Copyright 2008-2024 pydicom authors. See LICENSE file for details.
|
||||
"""Use Pillow <https://github.com/python-pillow/Pillow> to decompress encoded
|
||||
*Pixel Data*.
|
||||
|
||||
This module is not intended to be used directly.
|
||||
"""
|
||||
|
||||
from io import BytesIO
|
||||
from typing import cast
|
||||
|
||||
from pydicom import uid
|
||||
from pydicom.pixels.utils import _passes_version_check
|
||||
from pydicom.pixels.common import PhotometricInterpretation as PI
|
||||
from pydicom.pixels.decoders.base import DecodeRunner
|
||||
|
||||
try:
|
||||
from PIL import Image, features
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
import numpy as np
|
||||
|
||||
HAVE_NP = True
|
||||
except ImportError:
|
||||
HAVE_NP = False
|
||||
|
||||
|
||||
DECODER_DEPENDENCIES = {
|
||||
uid.JPEGBaseline8Bit: ("pillow>=10.0",),
|
||||
uid.JPEGExtended12Bit: ("pillow>=10.0",),
|
||||
uid.JPEG2000Lossless: ("numpy", "pillow>=10.0"),
|
||||
uid.JPEG2000: ("numpy", "pillow>=10.0"),
|
||||
}
|
||||
|
||||
_LIBJPEG_SYNTAXES = [uid.JPEGBaseline8Bit, uid.JPEGExtended12Bit]
|
||||
_OPENJPEG_SYNTAXES = [uid.JPEG2000Lossless, uid.JPEG2000]
|
||||
|
||||
|
||||
def is_available(uid: str) -> bool:
|
||||
"""Return ``True`` if a pixel data decoder for `uid` is available for use,
|
||||
``False`` otherwise.
|
||||
"""
|
||||
if not _passes_version_check("PIL", (10, 0)):
|
||||
return False
|
||||
|
||||
if uid in _LIBJPEG_SYNTAXES:
|
||||
return bool(features.check_codec("jpg")) # type: ignore[no-untyped-call]
|
||||
|
||||
if uid in _OPENJPEG_SYNTAXES:
|
||||
return bool(features.check_codec("jpg_2000")) and HAVE_NP # type: ignore[no-untyped-call]
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _decode_frame(src: bytes, runner: DecodeRunner) -> bytes:
|
||||
"""Return the decoded image data in `src` as a :class:`bytes`."""
|
||||
tsyntax = runner.transfer_syntax
|
||||
|
||||
# libjpeg only supports 8-bit JPEG Extended (can be 8 or 12 in the JPEG standard)
|
||||
if tsyntax == uid.JPEGExtended12Bit and runner.bits_stored != 8:
|
||||
raise NotImplementedError(
|
||||
"Pillow does not support 'JPEG Extended' for samples with 12-bit precision"
|
||||
)
|
||||
|
||||
image = Image.open(BytesIO(src), formats=("JPEG", "JPEG2000"))
|
||||
if tsyntax in _LIBJPEG_SYNTAXES:
|
||||
if runner.samples_per_pixel != 1:
|
||||
# If the Adobe APP14 marker is not present then Pillow assumes
|
||||
# that JPEG images were transformed into YCbCr color space prior
|
||||
# to compression, so setting the image mode to YCbCr signals we
|
||||
# don't want any color transformations.
|
||||
# Any color transformations would be inconsistent with the
|
||||
# behavior required by the `raw` flag
|
||||
if "adobe_transform" not in image.info:
|
||||
image.draft("YCbCr", image.size) # type: ignore[no-untyped-call]
|
||||
|
||||
return cast(bytes, image.tobytes())
|
||||
|
||||
# JPEG 2000
|
||||
# The precision from the J2K codestream is more appropriate because the
|
||||
# decoder will use it to create the output integers
|
||||
precision = runner.get_option("j2k_precision", runner.bits_stored)
|
||||
# pillow's pixel container size is based on precision
|
||||
if 0 < precision <= 8:
|
||||
runner.set_option("bits_allocated", 8)
|
||||
elif 8 < precision <= 16:
|
||||
# Pillow converts >= 9-bit RGB/YCbCr data to 8-bit
|
||||
if runner.samples_per_pixel > 1:
|
||||
raise ValueError(
|
||||
f"Pillow cannot decode {precision}-bit multi-sample data correctly"
|
||||
)
|
||||
|
||||
runner.set_option("bits_allocated", 16)
|
||||
else:
|
||||
raise ValueError(
|
||||
"only (0028,0101) 'Bits Stored' values of up to 16 are supported"
|
||||
)
|
||||
|
||||
# Pillow converts N-bit signed/unsigned data to 8- or 16-bit unsigned data
|
||||
# See Pillow src/libImaging/Jpeg2KDecode.c::j2ku_gray_i
|
||||
buffer = bytearray(image.tobytes()) # so the array is writeable
|
||||
del image
|
||||
dtype = runner.pixel_dtype
|
||||
arr = np.frombuffer(buffer, dtype=f"u{dtype.itemsize}")
|
||||
|
||||
is_signed = runner.pixel_representation
|
||||
if runner.get_option("apply_j2k_sign_correction", False):
|
||||
is_signed = runner.get_option("j2k_is_signed", is_signed)
|
||||
|
||||
if is_signed and runner.pixel_representation == 1:
|
||||
# Re-view the unsigned integers as signed
|
||||
# e.g. [0, 127, 128, 255] -> [0, 127, -128, -1]
|
||||
arr = arr.view(dtype)
|
||||
# Level-shift to match the unsigned integers range
|
||||
# e.g. [0, 127, -128, -1] -> [-128, -1, 0, 127]
|
||||
arr -= np.int32(2 ** (runner.bits_allocated - 1))
|
||||
|
||||
if bit_shift := (runner.bits_allocated - precision):
|
||||
# Bit shift to undo the upscaling of N-bit to 8- or 16-bit
|
||||
np.right_shift(arr, bit_shift, out=arr)
|
||||
|
||||
# pillow returns YBR_ICT and YBR_RCT as RGB
|
||||
if runner.photometric_interpretation in (PI.YBR_ICT, PI.YBR_RCT):
|
||||
runner.set_option("photometric_interpretation", PI.RGB)
|
||||
|
||||
return cast(bytes, arr.tobytes())
|
||||
47
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/pyjpegls.py
vendored
Executable file
47
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/pyjpegls.py
vendored
Executable file
@@ -0,0 +1,47 @@
|
||||
# Copyright 2008-2024 pydicom authors. See LICENSE file for details.
|
||||
"""Use pyjpegls <https://github.com/pydicom/pyjpegls> to decompress encoded
|
||||
*Pixel Data*.
|
||||
|
||||
This module is not intended to be used directly.
|
||||
"""
|
||||
from typing import cast
|
||||
|
||||
from pydicom import uid
|
||||
from pydicom.pixels.utils import _passes_version_check
|
||||
from pydicom.pixels.decoders.base import DecodeRunner
|
||||
|
||||
try:
|
||||
import jpeg_ls
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
DECODER_DEPENDENCIES = {
|
||||
uid.JPEGLSLossless: ("numpy", "pyjpegls>=1.2"),
|
||||
uid.JPEGLSNearLossless: ("numpy", "pyjpegls>=1.2"),
|
||||
}
|
||||
|
||||
|
||||
def is_available(uid: str) -> bool:
|
||||
"""Return ``True`` if the decoder has its dependencies met, ``False`` otherwise"""
|
||||
return _passes_version_check("jpeg_ls", (1, 2))
|
||||
|
||||
|
||||
def _decode_frame(src: bytes, runner: DecodeRunner) -> bytearray:
|
||||
"""Return the decoded image data in `src` as a :class:`bytearray`."""
|
||||
buffer, info = jpeg_ls.decode_pixel_data(src)
|
||||
# Interleave mode 0 is colour-by-plane, 1 and 2 are colour-by-pixel
|
||||
if info["components"] > 1:
|
||||
if info["interleave_mode"] == 0:
|
||||
runner.set_option("planar_configuration", 1)
|
||||
else:
|
||||
runner.set_option("planar_configuration", 0)
|
||||
|
||||
precision = info["bits_per_sample"]
|
||||
if 0 < precision <= 8:
|
||||
runner.set_option("bits_allocated", 8)
|
||||
elif 8 < precision <= 16:
|
||||
runner.set_option("bits_allocated", 16)
|
||||
|
||||
return cast(bytearray, buffer)
|
||||
115
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/pylibjpeg.py
vendored
Executable file
115
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/pylibjpeg.py
vendored
Executable file
@@ -0,0 +1,115 @@
|
||||
# Copyright 2008-2024 pydicom authors. See LICENSE file for details.
|
||||
"""Use pylibjpeg <https://github.com/pydicom/pylibjpeg> to decompress encoded
|
||||
*Pixel Data*.
|
||||
|
||||
This module is not intended to be used directly.
|
||||
"""
|
||||
|
||||
from typing import cast
|
||||
|
||||
from pydicom import uid
|
||||
from pydicom.pixels.decoders.base import DecodeRunner
|
||||
from pydicom.pixels.utils import _passes_version_check
|
||||
from pydicom.pixels.common import PhotometricInterpretation as PI
|
||||
|
||||
try:
|
||||
from pylibjpeg.utils import get_pixel_data_decoders, Decoder
|
||||
|
||||
# {UID: {plugin name: function}}
|
||||
_DECODERS = cast(
|
||||
dict[uid.UID, dict[str, "Decoder"]], get_pixel_data_decoders(version=2)
|
||||
)
|
||||
except ImportError:
|
||||
_DECODERS = {}
|
||||
|
||||
|
||||
DECODER_DEPENDENCIES = {
|
||||
uid.JPEGBaseline8Bit: ("pylibjpeg>=2.0", "pylibjpeg-libjpeg>=2.1"),
|
||||
uid.JPEGExtended12Bit: ("pylibjpeg>=2.0", "pylibjpeg-libjpeg>=2.1"),
|
||||
uid.JPEGLossless: ("pylibjpeg>=2.0", "pylibjpeg-libjpeg>=2.1"),
|
||||
uid.JPEGLosslessSV1: ("pylibjpeg>=2.0", "pylibjpeg-libjpeg>=2.1"),
|
||||
uid.JPEGLSLossless: ("pylibjpeg>=2.0", "pylibjpeg-libjpeg>=2.1"),
|
||||
uid.JPEGLSNearLossless: ("pylibjpeg>=2.0", "pylibjpeg-libjpeg>=2.1"),
|
||||
uid.JPEG2000Lossless: ("pylibjpeg>=2.0", "pylibjpeg-openjpeg>=2.0"),
|
||||
uid.JPEG2000: ("pylibjpeg>=2.0", "pylibjpeg-openjpeg>=2.0"),
|
||||
uid.HTJ2KLossless: ("pylibjpeg>=2.0", "pylibjpeg-openjpeg>=2.0"),
|
||||
uid.HTJ2KLosslessRPCL: ("pylibjpeg>=2.0", "pylibjpeg-openjpeg>=2.0"),
|
||||
uid.HTJ2K: ("pylibjpeg>=2.0", "pylibjpeg-openjpeg>=2.0"),
|
||||
uid.RLELossless: ("pylibjpeg>=2.0", "pylibjpeg-rle>=2.0"),
|
||||
}
|
||||
|
||||
_LIBJPEG_SYNTAXES = [
|
||||
uid.JPEGBaseline8Bit,
|
||||
uid.JPEGExtended12Bit,
|
||||
uid.JPEGLossless,
|
||||
uid.JPEGLosslessSV1,
|
||||
uid.JPEGLSLossless,
|
||||
uid.JPEGLSNearLossless,
|
||||
]
|
||||
_OPENJPEG_SYNTAXES = [
|
||||
uid.JPEG2000Lossless,
|
||||
uid.JPEG2000,
|
||||
uid.HTJ2KLossless,
|
||||
uid.HTJ2KLosslessRPCL,
|
||||
uid.HTJ2K,
|
||||
]
|
||||
_RLE_SYNTAXES = [uid.RLELossless]
|
||||
|
||||
|
||||
def is_available(uid: str) -> bool:
|
||||
"""Return ``True`` if the decoder has its dependencies met, ``False`` otherwise"""
|
||||
if not _passes_version_check("pylibjpeg", (2, 0)):
|
||||
return False
|
||||
|
||||
if uid in _LIBJPEG_SYNTAXES:
|
||||
return _passes_version_check("libjpeg", (2, 0, 2))
|
||||
|
||||
if uid in _OPENJPEG_SYNTAXES:
|
||||
return _passes_version_check("openjpeg", (2, 0))
|
||||
|
||||
if uid in _RLE_SYNTAXES:
|
||||
return _passes_version_check("rle", (2, 0))
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _decode_frame(src: bytes, runner: DecodeRunner) -> bytearray: # type: ignore[return]
|
||||
"""Return the decoded image data in `src` as a :class:`bytearray`."""
|
||||
tsyntax = runner.transfer_syntax
|
||||
|
||||
# Currently only one pylibjpeg plugin is available per UID
|
||||
# so decode using the first available decoder
|
||||
for _, func in sorted(_DECODERS[tsyntax].items()):
|
||||
# `version=2` to return frame as bytearray
|
||||
frame = cast(bytearray, func(src, version=2, **runner.options))
|
||||
|
||||
# pylibjpeg-rle returns decoded data as planar configuration 1
|
||||
if tsyntax == uid.RLELossless:
|
||||
runner.set_option("planar_configuration", 1)
|
||||
|
||||
if tsyntax in _OPENJPEG_SYNTAXES:
|
||||
# pylibjpeg-openjpeg returns YBR_ICT and YBR_RCT as RGB
|
||||
if runner.photometric_interpretation in (PI.YBR_ICT, PI.YBR_RCT):
|
||||
runner.set_option("photometric_interpretation", PI.RGB)
|
||||
|
||||
# pylibjpeg-openjpeg pixel container size is based on J2K precision
|
||||
precision = runner.get_option("j2k_precision", runner.bits_stored)
|
||||
if 0 < precision <= 8:
|
||||
runner.set_option("bits_allocated", 8)
|
||||
elif 8 < precision <= 16:
|
||||
runner.set_option("bits_allocated", 16)
|
||||
elif 16 < precision <= 32:
|
||||
runner.set_option("bits_allocated", 32)
|
||||
|
||||
if tsyntax in uid.JPEGLSTransferSyntaxes:
|
||||
# pylibjpeg-libjpeg always returns JPEG-LS data as color-by-pixel
|
||||
runner.set_option("planar_configuration", 0)
|
||||
|
||||
# pylibjpeg-libjpeg pixel container size is based on JPEG-LS precision
|
||||
precision = runner.get_option("jls_precision", runner.bits_stored)
|
||||
if 0 < precision <= 8:
|
||||
runner.set_option("bits_allocated", 8)
|
||||
elif 8 < precision <= 16:
|
||||
runner.set_option("bits_allocated", 16)
|
||||
|
||||
return frame
|
||||
278
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/rle.py
vendored
Executable file
278
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/pixels/decoders/rle.py
vendored
Executable file
@@ -0,0 +1,278 @@
|
||||
# Copyright 2008-2024 pydicom authors. See LICENSE file for details.
|
||||
"""Use Python to decode RLE Lossless encoded *Pixel Data*.
|
||||
|
||||
This module is not intended to be used directly.
|
||||
"""
|
||||
|
||||
from struct import unpack
|
||||
|
||||
from pydicom.misc import warn_and_log
|
||||
from pydicom.pixels.decoders.base import DecodeRunner
|
||||
from pydicom.uid import RLELossless
|
||||
|
||||
|
||||
DECODER_DEPENDENCIES = {RLELossless: ()}
|
||||
|
||||
|
||||
def is_available(uid: str) -> bool:
|
||||
"""Return ``True`` if a pixel data decoder for `uid` is available for use,
|
||||
``False`` otherwise.
|
||||
"""
|
||||
return uid in DECODER_DEPENDENCIES
|
||||
|
||||
|
||||
def _decode_frame(src: bytes, runner: DecodeRunner) -> bytearray:
|
||||
"""Wrapper for use with the decoder interface.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
src : bytes
|
||||
A single frame of RLE encoded data.
|
||||
runner : pydicom.pixels.decoders.base.DecodeRunner
|
||||
|
||||
|
||||
Required parameters:
|
||||
|
||||
* `rows`: int
|
||||
* `columns`: int
|
||||
* `samples_per_pixel`: int
|
||||
* `bits_allocated`: int
|
||||
|
||||
Optional parameters:
|
||||
|
||||
* `rle_segment_order`: str, "<" for little endian segment order, or
|
||||
">" for big endian (default)
|
||||
|
||||
Returns
|
||||
-------
|
||||
bytearray
|
||||
The decoded frame, ordered as planar configuration 1.
|
||||
"""
|
||||
frame = _rle_decode_frame(
|
||||
src,
|
||||
runner.rows,
|
||||
runner.columns,
|
||||
runner.samples_per_pixel,
|
||||
runner.bits_allocated,
|
||||
runner.get_option("rle_segment_order", ">"),
|
||||
)
|
||||
# Update the runner options to ensure the reshaping is correct
|
||||
# Only do this if we successfully decoded the frame
|
||||
runner.set_option("planar_configuration", 1)
|
||||
|
||||
return frame
|
||||
|
||||
|
||||
def _rle_decode_frame(
|
||||
src: bytes,
|
||||
rows: int,
|
||||
columns: int,
|
||||
nr_samples: int,
|
||||
nr_bits: int,
|
||||
segment_order: str = ">",
|
||||
) -> bytearray:
|
||||
"""Decodes a single frame of RLE encoded data.
|
||||
|
||||
Each frame may contain up to 15 segments of encoded data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
src : bytes
|
||||
The RLE frame data
|
||||
rows : int
|
||||
The number of output rows
|
||||
columns : int
|
||||
The number of output columns
|
||||
nr_samples : int
|
||||
Number of samples per pixel (e.g. 3 for RGB data).
|
||||
nr_bits : int
|
||||
Number of bits per sample - must be a multiple of 8
|
||||
segment_order : str
|
||||
The segment order of the `data`, '>' for big endian (default),
|
||||
'<' for little endian (non-conformant).
|
||||
|
||||
Returns
|
||||
-------
|
||||
bytearray
|
||||
The frame's decoded data in little endian and planar configuration 1
|
||||
byte ordering (i.e. for RGB data this is all red pixels then all
|
||||
green then all blue, with the bytes for each pixel ordered from
|
||||
MSB to LSB when reading left to right).
|
||||
"""
|
||||
if nr_bits % 8:
|
||||
raise NotImplementedError(
|
||||
f"Unable to decode RLE encoded pixel data with {nr_bits} bits allocated"
|
||||
)
|
||||
|
||||
# Parse the RLE Header
|
||||
offsets = _rle_parse_header(src[:64])
|
||||
nr_segments = len(offsets)
|
||||
|
||||
# Check that the actual number of segments is as expected
|
||||
bytes_per_sample = nr_bits // 8
|
||||
if nr_segments != nr_samples * bytes_per_sample:
|
||||
raise ValueError(
|
||||
"The number of RLE segments in the pixel data doesn't match the "
|
||||
f"expected amount ({nr_segments} vs. {nr_samples * bytes_per_sample} "
|
||||
"segments)"
|
||||
)
|
||||
|
||||
# Ensure the last segment gets decoded
|
||||
offsets.append(len(src))
|
||||
|
||||
# Preallocate with null bytes
|
||||
decoded = bytearray(rows * columns * nr_samples * bytes_per_sample)
|
||||
|
||||
# Example:
|
||||
# RLE encoded data is ordered like this (for 16-bit, 3 sample):
|
||||
# Segment: 0 | 1 | 2 | 3 | 4 | 5
|
||||
# R MSB | R LSB | G MSB | G LSB | B MSB | B LSB
|
||||
# A segment contains only the MSB or LSB parts of all the sample pixels
|
||||
|
||||
# To minimise the amount of array manipulation later, and to make things
|
||||
# faster we interleave each segment in a manner consistent with a planar
|
||||
# configuration of 1 (and use little endian byte ordering):
|
||||
# All red samples | All green samples | All blue
|
||||
# Pxl 1 Pxl 2 ... Pxl N | Pxl 1 Pxl 2 ... Pxl N | ...
|
||||
# LSB MSB LSB MSB ... LSB MSB | LSB MSB LSB MSB ... LSB MSB | ...
|
||||
|
||||
# `stride` is the total number of bytes of each sample plane
|
||||
stride = bytes_per_sample * rows * columns
|
||||
for sample_number in range(nr_samples):
|
||||
le_gen = range(bytes_per_sample)
|
||||
byte_offsets = le_gen if segment_order == "<" else reversed(le_gen)
|
||||
for byte_offset in byte_offsets:
|
||||
# Decode the segment
|
||||
ii = sample_number * bytes_per_sample + byte_offset
|
||||
# ii is 1, 0, 3, 2, 5, 4 for the example above
|
||||
# This is where the segment order correction occurs
|
||||
segment = _rle_decode_segment(src[offsets[ii] : offsets[ii + 1]])
|
||||
|
||||
# Check that the number of decoded bytes is correct
|
||||
actual_length = len(segment)
|
||||
if actual_length < rows * columns:
|
||||
raise ValueError(
|
||||
"The amount of decoded RLE segment data doesn't match the "
|
||||
f"expected amount ({actual_length} vs. {rows * columns} bytes)"
|
||||
)
|
||||
elif actual_length != rows * columns:
|
||||
warn_and_log(
|
||||
"The decoded RLE segment contains non-conformant padding "
|
||||
f"- {actual_length} vs. {rows * columns} bytes expected"
|
||||
)
|
||||
|
||||
if segment_order == ">":
|
||||
byte_offset = bytes_per_sample - byte_offset - 1
|
||||
|
||||
# For 100 pixel/plane, 32-bit, 3 sample data, `start` will be
|
||||
# 0, 1, 2, 3, 400, 401, 402, 403, 800, 801, 802, 803
|
||||
start = byte_offset + (sample_number * stride)
|
||||
decoded[start : start + stride : bytes_per_sample] = segment[
|
||||
: rows * columns
|
||||
]
|
||||
|
||||
return decoded
|
||||
|
||||
|
||||
def _rle_decode_segment(src: bytes) -> bytearray:
|
||||
"""Return a single segment of decoded RLE data as bytearray.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
buffer : bytes
|
||||
The segment data to be decoded.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bytearray
|
||||
The decoded segment.
|
||||
"""
|
||||
result = bytearray()
|
||||
pos = 0
|
||||
result_extend = result.extend
|
||||
|
||||
try:
|
||||
while True:
|
||||
# header_byte is N + 1
|
||||
header_byte = src[pos] + 1
|
||||
pos += 1
|
||||
if header_byte > 129:
|
||||
# Extend by copying the next byte (-N + 1) times
|
||||
# however since using uint8 instead of int8 this will be
|
||||
# (256 - N + 1) times
|
||||
result_extend(src[pos : pos + 1] * (258 - header_byte))
|
||||
pos += 1
|
||||
elif header_byte < 129:
|
||||
# Extend by literally copying the next (N + 1) bytes
|
||||
result_extend(src[pos : pos + header_byte])
|
||||
pos += header_byte
|
||||
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _rle_parse_header(header: bytes) -> list[int]:
|
||||
"""Return a list of byte offsets for the segments in RLE data.
|
||||
|
||||
**RLE Header Format**
|
||||
|
||||
The RLE Header contains the number of segments for the image and the
|
||||
starting offset of each segment. Each of these numbers is represented as
|
||||
an unsigned long stored in little-endian. The RLE Header is 16 long words
|
||||
in length (i.e. 64 bytes) which allows it to describe a compressed image
|
||||
with up to 15 segments. All unused segment offsets shall be set to zero.
|
||||
|
||||
As an example, the table below describes an RLE Header with 3 segments as
|
||||
would typically be used with 8-bit RGB or YCbCr data (with 1 segment per
|
||||
channel).
|
||||
|
||||
+--------------+---------------------------------+------------+
|
||||
| Byte offset | Description | Value |
|
||||
+==============+=================================+============+
|
||||
| 0 | Number of segments | 3 |
|
||||
+--------------+---------------------------------+------------+
|
||||
| 4 | Offset of segment 1, N bytes | 64 |
|
||||
+--------------+---------------------------------+------------+
|
||||
| 8 | Offset of segment 2, M bytes | 64 + N |
|
||||
+--------------+---------------------------------+------------+
|
||||
| 12 | Offset of segment 3 | 64 + N + M |
|
||||
+--------------+---------------------------------+------------+
|
||||
| 16 | Offset of segment 4 (not used) | 0 |
|
||||
+--------------+---------------------------------+------------+
|
||||
| ... | ... | 0 |
|
||||
+--------------+---------------------------------+------------+
|
||||
| 60 | Offset of segment 15 (not used) | 0 |
|
||||
+--------------+---------------------------------+------------+
|
||||
|
||||
Parameters
|
||||
----------
|
||||
header : bytes
|
||||
The RLE header data (i.e. the first 64 bytes of an RLE frame).
|
||||
|
||||
Returns
|
||||
-------
|
||||
list of int
|
||||
The byte offsets for each segment in the RLE data.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If there are more than 15 segments or if the header is not 64 bytes
|
||||
long.
|
||||
|
||||
References
|
||||
----------
|
||||
DICOM Standard, Part 5, :dcm:`Annex G<part05/chapter_G.html>`
|
||||
"""
|
||||
if len(header) != 64:
|
||||
raise ValueError("The RLE header can only be 64 bytes long")
|
||||
|
||||
nr_segments = unpack("<L", header[:4])[0]
|
||||
if nr_segments > 15:
|
||||
raise ValueError(
|
||||
f"The RLE header specifies an invalid number of segments ({nr_segments})"
|
||||
)
|
||||
|
||||
return list(unpack(f"<{nr_segments}L", header[4 : 4 * (nr_segments + 1)]))
|
||||
Reference in New Issue
Block a user