Skip to content

Commit 3736a2b

Browse files
[pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
1 parent 3b5bd1c commit 3736a2b

File tree

3 files changed

+72
-71
lines changed

3 files changed

+72
-71
lines changed

monailabel/datastore/utils/convert.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,10 @@ def dicom_to_nifti(series_dir, is_seg=False):
169169

170170
try:
171171
from monai.transforms import LoadImage
172+
172173
from monailabel.transform.reader import NvDicomReader
173174
from monailabel.transform.writer import write_itk
174-
175+
175176
# Use NvDicomReader with LoadImage
176177
reader = NvDicomReader(reverse_indexing=True, use_nvimgcodec=True)
177178
loader = LoadImage(reader=reader, image_only=False)
@@ -512,9 +513,10 @@ def nifti_to_dicom_seg(
512513

513514

514515
def itk_image_to_dicom_seg(label, series_dir, template) -> str:
515-
from monailabel.utils.others.generic import run_command
516516
import shutil
517517

518+
from monailabel.utils.others.generic import run_command
519+
518520
command = "itkimage2segimage"
519521
if not shutil.which(command):
520522
error_msg = (

tests/integration/radiology_serverless/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,3 @@
88
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
99
# See the License for the specific language governing permissions and
1010
# limitations under the License.
11-

tests/integration/radiology_serverless/test_dicom_segmentation.py

Lines changed: 68 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -33,50 +33,50 @@
3333
class TestDicomSegmentation(unittest.TestCase):
3434
"""
3535
Test direct MONAI Label inference on DICOM series without server.
36-
36+
3737
This test demonstrates serverless usage of MONAILabel for DICOM segmentation,
3838
loading DICOM series from test data directories and running inference directly
3939
through the app instance.
4040
"""
41-
41+
4242
app = None
4343
base_dir = os.path.realpath(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))
4444
data_dir = os.path.join(base_dir, "tests", "data")
45-
45+
4646
app_dir = os.path.join(base_dir, "sample-apps", "radiology")
4747
studies = os.path.join(data_dir, "dataset", "local", "spleen")
48-
48+
4949
# DICOM test data directories
5050
dicomweb_dir = os.path.join(data_dir, "dataset", "dicomweb")
5151
dicomweb_htj2k_dir = os.path.join(data_dir, "dataset", "dicomweb_htj2k")
52-
52+
5353
# Specific DICOM series for testing
5454
dicomweb_series = os.path.join(
55-
data_dir,
56-
"dataset",
57-
"dicomweb",
55+
data_dir,
56+
"dataset",
57+
"dicomweb",
5858
"e7567e0a064f0c334226a0658de23afd",
59-
"1.2.826.0.1.3680043.8.274.1.1.8323329.686521.1629744176.620266"
59+
"1.2.826.0.1.3680043.8.274.1.1.8323329.686521.1629744176.620266",
6060
)
6161
dicomweb_htj2k_series = os.path.join(
6262
data_dir,
6363
"dataset",
6464
"dicomweb_htj2k",
6565
"e7567e0a064f0c334226a0658de23afd",
66-
"1.2.826.0.1.3680043.8.274.1.1.8323329.686521.1629744176.620266"
66+
"1.2.826.0.1.3680043.8.274.1.1.8323329.686521.1629744176.620266",
6767
)
68-
68+
6969
@classmethod
7070
def setUpClass(cls) -> None:
7171
"""Initialize MONAI Label app for direct usage without server."""
7272
settings.MONAI_LABEL_APP_DIR = cls.app_dir
7373
settings.MONAI_LABEL_STUDIES = cls.studies
7474
settings.MONAI_LABEL_DATASTORE_AUTO_RELOAD = False
75-
75+
7676
if torch.cuda.is_available():
7777
logger.info(f"Initializing MONAI Label app from: {cls.app_dir}")
7878
logger.info(f"Studies directory: {cls.studies}")
79-
79+
8080
cls.app: MONAILabelApp = app_instance(
8181
app_dir=cls.app_dir,
8282
studies=cls.studies,
@@ -85,28 +85,28 @@ def setUpClass(cls) -> None:
8585
"models": "segmentation_spleen",
8686
},
8787
)
88-
88+
8989
logger.info("App initialized successfully")
90-
90+
9191
@classmethod
9292
def tearDownClass(cls) -> None:
9393
"""Clean up after tests."""
9494
pass
95-
95+
9696
def _run_inference(self, image_path: str, model_name: str = "segmentation_spleen") -> tuple:
9797
"""
9898
Run segmentation inference on an image (DICOM series directory or NIfTI file).
99-
99+
100100
Args:
101101
image_path: Path to DICOM series directory or NIfTI file
102102
model_name: Name of the segmentation model to use
103-
103+
104104
Returns:
105105
Tuple of (label_data, label_json, inference_time)
106106
"""
107107
logger.info(f"Running inference on: {image_path}")
108108
logger.info(f"Model: {model_name}")
109-
109+
110110
# Prepare inference request
111111
request = {
112112
"model": model_name,
@@ -115,202 +115,202 @@ def _run_inference(self, image_path: str, model_name: str = "segmentation_spleen
115115
"result_extension": ".nii.gz", # Force NIfTI output format
116116
"result_dtype": "uint8", # Set output data type
117117
}
118-
118+
119119
# Get the inference task directly
120120
task = self.app._infers[model_name]
121-
121+
122122
# Run inference
123123
inference_start = time.time()
124124
label_data, label_json = task(request)
125125
inference_time = time.time() - inference_start
126-
126+
127127
logger.info(f"Inference completed in {inference_time:.3f} seconds")
128-
128+
129129
return label_data, label_json, inference_time
130-
130+
131131
def _validate_segmentation_output(self, label_data, label_json):
132132
"""
133133
Validate that the segmentation output is correct.
134-
134+
135135
Args:
136136
label_data: The segmentation result (file path or numpy array)
137137
label_json: Metadata about the segmentation
138138
"""
139139
self.assertIsNotNone(label_data, "Label data should not be None")
140140
self.assertIsNotNone(label_json, "Label JSON should not be None")
141-
141+
142142
# Check if it's a file path or numpy array
143143
if isinstance(label_data, str):
144144
self.assertTrue(os.path.exists(label_data), f"Output file should exist: {label_data}")
145145
logger.info(f"Segmentation saved to: {label_data}")
146-
146+
147147
# Try to load and verify the file
148148
try:
149149
import nibabel as nib
150+
150151
nii = nib.load(label_data)
151152
array = nii.get_fdata()
152153
self.assertGreater(array.size, 0, "Segmentation array should not be empty")
153154
logger.info(f"Segmentation shape: {array.shape}, dtype: {array.dtype}")
154155
logger.info(f"Unique labels: {np.unique(array)}")
155156
except Exception as e:
156157
logger.warning(f"Could not load segmentation file: {e}")
157-
158+
158159
elif isinstance(label_data, np.ndarray):
159160
self.assertGreater(label_data.size, 0, "Segmentation array should not be empty")
160161
logger.info(f"Segmentation shape: {label_data.shape}, dtype: {label_data.dtype}")
161162
logger.info(f"Unique labels: {np.unique(label_data)}")
162163
else:
163164
self.fail(f"Unexpected label data type: {type(label_data)}")
164-
165+
165166
# Validate metadata
166167
self.assertIsInstance(label_json, dict, "Label JSON should be a dictionary")
167168
logger.info(f"Label metadata keys: {list(label_json.keys())}")
168-
169+
169170
def test_01_app_initialized(self):
170171
"""Test that the app is properly initialized."""
171172
if not torch.cuda.is_available():
172173
self.skipTest("CUDA not available")
173-
174+
174175
self.assertIsNotNone(self.app, "App should be initialized")
175176
self.assertIn("segmentation_spleen", self.app._infers, "segmentation_spleen model should be available")
176177
logger.info(f"Available models: {list(self.app._infers.keys())}")
177-
178+
178179
def test_02_dicom_inference_dicomweb(self):
179180
"""Test inference on DICOM series from dicomweb directory."""
180181
if not torch.cuda.is_available():
181182
self.skipTest("CUDA not available")
182-
183+
183184
if not self.app:
184185
self.skipTest("App not initialized")
185-
186+
186187
# Use specific DICOM series
187188
if not os.path.exists(self.dicomweb_series):
188189
self.skipTest(f"DICOM series not found: {self.dicomweb_series}")
189-
190+
190191
logger.info(f"Testing on DICOM series: {self.dicomweb_series}")
191-
192+
192193
# Run inference
193194
label_data, label_json, inference_time = self._run_inference(self.dicomweb_series)
194-
195+
195196
# Validate output
196197
self._validate_segmentation_output(label_data, label_json)
197-
198+
198199
# Performance check
199200
self.assertLess(inference_time, 60.0, "Inference should complete within 60 seconds")
200201
logger.info(f"✓ DICOM inference test passed (dicomweb) in {inference_time:.3f}s")
201-
202+
202203
def test_03_dicom_inference_dicomweb_htj2k(self):
203204
"""Test inference on DICOM series from dicomweb_htj2k directory (HTJ2K compressed)."""
204205
if not torch.cuda.is_available():
205206
self.skipTest("CUDA not available")
206-
207+
207208
if not self.app:
208209
self.skipTest("App not initialized")
209-
210+
210211
# Use specific HTJ2K DICOM series
211212
if not os.path.exists(self.dicomweb_htj2k_series):
212213
self.skipTest(f"HTJ2K DICOM series not found: {self.dicomweb_htj2k_series}")
213-
214+
214215
logger.info(f"Testing on HTJ2K compressed DICOM series: {self.dicomweb_htj2k_series}")
215-
216+
216217
# Run inference
217218
label_data, label_json, inference_time = self._run_inference(self.dicomweb_htj2k_series)
218-
219+
219220
# Validate output
220221
self._validate_segmentation_output(label_data, label_json)
221-
222+
222223
# Performance check
223224
self.assertLess(inference_time, 60.0, "Inference should complete within 60 seconds")
224225
logger.info(f"✓ DICOM inference test passed (HTJ2K) in {inference_time:.3f}s")
225-
226+
226227
def test_04_dicom_inference_both_formats(self):
227228
"""Test inference on both standard and HTJ2K compressed DICOM series."""
228229
if not torch.cuda.is_available():
229230
self.skipTest("CUDA not available")
230-
231+
231232
if not self.app:
232233
self.skipTest("App not initialized")
233-
234+
234235
# Test both series types
235236
test_series = [
236237
("Standard DICOM", self.dicomweb_series),
237238
("HTJ2K DICOM", self.dicomweb_htj2k_series),
238239
]
239-
240+
240241
total_time = 0
241242
successful = 0
242-
243+
243244
for series_type, dicom_dir in test_series:
244245
if not os.path.exists(dicom_dir):
245246
logger.warning(f"Skipping {series_type}: {dicom_dir} not found")
246247
continue
247-
248+
248249
logger.info(f"\nProcessing {series_type}: {dicom_dir}")
249-
250+
250251
try:
251252
label_data, label_json, inference_time = self._run_inference(dicom_dir)
252253
self._validate_segmentation_output(label_data, label_json)
253-
254+
254255
total_time += inference_time
255256
successful += 1
256257
logger.info(f"✓ {series_type} success in {inference_time:.3f}s")
257-
258+
258259
except Exception as e:
259260
logger.error(f"✗ {series_type} failed: {e}", exc_info=True)
260-
261+
261262
logger.info(f"\n{'='*60}")
262263
logger.info(f"Summary: {successful}/{len(test_series)} series processed successfully")
263264
if successful > 0:
264265
logger.info(f"Total inference time: {total_time:.3f}s")
265266
logger.info(f"Average time per series: {total_time/successful:.3f}s")
266267
logger.info(f"{'='*60}")
267-
268+
268269
# At least one should succeed
269270
self.assertGreater(successful, 0, "At least one DICOM series should be processed successfully")
270-
271+
271272
def test_05_compare_dicom_vs_nifti(self):
272273
"""Compare inference results between DICOM series and pre-converted NIfTI files."""
273274
if not torch.cuda.is_available():
274275
self.skipTest("CUDA not available")
275-
276+
276277
if not self.app:
277278
self.skipTest("App not initialized")
278-
279+
279280
# Use specific DICOM series and its NIfTI equivalent
280281
dicom_dir = self.dicomweb_series
281282
nifti_file = f"{dicom_dir}.nii.gz"
282-
283+
283284
if not os.path.exists(dicom_dir):
284285
self.skipTest(f"DICOM series not found: {dicom_dir}")
285-
286+
286287
if not os.path.exists(nifti_file):
287288
self.skipTest(f"Corresponding NIfTI file not found: {nifti_file}")
288-
289+
289290
logger.info(f"Comparing DICOM vs NIfTI inference:")
290291
logger.info(f" DICOM: {dicom_dir}")
291292
logger.info(f" NIfTI: {nifti_file}")
292-
293+
293294
# Run inference on DICOM
294295
logger.info("\n--- Running inference on DICOM series ---")
295296
dicom_label, dicom_json, dicom_time = self._run_inference(dicom_dir)
296-
297+
297298
# Run inference on NIfTI
298299
logger.info("\n--- Running inference on NIfTI file ---")
299300
nifti_label, nifti_json, nifti_time = self._run_inference(nifti_file)
300-
301+
301302
# Validate both
302303
self._validate_segmentation_output(dicom_label, dicom_json)
303304
self._validate_segmentation_output(nifti_label, nifti_json)
304-
305+
305306
logger.info(f"\nPerformance comparison:")
306307
logger.info(f" DICOM inference time: {dicom_time:.3f}s")
307308
logger.info(f" NIfTI inference time: {nifti_time:.3f}s")
308-
309+
309310
# Both should complete successfully
310311
self.assertIsNotNone(dicom_label, "DICOM inference should succeed")
311312
self.assertIsNotNone(nifti_label, "NIfTI inference should succeed")
312313

313314

314315
if __name__ == "__main__":
315316
unittest.main()
316-

0 commit comments

Comments
 (0)