Skip to content

Commit 09c0e33

Browse files
committed
Add configurable progression order support for HTJ2K encoding
This commit adds support for all five JPEG2000 progression orders in HTJ2K encoding, allowing users to optimize compression for different use cases: - LRCP: Layer-Resolution-Component-Position (quality scalability) - RLCP: Resolution-Layer-Component-Position (resolution scalability) - RPCL: Resolution-Position-Component-Layer (progressive by resolution, default) - PCRL: Position-Component-Resolution-Layer (progressive by spatial area) - CPRL: Component-Position-Resolution-Layer (component scalability) Changes: - Extended _setup_htj2k_encode_params() to accept progression_order parameter with validation against supported values - Added proper Transfer Syntax UID mapping for each progression order (1.2.840.10008.1.2.4.201 for LRCP/RLCP/PCRL/CPRL, 1.2.840.10008.1.2.4.202 for RPCL) - Changed bitstream type from JP2 to J2K format - Updated transcode_dicom_to_htj2k() to expose progression_order parameter - Added comprehensive test suite covering all progression orders with various DICOM configurations This enables better control over HTJ2K encoding characteristics based on specific deployment requirements (streaming, quality, resolution scalability). Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
1 parent 13f3377 commit 09c0e33

File tree

2 files changed

+409
-6
lines changed

2 files changed

+409
-6
lines changed

monailabel/datastore/utils/convert_htj2k.py

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,28 +53,60 @@ def _setup_htj2k_decode_params(color_spec=None):
5353
return decode_params
5454

5555

56-
def _setup_htj2k_encode_params(num_resolutions: int = 6, code_block_size: tuple = (64, 64)):
56+
def _setup_htj2k_encode_params(
57+
num_resolutions: int = 6,
58+
code_block_size: tuple = (64, 64),
59+
progression_order: str = "RPCL"
60+
):
5761
"""
5862
Create nvimgcodec encoding parameters for HTJ2K lossless compression.
5963
6064
Args:
6165
num_resolutions: Number of wavelet decomposition levels
6266
code_block_size: Code block size as (height, width) tuple
67+
progression_order: Progression order for encoding. Must be one of:
68+
- "LRCP": Layer-Resolution-Component-Position (quality scalability)
69+
- "RLCP": Resolution-Layer-Component-Position (resolution scalability)
70+
- "RPCL": Resolution-Position-Component-Layer (progressive by resolution)
71+
- "PCRL": Position-Component-Resolution-Layer (progressive by spatial area)
72+
- "CPRL": Component-Position-Resolution-Layer (component scalability)
6373
6474
Returns:
6575
tuple: (encode_params, target_transfer_syntax)
76+
77+
Raises:
78+
ValueError: If progression_order is not one of the valid values
6679
"""
6780
from nvidia import nvimgcodec
6881

69-
target_transfer_syntax = "1.2.840.10008.1.2.4.202" # HTJ2K with RPCL Options (Lossless)
82+
# Valid progression orders and their mappings
83+
VALID_PROG_ORDERS = {
84+
"LRCP": (nvimgcodec.Jpeg2kProgOrder.LRCP, "1.2.840.10008.1.2.4.201"), # HTJ2K (Lossless Only)
85+
"RLCP": (nvimgcodec.Jpeg2kProgOrder.RLCP, "1.2.840.10008.1.2.4.201"), # HTJ2K (Lossless Only)
86+
"RPCL": (nvimgcodec.Jpeg2kProgOrder.RPCL, "1.2.840.10008.1.2.4.202"), # HTJ2K with RPCL Options
87+
"PCRL": (nvimgcodec.Jpeg2kProgOrder.PCRL, "1.2.840.10008.1.2.4.201"), # HTJ2K (Lossless Only)
88+
"CPRL": (nvimgcodec.Jpeg2kProgOrder.CPRL, "1.2.840.10008.1.2.4.201"), # HTJ2K (Lossless Only)
89+
}
90+
91+
# Validate progression order
92+
if progression_order not in VALID_PROG_ORDERS:
93+
valid_orders = ", ".join(f"'{o}'" for o in VALID_PROG_ORDERS.keys())
94+
raise ValueError(
95+
f"Invalid progression_order '{progression_order}'. "
96+
f"Must be one of: {valid_orders}"
97+
)
98+
99+
# Get progression order enum and transfer syntax
100+
prog_order_enum, target_transfer_syntax = VALID_PROG_ORDERS[progression_order]
101+
70102
quality_type = nvimgcodec.QualityType.LOSSLESS
71103

72104
# Configure JPEG2K encoding parameters
73105
jpeg2k_encode_params = nvimgcodec.Jpeg2kEncodeParams()
74106
jpeg2k_encode_params.num_resolutions = num_resolutions
75107
jpeg2k_encode_params.code_block_size = code_block_size
76-
jpeg2k_encode_params.bitstream_type = nvimgcodec.Jpeg2kBitstreamType.JP2
77-
jpeg2k_encode_params.prog_order = nvimgcodec.Jpeg2kProgOrder.LRCP
108+
jpeg2k_encode_params.bitstream_type = nvimgcodec.Jpeg2kBitstreamType.J2K
109+
jpeg2k_encode_params.prog_order = prog_order_enum
78110
jpeg2k_encode_params.ht = True # Enable High Throughput mode
79111

80112
encode_params = nvimgcodec.EncodeParams(
@@ -223,6 +255,7 @@ def transcode_dicom_to_htj2k(
223255
output_dir: str = None,
224256
num_resolutions: int = 6,
225257
code_block_size: tuple = (64, 64),
258+
progression_order: str = "RPCL",
226259
max_batch_size: int = 256,
227260
add_basic_offset_table: bool = True,
228261
) -> str:
@@ -262,6 +295,13 @@ def transcode_dicom_to_htj2k(
262295
Higher values = better compression but slower encoding
263296
code_block_size: Code block size as (height, width) tuple (default: (64, 64))
264297
Must be powers of 2. Common values: (32,32), (64,64), (128,128)
298+
progression_order: Progression order for HTJ2K encoding (default: "RPCL")
299+
Must be one of: "LRCP", "RLCP", "RPCL", "PCRL", "CPRL"
300+
- "LRCP": Layer-Resolution-Component-Position (quality scalability)
301+
- "RLCP": Resolution-Layer-Component-Position (resolution scalability)
302+
- "RPCL": Resolution-Position-Component-Layer (progressive by resolution)
303+
- "PCRL": Position-Component-Resolution-Layer (progressive by spatial area)
304+
- "CPRL": Component-Position-Resolution-Layer (component scalability)
265305
max_batch_size: Maximum number of DICOM files to process in each batch (default: 256)
266306
Lower values reduce memory usage, higher values may improve speed
267307
add_basic_offset_table: If True, creates Basic Offset Table for multi-frame DICOMs (default: True)
@@ -275,6 +315,7 @@ def transcode_dicom_to_htj2k(
275315
ImportError: If nvidia-nvimgcodec is not available
276316
ValueError: If input directory doesn't exist or contains no valid DICOM files
277317
ValueError: If DICOM files are missing required attributes (TransferSyntaxUID, PixelData)
318+
ValueError: If progression_order is not one of: "LRCP", "RLCP", "RPCL", "PCRL", "CPRL"
278319
279320
Example:
280321
>>> # Basic usage with default settings
@@ -345,7 +386,8 @@ def transcode_dicom_to_htj2k(
345386
# Setup HTJ2K encoding parameters
346387
encode_params, target_transfer_syntax = _setup_htj2k_encode_params(
347388
num_resolutions=num_resolutions,
348-
code_block_size=code_block_size
389+
code_block_size=code_block_size,
390+
progression_order=progression_order
349391
)
350392
# Note: decode_params is created per-PhotometricInterpretation group in the batch processing
351393
logger.info("Using lossless HTJ2K compression")
@@ -542,6 +584,7 @@ def convert_single_frame_dicom_series_to_multiframe(
542584
convert_to_htj2k: bool = False,
543585
num_resolutions: int = 6,
544586
code_block_size: tuple = (64, 64),
587+
progression_order: str = "RPCL",
545588
add_basic_offset_table: bool = True,
546589
) -> str:
547590
"""
@@ -567,6 +610,13 @@ def convert_single_frame_dicom_series_to_multiframe(
567610
convert_to_htj2k: If True, convert frames to HTJ2K compression; if False, use uncompressed format (default: False)
568611
num_resolutions: Number of wavelet decomposition levels (default: 6, only used if convert_to_htj2k=True)
569612
code_block_size: Code block size as (height, width) tuple (default: (64, 64), only used if convert_to_htj2k=True)
613+
progression_order: Progression order for HTJ2K encoding (default: "RPCL", only used if convert_to_htj2k=True)
614+
Must be one of: "LRCP", "RLCP", "RPCL", "PCRL", "CPRL"
615+
- "LRCP": Layer-Resolution-Component-Position (quality scalability)
616+
- "RLCP": Resolution-Layer-Component-Position (resolution scalability)
617+
- "RPCL": Resolution-Position-Component-Layer (progressive by resolution)
618+
- "PCRL": Position-Component-Resolution-Layer (progressive by spatial area)
619+
- "CPRL": Component-Position-Resolution-Layer (component scalability)
570620
add_basic_offset_table: If True, creates Basic Offset Table for multi-frame DICOMs (default: True)
571621
BOT enables O(1) frame access without parsing entire pixel data stream
572622
Per DICOM Part 5 Section A.4. Only affects multi-frame files.
@@ -577,6 +627,7 @@ def convert_single_frame_dicom_series_to_multiframe(
577627
Raises:
578628
ImportError: If nvidia-nvimgcodec is not available and convert_to_htj2k=True
579629
ValueError: If input directory doesn't exist or contains no valid DICOM files
630+
ValueError: If progression_order is not one of: "LRCP", "RLCP", "RPCL", "PCRL", "CPRL"
580631
581632
Example:
582633
>>> # Combine series without HTJ2K conversion (uncompressed)
@@ -693,7 +744,8 @@ def convert_single_frame_dicom_series_to_multiframe(
693744
# Setup HTJ2K encoding parameters
694745
encode_params, target_transfer_syntax = _setup_htj2k_encode_params(
695746
num_resolutions=num_resolutions,
696-
code_block_size=code_block_size
747+
code_block_size=code_block_size,
748+
progression_order=progression_order
697749
)
698750
# Note: decode_params is created per-series based on PhotometricInterpretation
699751
logger.info("HTJ2K conversion enabled")

0 commit comments

Comments
 (0)