From d099150496f13212f96f52f89005ba6fb891be42 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Sat, 5 Aug 2023 00:38:31 +0200 Subject: [PATCH 1/3] inital LUNA22 Ismi baseline model implementation --- .../config/default.yml | 22 +++++++++ .../dockerfiles/Dockerfile | 44 ++++++++++++++++++ .../utils/Luna22IsmiBaselineRunner.py | 45 +++++++++++++++++++ .../gc_luna22_ismi_baseline/utils/__init__.py | 1 + 4 files changed, 112 insertions(+) create mode 100644 models/gc_luna22_ismi_baseline/config/default.yml create mode 100644 models/gc_luna22_ismi_baseline/dockerfiles/Dockerfile create mode 100644 models/gc_luna22_ismi_baseline/utils/Luna22IsmiBaselineRunner.py create mode 100644 models/gc_luna22_ismi_baseline/utils/__init__.py diff --git a/models/gc_luna22_ismi_baseline/config/default.yml b/models/gc_luna22_ismi_baseline/config/default.yml new file mode 100644 index 00000000..2e631a15 --- /dev/null +++ b/models/gc_luna22_ismi_baseline/config/default.yml @@ -0,0 +1,22 @@ +general: + data_base_dir: /app/data + version: 1.0 + description: LUNA22 ISMI Baseline lung CT nodule malignancy and nodule type classification (dicom to json) + +execute: +- DicomImporter +- MhaConverter +- Luna22IsmiBaselineRunner +- DataOrganizer + +modules: + DicomImporter: + source_dir: input_data + import_dir: sorted_data + sort_data: True + meta: + mod: ct + + DataOrganizer: + targets: + - json-->[i:sid]/nodule_predictions.json diff --git a/models/gc_luna22_ismi_baseline/dockerfiles/Dockerfile b/models/gc_luna22_ismi_baseline/dockerfiles/Dockerfile new file mode 100644 index 00000000..447c0129 --- /dev/null +++ b/models/gc_luna22_ismi_baseline/dockerfiles/Dockerfile @@ -0,0 +1,44 @@ +FROM mhubai/base:latest + +# Update authors label +LABEL authors="s.vandeleemput@radboudumc.nl" + +# Install git-lfs (required for unpacking model weights) +RUN apt update && apt install -y --no-install-recommends git-lfs && rm -rf /var/lib/apt/lists/* + +# build/install CUDA 11 toolkit cudnn 8 and tensorflow 2.11.0 with GPU support (without conda) +RUN pip3 install --no-cache-dir \ + nvidia-cuda-runtime-cu11 \ + nvidia-cusolver-cu11 \ + nvidia-curand-cu11 \ + nvidia-cufft-cu11 \ + nvidia-cublas-cu11 \ + nvidia-cusparse-cu11 \ + nvidia-cudnn-cu11 \ + tensorflow==2.11.0 \ + nvidia-tensorrt==7.2.3.4 \ + --extra-index-url https://pypi.ngc.nvidia.com + +# Configure required paths for tensorflow with GPU support +ENV NVIDIA_DIR /usr/local/lib/python3.8/dist-packages/nvidia +ENV LD_LIBRARY_PATH /usr/local/lib/python3.8/dist-packages/tensorrt:$NVIDIA_DIR/cublas/lib:$NVIDIA_DIR/cuda_runtime/lib:$NVIDIA_DIR/cudnn/lib:$NVIDIA_DIR/cufft/lib:$NVIDIA_DIR/curand/lib:$NVIDIA_DIR/cusolver/lib:$NVIDIA_DIR/cusparse/lib + +# Clone the main branch of MHubAI/models TODO enable +#RUN git stash \ +# && git fetch https://github.com/MHubAI/models.git main \ +# && git merge FETCH_HEAD \ +# && git sparse-checkout set "models/gc_luna22_ismi_baseline" \ +# && git fetch https://github.com/MHubAI/models.git main + +# Install luna22 ismi baseline algorithm and model weights +RUN git clone --depth 1 https://github.com/DIAGNijmegen/bodyct-luna22-ismi-algorithm-baseline.git /opt/algorithm + +# Install algorithm requirements +RUN pip3 install --no-cache-dir evalutils==0.3.1 + +# Set PYTHONPATH to include algorithm code +ENV PYTHONPATH "/app:/opt/algorithm" + +# Default run script +ENTRYPOINT ["python3", "-m", "mhubio.run"] +CMD ["--config", "/app/models/gc_luna22_ismi_baseline/config/default.yml"] diff --git a/models/gc_luna22_ismi_baseline/utils/Luna22IsmiBaselineRunner.py b/models/gc_luna22_ismi_baseline/utils/Luna22IsmiBaselineRunner.py new file mode 100644 index 00000000..a2ae801c --- /dev/null +++ b/models/gc_luna22_ismi_baseline/utils/Luna22IsmiBaselineRunner.py @@ -0,0 +1,45 @@ +""" +------------------------------------------------------------------ +Mhub / DIAG - Run Module for the GC Luna22 ISMI baseline Algorithm +------------------------------------------------------------------ + +------------------------------------------------------------------ +Author: Sil van de Leemput +Email: sil.vandeleemput@radboudumc.nl +------------------------------------------------------------------ +""" + +import json +from pathlib import Path +from typing import Optional + +from mhubio.core import Instance, InstanceData, IO, Module + +import SimpleITK + +from process import Nodule_classifier as NoduleClassifier + +import tensorflow as tf + + +class Luna22IsmiBaselineRunner(Module): + + _cached_classifier: Optional[NoduleClassifier] = None + + @IO.Instance() + @IO.Input('in_data', 'mha|nrrd|nifti:mod=ct', the='input volume centered around a nodule from a lung CT image') + @IO.Output('out_data', 'nodule_predictions.json', 'json:model=Luna22IsmiBaseline', 'in_data', the='Luna22 Ismi nodule predictions (malignancy / nodule type)') + def task(self, instance: Instance, in_data: InstanceData, out_data: InstanceData) -> None: + assert len(tf.config.list_physical_devices("GPU")) >= 1, \ + "Error: Luna22IsmiBaselineRunner must be run on a GPU! Because: " \ + "The Conv2D op currently only supports the NHWC tensor format on" \ + " the CPU. The op was given the format: NCHW [Op:Conv2D]" + + input_image = SimpleITK.ReadImage(in_data.abspath) + if self._cached_classifier is None: + self.v("Luna22IsmiBaselineRunner - Loading and caching model weights") + self._cached_classifier = NoduleClassifier() + classifier = self._cached_classifier + predictions = classifier.predict(input_image=input_image) + with open(out_data.abspath, "w") as f: + json.dump(predictions, f, indent=4) diff --git a/models/gc_luna22_ismi_baseline/utils/__init__.py b/models/gc_luna22_ismi_baseline/utils/__init__.py new file mode 100644 index 00000000..a7631d89 --- /dev/null +++ b/models/gc_luna22_ismi_baseline/utils/__init__.py @@ -0,0 +1 @@ +from Luna22IsmiBaselineRunner import * From 50a29401b4773a397e09fcee8cb7f9c336617b30 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Tue, 29 Aug 2023 17:23:28 +0200 Subject: [PATCH 2/3] add reportexporter support and switch to panimg mhaconverter backend --- .../config/default.yml | 16 +++++++++- .../utils/Luna22IsmiBaselineRunner.py | 30 +++++++++++++++---- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/models/gc_luna22_ismi_baseline/config/default.yml b/models/gc_luna22_ismi_baseline/config/default.yml index 2e631a15..1e5cfd85 100644 --- a/models/gc_luna22_ismi_baseline/config/default.yml +++ b/models/gc_luna22_ismi_baseline/config/default.yml @@ -7,6 +7,7 @@ execute: - DicomImporter - MhaConverter - Luna22IsmiBaselineRunner +- ReportExporter - DataOrganizer modules: @@ -17,6 +18,19 @@ modules: meta: mod: ct + MhaConverter: + engine: panimg + + ReportExporter: + format: compact + includes: + - data: malignancy_risk + label: malignancy_risk + value: value + - data: texture + label: texture + value: value + DataOrganizer: targets: - - json-->[i:sid]/nodule_predictions.json + - json:mod=report-->[i:sid]/nodule_predictions.json diff --git a/models/gc_luna22_ismi_baseline/utils/Luna22IsmiBaselineRunner.py b/models/gc_luna22_ismi_baseline/utils/Luna22IsmiBaselineRunner.py index a2ae801c..8d26dc32 100644 --- a/models/gc_luna22_ismi_baseline/utils/Luna22IsmiBaselineRunner.py +++ b/models/gc_luna22_ismi_baseline/utils/Luna22IsmiBaselineRunner.py @@ -13,7 +13,7 @@ from pathlib import Path from typing import Optional -from mhubio.core import Instance, InstanceData, IO, Module +from mhubio.core import Instance, InstanceData, IO, Module, ValueOutput, ClassOutput, Meta import SimpleITK @@ -22,14 +22,34 @@ import tensorflow as tf +@ValueOutput.Name('malignancy_risk') +@ValueOutput.Meta(Meta(key="value")) +@ValueOutput.Label('MalignancyRisk') +@ValueOutput.Type(float) +@ValueOutput.Description('Probability of the nodule malignancy.') +class MalignancyRiskOutput(ValueOutput): + pass + + +@ClassOutput.Name('texture') +@ClassOutput.Label('NoduleType') +@ClassOutput.Description('Prediction of the nodule type.') +@ClassOutput.Class(0, 'Non-solid', 'Non-solid.') +@ClassOutput.Class(1, 'Part-solid', 'Part-solid.') +@ClassOutput.Class(2, 'Solid', 'Solid.') +class NoduleTypeOutput(ClassOutput): + pass + + class Luna22IsmiBaselineRunner(Module): _cached_classifier: Optional[NoduleClassifier] = None @IO.Instance() @IO.Input('in_data', 'mha|nrrd|nifti:mod=ct', the='input volume centered around a nodule from a lung CT image') - @IO.Output('out_data', 'nodule_predictions.json', 'json:model=Luna22IsmiBaseline', 'in_data', the='Luna22 Ismi nodule predictions (malignancy / nodule type)') - def task(self, instance: Instance, in_data: InstanceData, out_data: InstanceData) -> None: + @IO.OutputData('malignancy_risk', MalignancyRiskOutput, data='in_data', the='Luna22 Ismi nodule malignancy probability') + @IO.OutputData('texture', NoduleTypeOutput, data='in_data', the='Luna22 Ismi nodule type classification') + def task(self, instance: Instance, in_data: InstanceData, malignancy_risk: MalignancyRiskOutput, texture: NoduleTypeOutput) -> None: assert len(tf.config.list_physical_devices("GPU")) >= 1, \ "Error: Luna22IsmiBaselineRunner must be run on a GPU! Because: " \ "The Conv2D op currently only supports the NHWC tensor format on" \ @@ -41,5 +61,5 @@ def task(self, instance: Instance, in_data: InstanceData, out_data: InstanceData self._cached_classifier = NoduleClassifier() classifier = self._cached_classifier predictions = classifier.predict(input_image=input_image) - with open(out_data.abspath, "w") as f: - json.dump(predictions, f, indent=4) + malignancy_risk.value = predictions["malignancy_risk"] + texture.value = predictions["texture"] From caa5b986ec3f30a4926d928e2a96bee2205dc0e8 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Thu, 14 Sep 2023 16:15:45 +0200 Subject: [PATCH 3/3] fixed commit hash in Dockerfile, added comments to runner --- .../gc_luna22_ismi_baseline/dockerfiles/Dockerfile | 7 ++++--- .../utils/Luna22IsmiBaselineRunner.py | 12 ++++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/models/gc_luna22_ismi_baseline/dockerfiles/Dockerfile b/models/gc_luna22_ismi_baseline/dockerfiles/Dockerfile index 447c0129..e756ea49 100644 --- a/models/gc_luna22_ismi_baseline/dockerfiles/Dockerfile +++ b/models/gc_luna22_ismi_baseline/dockerfiles/Dockerfile @@ -1,7 +1,7 @@ FROM mhubai/base:latest # Update authors label -LABEL authors="s.vandeleemput@radboudumc.nl" +LABEL authors="sil.vandeleemput@radboudumc.nl" # Install git-lfs (required for unpacking model weights) RUN apt update && apt install -y --no-install-recommends git-lfs && rm -rf /var/lib/apt/lists/* @@ -30,8 +30,9 @@ ENV LD_LIBRARY_PATH /usr/local/lib/python3.8/dist-packages/tensorrt:$NVIDIA_DIR/ # && git sparse-checkout set "models/gc_luna22_ismi_baseline" \ # && git fetch https://github.com/MHubAI/models.git main -# Install luna22 ismi baseline algorithm and model weights -RUN git clone --depth 1 https://github.com/DIAGNijmegen/bodyct-luna22-ismi-algorithm-baseline.git /opt/algorithm +# Install luna22 ismi baseline algorithm and model weights (main branch, commit 68c5e93be62dae54d15a8a3d8821ca2138e0fe2e) +RUN git clone https://github.com/DIAGNijmegen/bodyct-luna22-ismi-algorithm-baseline.git /opt/algorithm && \ + cd /opt/algorithm && git reset --hard 68c5e93be62dae54d15a8a3d8821ca2138e0fe2e # Install algorithm requirements RUN pip3 install --no-cache-dir evalutils==0.3.1 diff --git a/models/gc_luna22_ismi_baseline/utils/Luna22IsmiBaselineRunner.py b/models/gc_luna22_ismi_baseline/utils/Luna22IsmiBaselineRunner.py index 8d26dc32..0723da4d 100644 --- a/models/gc_luna22_ismi_baseline/utils/Luna22IsmiBaselineRunner.py +++ b/models/gc_luna22_ismi_baseline/utils/Luna22IsmiBaselineRunner.py @@ -17,10 +17,11 @@ import SimpleITK -from process import Nodule_classifier as NoduleClassifier - import tensorflow as tf +# Import the Luna22 baseline algorithm class from the luna22-ismi-algorithm repository +from process import Nodule_classifier as NoduleClassifier + @ValueOutput.Name('malignancy_risk') @ValueOutput.Meta(Meta(key="value")) @@ -55,11 +56,18 @@ def task(self, instance: Instance, in_data: InstanceData, malignancy_risk: Malig "The Conv2D op currently only supports the NHWC tensor format on" \ " the CPU. The op was given the format: NCHW [Op:Conv2D]" + # Read input image input_image = SimpleITK.ReadImage(in_data.abspath) + + # Create classifier and load model weights if not already loaded if self._cached_classifier is None: self.v("Luna22IsmiBaselineRunner - Loading and caching model weights") self._cached_classifier = NoduleClassifier() classifier = self._cached_classifier + + # Run the classifier on the input image predictions = classifier.predict(input_image=input_image) + + # Output the predicted values malignancy_risk.value = predictions["malignancy_risk"] texture.value = predictions["texture"]