Skip to content

Commit 813e5b8

Browse files
committed
Added note on using nvimgcodec and enhancing perf test to support user supplied dicom files
Signed-off-by: M Q <[email protected]>
1 parent e3ff7e1 commit 813e5b8

File tree

2 files changed

+52
-14
lines changed

2 files changed

+52
-14
lines changed

monai/deploy/operators/dicom_series_to_volume_operator.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,25 @@ class DICOMSeriesToVolumeOperator(Operator):
5353
def __init__(self, fragment: Fragment, *args, affine_lps_to_ras: bool = True, **kwargs):
5454
"""Create an instance for a containing application object.
5555
56+
This operator converts instances of DICOMSeries into an Image object.
57+
The loaded Image Object can be used for further processing via other operators.
58+
The data array will be a 3D image NumPy array with index order of `DHW`.
59+
Channel is limited to 1 as of now, and `C` is absent in the NumPy array.
60+
61+
This operator registers `nvimgcodec` based compressed pixel data decoder plugin with Pydicom
62+
at application startup to support and improve the performance of decoding DICOM files with compressed
63+
pixel data of in JPEG, JPEG 2000, and HTJ2K, irrespective of if python-gdcm, Python libjpg and openjpeg
64+
based decoder plugins are available at runtime.
65+
66+
Registering the decoder plugin is all automatic and does not require any additional change in user's application
67+
except for adding a dependency on the `nvimgcodec-cu12` and `nvidia-nvjpeg2k-cu12` packages (suffix of cu12 means
68+
CUDA 12.0 though cu13 is also supported).
69+
70+
Named Input:
71+
study_selected_series_list: List of StudySelectedSeries.
72+
Named Output:
73+
image: Image object.
74+
5675
Args:
5776
fragment (Fragment): An instance of the Application class which is derived from Fragment.
5877
affine_lps_to_ras (bool): If true, the affine transform in the image metadata is RAS oriented,

tests/unit/test_decoder_nvimgcodec.py

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import time
22
from pathlib import Path
3+
from typing import Any
34

45
import numpy as np
56
import pytest
@@ -14,18 +15,33 @@
1415
)
1516

1617

17-
def get_test_dicoms():
18-
"""Use pydicom package's embedded test DICOM files for testing."""
18+
def get_test_dicoms(folder_path: str | None = None):
19+
"""Use pydicom package's embedded test DICOM files for testing or a custom folder of DICOM files."""
1920

20-
for path in get_testdata_files("*.dcm"):
21+
# function's own util function
22+
def _is_supported_dicom_file(path: str) -> bool:
2123
try:
2224
dataset = dcmread(path, stop_before_pixels=True) # ignore non-compliant DICOM files
2325
transfer_syntax = dataset.file_meta.TransferSyntaxUID
24-
if transfer_syntax not in SUPPORTED_TRANSFER_SYNTAXES:
25-
continue
26-
yield path
26+
return transfer_syntax in SUPPORTED_TRANSFER_SYNTAXES
2727
except Exception:
28+
return False
29+
30+
dcm_paths = []
31+
if folder_path is not None:
32+
folder_path_p = Path(folder_path)
33+
if folder_path_p.exists():
34+
dcm_paths = sorted(folder_path_p.glob("*.dcm"))
35+
else:
36+
raise FileNotFoundError(f"Custom folder {folder_path} does not exist")
37+
else:
38+
# use pydicom package's embedded test DICOM files for testing
39+
dcm_paths = [Path(x) for x in get_testdata_files("*.dcm")]
40+
41+
for dcm_path in dcm_paths:
42+
if not _is_supported_dicom_file(str(dcm_path)):
2843
continue
44+
yield str(dcm_path)
2945

3046

3147
@pytest.mark.skipif(
@@ -82,22 +98,22 @@ def test_nvimgcodec_decoder_matches_default(path: str) -> None:
8298
np.testing.assert_allclose(baseline_pixels, nv_pixels, rtol=rtol, atol=atol)
8399

84100

85-
def performance_test_nvimgcodec_decoder_against_defaults():
101+
def performance_test_nvimgcodec_decoder_against_defaults(folder_path: str | None = None) -> None:
86102
"""Test and compare the performance of the nvimgcodec decoder against the default decoders
87-
with all DICOM files of supported transfer syntaxes"""
103+
with all DICOM files of supported transfer syntaxes in a custom folder or pidicom dataset"""
88104

89105
total_baseline_time = 0.0
90106
total_nvimgcodec_time = 0.0
91107

92-
files_tested_with_perf = {} # key: path, value: performance_metrics
108+
files_tested_with_perf: dict[str, dict[str, Any]] = {} # key: path, value: performance_metrics
93109
files_with_errors = []
94110

95111
try:
96112
unregister_as_decoder_plugin() # Make sure nvimgcodec decoder plugin is not registered
97113
except Exception:
98114
pass
99115

100-
for path in get_test_dicoms():
116+
for path in get_test_dicoms(folder_path):
101117
try:
102118
ds_default = dcmread(path)
103119
transfer_syntax = ds_default.file_meta.TransferSyntaxUID
@@ -106,7 +122,7 @@ def performance_test_nvimgcodec_decoder_against_defaults():
106122
baseline_execution_time = time.perf_counter() - start
107123
total_baseline_time += baseline_execution_time
108124

109-
perf = {}
125+
perf: dict[str, Any] = {}
110126
perf["transfer_syntax"] = transfer_syntax
111127
perf["baseline_execution_time"] = baseline_execution_time
112128
files_tested_with_perf[path] = perf
@@ -153,6 +169,9 @@ def performance_test_nvimgcodec_decoder_against_defaults():
153169

154170
if __name__ == "__main__":
155171

156-
# Use pytest to test the functionality with each DICOM file of supported transfer syntaxes individually
157-
# and the following ompares the performance of the nvimgcodec decoder against the default decoders
158-
performance_test_nvimgcodec_decoder_against_defaults()
172+
# Use pytest to test the functionality with pydicom embedded DICOM files of supported transfer syntaxes individually
173+
# python -m pytest test_decoder_nvimgcodec.py
174+
#
175+
# The following compares the performance of the nvimgcodec decoder against the default decoders
176+
# with DICOM files in pidicom embedded dataset or an optional custom folder
177+
performance_test_nvimgcodec_decoder_against_defaults() # e.g. "/tmp/multi-frame-dcm"

0 commit comments

Comments
 (0)