Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pip install monai-deploy-app-sdk
- 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.
- [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.
- 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```
- Python: 3.9 to 3.12
- Python: 3.10 to 3.13

## Getting Started

Expand Down
4 changes: 2 additions & 2 deletions docs/source/getting_started/tutorials/mednist_app.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ This tutorial demos the process of packaging up a trained model using MONAI Depl
## Setup

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

# Launch JupyterLab if you want to work on Jupyter Notebook
Expand Down
4 changes: 2 additions & 2 deletions docs/source/getting_started/tutorials/monai_bundle_app.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ This tutorial shows how to create an organ segmentation application for a PyTorc
## Setup

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

# Launch JupyterLab if you want to work on Jupyter Notebook
Expand Down
4 changes: 2 additions & 2 deletions docs/source/getting_started/tutorials/multi_model_app.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ The models used in this example are trained with MONAI, and are packaged in the
## Setup

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

# Launch JupyterLab if you want to work on Jupyter Notebook
Expand Down
4 changes: 2 additions & 2 deletions docs/source/getting_started/tutorials/segmentation_app.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ Please note that the following steps are for demonstration purpose. The code pul
## Setup

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

# Launch JupyterLab if you want to work on Jupyter Notebook
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ This tutorial shows how to create an organ segmentation application for a PyTorc
## Setup

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

# Launch JupyterLab if you want to work on Jupyter Notebook
Expand Down
4 changes: 2 additions & 2 deletions docs/source/getting_started/tutorials/simple_app.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ This tutorial shows how a simple image processing application can be created wit
## Setup

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

# Launch JupyterLab if you want to work on Jupyter Notebook
Expand Down
4 changes: 2 additions & 2 deletions monai/deploy/operators/dicom_data_loader_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,10 +437,10 @@ def test():
print(f" 'SeriesDescription': {ds.SeriesDescription if ds.SeriesDescription else ''}")
print(
" 'IssuerOfPatientID':"
f" {ds.get('IssuerOfPatientID', '').repval if ds.get('IssuerOfPatientID', '') else '' }"
f" {ds.get('IssuerOfPatientID', '').repval if ds.get('IssuerOfPatientID', '') else ''}"
)
try:
print(f" 'IssuerOfPatientID': {ds.IssuerOfPatientID if ds.IssuerOfPatientID else '' }")
print(f" 'IssuerOfPatientID': {ds.IssuerOfPatientID if ds.IssuerOfPatientID else ''}")
except AttributeError:
print(
" If the IssuerOfPatientID does not exist, ds.IssuerOfPatientID would throw AttributeError."
Expand Down
74 changes: 28 additions & 46 deletions monai/deploy/operators/dicom_series_to_volume_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

import numpy as np

from monai.deploy.utils.importutil import optional_import

apply_rescale, _ = optional_import("pydicom.pixels", name="apply_rescale")

from monai.deploy.core import ConditionType, Fragment, Operator, OperatorSpec
from monai.deploy.core.domain.dicom_series_selection import StudySelectedSeries
from monai.deploy.core.domain.image import Image
Expand Down Expand Up @@ -112,10 +116,18 @@ def generate_voxel_data(self, series):
# with the NumPy array returned from the ITK GetArrayViewFromImage on the image
# loaded from the same DICOM series.
vol_data = np.stack([s.get_pixel_array() for s in slices], axis=0)
# The above get_pixel_array() already considers the PixelRepresentation attribute,
# 0 is unsigned int, 1 is signed int
if slices[0][0x0028, 0x0103].value == 0:
vol_data = vol_data.astype(np.uint16)

# Use pydicom utility to apply a modality lookup table or rescale operator to the pixel array.
# The pydicom Dataset is required which can be obtained from the first slice's native SOP instance.
# If Modality LUT is present the return array is of np.uint8 or np.uint16, and if Rescale
# Intercept and Rescale Slope are present, np.float64.
# If the pixel array is already in the correct type, the return array is the same as the input array.
try:
native_sop = slices[0].get_native_sop_instance()
vol_data = apply_rescale(vol_data, native_sop)
except Exception as e:
logging.error(f"Failed to apply rescale to DICOM volume: {e}")
raise RuntimeError("Failed to apply rescale to DICOM volume.") from e

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

# Rescale Intercept and Slope attributes might be missing, but safe to assume defaults.
try:
intercept = slices[0][0x0028, 0x1052].value
except KeyError:
intercept = 0

try:
slope = slices[0][0x0028, 0x1053].value
except KeyError:
slope = 1

# check if vol_data, intercept, and slope can be cast to uint16 without data loss
if (
np.can_cast(vol_data, np.uint16, casting="safe")
and np.can_cast(intercept, np.uint16, casting="safe")
and np.can_cast(slope, np.uint16, casting="safe")
):
# NumPy's np.can_cast function, as of version 2.0, no longer supports Python scalars directly and
# does not apply value-based logic for 0-D arrays and NumPy scalars.
# The following can_cast calls are expecting the array is already of the correct type.
if vol_data.dtype == np.uint8:
logging.info("Rescaled pixel data is of type uint8.")
elif np.can_cast(vol_data, np.uint16, casting="safe"):
logging.info("Casting to uint16")
vol_data = np.array(vol_data, dtype=np.uint16)
intercept = np.uint16(intercept)
slope = np.uint16(slope)
elif (
np.can_cast(vol_data, np.float32, casting="safe")
and np.can_cast(intercept, np.float32, casting="safe")
and np.can_cast(slope, np.float32, casting="safe")
):
vol_data = vol_data.astype(dtype=np.uint16, casting="safe")
elif np.can_cast(vol_data, np.float32, casting="safe"):
logging.info("Casting to float32")
vol_data = np.array(vol_data, dtype=np.float32)
intercept = np.float32(intercept)
slope = np.float32(slope)
elif (
np.can_cast(vol_data, np.float64, casting="safe")
and np.can_cast(intercept, np.float64, casting="safe")
and np.can_cast(slope, np.float64, casting="safe")
):
logging.info("Casting to float64")
vol_data = np.array(vol_data, dtype=np.float64)
intercept = np.float64(intercept)
slope = np.float64(slope)

if slope != 1:
vol_data = slope * vol_data

vol_data += intercept
vol_data = vol_data.astype(dtype=np.float32, casting="safe")
else:
logging.info("Rescaled pixel data remains as of type float64.")

return vol_data

def create_volumetric_image(self, vox_data, metadata):
Expand Down
813 changes: 544 additions & 269 deletions notebooks/tutorials/01_simple_app.ipynb

Large diffs are not rendered by default.

Loading