diff --git a/docs/clinical_dicom_workflow.pdf b/docs/clinical_dicom_workflow.pdf new file mode 100644 index 0000000000..2b4b018e04 Binary files /dev/null and b/docs/clinical_dicom_workflow.pdf differ diff --git a/monai/docs/clinical_dicom_workflow.pdf b/monai/docs/clinical_dicom_workflow.pdf new file mode 100644 index 0000000000..2b4b018e04 Binary files /dev/null and b/monai/docs/clinical_dicom_workflow.pdf differ diff --git a/monai/tests/test_clinical_preprocessing.py b/monai/tests/test_clinical_preprocessing.py new file mode 100644 index 0000000000..bd2501972a --- /dev/null +++ b/monai/tests/test_clinical_preprocessing.py @@ -0,0 +1,45 @@ +import numpy as np + +from monai.transforms import ScaleIntensityRange, NormalizeIntensity + + +def test_ct_windowing_range_and_shape(): + rng = np.random.default_rng(0) + + sample_ct = rng.integers( + -1024, 2048, size=(64, 64, 64), dtype=np.int16 + ) + + transform = ScaleIntensityRange( + a_min=-1000, + a_max=400, + b_min=0.0, + b_max=1.0, + clip=True, + ) + + output = transform(sample_ct) + output = np.asarray(output) + + assert output.shape == sample_ct.shape + assert np.isfinite(output).all() + assert output.min() >= -1e-6 + assert output.max() <= 1.0 + 1e-6 + + +def test_mri_normalization_mean_std(): + rng = np.random.default_rng(0) + + sample_mri = rng.random((64, 64, 64), dtype=np.float32) + + transform = NormalizeIntensity(nonzero=True) + + output = transform(sample_mri) + output = np.asarray(output) + + mean_val = float(output.mean()) + std_val = float(output.std()) + + assert output.shape == sample_mri.shape + assert np.isclose(mean_val, 0.0, atol=0.1) + assert np.isclose(std_val, 1.0, atol=0.1) diff --git a/monai/transforms/clinical_preprocessing.py b/monai/transforms/clinical_preprocessing.py new file mode 100644 index 0000000000..ab6a10cb74 --- /dev/null +++ b/monai/transforms/clinical_preprocessing.py @@ -0,0 +1,70 @@ +from typing import Union + +from monai.transforms import ( + Compose, + LoadImage, + EnsureChannelFirst, + ScaleIntensityRange, + NormalizeIntensity, +) + + +def get_ct_preprocessing_pipeline(): + """ + CT preprocessing pipeline using standard HU windowing. + """ + return Compose( + [ + LoadImage(image_only=True), + EnsureChannelFirst(), + ScaleIntensityRange( + a_min=-1000, + a_max=400, + b_min=0.0, + b_max=1.0, + clip=True, + ), + ] + ) + + +def get_mri_preprocessing_pipeline(): + """ + MRI preprocessing pipeline using intensity normalization. + """ + return Compose( + [ + LoadImage(image_only=True), + EnsureChannelFirst(), + NormalizeIntensity(nonzero=True), + ] + ) + + +def preprocess_dicom_series( + dicom_path: Union[str, bytes], + modality: str, +): + """ + Preprocess a DICOM series based on modality. + + Args: + dicom_path: Path to DICOM file or directory. + modality: CT, MR, or MRI. + + Returns: + Preprocessed image. + """ + if not isinstance(modality, str): + raise TypeError("modality must be a string") + + modality = modality.strip().upper() + + if modality == "CT": + transform = get_ct_preprocessing_pipeline() + elif modality in ("MR", "MRI"): + transform = get_mri_preprocessing_pipeline() + else: + raise ValueError("Unsupported modality") + + return transform(dicom_path)