Skip to content

Commit d9df3d4

Browse files
committed
Enhance model loading and configuration handling in MonaiBundleInferenceOperator
- Introduced helper functions to streamline model loading from directory-based bundles and ensure proper configuration parsing. - Improved error handling for missing model files and configuration metadata. - Refactored the get_bundle_config function to utilize the new helper for better code organization and readability. - Updated the NiftiDataLoader to handle image dimensionalities more effectively using SimpleITK metadata. Signed-off-by: Victor Chang <[email protected]>
1 parent c7cdd48 commit d9df3d4

File tree

7 files changed

+277
-131
lines changed

7 files changed

+277
-131
lines changed

monai/deploy/operators/monai_bundle_inference_operator.py

Lines changed: 232 additions & 104 deletions
Large diffs are not rendered by default.

monai/deploy/operators/nii_data_loader_operator.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,20 +82,34 @@ def convert_and_save(self, nii_path):
8282
image = image_reader.Execute()
8383
image_np = SimpleITK.GetArrayFromImage(image)
8484

85-
# Handle different dimensionalities properly
86-
if image_np.ndim == 3:
87-
# Standard 3D volume: transpose from (z, y, x) to (x, y, z)
88-
image_np = np.transpose(image_np, [2, 1, 0])
89-
elif image_np.ndim == 4:
90-
# 4D volume with channels: (c, z, y, x) to (c, x, y, z)
91-
image_np = np.transpose(image_np, [0, 3, 2, 1])
92-
elif image_np.ndim == 2:
93-
# 2D slice: transpose from (y, x) to (x, y)
94-
image_np = np.transpose(image_np, [1, 0])
85+
# Get image metadata to properly distinguish between different image types
86+
spatial_dims = image.GetDimension() # Actual spatial dimensions (2D, 3D, etc.)
87+
num_components = image.GetNumberOfComponentsPerPixel() # Components/channels per pixel
88+
89+
self._logger.debug(f"Image spatial dimensions: {spatial_dims}, components per pixel: {num_components}, array shape: {image_np.shape}")
90+
91+
# Handle different dimensionalities properly using SimpleITK metadata
92+
if spatial_dims == 2:
93+
if num_components == 1:
94+
# 2D grayscale: transpose from (y, x) to (x, y)
95+
image_np = np.transpose(image_np, [1, 0])
96+
else:
97+
# 2D with multiple components/channels: transpose from (y, x, c) to (x, y, c)
98+
# SimpleITK stores multi-component 2D images as (y, x, c)
99+
image_np = np.transpose(image_np, [1, 0, 2])
100+
elif spatial_dims == 3:
101+
if num_components == 1:
102+
# 3D grayscale volume: transpose from (z, y, x) to (x, y, z)
103+
image_np = np.transpose(image_np, [2, 1, 0])
104+
else:
105+
# 3D volume with multiple components: transpose from (z, y, x, c) to (x, y, z, c)
106+
# SimpleITK stores multi-component 3D images as (z, y, x, c)
107+
image_np = np.transpose(image_np, [2, 1, 0, 3])
95108
else:
96-
# For other dimensions, log a warning and return as-is
109+
# For other spatial dimensions, log a warning and return as-is
97110
self._logger.warning(
98-
f"Unexpected {image_np.ndim}D NIfTI file shape {image_np.shape} from {nii_path}, returning without transpose"
111+
f"Unexpected {spatial_dims}D spatial image with {num_components} components per pixel, "
112+
f"array shape {image_np.shape} from {nii_path}, returning without transpose"
99113
)
100114

101115
return image_np

tools/pipeline-generator/README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ uv run pg gen MONAI/spleen_ct_segmentation --output my_app
5252
uv run pg run my_app --input /path/to/test/data --output ./results
5353
```
5454

55+
> [!NOTE]
56+
> Input can be NIfTI files (.nii, .nii.gz) or DICOM series directories Refer to [config.yaml](tools/pipeline-generator/pipeline_generator/config/config.yaml) for details.
57+
58+
> [!NOTE]
59+
> For DICOM input types, refer to the model documentation for DICOM series processing limitations.
60+
5561
### List Available Models
5662

5763
List all models from configured endpoints:
@@ -107,6 +113,7 @@ Options:
107113
- `--format`: Input/output data format (optional): auto, dicom, or nifti (default: auto)
108114
- For tested models, format is automatically detected from configuration
109115
- For untested models, attempts detection from model metadata
116+
- **DICOM Limitation**: Refer to the model documentation for multi-series support.
110117
- `--force, -f`: Overwrite existing output directory
111118

112119
Generate with custom application class name:
@@ -196,7 +203,7 @@ output/
196203
├── README.md # Documentation
197204
├── operators/ # Custom operators (if needed)
198205
│ └── nifti_operators.py
199-
└── model/ # Downloaded MONAI Bundle
206+
└── model/ # Downloaded model files
200207
├── configs/
201208
├── models/
202209
└── docs/

tools/pipeline-generator/docs/design.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ By bridging the gap between model packaging and application deployment, this pro
1313

1414
## **Background**
1515

16-
The **MONAI Bundle** is a standardized format designed to package deep learning models for medical imaging. It includes the model weights, metadata, transforms, and documentation needed to make the model self-contained and portable.
16+
The **[MONAI Bundle](https://docs.monai.io/en/latest/mb_specification.html#archive-format)** is a standardized format designed to package deep learning models for medical imaging. It includes the model weights, metadata, transforms, and documentation needed to make the model self-contained and portable.
1717

18-
The **Holoscan SDK** is NVIDIA’s real-time streaming application SDK for AI workloads in healthcare and edge devices. The **MONAI Deploy App SDK** is designed for building composable inference applications, often used in radiology and imaging pipelines.
18+
The **Holoscan SDK** is NVIDIA’s real-time streaming application SDK for AI workloads in healthcare and edge devices. The **MONAI Deploy App SDK** builds on top of the Holoscan SDK, adding medical imaging domain-specific operators and functionalities to enable the creation of composable inference applications, particularly for radiology and imaging pipelines.
1919

2020
As of MONAI Core, bundles can also be exported in a **Hugging Face-compatible format**, which allows sharing through the Hugging Face Model Hub. Supporting this format increases reach and adoption.
2121

@@ -28,9 +28,8 @@ As of MONAI Core, bundles can also be exported in a **Hugging Face-compatible fo
2828

2929
## **Assumptions/Limitations**
3030

31-
- The tool does not convert input formats given that each model may expect a different type of input
3231
- The tool does not convert output formats given that each model may output a different type of result
33-
- The tool supports only torchscript (ts) models
32+
- The generated application does not convert input formats given that each model may expect a different type of input
3433

3534
## **Scope**
3635

@@ -55,7 +54,7 @@ This project includes:
5554
- PyTorch state dict (.pt): Loaded with model definition code/config
5655
- Hugging Face-compatible: Recognized and unpacked with reference to Hugging Face conventions
5756
- **AI Inference Operator Integration**
58-
- Python and C++ support for TorchScript/ONNX-based inference
57+
- Python support for TorchScript/ONNX-based inference
5958
- Auto-configures model inputs/outputs based on network_data_format
6059
- Embeds optional postprocessing like argmax, thresholding, etc.
6160
- **Preprocessing/Postprocessing Pipeline**
@@ -70,7 +69,6 @@ This project includes:
7069
- **Tooling**
7170
- Command-line tool to:
7271
- Validate MONAI Bundles
73-
- Convert .pt → .ts/.onnx
7472
- Generate MONAI Deploy and Holoscan-ready configs
7573
- Extract and display metadata (task, inputs, author, etc.)
7674

tools/pipeline-generator/pipeline_generator/templates/README.md.j2

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -168,21 +168,21 @@ Run directly using Python as shown above.
168168

169169
### Container Deployment
170170

171-
Package the application as a container using Holoscan CLI:
171+
Package the application as a container using MONAI Deploy CLI:
172172

173173
```bash
174174
# Package for x64 workstations
175-
holoscan package app -c app.yaml --platform linux/amd64 -t {{ model_short_name|lower }}:latest
175+
monai-deploy package app -c app.yaml --platform linux/amd64 -t {{ model_short_name|lower }}:latest
176176

177177
# Package for IGX Orin devkits
178-
holoscan package app -c app.yaml --platform linux/arm64 -t {{ model_short_name|lower }}:latest
178+
monai-deploy package app -c app.yaml --platform linux/arm64 -t {{ model_short_name|lower }}:latest
179179
```
180180

181181
Run the containerized application:
182182

183183
```bash
184-
# Using Holoscan CLI
185-
holoscan run -i /path/to/input -o /path/to/output {{ model_short_name|lower }}:latest
184+
# Using MONAI Deploy CLI
185+
monai-deploy run -i /path/to/input -o /path/to/output {{ model_short_name|lower }}:latest
186186

187187
# Or using Docker directly
188188
docker run -v /path/to/input:/input -v /path/to/output:/output {{ model_short_name|lower }}:latest

tools/pipeline-generator/pipeline_generator/templates/operators/generic_directory_scanner_operator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,14 @@ def __init__(
5656
self._case_sensitive = bool(case_sensitive)
5757

5858
# State tracking
59-
self._files: list[Path] = []
59+
self._files: List[Path] = []
6060
self._current_index = 0
6161

6262
super().__init__(fragment, *args, **kwargs)
6363

6464
def _find_files(self) -> List[Path]:
6565
"""Find all files matching the specified extensions."""
66-
files = []
66+
files: List[Path] = []
6767

6868
# Normalize extensions for comparison
6969
if not self._case_sensitive:

tools/pipeline-generator/pipeline_generator/templates/requirements.txt.j2

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
# MONAI Deploy App SDK and dependencies
55
monai-deploy-app-sdk>=3.0.0
66

7-
87
# Required by MONAI Deploy SDK (always needed)
98
pydicom>=2.3.0 # Required by MONAI Deploy SDK even for NIfTI apps
109
highdicom>=0.18.2 # Required for DICOM segmentation support

0 commit comments

Comments
 (0)