Skip to content

Commit b063423

Browse files
committed
Make affine and space consistent as space is properly parse by MONAI transforms
Signed-off-by: M Q <mingmelvinq@nvidia.com>
1 parent ae4c045 commit b063423

File tree

3 files changed

+1044
-233
lines changed

3 files changed

+1044
-233
lines changed

monai/deploy/operators/dicom_series_to_volume_operator.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ class DICOMSeriesToVolumeOperator(Operator):
4040
"""
4141

4242
# Use constants instead of enums in monai to avoid dependency at this level.
43-
MONAI_UTIL_ENUMS_SPACEKEYS_LPS = "LPS"
43+
MONAI_UTIL_ENUMS_SPACEKEYS_RAS = "RAS"
4444
MONAI_TRANSFORMS_SPATIAL_METADATA_NAME = "space"
45-
METADATA_SPACE_LPS = {MONAI_TRANSFORMS_SPATIAL_METADATA_NAME: MONAI_UTIL_ENUMS_SPACEKEYS_LPS}
45+
METADATA_SPACE_RAS = {MONAI_TRANSFORMS_SPATIAL_METADATA_NAME: MONAI_UTIL_ENUMS_SPACEKEYS_RAS}
4646

4747
def __init__(self, fragment: Fragment, *args, **kwargs):
4848
"""Create an instance for a containing application object.
@@ -100,7 +100,7 @@ def convert_to_image(self, study_selected_series_list: List[StudySelectedSeries]
100100
# due part to previous MONAI versions did not correctly parse this metadata from
101101
# the input MetaTensor and defaulting to RAS. Now with LPS properly set, the inference
102102
# configs then need to be updated to specify LPS, to achieve the same result.
103-
metadata.update(self.METADATA_SPACE_LPS)
103+
metadata.update(self.METADATA_SPACE_RAS)
104104

105105
voxel_data = self.generate_voxel_data(dicom_series)
106106
image = self.create_volumetric_image(voxel_data, metadata)

monai/deploy/operators/monai_seg_inference_operator.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,20 @@
3131
ImageReader: Any = ImageReader_
3232
if not image_reader_ok_:
3333
ImageReader = object # for 'class InMemImageReader(ImageReader):' to work
34+
is_no_channel, _ = optional_import("monai.data.utils", name="is_no_channel")
3435
decollate_batch, _ = optional_import("monai.data", name="decollate_batch")
3536
sliding_window_inference, _ = optional_import("monai.inferers", name="sliding_window_inference")
3637
simple_inference, _ = optional_import("monai.inferers", name="SimpleInferer")
3738
ensure_tuple, _ = optional_import(MONAI_UTILS, name="ensure_tuple")
3839
MetaKeys, _ = optional_import(MONAI_UTILS, name="MetaKeys")
3940
SpaceKeys, _ = optional_import(MONAI_UTILS, name="SpaceKeys")
41+
TraceKeys, _ = optional_import(MONAI_UTILS, name="TraceKeys")
4042
Compose_, _ = optional_import("monai.transforms", name="Compose")
4143
# Dynamic class is not handled so make it Any for now: https://github.com/python/mypy/issues/2477
4244
Compose: Any = Compose_
4345

46+
cp, has_cp = optional_import("cupy")
47+
4448
from monai.deploy.core import AppContext, Condition, ConditionType, Fragment, Image, OperatorSpec, Resource
4549

4650
from .inference_operator import InferenceOperator
@@ -362,6 +366,7 @@ def compute(self, op_input, op_output, context):
362366
self._executing = True
363367
try:
364368
input_image = op_input.receive(self._input_name_image)
369+
365370
if not input_image:
366371
raise ValueError("Input is None.")
367372
op_output.emit(self.compute_impl(input_image, context), self._output_name_seg)
@@ -592,7 +597,7 @@ def _get_meta_dict(self, img: Image) -> Dict:
592597
return meta_dict
593598

594599

595-
# Reuse MONAI code for the derived ImageReader
600+
# Reuse MONAI code for the derived ImageReader as it is not exposed
596601
def _copy_compatible_dict(from_dict: Dict, to_dict: Dict):
597602
if not isinstance(to_dict, dict):
598603
raise ValueError(f"to_dict must be a Dict, got {type(to_dict)}.")
@@ -601,7 +606,7 @@ def _copy_compatible_dict(from_dict: Dict, to_dict: Dict):
601606
datum = from_dict[key]
602607
if isinstance(datum, np.ndarray) and np_str_obj_array_pattern.search(datum.dtype.str) is not None:
603608
continue
604-
to_dict[key] = datum
609+
to_dict[key] = str(TraceKeys.NONE) if datum is None else datum # NoneType to string for default_collate
605610
else:
606611
affine_key, shape_key = MetaKeys.AFFINE, MetaKeys.SPATIAL_SHAPE
607612
if affine_key in from_dict and not np.allclose(from_dict[affine_key], to_dict[affine_key]):
@@ -616,12 +621,16 @@ def _copy_compatible_dict(from_dict: Dict, to_dict: Dict):
616621
)
617622

618623

619-
def _stack_images(image_list: List, meta_dict: Dict):
624+
def _stack_images(image_list: list, meta_dict: dict, to_cupy: bool = False):
620625
if len(image_list) <= 1:
621626
return image_list[0]
622-
if meta_dict.get(MetaKeys.ORIGINAL_CHANNEL_DIM, None) not in ("no_channel", None):
627+
if not is_no_channel(meta_dict.get(MetaKeys.ORIGINAL_CHANNEL_DIM, None)):
623628
channel_dim = int(meta_dict[MetaKeys.ORIGINAL_CHANNEL_DIM])
629+
if to_cupy and has_cp:
630+
return cp.concatenate(image_list, axis=channel_dim)
624631
return np.concatenate(image_list, axis=channel_dim)
625632
# stack at a new first dim as the channel dim, if `'original_channel_dim'` is unspecified
626633
meta_dict[MetaKeys.ORIGINAL_CHANNEL_DIM] = 0
634+
if to_cupy and has_cp:
635+
return cp.stack(image_list, axis=0)
627636
return np.stack(image_list, axis=0)

0 commit comments

Comments
 (0)