Skip to content

Commit 587f7aa

Browse files
committed
Minor updates to ensure compatibility with HSDK v3.5.0
Min Python version is now 3.10 Use of NumPy can_cast with Python type is removed Update the DICOM series to volume image to use pydicom apply_rescale directly Tested Jupyter notebooks (using locally built SDK for monai-deploy package command) Signed-off-by: M Q <[email protected]>
1 parent dec9305 commit 587f7aa

File tree

12 files changed

+903
-1348
lines changed

12 files changed

+903
-1348
lines changed

.github/workflows/pr.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ on:
1212
jobs:
1313
test:
1414
runs-on: ubuntu-latest
15+
strategy:
16+
matrix:
17+
python-version: [ "3.10", "3.11", "3.12", "3.13" ]
1518
steps:
1619
- uses: actions/checkout@v2
17-
- name: Set up Python 3.9
20+
- name: Set up Python ${{ matrix.python-version }}
1821
uses: actions/setup-python@v2
1922
with:
20-
python-version: "3.9"
23+
python-version: ${{ matrix.python-version }}
2124
- name: Setup Dev Environment
2225
run: |
2326
pip install virtualenv

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ pip install monai-deploy-app-sdk
4141
- This SDK depends on [NVIDIA Holoscan SDK](https://pypi.org/project/holoscan/) for its core implementation as well as its CLI, hence inherits its prerequisites, e.g. Ubuntu 22.04 with glibc 2.35 on X86-64 and NVIDIA dGPU drivers version 535 or above.
4242
- [CUDA 12.2](https://developer.nvidia.com/cuda-12-2-0-download-archive) or above is required along with a supported NVIDIA GPU with at least 8GB of video RAM.
4343
- If inference is not used in an example application and a GPU is not installed, at least [CUDA 12 runtime](https://pypi.org/project/nvidia-cuda-runtime-cu12/) is required, as this is one of the requirements of Holoscan SDK. In addition, the `LIB_LIBRARY_PATH` must be set to include the installed shared library, e.g. in a Python 3.10 env, ```export LD_LIBRARY_PATH=`pwd`/.venv/lib/python3.10/site-packages/nvidia/cuda_runtime/lib:$LD_LIBRARY_PATH```
44-
- Python: 3.9 to 3.12
44+
- Python: 3.10 to 3.13
4545

4646
## Getting Started
4747

docs/source/getting_started/tutorials/mednist_app.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ This tutorial demos the process of packaging up a trained model using MONAI Depl
55
## Setup
66

77
```bash
8-
# Create a virtual environment with Python 3.9.
8+
# Create a virtual environment with Python 3.10.
99
# Skip if you are already in a virtual environment.
10-
conda create -n mednist python=3.9 pytorch jupyterlab cudatoolkit=12.2 -c pytorch -c conda-forge
10+
conda create -n mednist python=3.10 pytorch jupyterlab cudatoolkit=12.2 -c pytorch -c conda-forge
1111
conda activate mednist
1212

1313
# Launch JupyterLab if you want to work on Jupyter Notebook

docs/source/getting_started/tutorials/monai_bundle_app.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ This tutorial shows how to create an organ segmentation application for a PyTorc
55
## Setup
66

77
```bash
8-
# Create a virtual environment with Python 3.9.
8+
# Create a virtual environment with Python 3.10.
99
# Skip if you are already in a virtual environment.
10-
conda create -n monai python=3.9 pytorch torchvision jupyterlab cudatoolkit=12.2 -c pytorch -c conda-forge
10+
conda create -n monai python=3.10 pytorch torchvision jupyterlab cudatoolkit=12.2 -c pytorch -c conda-forge
1111
conda activate monai
1212

1313
# Launch JupyterLab if you want to work on Jupyter Notebook

docs/source/getting_started/tutorials/multi_model_app.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ The models used in this example are trained with MONAI, and are packaged in the
77
## Setup
88

99
```bash
10-
# Create a virtual environment with Python 3.9.
10+
# Create a virtual environment with Python 3.10.
1111
# Skip if you are already in a virtual environment.
12-
conda create -n monai python=3.9 pytorch torchvision jupyterlab cudatoolkit=12.2 -c pytorch -c conda-forge
12+
conda create -n monai python=3.10 pytorch torchvision jupyterlab cudatoolkit=12.2 -c pytorch -c conda-forge
1313
conda activate monai
1414

1515
# Launch JupyterLab if you want to work on Jupyter Notebook

docs/source/getting_started/tutorials/segmentation_app.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ Please note that the following steps are for demonstration purpose. The code pul
77
## Setup
88

99
```bash
10-
# Create a virtual environment with Python 3.9.
10+
# Create a virtual environment with Python 3.10.
1111
# Skip if you are already in a virtual environment.
12-
conda create -n monai python=3.9 pytorch torchvision jupyterlab cudatoolkit=12.2 -c pytorch -c conda-forge
12+
conda create -n monai python=3.10 pytorch torchvision jupyterlab cudatoolkit=12.2 -c pytorch -c conda-forge
1313
conda activate monai
1414

1515
# Launch JupyterLab if you want to work on Jupyter Notebook

docs/source/getting_started/tutorials/segmentation_clara-viz_app.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ This tutorial shows how to create an organ segmentation application for a PyTorc
55
## Setup
66

77
```bash
8-
# Create a virtual environment with Python 3.9.
8+
# Create a virtual environment with Python 3.10.
99
# Skip if you are already in a virtual environment.
10-
conda create -n monai python=3.9 pytorch torchvision jupyterlab cudatoolkit=12.2 -c pytorch -c conda-forge
10+
conda create -n monai python=3.10 pytorch torchvision jupyterlab cudatoolkit=12.2 -c pytorch -c conda-forge
1111
conda activate monai
1212

1313
# Launch JupyterLab if you want to work on Jupyter Notebook

docs/source/getting_started/tutorials/simple_app.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ This tutorial shows how a simple image processing application can be created wit
55
## Setup
66

77
```bash
8-
# Create a virtual environment with Python 3.9.
8+
# Create a virtual environment with Python 3.10.
99
# Skip if you are already in a virtual environment.
10-
conda create -n monai python=3.9 pytorch torchvision jupyterlab cudatoolkit=12.2 -c pytorch -c conda-forge
10+
conda create -n monai python=3.10 pytorch torchvision jupyterlab cudatoolkit=12.2 -c pytorch -c conda-forge
1111
conda activate monai
1212

1313
# Launch JupyterLab if you want to work on Jupyter Notebook

monai/deploy/operators/dicom_series_to_volume_operator.py

Lines changed: 23 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
import numpy as np
1818

19+
from monai.deploy.utils.importutil import optional_import
20+
21+
apply_rescale, _ = optional_import("pydicom.pixels", name="apply_rescale")
22+
1923
from monai.deploy.core import ConditionType, Fragment, Operator, OperatorSpec
2024
from monai.deploy.core.domain.dicom_series_selection import StudySelectedSeries
2125
from monai.deploy.core.domain.image import Image
@@ -112,10 +116,13 @@ def generate_voxel_data(self, series):
112116
# with the NumPy array returned from the ITK GetArrayViewFromImage on the image
113117
# loaded from the same DICOM series.
114118
vol_data = np.stack([s.get_pixel_array() for s in slices], axis=0)
115-
# The above get_pixel_array() already considers the PixelRepresentation attribute,
116-
# 0 is unsigned int, 1 is signed int
117-
if slices[0][0x0028, 0x0103].value == 0:
118-
vol_data = vol_data.astype(np.uint16)
119+
120+
# Use pydicom utility to apply a modality lookup table or rescale operator to the pixel array.
121+
# The pydicom Dataset is required which can be obtained from the first slice's native SOP instance.
122+
# If Modality LUT is present the return array is of np.uint8 or np.uint16, and if Rescale
123+
# Intercept and Rescale Slope are present, np.float64.
124+
# If the pixel array is already in the correct type, the return array is the same as the input array.
125+
vol_data = apply_rescale(vol_data, slices[0].get_native_sop_instance())
119126

120127
# For now we support monochrome image only, for which DICOM Photometric Interpretation
121128
# (0028,0004) has defined terms, MONOCHROME1 and MONOCHROME2, with the former being:
@@ -146,50 +153,20 @@ def generate_voxel_data(self, series):
146153
f"Cannot process pixel data with Photometric Interpretation of {photometric_interpretation}."
147154
)
148155

149-
# Rescale Intercept and Slope attributes might be missing, but safe to assume defaults.
150-
try:
151-
intercept = slices[0][0x0028, 0x1052].value
152-
except KeyError:
153-
intercept = 0
154-
155-
try:
156-
slope = slices[0][0x0028, 0x1053].value
157-
except KeyError:
158-
slope = 1
159-
160-
# check if vol_data, intercept, and slope can be cast to uint16 without data loss
161-
if (
162-
np.can_cast(vol_data, np.uint16, casting="safe")
163-
and np.can_cast(intercept, np.uint16, casting="safe")
164-
and np.can_cast(slope, np.uint16, casting="safe")
165-
):
156+
# NumPy's np.can_cast function, as of version 2.0, no longer supports Python scalars directly and
157+
# does not apply value-based logic for 0-D arrays and NumPy scalars.
158+
# The following can_cast calls are expecting the array is already of the correct type.
159+
if vol_data.dtype == np.uint8:
160+
logging.info("Rescaled pixel data is of type uint8.")
161+
elif np.can_cast(vol_data, np.uint16, casting="safe"):
166162
logging.info("Casting to uint16")
167-
vol_data = np.array(vol_data, dtype=np.uint16)
168-
intercept = np.uint16(intercept)
169-
slope = np.uint16(slope)
170-
elif (
171-
np.can_cast(vol_data, np.float32, casting="safe")
172-
and np.can_cast(intercept, np.float32, casting="safe")
173-
and np.can_cast(slope, np.float32, casting="safe")
174-
):
163+
vol_data = vol_data.astype(dtype=np.uint16, casting="safe")
164+
elif np.can_cast(vol_data, np.float32, casting="safe"):
175165
logging.info("Casting to float32")
176-
vol_data = np.array(vol_data, dtype=np.float32)
177-
intercept = np.float32(intercept)
178-
slope = np.float32(slope)
179-
elif (
180-
np.can_cast(vol_data, np.float64, casting="safe")
181-
and np.can_cast(intercept, np.float64, casting="safe")
182-
and np.can_cast(slope, np.float64, casting="safe")
183-
):
184-
logging.info("Casting to float64")
185-
vol_data = np.array(vol_data, dtype=np.float64)
186-
intercept = np.float64(intercept)
187-
slope = np.float64(slope)
188-
189-
if slope != 1:
190-
vol_data = slope * vol_data
191-
192-
vol_data += intercept
166+
vol_data = vol_data.astype(dtype=np.float32, casting="safe")
167+
else:
168+
logging.info("Rescaled pixel data remains as of type float64.")
169+
193170
return vol_data
194171

195172
def create_volumetric_image(self, vox_data, metadata):

notebooks/tutorials/01_simple_app.ipynb

Lines changed: 544 additions & 269 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)