Skip to content

Commit 9753c34

Browse files
Fix CI compliance for clinical preprocessing
- Add proper copyright headers - Fix type hints and imports - Follow MONAI coding standards - Improve test coverage Signed-off-by: Hitendrasinh Rathod <[email protected]>
1 parent d0961e1 commit 9753c34

File tree

2 files changed

+99
-15
lines changed

2 files changed

+99
-15
lines changed

monai/tests/test_clinical_preprocessing.py

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
get_ct_preprocessing_pipeline,
2323
get_mri_preprocessing_pipeline,
2424
preprocess_dicom_series,
25+
preprocess_medical_image,
2526
)
2627

2728

@@ -64,6 +65,11 @@ def test_invalid_modality_type():
6465

6566
assert "modality must be a string" in str(exc.value)
6667

68+
with pytest.raises(ModalityTypeError) as exc:
69+
preprocess_medical_image("dummy", None)
70+
71+
assert "modality must be a string" in str(exc.value)
72+
6773

6874
def test_unsupported_modality():
6975
"""Test unsupported modality."""
@@ -76,15 +82,39 @@ def test_unsupported_modality():
7682
assert "MR" in msg
7783
assert "MRI" in msg
7884

85+
with pytest.raises(UnsupportedModalityError) as exc:
86+
preprocess_medical_image("dummy", "PET")
87+
88+
msg = str(exc.value)
89+
assert "Unsupported modality" in msg
90+
assert "CT" in msg
91+
assert "MR" in msg
92+
assert "MRI" in msg
93+
7994

8095
@patch("monai.transforms.clinical_preprocessing.LoadImage")
8196
def test_modality_case_insensitivity(mock_load):
82-
"""Test case-insensitive modality handling."""
97+
"""Test case-insensitive modality handling with whitespace trimming."""
8398
mock_load.return_value = Mock(return_value=Mock())
8499

85-
for modality in ["CT", "ct", "Ct", "CT ", "MR", "mr", "MRI", "mri", " MrI "]:
100+
test_cases = [
101+
("CT", True),
102+
("ct", True),
103+
("Ct", True),
104+
("CT ", True),
105+
(" CT", True),
106+
("MR", True),
107+
("mr", True),
108+
("MRI", True),
109+
("mri", True),
110+
(" MrI ", True),
111+
]
112+
113+
for modality, _ in test_cases:
86114
result = preprocess_dicom_series("dummy.dcm", modality)
87-
assert result is not None
115+
assert result is not None, f"Failed for modality: '{modality}'"
116+
result2 = preprocess_medical_image("dummy.dcm", modality)
117+
assert result2 is not None, f"preprocess_medical_image failed for modality: '{modality}'"
88118

89119

90120
@patch("monai.transforms.clinical_preprocessing.LoadImage")
@@ -93,6 +123,24 @@ def test_mr_modality_distinct(mock_load):
93123
mock_load.return_value = Mock(return_value=Mock())
94124
result = preprocess_dicom_series("dummy.dcm", "MR")
95125
assert result is not None
126+
result2 = preprocess_medical_image("dummy.dcm", "MR")
127+
assert result2 is not None
128+
129+
130+
@patch("monai.transforms.clinical_preprocessing.LoadImage")
131+
def test_edge_cases(mock_load):
132+
"""Test edge cases for modality input."""
133+
mock_load.return_value = Mock(return_value=Mock())
134+
135+
with pytest.raises(UnsupportedModalityError):
136+
preprocess_dicom_series("dummy.dcm", "")
137+
138+
with pytest.raises(UnsupportedModalityError):
139+
preprocess_dicom_series("dummy.dcm", " ")
140+
141+
long_modality = "CT" * 100
142+
with pytest.raises(UnsupportedModalityError):
143+
preprocess_dicom_series("dummy.dcm", long_modality)
96144

97145

98146
def test_preprocess_dicom_series_integration(tmp_path):
@@ -106,3 +154,6 @@ def test_preprocess_dicom_series_integration(tmp_path):
106154
result = preprocess_dicom_series(str(test_file), modality)
107155
assert result is not None
108156
assert hasattr(result, "shape")
157+
result2 = preprocess_medical_image(str(test_file), modality)
158+
assert result2 is not None
159+
assert hasattr(result2, "shape")

monai/transforms/clinical_preprocessing.py

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
This module provides modality-specific preprocessing pipelines for common medical imaging modalities.
1616
"""
1717

18-
from typing import Any
18+
from typing import Union
19+
20+
import numpy as np
21+
import torch
1922

2023
from monai.transforms import (
2124
Compose,
@@ -42,6 +45,10 @@ def get_ct_preprocessing_pipeline() -> Compose:
4245
Compose: Transform composition for CT preprocessing.
4346
Applies Hounsfield Unit (HU) windowing [-1000, 400] scaled to [0, 1].
4447
This range captures lung (-1000 to -400 HU) and soft tissue (0 to 100 HU) contrast.
48+
49+
Note:
50+
Output will be a single-channel tensor with shape (1, H, W, D)
51+
and values in range [0, 1].
4552
"""
4653
return Compose(
4754
[
@@ -66,6 +73,10 @@ def get_mri_preprocessing_pipeline() -> Compose:
6673
Compose: Transform composition for MRI preprocessing.
6774
Normalizes intensities using nonzero voxels only, excluding background regions
6875
typical in MRI acquisitions.
76+
77+
Note:
78+
Output will be a single-channel tensor with shape (1, H, W, D)
79+
normalized based on nonzero voxel statistics.
6980
"""
7081
return Compose(
7182
[
@@ -76,23 +87,24 @@ def get_mri_preprocessing_pipeline() -> Compose:
7687
)
7788

7889

79-
def preprocess_dicom_series(path: str, modality: str) -> Any:
80-
"""Preprocess a DICOM series or file based on imaging modality.
90+
def preprocess_medical_image(path: str, modality: str) -> Union[torch.Tensor, np.ndarray]:
91+
"""
92+
Preprocess a medical image based on imaging modality.
8193
8294
Args:
83-
path: Path to the DICOM file or directory containing a DICOM series.
95+
path: Path to the medical image file. Supports various formats including
96+
DICOM, NIfTI, and others supported by MONAI's LoadImage transform.
8497
modality: Imaging modality. Supported values are "CT", "MR", and "MRI" (case-insensitive).
8598
8699
Returns:
87-
Any: Preprocessed image data.
100+
Preprocessed image data as a tensor or numpy array.
88101
89102
Raises:
90103
ModalityTypeError: If modality is not a string.
91104
UnsupportedModalityError: If the provided modality is not supported.
92105
"""
93106
if not isinstance(modality, str):
94-
error_msg = "modality must be a string"
95-
raise ModalityTypeError(error_msg)
107+
raise ModalityTypeError("modality must be a string")
96108

97109
modality_clean = modality.strip().upper()
98110

@@ -101,19 +113,40 @@ def preprocess_dicom_series(path: str, modality: str) -> Any:
101113
elif modality_clean == "CT":
102114
pipeline = get_ct_preprocessing_pipeline()
103115
else:
104-
error_msg = (
105-
f"Unsupported modality '{modality}'. "
106-
f"Supported modalities: CT, MR, MRI"
116+
raise UnsupportedModalityError(
117+
f"Unsupported modality '{modality}'. Supported modalities: CT, MR, MRI"
107118
)
108-
raise UnsupportedModalityError(error_msg)
109119

110120
return pipeline(path)
111121

112122

123+
# Keep the old function name for backward compatibility
124+
def preprocess_dicom_series(path: str, modality: str) -> Union[torch.Tensor, np.ndarray]:
125+
"""
126+
Preprocess a DICOM series or file based on imaging modality.
127+
128+
Note: This function also supports other medical image formats
129+
(NIfTI, etc.) through MONAI's LoadImage transform.
130+
131+
Args:
132+
path: Path to the DICOM file or directory containing a DICOM series.
133+
modality: Imaging modality. Supported values are "CT", "MR", and "MRI" (case-insensitive).
134+
135+
Returns:
136+
Preprocessed image data.
137+
138+
Raises:
139+
ModalityTypeError: If modality is not a string.
140+
UnsupportedModalityError: If the provided modality is not supported.
141+
"""
142+
return preprocess_medical_image(path, modality)
143+
144+
113145
__all__ = [
114146
"ModalityTypeError",
115147
"UnsupportedModalityError",
116148
"get_ct_preprocessing_pipeline",
117149
"get_mri_preprocessing_pipeline",
118150
"preprocess_dicom_series",
119-
]
151+
"preprocess_medical_image",
152+
]

0 commit comments

Comments
 (0)