Skip to content

Commit 8f659b2

Browse files
committed
Make image metadata Affine and Space consistent, either LPS or RAS
Signed-off-by: M Q <mingmelvinq@nvidia.com>
1 parent 4a8fba0 commit 8f659b2

File tree

6 files changed

+554
-677
lines changed

6 files changed

+554
-677
lines changed

examples/apps/ai_remote_infer_app/spleen_seg_operator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def pre_process(self, img_reader, out_dir: str = "./input_images") -> Compose:
127127
resample=False,
128128
output_ext=".nii",
129129
),
130-
Orientationd(keys=my_key, axcodes="LPS"),
130+
Orientationd(keys=my_key, axcodes="RAS"),
131131
Spacingd(keys=my_key, pixdim=[1.5, 1.5, 2.9], mode=["bilinear"]),
132132
ScaleIntensityRanged(keys=my_key, a_min=-57, a_max=164, b_min=0.0, b_max=1.0, clip=True),
133133
EnsureTyped(keys=my_key),

examples/apps/ai_unetr_seg_app/unetr_seg_operator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ def pre_process(self, img_reader, out_dir: str = "./input_images") -> Compose:
143143
output_ext=".nii",
144144
),
145145
Spacingd(keys=my_key, pixdim=(1.5, 1.5, 2.0), mode=("bilinear")),
146-
Orientationd(keys=my_key, axcodes="LPS"),
146+
Orientationd(keys=my_key, axcodes="RAS"),
147147
ScaleIntensityRanged(my_key, a_min=-175, a_max=250, b_min=0.0, b_max=1.0, clip=True),
148148
CropForegroundd(my_key, source_key=my_key),
149149
]

examples/apps/cchmc_ped_abd_ct_seg_app/abdomen_seg_operator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ def pre_process(self, img_reader) -> Compose:
226226
# img_reader: specialized InMemImageReader, derived from MONAI ImageReader
227227
LoadImaged(keys=my_key, reader=img_reader),
228228
EnsureChannelFirstd(keys=my_key),
229-
Orientationd(keys=my_key, axcodes="LPS"),
229+
Orientationd(keys=my_key, axcodes="RAS"),
230230
Spacingd(keys=my_key, pixdim=[1.5, 1.5, 3.0], mode=["bilinear"]),
231231
ScaleIntensityRanged(keys=my_key, a_min=-250, a_max=400, b_min=0.0, b_max=1.0, clip=True),
232232
CropForegroundd(keys=my_key, source_key=my_key, mode="minimum"),

monai/deploy/operators/dicom_series_to_volume_operator.py

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,25 @@ class DICOMSeriesToVolumeOperator(Operator):
4141

4242
# Use constants instead of enums in monai to avoid dependency at this level.
4343
MONAI_UTIL_ENUMS_SPACEKEYS_RAS = "RAS"
44+
MONAI_UTIL_ENUMS_SPACEKEYS_LPS = "LPS"
4445
MONAI_TRANSFORMS_SPATIAL_METADATA_NAME = "space"
4546
METADATA_SPACE_RAS = {MONAI_TRANSFORMS_SPATIAL_METADATA_NAME: MONAI_UTIL_ENUMS_SPACEKEYS_RAS}
47+
METADATA_SPACE_LPS = {MONAI_TRANSFORMS_SPATIAL_METADATA_NAME: MONAI_UTIL_ENUMS_SPACEKEYS_LPS}
48+
ATTRIBUTE_NIFTI_AFFINE = "nifti_affine_transform"
49+
ATTRIBUTE_DICOM_AFFINE = "dicom_affine_transform"
4650

47-
def __init__(self, fragment: Fragment, *args, **kwargs):
51+
def __init__(self, fragment: Fragment, *args, affine_lps_to_ras: bool = True, **kwargs):
4852
"""Create an instance for a containing application object.
4953
5054
Args:
5155
fragment (Fragment): An instance of the Application class which is derived from Fragment.
56+
affine_lps_to_ras (bool): If true, the affine transform in the image metadata is RAS oriented,
57+
otherwise it is LPS oriented. Default is True.
5258
"""
5359

5460
self.input_name_series = "study_selected_series_list"
5561
self.output_name_image = "image"
62+
self.affine_lps_to_ras = affine_lps_to_ras
5663
# Need to call the base class constructor last
5764
super().__init__(fragment, *args, **kwargs)
5865

@@ -89,18 +96,16 @@ def convert_to_image(self, study_selected_series_list: List[StudySelectedSeries]
8996
metadata.update(self._get_instance_properties(study_selected_series.study))
9097
selection_metadata = {"selection_name": selection_name}
9198
metadata.update(selection_metadata)
92-
# Add the metadata to specify LPS.
93-
# Previously, this was set in ImageReader class, but moving it here allows other loaders
94-
# to determine this value on its own, e.g. NIfTI loader but it does not set this
95-
# resulting in the MONAI Orientation transform to default the labels to RAS.
96-
# It is assumed that the ImageOrientationPatient will be set accordingly if the
97-
# PatientPosition is other than HFS.
98-
# NOTE: This value is properly parsed by MONAI Orientation transform from v1.5.1 onwards.
99-
# Some early MONAI model inference configs incorrectly specify orientation to RAS
100-
# due part to previous MONAI versions did not correctly parse this metadata from
101-
# the input MetaTensor and defaulting to RAS. Now with LPS properly set, the inference
102-
# configs then need to be updated to specify LPS, to achieve the same result.
103-
metadata.update(self.METADATA_SPACE_RAS)
99+
# The affine transform and the coordinate space are set based on the flag affine_lps_to_ras.
100+
# If the flag is true, the NIFTI affine (RAS) is used, otherwise the DICOM affine (LPS) is used.
101+
if self.affine_lps_to_ras:
102+
if hasattr(dicom_series, self.ATTRIBUTE_NIFTI_AFFINE):
103+
metadata["affine"] = getattr(dicom_series, self.ATTRIBUTE_NIFTI_AFFINE)
104+
metadata.update(self.METADATA_SPACE_RAS)
105+
else:
106+
if hasattr(dicom_series, self.ATTRIBUTE_DICOM_AFFINE):
107+
metadata["affine"] = getattr(dicom_series, self.ATTRIBUTE_DICOM_AFFINE)
108+
metadata.update(self.METADATA_SPACE_LPS)
104109

105110
voxel_data = self.generate_voxel_data(dicom_series)
106111
image = self.create_volumetric_image(voxel_data, metadata)
@@ -366,7 +371,7 @@ def compute_affine_transform(self, s_1, s_n, n, series):
366371
zn = 0.0
367372

368373
ip1 = None
369-
ip2 = None
374+
ipn = None
370375
try:
371376
ip1_de = s_1[0x0020, 0x0032]
372377
ipn_de = s_n[0x0020, 0x0032]
@@ -404,7 +409,7 @@ def compute_affine_transform(self, s_1, s_n, n, series):
404409
m1[3, 2] = 0
405410
m1[3, 3] = 1
406411

407-
series.dicom_affine_transform = m1
412+
setattr(series, self.ATTRIBUTE_DICOM_AFFINE, m1)
408413

409414
m2[0, 0] = -rx * vr
410415
m2[0, 1] = -cx * vc
@@ -426,7 +431,7 @@ def compute_affine_transform(self, s_1, s_n, n, series):
426431
m2[3, 2] = 0
427432
m2[3, 3] = 1
428433

429-
series.nifti_affine_transform = m2
434+
setattr(series, self.ATTRIBUTE_NIFTI_AFFINE, m2)
430435

431436
def create_metadata(self, series) -> Dict:
432437
"""Collects all relevant metadata from the DICOM Series and creates a dictionary.

0 commit comments

Comments
 (0)