From 0d7ec9d726f68b38bdbe67d79e939916a476bcf8 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Tue, 4 Jul 2023 12:50:40 +0200 Subject: [PATCH 01/21] WIP initial pancreas_pdac model commit --- models/nnunet_pancreas_pdac/config/config.yml | 46 ++++++++++++++++ models/nnunet_pancreas_pdac/config/dseg.json | 53 ++++++++++++++++++ models/nnunet_pancreas_pdac/config/slicer.yml | 27 +++++++++ .../dockerfiles/cuda12.0/Dockerfile | 55 +++++++++++++++++++ models/nnunet_pancreas_pdac/scripts/run.py | 50 +++++++++++++++++ 5 files changed, 231 insertions(+) create mode 100644 models/nnunet_pancreas_pdac/config/config.yml create mode 100644 models/nnunet_pancreas_pdac/config/dseg.json create mode 100644 models/nnunet_pancreas_pdac/config/slicer.yml create mode 100644 models/nnunet_pancreas_pdac/dockerfiles/cuda12.0/Dockerfile create mode 100644 models/nnunet_pancreas_pdac/scripts/run.py diff --git a/models/nnunet_pancreas_pdac/config/config.yml b/models/nnunet_pancreas_pdac/config/config.yml new file mode 100644 index 00000000..02bd8319 --- /dev/null +++ b/models/nnunet_pancreas_pdac/config/config.yml @@ -0,0 +1,46 @@ +general: + version: 1.0 + data_base_dir: /app/data + description: base configuration for nnunet pancreas PDAC model + +execute: + - DicomImporter + - NiftiConverter + - NNUnetRunner + - DsegConverter + - DataOrganizer + +modules: + + DicomImporter: + source_dir: input_data + import_dir: sorted_data + sort_data: true + meta: + mod: ct + + NNUnetRunner: + input_data_type: nifti:mod=ct + nnunet_task: Task103_AllStructures + nnunet_model: 3d_fullres + checkpoint: model_final_checkpoint + folds: 0,1,2,3,4 + disable_augmentations: False + disable_patch_overlap: False + export_prob_maps: True + roi: PANCREAS,PANCREAS+NEOPLASM_MALIGNANT_PRIMARY + prob_map_segments: [Background, Pancreas, Pancreatic_cancer] + + DsegConverter: + #source_segs: [nifti:mod=seg] + #json_config_path: /app/models/nnunet_pancreas/config/dseg.json + source_segs: [nifti:mod=seg:roi=*] + model_name: NNUnet Pancreas PDAC + skip_empty_slices: True + + DataOrganizer: + targets: + - nifti:mod=ct-->/app/data/output_data/[i:sid]/image.nii.gz + - nifti:mod=seg-->/app/data/output_data/[i:sid]/pancreas.nii.gz + - dicomseg:mod=seg-->/app/data/output_data/[i:sid]/pancreas.seg.dcm + - nrrd:mod=prob_mask-->/app/data/output_data/[i:sid]/prob_masks/[path] \ No newline at end of file diff --git a/models/nnunet_pancreas_pdac/config/dseg.json b/models/nnunet_pancreas_pdac/config/dseg.json new file mode 100644 index 00000000..31ecb2aa --- /dev/null +++ b/models/nnunet_pancreas_pdac/config/dseg.json @@ -0,0 +1,53 @@ +{ + "ContentCreatorName": "IDC", + "ClinicalTrialSeriesID": "0", + "ClinicalTrialTimePointID": "1", + "SeriesDescription": "Segmentation", + "SeriesNumber": "42", + "InstanceNumber": "1", + "BodyPartExamined": "ABDOMEN", + "segmentAttributes": [ + [ + { + "labelID": 1, + "SegmentDescription": "Pancreas", + "SegmentAlgorithmType": "AUTOMATIC", + "SegmentAlgorithmName": "nnU-Net", + "SegmentedPropertyCategoryCodeSequence": { + "CodeValue": "123037004", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Anatomical Structure" + }, + "SegmentedPropertyTypeCodeSequence": { + "CodeValue": "15776009", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Pancreas" + }, + "recommendedDisplayRGBValue": [ + 249, + 180, + 111 + ] + }, + { + "labelID": 2, + "SegmentDescription": "Pancreatic Cancer", + "SegmentAlgorithmType": "AUTOMATIC", + "SegmentAlgorithmName": "nnU-Net", + "SegmentedPropertyCategoryCodeSequence": { + "CodeValue": "49755003", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Morphologically Altered Structure" + }, + "SegmentedPropertyTypeCodeSequence": { + "CodeValue": "86049000", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Neoplasm, Primary" + } + } + ] + ], + "ContentLabel": "SEGMENTATION", + "ContentDescription": "Image segmentation", + "ClinicalTrialCoordinatingCenterName": "dcmqi" + } \ No newline at end of file diff --git a/models/nnunet_pancreas_pdac/config/slicer.yml b/models/nnunet_pancreas_pdac/config/slicer.yml new file mode 100644 index 00000000..e6e84d4e --- /dev/null +++ b/models/nnunet_pancreas_pdac/config/slicer.yml @@ -0,0 +1,27 @@ +general: + version: 1.0 + data_base_dir: /app/data + description: 3D Slicer configuration for nnuner pancreas model + +execute: + - NrrdImporter + - NiftiConverter + - NNUnetRunner + - DataOrganizer + +modules: + + NrrdImporter: + input_dir: input_data + input_file_name: image.nrrd + + NNUnetRunner: + input_data_type: nifti:mod=ct + nnunet_task: Task007_Pancreas + nnunet_model: 3d_lowres + export_prob_maps: False + roi: pancreas + + DataOrganizer: + targets: + - nifti:mod=seg-->/app/data/output_data/[d:roi].nii.gz \ No newline at end of file diff --git a/models/nnunet_pancreas_pdac/dockerfiles/cuda12.0/Dockerfile b/models/nnunet_pancreas_pdac/dockerfiles/cuda12.0/Dockerfile new file mode 100644 index 00000000..fe2f9138 --- /dev/null +++ b/models/nnunet_pancreas_pdac/dockerfiles/cuda12.0/Dockerfile @@ -0,0 +1,55 @@ +# syntax=docker/dockerfile:experimental + +# Specify the base image for the environment +FROM mhubai/base:cuda12.0 + +# Specify/override authors label +LABEL authors="sil.vandeleemput@radboudumc.nl" + +# Install panimg to test conversion integration TODO should later be installed with MHub/mhubio +#RUN apt-get update && apt-get install -y --no-install-recommends \ +# python3-openslide \ +# && rm -rf /var/lib/apt/lists/* +#RUN pip3 install panimg + +# Clone MHub model (m-nnunet-pancreas branch, fixed to commit 407f1f884f09898bef9a9173e6434d681a50d399) # TODO +#RUN git init \ +# && git sparse-checkout set "models/nnunet_pancreas" \ +# && git fetch https://github.com/MHubAI/models.git m-nnunet-pancreas \ +# && git merge 407f1f884f09898bef9a9173e6434d681a50d399 + + +# Install git-lfs (required for downloading the model weights) +RUN apt update && apt install -y --no-install-recommends \ + git-lfs \ + && rm -rf /var/lib/apt/lists/* + +# TODO remove later ==== Temporariy SSH fix as long as repo is private ======= +RUN apt-get update && apt-get install -y --no-install-recommends \ + openssh-client \ + && rm -rf /var/lib/apt/lists/* +# Add github public key to known_hosts for SSH +RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts +# TODO remove later =============================== + + +RUN --mount=type=ssh git clone --depth 1 git@github.com:DIAGNijmegen/CE-CT_PDAC_AutomaticDetection_nnUnet.git /nnunet_pancreas_pdac && \ + cd /nnunet_pancreas_pdac && \ + git reset --hard 117bb4ebf8bc9e90509a468a5d56e0515987b5a7 && \ + rm -rf /nnunet_pancreas_pdac/.git + +# FIXME: set this environment variable as a shortcut to avoid nnunet crashing the build +# by pulling sklearn instead of scikit-learn +# N.B. this is a known issue: +# https://github.com/MIC-DKFZ/nnUNet/issues/1281 +# https://github.com/MIC-DKFZ/nnUNet/pull/1209 +ENV SKLEARN_ALLOW_DEPRECATED_SKLEARN_PACKAGE_INSTALL=True + +# Install nnunet and other requirements +RUN pip3 install --no-cache-dir -r /nnunet_pancreas_pdac/requirements.txt + +# specify nnunet specific environment variables +ENV WEIGHTS_FOLDER=/nnunet_pancreas_pdac/nnunet/results/nnUNet + +# Default run script +CMD ["python3", "-m", "mhubio.run", "--config", "/app/models/nnunet_pancreas_pdac/config/config.yml"] \ No newline at end of file diff --git a/models/nnunet_pancreas_pdac/scripts/run.py b/models/nnunet_pancreas_pdac/scripts/run.py new file mode 100644 index 00000000..3491f58b --- /dev/null +++ b/models/nnunet_pancreas_pdac/scripts/run.py @@ -0,0 +1,50 @@ +""" +------------------------------------------------- +MHub - run the NNUnet pancreas segmentation + pipeline +------------------------------------------------- + +------------------------------------------------- +Author: Sil van de Leemput +Email: sil.vandeleemput@radboudumc.nl +------------------------------------------------- +""" + +import sys +sys.path.append('.') + +from mhubio.core import Config, DataType, FileType, CT, SEG +from mhubio.modules.importer.DicomImporter import DicomImporter +from mhubio.modules.convert.NiftiConverter import NiftiConverter +from mhubio.modules.runner.NNUnetRunner import NNUnetRunner +from mhubio.modules.convert.DsegConverter import DsegConverter +from mhubio.modules.organizer.DataOrganizer import DataOrganizer + +# clean-up +import shutil +shutil.rmtree("/app/data/sorted_data", ignore_errors=True) +shutil.rmtree("/app/tmp", ignore_errors=True) +shutil.rmtree("/app/data/output_data", ignore_errors=True) + +# config +config = Config('/app/models/nnunet_pancreas_pdac/config/config.yml') +config.verbose = True # TODO: define levels of verbosity and integrate consistently. + +# import (ct:dicom) +DicomImporter(config).execute() + +# convert (ct:dicom -> ct:nifti) +NiftiConverter(config).execute() + +# execute model (nnunet) +NNUnetRunner(config).execute() + +# convert (seg:nifti -> seg:dcm) +DsegConverter(config).execute() + +# organize data into output folder +organizer = DataOrganizer(config, set_file_permissions=sys.platform.startswith('linux')) +organizer.setTarget(DataType(FileType.NIFTI, CT), "/app/data/output_data/[i:sid]/image.nii.gz") +organizer.setTarget(DataType(FileType.NIFTI, SEG), "/app/data/output_data/[i:sid]/pancreas.nii.gz") +organizer.setTarget(DataType(FileType.DICOMSEG, SEG), "/app/data/output_data/[i:sid]/pancreas.seg.dcm") +organizer.execute() \ No newline at end of file From af79adb21687cb7968ce8f8f08804c9c0820b936 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Thu, 20 Jul 2023 16:33:22 +0200 Subject: [PATCH 02/21] renamed nnunet_pancreas_pdac -> gc_nnunet_pancreas, addded run script, mhaimporter and specific gcnnunetpancreasrunner * Fixed Dockerfile with nocuda for now using the new base image * Added two utils for this runner (MHAImporter, custom GCnnunetPancreasRunner) * Marked bunch of things as TODO --- models/gc_nnunet_pancreas/__init__.py | 1 + models/gc_nnunet_pancreas/config/config.yml | 33 ++++++++++ .../dockerfiles/nocuda/Dockerfile | 47 ++++++++++++++ models/gc_nnunet_pancreas/scripts/run.py | 54 ++++++++++++++++ .../utils/GCnnUnetPancreasRunner.py | 62 +++++++++++++++++++ .../gc_nnunet_pancreas/utils/MhaImporter.py | 45 ++++++++++++++ models/gc_nnunet_pancreas/utils/__init__.py | 2 + models/nnunet_pancreas_pdac/config/config.yml | 46 -------------- models/nnunet_pancreas_pdac/config/dseg.json | 53 ---------------- models/nnunet_pancreas_pdac/config/slicer.yml | 27 -------- .../dockerfiles/cuda12.0/Dockerfile | 55 ---------------- models/nnunet_pancreas_pdac/scripts/run.py | 50 --------------- 12 files changed, 244 insertions(+), 231 deletions(-) create mode 100644 models/gc_nnunet_pancreas/__init__.py create mode 100644 models/gc_nnunet_pancreas/config/config.yml create mode 100644 models/gc_nnunet_pancreas/dockerfiles/nocuda/Dockerfile create mode 100644 models/gc_nnunet_pancreas/scripts/run.py create mode 100644 models/gc_nnunet_pancreas/utils/GCnnUnetPancreasRunner.py create mode 100644 models/gc_nnunet_pancreas/utils/MhaImporter.py create mode 100644 models/gc_nnunet_pancreas/utils/__init__.py delete mode 100644 models/nnunet_pancreas_pdac/config/config.yml delete mode 100644 models/nnunet_pancreas_pdac/config/dseg.json delete mode 100644 models/nnunet_pancreas_pdac/config/slicer.yml delete mode 100644 models/nnunet_pancreas_pdac/dockerfiles/cuda12.0/Dockerfile delete mode 100644 models/nnunet_pancreas_pdac/scripts/run.py diff --git a/models/gc_nnunet_pancreas/__init__.py b/models/gc_nnunet_pancreas/__init__.py new file mode 100644 index 00000000..16281fe0 --- /dev/null +++ b/models/gc_nnunet_pancreas/__init__.py @@ -0,0 +1 @@ +from .utils import * diff --git a/models/gc_nnunet_pancreas/config/config.yml b/models/gc_nnunet_pancreas/config/config.yml new file mode 100644 index 00000000..3b436dac --- /dev/null +++ b/models/gc_nnunet_pancreas/config/config.yml @@ -0,0 +1,33 @@ +general: + version: 1.0 + data_base_dir: /app/data + description: base configuration for nnunet pancreas PDAC model + +modules: + + DicomImporter: + source_dir: input_data + import_dir: sorted_data + sort_data: true + meta: + mod: ct + + MhaImporter: + source_dir: input_data + import_dir: sorted_data + + GCNNUnetPancreasRunner: + +# TODO configure DsegConverter +# DsegConverter: +# #source_segs: [nifti:mod=seg] +# #json_config_path: /app/models/nnunet_pancreas/config/dseg.json +# source_segs: [mha:mod=seg:roi=*] +# model_name: GC NNUnet Pancreas +# skip_empty_slices: True + + DataOrganizer: + targets: + - mha:mod=heatmap-->/app/data/output_data/[i:sid]/heatmap.mha + - mha:mod=seg-->/app/data/output_data/[i:sid]/pancreas.seg.mha +# - dicomseg:mod=seg-->/app/data/output_data/[i:sid]/pancreas.seg.dcm diff --git a/models/gc_nnunet_pancreas/dockerfiles/nocuda/Dockerfile b/models/gc_nnunet_pancreas/dockerfiles/nocuda/Dockerfile new file mode 100644 index 00000000..d1945b36 --- /dev/null +++ b/models/gc_nnunet_pancreas/dockerfiles/nocuda/Dockerfile @@ -0,0 +1,47 @@ +# Specify the base image for the environment +FROM mhubai/base:latest +# TODO add CUDA support since algorithm takes eons otherwise... + +# Specify/override authors label +LABEL authors="sil.vandeleemput@radboudumc.nl" + +# Clone MHub model (m-gc-nnunet-pancreas branch, fixed to commit 407f1f884f09898bef9a9173e6434d681a50d399) # TODO +#RUN git init \ +# && git sparse-checkout set "models/gc_nnunet_pancreas" \ +# && git fetch https://github.com/MHubAI/models.git m-gc-nnunet-pancreas \ +# && git merge TODO + +# Install git-lfs (required for downloading the model weights) +RUN apt update && apt install -y --no-install-recommends \ + git-lfs \ + && rm -rf /var/lib/apt/lists/* + +# Install the model weights and the algorithm files +# * Pull algorithm from repo into /opt/algorithm for commit e4f4008c6e18e60a79f693448562a340a9252aa8 +# * Remove .git folder to keep docker layer small +# * Replace input images path in process.py with an existing folder to avoid errors +RUN git clone --depth 1 https://github.com/DIAGNijmegen/CE-CT_PDAC_AutomaticDetection_nnUnet.git /opt/algorithm && \ + cd /opt/algorithm && \ + git reset --hard e4f4008c6e18e60a79f693448562a340a9252aa8 && \ + rm -rf /opt/algorithm/.git && \ + sed -i 's/Path("\/input\/images\/")/Path("\/app")/g' /opt/algorithm/process.py + +# FIXME: set this environment variable as a shortcut to avoid nnunet crashing the build +# by pulling sklearn instead of scikit-learn +# N.B. this is a known issue: +# https://github.com/MIC-DKFZ/nnUNet/issues/1281 +# https://github.com/MIC-DKFZ/nnUNet/pull/1209 +ENV SKLEARN_ALLOW_DEPRECATED_SKLEARN_PACKAGE_INSTALL=True + +# Install nnUNet and other requirements +RUN pip3 install --no-cache-dir -r /opt/algorithm/requirements.txt + +# Extend the nnUNet installation with custom trainers +RUN SITE_PKG=`pip3 show nnunet | grep "Location:" | awk '{print $2}'` && \ + mv /opt/algorithm/nnUNetTrainerV2_Loss_CE_checkpoints.py "$SITE_PKG/nnunet/training/network_training/nnUNetTrainerV2_Loss_CE_checkpoints.py" + +# Add algorithm files to python path +ENV PYTHONPATH=/opt/algorithm:/app + +# Default run script +CMD ["python3", "/app/models/gc_nnunet_pancreas/scripts/run.py"] \ No newline at end of file diff --git a/models/gc_nnunet_pancreas/scripts/run.py b/models/gc_nnunet_pancreas/scripts/run.py new file mode 100644 index 00000000..8421566f --- /dev/null +++ b/models/gc_nnunet_pancreas/scripts/run.py @@ -0,0 +1,54 @@ +""" +--------------------------------------------------- +GC / MHub - run the NNUnet GC pancreas segmentation + pipeline +--------------------------------------------------- + +--------------------------------------------------- +Author: Sil van de Leemput +Email: sil.vandeleemput@radboudumc.nl +--------------------------------------------------- +""" + +import sys +sys.path.append('.') + +from mhubio.core import Config, DataType, FileType, CT, SEG, Meta +from mhubio.modules.importer.FileStructureImporter import FileStructureImporter +from mhubio.modules.importer.DicomImporter import DicomImporter +from mhubio.modules.importer.NrrdImporter import NrrdImporter +from mhubio.modules.convert.NiftiConverter import NiftiConverter +from mhubio.modules.runner.NNUnetRunner import NNUnetRunner +from mhubio.modules.convert.DsegConverter import DsegConverter +from mhubio.modules.organizer.DataOrganizer import DataOrganizer +from models.gc_nnunet_pancreas import MhaImporter, GCNNUnetPancreasRunner, HEATMAP + +# clean-up +import shutil +shutil.rmtree("/app/data/sorted_data", ignore_errors=True) +shutil.rmtree("/app/tmp", ignore_errors=True) +shutil.rmtree("/app/data/output_data", ignore_errors=True) + +# config +config = Config('/app/models/gc_nnunet_pancreas/config/config.yml') +config.verbose = True # TODO: define levels of verbosity and integrate consistently. + +# import (ct:dicom) +#DicomImporter(config).execute() + +# import (ct:mha) +MhaImporter(config).execute() +#FileStructureImporter(config).execute() + +# execute model (nnunet ct:mha -> (hm:mha, seg:mha)) +GCNNUnetPancreasRunner(config).execute() + +# convert (seg:nifti -> seg:dcm) +# DsegConverter(config).execute() + +# organize data into output folder +organizer = DataOrganizer(config, set_file_permissions=sys.platform.startswith('linux')) +organizer.setTarget(DataType(FileType.MHA, HEATMAP), "/app/data/output_data/[i:sid]/heatmap.mha") +organizer.setTarget(DataType(FileType.MHA, SEG), "/app/data/output_data/[i:sid]/pancreas.seg.mha") +#organizer.setTarget(DataType(FileType.DICOMSEG, SEG), "/app/data/output_data/[i:sid]/pancreas.seg.dcm") +organizer.execute() \ No newline at end of file diff --git a/models/gc_nnunet_pancreas/utils/GCnnUnetPancreasRunner.py b/models/gc_nnunet_pancreas/utils/GCnnUnetPancreasRunner.py new file mode 100644 index 00000000..b63badbd --- /dev/null +++ b/models/gc_nnunet_pancreas/utils/GCnnUnetPancreasRunner.py @@ -0,0 +1,62 @@ +""" +----------------------------------------------------------- +GC / MHub - Run Module for the GC NNUnet Pancreas Algorithm +----------------------------------------------------------- + +----------------------------------------------------------- +Author: Sil van de Leemput +Email: sil.vandeleemput@radboudumc.nl +----------------------------------------------------------- +""" + +from mhubio.core import Module, Instance, InstanceData, DataType, FileType, CT, SEG, IO, Meta +import os, subprocess, shutil + +from pathlib import Path + +from process import PDACDetectionContainer + +# TODO should move to MHubio/core/templates.py +HEATMAP = Meta(mod="heatmap") + +# @IO.Config('output_dir', str, "/app/tmp/gc_nnunet_pancreas/", the='directory to output the segmentation and the heatmap') +class GCNNUnetPancreasRunner(Module): + + # output_dir: str + + @IO.Instance() + @IO.Input('in_data', 'mha:mod=ct', the="input data") + @IO.Output('heatmap', 'heatmap.mha', 'mha:mod=heatmap:model=GCNNUnetPancreas', data="in_data", + the="heatmap of the pancreatic tumor likelihood") + @IO.Output('segmentation', 'segmentation.mha', 'mha:mod=seg:model=GCNNUnetPancreas', data="in_data", + the="segmentation of the pancreas, with the following classes: 1-veins, 2-arteries, 3-pancreas, 4-pancreatic duct, 5-bile duct, 6-cysts, 7-renal vein") + # @IO.Output('vei', 'Veins.mha', 'mha:mod=seg:model=GCNNUnetPancreas:roi=HEART', bundle='gc_nnunet_pancreas', in_signature=False, + # the="segmentation of the veins") + # @IO.Output('art', 'Arteries.mha', 'mha:mod=seg:model=GCNNUnetPancreas:roi=PULMONARY_ARTERY', bundle='gc_nnunet_pancreas', in_signature=False, + # the="segmentation of the arteries") + # @IO.Output('pan', 'Pancreas.mha', 'mha:mod=seg:model=GCNNUnetPancreas:roi=AORTA', bundle='gc_nnunet_pancreas', in_signature=False, + # the="segmentation of the pancreas") + # @IO.Output('pdc', 'PDC.mha', 'mha:mod=seg:model=GCNNUnetPancreas:roi=HEART', bundle='gc_nnunet_pancreas', in_signature=False, + # the="segmentation of the pancreatic duct") + # @IO.Output('bdt', 'BileDuct.mha', 'mha:mod=seg:model=GCNNUnetPancreas:roi=PULMONARY_ARTERY', bundle='gc_nnunet_pancreas', in_signature=False, + # the="segmentation of the bile duct") + # @IO.Output('cys', 'Cysts.mha', 'mha:mod=seg:model=GCNNUnetPancreas:roi=AORTA', bundle='gc_nnunet_pancreas', in_signature=False, + # the="segmentation of cysts") + # @IO.Output('rve', 'RenalVein.mha', 'mha:mod=seg:model=GCNNUnetPancreas:roi=AORTA', bundle='gc_nnunet_pancreas', in_signature=False, + # the="segmentation of the renal vein") + def task(self, instance: Instance, in_data: InstanceData, heatmap: InstanceData, segmentation: InstanceData, **kwargs) -> None: + algorithm = PDACDetectionContainer() + #algorithm.ct_ip_dir = Path("/input/images/") + algorithm.ct_image = in_data.abspath # set as str not Path + #algorithm.output_dir = Path(self.output_dir) + #algorithm.output_dir_tlm = algorithm.output_dir / "pancreatic-tumor-likelihood-map" + #algorithm.output_dir_seg = algorithm.output_dir / "pancreas-anatomy-and-vessel-segmentation" + algorithm.heatmap = Path(heatmap.abspath) # algorithm.output_dir_tlm / "heatmap.mha" + algorithm.segmentation = Path(segmentation.abspath) #algorithm.output_dir_seg / "segmentation.mha" + #algorithm.output_dir.mkdir(exist_ok=True, parents=True) + #algorithm.output_dir_tlm.mkdir(exist_ok=True, parents=True) + #algorithm.output_dir_seg.mkdir(exist_ok=True, parents=True) + self.v(in_data.abspath) + self.v(heatmap.abspath) + self.v(segmentation.abspath) + algorithm.process() diff --git a/models/gc_nnunet_pancreas/utils/MhaImporter.py b/models/gc_nnunet_pancreas/utils/MhaImporter.py new file mode 100644 index 00000000..f2ca93b4 --- /dev/null +++ b/models/gc_nnunet_pancreas/utils/MhaImporter.py @@ -0,0 +1,45 @@ +""" +-------------------------------------- +MHub / GC - MHA importer +-------------------------------------- + +-------------------------------------- +Author: Sil van de Leemput +Email: sil.vandeleemput@radboudumc.nl +-------------------------------------- +""" +import os +from typing import Optional +from pathlib import Path + +from mhubio.modules.importer.DataImporter import IDEF, DataImporter, FileType +from mhubio.core import Meta, DirectoryChain, CT + + +class MhaImporter(DataImporter): + def task(self) -> None: + source_dir = self.c['source_dir'] + source_dc = DirectoryChain(path=source_dir, parent=self.config.data.dc) + # input tiff file directory + input_dir = source_dc.abspath + self.v(f"{input_dir}") + + # add input tiff files as WSI images... + self.setBasePath(input_dir) + for input_tiff_file in Path(input_dir).glob("*.mha"): + self.v(f"{input_tiff_file}") + self.addMhaCT(str(input_tiff_file), ref=input_tiff_file.stem) + + # let the base module take over from here + super().task() + + def addMhaCT(self, path: str, ref: Optional[str] = None) -> None: + _path = self._resolvePath(path, ref) + self.v("adding CT in mha format with resolved path: ", _path) + assert os.path.isfile(_path) and _path.endswith('.mha'), f"Expect existing mha file, '{_path}' was given instead." + self._import_paths.append(IDEF( + ref = ref, + path = path, + ftype = FileType.MHA, + meta = CT + )) diff --git a/models/gc_nnunet_pancreas/utils/__init__.py b/models/gc_nnunet_pancreas/utils/__init__.py new file mode 100644 index 00000000..c35f6fef --- /dev/null +++ b/models/gc_nnunet_pancreas/utils/__init__.py @@ -0,0 +1,2 @@ +from .MhaImporter import * +from .GCnnUnetPancreasRunner import * \ No newline at end of file diff --git a/models/nnunet_pancreas_pdac/config/config.yml b/models/nnunet_pancreas_pdac/config/config.yml deleted file mode 100644 index 02bd8319..00000000 --- a/models/nnunet_pancreas_pdac/config/config.yml +++ /dev/null @@ -1,46 +0,0 @@ -general: - version: 1.0 - data_base_dir: /app/data - description: base configuration for nnunet pancreas PDAC model - -execute: - - DicomImporter - - NiftiConverter - - NNUnetRunner - - DsegConverter - - DataOrganizer - -modules: - - DicomImporter: - source_dir: input_data - import_dir: sorted_data - sort_data: true - meta: - mod: ct - - NNUnetRunner: - input_data_type: nifti:mod=ct - nnunet_task: Task103_AllStructures - nnunet_model: 3d_fullres - checkpoint: model_final_checkpoint - folds: 0,1,2,3,4 - disable_augmentations: False - disable_patch_overlap: False - export_prob_maps: True - roi: PANCREAS,PANCREAS+NEOPLASM_MALIGNANT_PRIMARY - prob_map_segments: [Background, Pancreas, Pancreatic_cancer] - - DsegConverter: - #source_segs: [nifti:mod=seg] - #json_config_path: /app/models/nnunet_pancreas/config/dseg.json - source_segs: [nifti:mod=seg:roi=*] - model_name: NNUnet Pancreas PDAC - skip_empty_slices: True - - DataOrganizer: - targets: - - nifti:mod=ct-->/app/data/output_data/[i:sid]/image.nii.gz - - nifti:mod=seg-->/app/data/output_data/[i:sid]/pancreas.nii.gz - - dicomseg:mod=seg-->/app/data/output_data/[i:sid]/pancreas.seg.dcm - - nrrd:mod=prob_mask-->/app/data/output_data/[i:sid]/prob_masks/[path] \ No newline at end of file diff --git a/models/nnunet_pancreas_pdac/config/dseg.json b/models/nnunet_pancreas_pdac/config/dseg.json deleted file mode 100644 index 31ecb2aa..00000000 --- a/models/nnunet_pancreas_pdac/config/dseg.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "ContentCreatorName": "IDC", - "ClinicalTrialSeriesID": "0", - "ClinicalTrialTimePointID": "1", - "SeriesDescription": "Segmentation", - "SeriesNumber": "42", - "InstanceNumber": "1", - "BodyPartExamined": "ABDOMEN", - "segmentAttributes": [ - [ - { - "labelID": 1, - "SegmentDescription": "Pancreas", - "SegmentAlgorithmType": "AUTOMATIC", - "SegmentAlgorithmName": "nnU-Net", - "SegmentedPropertyCategoryCodeSequence": { - "CodeValue": "123037004", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Anatomical Structure" - }, - "SegmentedPropertyTypeCodeSequence": { - "CodeValue": "15776009", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Pancreas" - }, - "recommendedDisplayRGBValue": [ - 249, - 180, - 111 - ] - }, - { - "labelID": 2, - "SegmentDescription": "Pancreatic Cancer", - "SegmentAlgorithmType": "AUTOMATIC", - "SegmentAlgorithmName": "nnU-Net", - "SegmentedPropertyCategoryCodeSequence": { - "CodeValue": "49755003", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Morphologically Altered Structure" - }, - "SegmentedPropertyTypeCodeSequence": { - "CodeValue": "86049000", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Neoplasm, Primary" - } - } - ] - ], - "ContentLabel": "SEGMENTATION", - "ContentDescription": "Image segmentation", - "ClinicalTrialCoordinatingCenterName": "dcmqi" - } \ No newline at end of file diff --git a/models/nnunet_pancreas_pdac/config/slicer.yml b/models/nnunet_pancreas_pdac/config/slicer.yml deleted file mode 100644 index e6e84d4e..00000000 --- a/models/nnunet_pancreas_pdac/config/slicer.yml +++ /dev/null @@ -1,27 +0,0 @@ -general: - version: 1.0 - data_base_dir: /app/data - description: 3D Slicer configuration for nnuner pancreas model - -execute: - - NrrdImporter - - NiftiConverter - - NNUnetRunner - - DataOrganizer - -modules: - - NrrdImporter: - input_dir: input_data - input_file_name: image.nrrd - - NNUnetRunner: - input_data_type: nifti:mod=ct - nnunet_task: Task007_Pancreas - nnunet_model: 3d_lowres - export_prob_maps: False - roi: pancreas - - DataOrganizer: - targets: - - nifti:mod=seg-->/app/data/output_data/[d:roi].nii.gz \ No newline at end of file diff --git a/models/nnunet_pancreas_pdac/dockerfiles/cuda12.0/Dockerfile b/models/nnunet_pancreas_pdac/dockerfiles/cuda12.0/Dockerfile deleted file mode 100644 index fe2f9138..00000000 --- a/models/nnunet_pancreas_pdac/dockerfiles/cuda12.0/Dockerfile +++ /dev/null @@ -1,55 +0,0 @@ -# syntax=docker/dockerfile:experimental - -# Specify the base image for the environment -FROM mhubai/base:cuda12.0 - -# Specify/override authors label -LABEL authors="sil.vandeleemput@radboudumc.nl" - -# Install panimg to test conversion integration TODO should later be installed with MHub/mhubio -#RUN apt-get update && apt-get install -y --no-install-recommends \ -# python3-openslide \ -# && rm -rf /var/lib/apt/lists/* -#RUN pip3 install panimg - -# Clone MHub model (m-nnunet-pancreas branch, fixed to commit 407f1f884f09898bef9a9173e6434d681a50d399) # TODO -#RUN git init \ -# && git sparse-checkout set "models/nnunet_pancreas" \ -# && git fetch https://github.com/MHubAI/models.git m-nnunet-pancreas \ -# && git merge 407f1f884f09898bef9a9173e6434d681a50d399 - - -# Install git-lfs (required for downloading the model weights) -RUN apt update && apt install -y --no-install-recommends \ - git-lfs \ - && rm -rf /var/lib/apt/lists/* - -# TODO remove later ==== Temporariy SSH fix as long as repo is private ======= -RUN apt-get update && apt-get install -y --no-install-recommends \ - openssh-client \ - && rm -rf /var/lib/apt/lists/* -# Add github public key to known_hosts for SSH -RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts -# TODO remove later =============================== - - -RUN --mount=type=ssh git clone --depth 1 git@github.com:DIAGNijmegen/CE-CT_PDAC_AutomaticDetection_nnUnet.git /nnunet_pancreas_pdac && \ - cd /nnunet_pancreas_pdac && \ - git reset --hard 117bb4ebf8bc9e90509a468a5d56e0515987b5a7 && \ - rm -rf /nnunet_pancreas_pdac/.git - -# FIXME: set this environment variable as a shortcut to avoid nnunet crashing the build -# by pulling sklearn instead of scikit-learn -# N.B. this is a known issue: -# https://github.com/MIC-DKFZ/nnUNet/issues/1281 -# https://github.com/MIC-DKFZ/nnUNet/pull/1209 -ENV SKLEARN_ALLOW_DEPRECATED_SKLEARN_PACKAGE_INSTALL=True - -# Install nnunet and other requirements -RUN pip3 install --no-cache-dir -r /nnunet_pancreas_pdac/requirements.txt - -# specify nnunet specific environment variables -ENV WEIGHTS_FOLDER=/nnunet_pancreas_pdac/nnunet/results/nnUNet - -# Default run script -CMD ["python3", "-m", "mhubio.run", "--config", "/app/models/nnunet_pancreas_pdac/config/config.yml"] \ No newline at end of file diff --git a/models/nnunet_pancreas_pdac/scripts/run.py b/models/nnunet_pancreas_pdac/scripts/run.py deleted file mode 100644 index 3491f58b..00000000 --- a/models/nnunet_pancreas_pdac/scripts/run.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -------------------------------------------------- -MHub - run the NNUnet pancreas segmentation - pipeline -------------------------------------------------- - -------------------------------------------------- -Author: Sil van de Leemput -Email: sil.vandeleemput@radboudumc.nl -------------------------------------------------- -""" - -import sys -sys.path.append('.') - -from mhubio.core import Config, DataType, FileType, CT, SEG -from mhubio.modules.importer.DicomImporter import DicomImporter -from mhubio.modules.convert.NiftiConverter import NiftiConverter -from mhubio.modules.runner.NNUnetRunner import NNUnetRunner -from mhubio.modules.convert.DsegConverter import DsegConverter -from mhubio.modules.organizer.DataOrganizer import DataOrganizer - -# clean-up -import shutil -shutil.rmtree("/app/data/sorted_data", ignore_errors=True) -shutil.rmtree("/app/tmp", ignore_errors=True) -shutil.rmtree("/app/data/output_data", ignore_errors=True) - -# config -config = Config('/app/models/nnunet_pancreas_pdac/config/config.yml') -config.verbose = True # TODO: define levels of verbosity and integrate consistently. - -# import (ct:dicom) -DicomImporter(config).execute() - -# convert (ct:dicom -> ct:nifti) -NiftiConverter(config).execute() - -# execute model (nnunet) -NNUnetRunner(config).execute() - -# convert (seg:nifti -> seg:dcm) -DsegConverter(config).execute() - -# organize data into output folder -organizer = DataOrganizer(config, set_file_permissions=sys.platform.startswith('linux')) -organizer.setTarget(DataType(FileType.NIFTI, CT), "/app/data/output_data/[i:sid]/image.nii.gz") -organizer.setTarget(DataType(FileType.NIFTI, SEG), "/app/data/output_data/[i:sid]/pancreas.nii.gz") -organizer.setTarget(DataType(FileType.DICOMSEG, SEG), "/app/data/output_data/[i:sid]/pancreas.seg.dcm") -organizer.execute() \ No newline at end of file From 4bb7483da36659b42a6658aac9309e753fea818b Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Tue, 1 Aug 2023 15:23:11 +0200 Subject: [PATCH 03/21] update for new base image, add dseg.json for class labels --- models/gc_nnunet_pancreas/config/dseg.json | 168 ++++++++++++++++++ .../dockerfiles/{nocuda => }/Dockerfile | 3 +- models/gc_nnunet_pancreas/scripts/run.py | 14 +- .../gc_nnunet_pancreas/utils/MhaImporter.py | 45 ----- .../utils/PanImgConverters.py | 122 +++++++++++++ models/gc_nnunet_pancreas/utils/__init__.py | 4 +- 6 files changed, 298 insertions(+), 58 deletions(-) create mode 100644 models/gc_nnunet_pancreas/config/dseg.json rename models/gc_nnunet_pancreas/dockerfiles/{nocuda => }/Dockerfile (95%) delete mode 100644 models/gc_nnunet_pancreas/utils/MhaImporter.py create mode 100644 models/gc_nnunet_pancreas/utils/PanImgConverters.py diff --git a/models/gc_nnunet_pancreas/config/dseg.json b/models/gc_nnunet_pancreas/config/dseg.json new file mode 100644 index 00000000..d5c84596 --- /dev/null +++ b/models/gc_nnunet_pancreas/config/dseg.json @@ -0,0 +1,168 @@ +{ + "ContentCreatorName": "Reader1", + "ClinicalTrialSeriesID": "Session1", + "ClinicalTrialTimePointID": "1", + "SeriesDescription": "Segmentation", + "SeriesNumber": "300", + "InstanceNumber": "1", + "BodyPartExamined": "Pancreas", + "segmentAttributes": [ + [ + { + "labelID": 1, + "SegmentDescription": "Veins", + "SegmentAlgorithmType": "AUTOMATIC", + "SegmentAlgorithmName": "GC nnUNet Pancreas", + "SegmentedPropertyCategoryCodeSequence": { + "CodeValue": "123037004", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Anatomical Structure" + }, + "SegmentedPropertyTypeCodeSequence": { + "CodeValue": "29092000", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Vein" + }, + "SegmentedPropertyTypeModifierCodeSequence": { + "CodeValue": "51440002", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Right and left" + }, + "recommendedDisplayRGBValue": [ + 0, + 151, + 206 + ] + }, + { + "labelID": 2, + "SegmentDescription": "Artery", + "SegmentAlgorithmType": "AUTOMATIC", + "SegmentAlgorithmName": "GC nnUNet Pancreas", + "SegmentedPropertyCategoryCodeSequence": { + "CodeValue": "123037004", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Anatomical Structure" + }, + "SegmentedPropertyTypeCodeSequence": { + "CodeValue": "51114001", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Artery" + }, + "SegmentedPropertyTypeModifierCodeSequence": { + "CodeValue": "51440002", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Right and left" + }, + "recommendedDisplayRGBValue": [ + 216, + 101, + 79 + ] + }, + { + "labelID": 3, + "SegmentDescription": "Pancreas", + "SegmentAlgorithmType": "AUTOMATIC", + "SegmentAlgorithmName": "GC nnUNet Pancreas", + "SegmentedPropertyCategoryCodeSequence": { + "CodeValue": "123037004", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Anatomical Structure" + }, + "SegmentedPropertyTypeCodeSequence": { + "CodeValue": "15776009", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Pancreas" + }, + "recommendedDisplayRGBValue": [ + 249, + 180, + 111 + ] + }, + { + "labelID": 4, + "SegmentDescription": "Pancreatic duct", + "SegmentAlgorithmType": "AUTOMATIC", + "SegmentAlgorithmName": "GC nnUNet Pancreas", + "SegmentedPropertyCategoryCodeSequence": { + "CodeValue": "123037004", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Anatomical Structure" + }, + "SegmentedPropertyTypeCodeSequence": { + "CodeValue": "69930009", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Pancreatic duct" + } + }, + { + "labelID": 5, + "SegmentDescription": "Bile duct", + "SegmentAlgorithmType": "AUTOMATIC", + "SegmentAlgorithmName": "GC nnUNet Pancreas", + "SegmentedPropertyCategoryCodeSequence": { + "CodeValue": "123037004", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Anatomical Structure" + }, + "SegmentedPropertyTypeCodeSequence": { + "CodeValue": "28273000", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Bile duct" + }, + "SegmentedPropertyTypeModifierCodeSequence": { + "CodeValue": "51440002", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Right and left" + }, + "recommendedDisplayRGBValue": [ + 0, + 145, + 30 + ] + }, + { + "labelID": 6, + "SegmentDescription": "Cysts", + "SegmentAlgorithmType": "AUTOMATIC", + "SegmentAlgorithmName": "GC nnUNet Pancreas", + "SegmentedPropertyCategoryCodeSequence": { + "CodeValue": "49755003", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Morphologically Altered Structure" + }, + "SegmentedPropertyTypeCodeSequence": { + "CodeValue": "367643001", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Cyst" + }, + "recommendedDisplayRGBValue": [ + 205, + 205, + 100 + ] + }, + { + "labelID": 7, + "SegmentDescription": "Renal vein", + "SegmentAlgorithmType": "AUTOMATIC", + "SegmentAlgorithmName": "GC nnUNet Pancreas", + "SegmentedPropertyCategoryCodeSequence": { + "CodeValue": "123037004", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Anatomical Structure" + }, + "SegmentedPropertyTypeCodeSequence": { + "CodeValue": "56400007", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Renal vein" + } + } + ] + ], + "ContentLabel": "SEGMENTATION", + "ContentDescription": "Image segmentation", + "ClinicalTrialCoordinatingCenterName": "dcmqi" +} \ No newline at end of file diff --git a/models/gc_nnunet_pancreas/dockerfiles/nocuda/Dockerfile b/models/gc_nnunet_pancreas/dockerfiles/Dockerfile similarity index 95% rename from models/gc_nnunet_pancreas/dockerfiles/nocuda/Dockerfile rename to models/gc_nnunet_pancreas/dockerfiles/Dockerfile index d1945b36..5eeeb0d8 100644 --- a/models/gc_nnunet_pancreas/dockerfiles/nocuda/Dockerfile +++ b/models/gc_nnunet_pancreas/dockerfiles/Dockerfile @@ -1,6 +1,5 @@ # Specify the base image for the environment FROM mhubai/base:latest -# TODO add CUDA support since algorithm takes eons otherwise... # Specify/override authors label LABEL authors="sil.vandeleemput@radboudumc.nl" @@ -33,7 +32,7 @@ RUN git clone --depth 1 https://github.com/DIAGNijmegen/CE-CT_PDAC_AutomaticDete # https://github.com/MIC-DKFZ/nnUNet/pull/1209 ENV SKLEARN_ALLOW_DEPRECATED_SKLEARN_PACKAGE_INSTALL=True -# Install nnUNet and other requirements +# Install nnUNet and other requirements (should install PyTorch as well...) RUN pip3 install --no-cache-dir -r /opt/algorithm/requirements.txt # Extend the nnUNet installation with custom trainers diff --git a/models/gc_nnunet_pancreas/scripts/run.py b/models/gc_nnunet_pancreas/scripts/run.py index 8421566f..9d25b15f 100644 --- a/models/gc_nnunet_pancreas/scripts/run.py +++ b/models/gc_nnunet_pancreas/scripts/run.py @@ -16,12 +16,10 @@ from mhubio.core import Config, DataType, FileType, CT, SEG, Meta from mhubio.modules.importer.FileStructureImporter import FileStructureImporter from mhubio.modules.importer.DicomImporter import DicomImporter -from mhubio.modules.importer.NrrdImporter import NrrdImporter -from mhubio.modules.convert.NiftiConverter import NiftiConverter -from mhubio.modules.runner.NNUnetRunner import NNUnetRunner from mhubio.modules.convert.DsegConverter import DsegConverter from mhubio.modules.organizer.DataOrganizer import DataOrganizer -from models.gc_nnunet_pancreas import MhaImporter, GCNNUnetPancreasRunner, HEATMAP +from models.gc_nnunet_pancreas import GCNNUnetPancreasRunner, HEATMAP +from models.gc_nnunet_pancreas.utils import MhaPanImgConverter # clean-up import shutil @@ -31,14 +29,12 @@ # config config = Config('/app/models/gc_nnunet_pancreas/config/config.yml') -config.verbose = True # TODO: define levels of verbosity and integrate consistently. # import (ct:dicom) -#DicomImporter(config).execute() +DicomImporter(config).execute() -# import (ct:mha) -MhaImporter(config).execute() -#FileStructureImporter(config).execute() +# convert (ct:dicom -> ct:mha) +MhaPanImgConverter(config).execute() # execute model (nnunet ct:mha -> (hm:mha, seg:mha)) GCNNUnetPancreasRunner(config).execute() diff --git a/models/gc_nnunet_pancreas/utils/MhaImporter.py b/models/gc_nnunet_pancreas/utils/MhaImporter.py deleted file mode 100644 index f2ca93b4..00000000 --- a/models/gc_nnunet_pancreas/utils/MhaImporter.py +++ /dev/null @@ -1,45 +0,0 @@ -""" --------------------------------------- -MHub / GC - MHA importer --------------------------------------- - --------------------------------------- -Author: Sil van de Leemput -Email: sil.vandeleemput@radboudumc.nl --------------------------------------- -""" -import os -from typing import Optional -from pathlib import Path - -from mhubio.modules.importer.DataImporter import IDEF, DataImporter, FileType -from mhubio.core import Meta, DirectoryChain, CT - - -class MhaImporter(DataImporter): - def task(self) -> None: - source_dir = self.c['source_dir'] - source_dc = DirectoryChain(path=source_dir, parent=self.config.data.dc) - # input tiff file directory - input_dir = source_dc.abspath - self.v(f"{input_dir}") - - # add input tiff files as WSI images... - self.setBasePath(input_dir) - for input_tiff_file in Path(input_dir).glob("*.mha"): - self.v(f"{input_tiff_file}") - self.addMhaCT(str(input_tiff_file), ref=input_tiff_file.stem) - - # let the base module take over from here - super().task() - - def addMhaCT(self, path: str, ref: Optional[str] = None) -> None: - _path = self._resolvePath(path, ref) - self.v("adding CT in mha format with resolved path: ", _path) - assert os.path.isfile(_path) and _path.endswith('.mha'), f"Expect existing mha file, '{_path}' was given instead." - self._import_paths.append(IDEF( - ref = ref, - path = path, - ftype = FileType.MHA, - meta = CT - )) diff --git a/models/gc_nnunet_pancreas/utils/PanImgConverters.py b/models/gc_nnunet_pancreas/utils/PanImgConverters.py new file mode 100644 index 00000000..824d20f4 --- /dev/null +++ b/models/gc_nnunet_pancreas/utils/PanImgConverters.py @@ -0,0 +1,122 @@ +""" +------------------------------------------------------------- +MHub - PanImg Conversion Modules Dicom2Mha and WSI-Dicom2Tiff +------------------------------------------------------------- + +------------------------------------------------------------- +Author: Sil van de Leemput +Email: sil.vandeleemput@radboudumc.nl +------------------------------------------------------------- +""" + + +from typing import Optional + +from mhubio.modules.convert.DataConverter import DataConverter +from mhubio.core import Instance, InstanceData, DataType, FileType + +import os +from pathlib import Path +import shutil + +from panimg.exceptions import UnconsumedFilesException +from panimg.image_builders.dicom import image_builder_dicom +from panimg.image_builders.tiff import image_builder_tiff +from panimg.image_builders.metaio_nrrd import image_builder_nrrd + +import SimpleITK + + +class MhaPanImgConverter(DataConverter): + """ + Conversion module. + Convert instance data from dicom or nrrd to mha. + """ + + def convert(self, instance: Instance) -> Optional[InstanceData]: + + # create a converted instance + has_instance_dicom = instance.hasType(DataType(FileType.DICOM)) + has_instance_nrrd = instance.hasType(DataType(FileType.NRRD)) + + assert has_instance_dicom or has_instance_nrrd, f"CONVERT ERROR: required datatype (dicom or nrrd) not available in instance {str(instance)}." + + # select input data, dicom has priority over nrrd + input_data = instance.data.filter(DataType(FileType.DICOM) if has_instance_dicom else DataType(FileType.NRRD)).first() + + # out data + mha_data = InstanceData("image.mha", DataType(FileType.MHA, input_data.type.meta)) + mha_data.instance = instance + + # paths + inp_data_dir = Path(input_data.abspath) + out_mha_file = Path(mha_data.abspath) + + # sanity check + assert(inp_data_dir.is_dir()) + + # DICOM CT to MHA conversion (if the file doesn't exist yet) + if out_mha_file.is_file(): + print("CONVERT ERROR: File already exists: ", out_mha_file) + return None + else: + # run conversion using panimg + input_files = {f for f in inp_data_dir.glob(["*.nrrd", "*.dcm"][has_instance_dicom]) if f.is_file()} + img_builder = image_builder_dicom if has_instance_dicom else image_builder_nrrd + try: + for result in img_builder(files=input_files): + sitk_image = result.image # SimpleITK image + SimpleITK.WriteImage(sitk_image, str(out_mha_file)) + except UnconsumedFilesException as e: + # e.file_errors is keyed with a Path to a file that could not be consumed, + # with a list of all the errors found with loading it, + # the user can then choose what to do with that information + print("CONVERT ERROR: UnconsumedFilesException during PanImg conversion: ", e.file_errors) + return None + + return mha_data + + +class TiffPanImgConverter(DataConverter): + """ + Conversion module. + Convert instance data from WSI-dicom to tiff. + """ + + def convert(self, instance: Instance) -> Optional[InstanceData]: + + # create a converted instance + assert instance.hasType(DataType(FileType.DICOM)), f"CONVERT ERROR: required datatype (dicom) not available in instance {str(instance)}." + dicom_data = instance.data.filter(DataType(FileType.DICOM)).first() + + # out data + tiff_data = InstanceData("image.tiff", DataType(FileType.TIFF, dicom_data.type.meta)) + tiff_data.instance = instance + + # paths + inp_dicom_dir = Path(dicom_data.abspath) + out_tiff_file = Path(tiff_data.abspath) + + # sanity check + assert(inp_dicom_dir.is_dir()) + + # WSI-DICOM to TIFF conversion (if the file doesn't exist yet) + if out_tiff_file.is_file(): + print("CONVERT ERROR: File already exists: ", out_tiff_file) + return None + else: + # run conversion using panimg + dcm_input_files = {f for f in inp_dicom_dir.glob("*.dcm") if f.is_file()} + + try: + for result in image_builder_tiff(files=dcm_input_files): + tiff_image = result.file # Path to the tiff file + shutil.move(str(tiff_image), str(out_tiff_file)) + except UnconsumedFilesException as e: + # e.file_errors is keyed with a Path to a file that could not be consumed, + # with a list of all the errors found with loading it, + # the user can then choose what to do with that information + print("CONVERT ERROR: UnconsumedFilesException during PanImg conversion: ", e.file_errors) + return None + + return tiff_data diff --git a/models/gc_nnunet_pancreas/utils/__init__.py b/models/gc_nnunet_pancreas/utils/__init__.py index c35f6fef..7fc72114 100644 --- a/models/gc_nnunet_pancreas/utils/__init__.py +++ b/models/gc_nnunet_pancreas/utils/__init__.py @@ -1,2 +1,2 @@ -from .MhaImporter import * -from .GCnnUnetPancreasRunner import * \ No newline at end of file +from .GCnnUnetPancreasRunner import * +from .PanImgConverters import * From 12f30d85cf81d659ba412f1d156b8209a97277c1 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Tue, 1 Aug 2023 17:14:15 +0200 Subject: [PATCH 04/21] cleanup code runner and run, configure dsegconverter, dataorganizer --- models/gc_nnunet_pancreas/config/config.yml | 34 +++++++++-------- models/gc_nnunet_pancreas/config/dseg.json | 8 ++-- models/gc_nnunet_pancreas/scripts/run.py | 15 +++----- .../utils/GCnnUnetPancreasRunner.py | 38 +++---------------- 4 files changed, 33 insertions(+), 62 deletions(-) diff --git a/models/gc_nnunet_pancreas/config/config.yml b/models/gc_nnunet_pancreas/config/config.yml index 3b436dac..2a449c6b 100644 --- a/models/gc_nnunet_pancreas/config/config.yml +++ b/models/gc_nnunet_pancreas/config/config.yml @@ -1,10 +1,16 @@ general: version: 1.0 data_base_dir: /app/data - description: base configuration for nnunet pancreas PDAC model + description: base configuration for GC NNUnet Pancreas model (dicom to dicom) -modules: +execute: +- DicomImporter +- MHAConverter +- GCNNUnetPancreasRunner +- DsegConverter +- DataOrganizer +modules: DicomImporter: source_dir: input_data import_dir: sorted_data @@ -12,22 +18,20 @@ modules: meta: mod: ct - MhaImporter: - source_dir: input_data - import_dir: sorted_data + MHAConverter: + # TODO add panimg backend here... GCNNUnetPancreasRunner: -# TODO configure DsegConverter -# DsegConverter: -# #source_segs: [nifti:mod=seg] -# #json_config_path: /app/models/nnunet_pancreas/config/dseg.json -# source_segs: [mha:mod=seg:roi=*] -# model_name: GC NNUnet Pancreas -# skip_empty_slices: True + DsegConverter: + model_name: 'GC NNUnet Pancreas' + source_segs: ['mha:mod=seg'] + target_dicom: dicom:mod=ct + skip_empty_slices: True + json_config_path: /app/models/gc_nnunet_pancreas/config/dseg.json DataOrganizer: targets: - - mha:mod=heatmap-->/app/data/output_data/[i:sid]/heatmap.mha - - mha:mod=seg-->/app/data/output_data/[i:sid]/pancreas.seg.mha -# - dicomseg:mod=seg-->/app/data/output_data/[i:sid]/pancreas.seg.dcm + - mha:mod=heatmap-->[i:sid]/nnunet_pancreas_heatmap.mha + - mha:mod=seg-->[i:sid]/nnunet_pancreas.seg.mha + - dicomseg:mod=seg-->[i:sid]/nnunet_pancreas.seg.dcm diff --git a/models/gc_nnunet_pancreas/config/dseg.json b/models/gc_nnunet_pancreas/config/dseg.json index d5c84596..1e52a967 100644 --- a/models/gc_nnunet_pancreas/config/dseg.json +++ b/models/gc_nnunet_pancreas/config/dseg.json @@ -1,11 +1,11 @@ { - "ContentCreatorName": "Reader1", - "ClinicalTrialSeriesID": "Session1", + "ContentCreatorName": "IDC", + "ClinicalTrialSeriesID": "0", "ClinicalTrialTimePointID": "1", "SeriesDescription": "Segmentation", - "SeriesNumber": "300", + "SeriesNumber": "42", "InstanceNumber": "1", - "BodyPartExamined": "Pancreas", + "BodyPartExamined": "ABDOMEN", "segmentAttributes": [ [ { diff --git a/models/gc_nnunet_pancreas/scripts/run.py b/models/gc_nnunet_pancreas/scripts/run.py index 9d25b15f..2baeb9e7 100644 --- a/models/gc_nnunet_pancreas/scripts/run.py +++ b/models/gc_nnunet_pancreas/scripts/run.py @@ -13,12 +13,11 @@ import sys sys.path.append('.') -from mhubio.core import Config, DataType, FileType, CT, SEG, Meta -from mhubio.modules.importer.FileStructureImporter import FileStructureImporter +from mhubio.core import Config from mhubio.modules.importer.DicomImporter import DicomImporter from mhubio.modules.convert.DsegConverter import DsegConverter from mhubio.modules.organizer.DataOrganizer import DataOrganizer -from models.gc_nnunet_pancreas import GCNNUnetPancreasRunner, HEATMAP +from models.gc_nnunet_pancreas import GCNNUnetPancreasRunner from models.gc_nnunet_pancreas.utils import MhaPanImgConverter # clean-up @@ -39,12 +38,8 @@ # execute model (nnunet ct:mha -> (hm:mha, seg:mha)) GCNNUnetPancreasRunner(config).execute() -# convert (seg:nifti -> seg:dcm) -# DsegConverter(config).execute() +# convert (seg:mha -> seg:dcm) +DsegConverter(config).execute() # organize data into output folder -organizer = DataOrganizer(config, set_file_permissions=sys.platform.startswith('linux')) -organizer.setTarget(DataType(FileType.MHA, HEATMAP), "/app/data/output_data/[i:sid]/heatmap.mha") -organizer.setTarget(DataType(FileType.MHA, SEG), "/app/data/output_data/[i:sid]/pancreas.seg.mha") -#organizer.setTarget(DataType(FileType.DICOMSEG, SEG), "/app/data/output_data/[i:sid]/pancreas.seg.dcm") -organizer.execute() \ No newline at end of file +DataOrganizer(config, set_file_permissions=sys.platform.startswith('linux')).execute() diff --git a/models/gc_nnunet_pancreas/utils/GCnnUnetPancreasRunner.py b/models/gc_nnunet_pancreas/utils/GCnnUnetPancreasRunner.py index b63badbd..0b9a1d3f 100644 --- a/models/gc_nnunet_pancreas/utils/GCnnUnetPancreasRunner.py +++ b/models/gc_nnunet_pancreas/utils/GCnnUnetPancreasRunner.py @@ -9,8 +9,7 @@ ----------------------------------------------------------- """ -from mhubio.core import Module, Instance, InstanceData, DataType, FileType, CT, SEG, IO, Meta -import os, subprocess, shutil +from mhubio.core import Module, Instance, InstanceData, DataType, Meta, IO from pathlib import Path @@ -19,44 +18,17 @@ # TODO should move to MHubio/core/templates.py HEATMAP = Meta(mod="heatmap") -# @IO.Config('output_dir', str, "/app/tmp/gc_nnunet_pancreas/", the='directory to output the segmentation and the heatmap') class GCNNUnetPancreasRunner(Module): - - # output_dir: str - @IO.Instance() @IO.Input('in_data', 'mha:mod=ct', the="input data") @IO.Output('heatmap', 'heatmap.mha', 'mha:mod=heatmap:model=GCNNUnetPancreas', data="in_data", the="heatmap of the pancreatic tumor likelihood") @IO.Output('segmentation', 'segmentation.mha', 'mha:mod=seg:model=GCNNUnetPancreas', data="in_data", - the="segmentation of the pancreas, with the following classes: 1-veins, 2-arteries, 3-pancreas, 4-pancreatic duct, 5-bile duct, 6-cysts, 7-renal vein") - # @IO.Output('vei', 'Veins.mha', 'mha:mod=seg:model=GCNNUnetPancreas:roi=HEART', bundle='gc_nnunet_pancreas', in_signature=False, - # the="segmentation of the veins") - # @IO.Output('art', 'Arteries.mha', 'mha:mod=seg:model=GCNNUnetPancreas:roi=PULMONARY_ARTERY', bundle='gc_nnunet_pancreas', in_signature=False, - # the="segmentation of the arteries") - # @IO.Output('pan', 'Pancreas.mha', 'mha:mod=seg:model=GCNNUnetPancreas:roi=AORTA', bundle='gc_nnunet_pancreas', in_signature=False, - # the="segmentation of the pancreas") - # @IO.Output('pdc', 'PDC.mha', 'mha:mod=seg:model=GCNNUnetPancreas:roi=HEART', bundle='gc_nnunet_pancreas', in_signature=False, - # the="segmentation of the pancreatic duct") - # @IO.Output('bdt', 'BileDuct.mha', 'mha:mod=seg:model=GCNNUnetPancreas:roi=PULMONARY_ARTERY', bundle='gc_nnunet_pancreas', in_signature=False, - # the="segmentation of the bile duct") - # @IO.Output('cys', 'Cysts.mha', 'mha:mod=seg:model=GCNNUnetPancreas:roi=AORTA', bundle='gc_nnunet_pancreas', in_signature=False, - # the="segmentation of cysts") - # @IO.Output('rve', 'RenalVein.mha', 'mha:mod=seg:model=GCNNUnetPancreas:roi=AORTA', bundle='gc_nnunet_pancreas', in_signature=False, - # the="segmentation of the renal vein") + the="segmentation of the pancreas, with the following classes: " + "1-veins, 2-arteries, 3-pancreas, 4-pancreatic duct, 5-bile duct, 6-cysts, 7-renal vein") def task(self, instance: Instance, in_data: InstanceData, heatmap: InstanceData, segmentation: InstanceData, **kwargs) -> None: algorithm = PDACDetectionContainer() - #algorithm.ct_ip_dir = Path("/input/images/") algorithm.ct_image = in_data.abspath # set as str not Path - #algorithm.output_dir = Path(self.output_dir) - #algorithm.output_dir_tlm = algorithm.output_dir / "pancreatic-tumor-likelihood-map" - #algorithm.output_dir_seg = algorithm.output_dir / "pancreas-anatomy-and-vessel-segmentation" - algorithm.heatmap = Path(heatmap.abspath) # algorithm.output_dir_tlm / "heatmap.mha" - algorithm.segmentation = Path(segmentation.abspath) #algorithm.output_dir_seg / "segmentation.mha" - #algorithm.output_dir.mkdir(exist_ok=True, parents=True) - #algorithm.output_dir_tlm.mkdir(exist_ok=True, parents=True) - #algorithm.output_dir_seg.mkdir(exist_ok=True, parents=True) - self.v(in_data.abspath) - self.v(heatmap.abspath) - self.v(segmentation.abspath) + algorithm.heatmap = Path(heatmap.abspath) + algorithm.segmentation = Path(segmentation.abspath) algorithm.process() From 16d18932d7d043fd3e2650c5200ec03694433166 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Wed, 30 Aug 2023 17:47:10 +0200 Subject: [PATCH 05/21] add panimg backend for mhaconverter and cleanup --- .../config/{config.yml => default.yml} | 8 +- .../gc_nnunet_pancreas/dockerfiles/Dockerfile | 5 +- models/gc_nnunet_pancreas/scripts/run.py | 45 ------- ...easRunner.py => GCNNUnetPancreasRunner.py} | 0 .../utils/PanImgConverters.py | 122 ------------------ models/gc_nnunet_pancreas/utils/__init__.py | 3 +- 6 files changed, 7 insertions(+), 176 deletions(-) rename models/gc_nnunet_pancreas/config/{config.yml => default.yml} (88%) delete mode 100644 models/gc_nnunet_pancreas/scripts/run.py rename models/gc_nnunet_pancreas/utils/{GCnnUnetPancreasRunner.py => GCNNUnetPancreasRunner.py} (100%) delete mode 100644 models/gc_nnunet_pancreas/utils/PanImgConverters.py diff --git a/models/gc_nnunet_pancreas/config/config.yml b/models/gc_nnunet_pancreas/config/default.yml similarity index 88% rename from models/gc_nnunet_pancreas/config/config.yml rename to models/gc_nnunet_pancreas/config/default.yml index 2a449c6b..5ae2cae2 100644 --- a/models/gc_nnunet_pancreas/config/config.yml +++ b/models/gc_nnunet_pancreas/config/default.yml @@ -5,7 +5,7 @@ general: execute: - DicomImporter -- MHAConverter +- MhaConverter - GCNNUnetPancreasRunner - DsegConverter - DataOrganizer @@ -18,10 +18,8 @@ modules: meta: mod: ct - MHAConverter: - # TODO add panimg backend here... - - GCNNUnetPancreasRunner: + MhaConverter: + engine: panimg DsegConverter: model_name: 'GC NNUnet Pancreas' diff --git a/models/gc_nnunet_pancreas/dockerfiles/Dockerfile b/models/gc_nnunet_pancreas/dockerfiles/Dockerfile index 5eeeb0d8..c19efbc6 100644 --- a/models/gc_nnunet_pancreas/dockerfiles/Dockerfile +++ b/models/gc_nnunet_pancreas/dockerfiles/Dockerfile @@ -42,5 +42,6 @@ RUN SITE_PKG=`pip3 show nnunet | grep "Location:" | awk '{print $2}'` && \ # Add algorithm files to python path ENV PYTHONPATH=/opt/algorithm:/app -# Default run script -CMD ["python3", "/app/models/gc_nnunet_pancreas/scripts/run.py"] \ No newline at end of file +# Configure main entrypoint +ENTRYPOINT ["python3", "-m", "mhubio.run"] +CMD ["--config", "/app/models/gc_nnunet_pancreas/config/default.yml"] \ No newline at end of file diff --git a/models/gc_nnunet_pancreas/scripts/run.py b/models/gc_nnunet_pancreas/scripts/run.py deleted file mode 100644 index 2baeb9e7..00000000 --- a/models/gc_nnunet_pancreas/scripts/run.py +++ /dev/null @@ -1,45 +0,0 @@ -""" ---------------------------------------------------- -GC / MHub - run the NNUnet GC pancreas segmentation - pipeline ---------------------------------------------------- - ---------------------------------------------------- -Author: Sil van de Leemput -Email: sil.vandeleemput@radboudumc.nl ---------------------------------------------------- -""" - -import sys -sys.path.append('.') - -from mhubio.core import Config -from mhubio.modules.importer.DicomImporter import DicomImporter -from mhubio.modules.convert.DsegConverter import DsegConverter -from mhubio.modules.organizer.DataOrganizer import DataOrganizer -from models.gc_nnunet_pancreas import GCNNUnetPancreasRunner -from models.gc_nnunet_pancreas.utils import MhaPanImgConverter - -# clean-up -import shutil -shutil.rmtree("/app/data/sorted_data", ignore_errors=True) -shutil.rmtree("/app/tmp", ignore_errors=True) -shutil.rmtree("/app/data/output_data", ignore_errors=True) - -# config -config = Config('/app/models/gc_nnunet_pancreas/config/config.yml') - -# import (ct:dicom) -DicomImporter(config).execute() - -# convert (ct:dicom -> ct:mha) -MhaPanImgConverter(config).execute() - -# execute model (nnunet ct:mha -> (hm:mha, seg:mha)) -GCNNUnetPancreasRunner(config).execute() - -# convert (seg:mha -> seg:dcm) -DsegConverter(config).execute() - -# organize data into output folder -DataOrganizer(config, set_file_permissions=sys.platform.startswith('linux')).execute() diff --git a/models/gc_nnunet_pancreas/utils/GCnnUnetPancreasRunner.py b/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py similarity index 100% rename from models/gc_nnunet_pancreas/utils/GCnnUnetPancreasRunner.py rename to models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py diff --git a/models/gc_nnunet_pancreas/utils/PanImgConverters.py b/models/gc_nnunet_pancreas/utils/PanImgConverters.py deleted file mode 100644 index 824d20f4..00000000 --- a/models/gc_nnunet_pancreas/utils/PanImgConverters.py +++ /dev/null @@ -1,122 +0,0 @@ -""" -------------------------------------------------------------- -MHub - PanImg Conversion Modules Dicom2Mha and WSI-Dicom2Tiff -------------------------------------------------------------- - -------------------------------------------------------------- -Author: Sil van de Leemput -Email: sil.vandeleemput@radboudumc.nl -------------------------------------------------------------- -""" - - -from typing import Optional - -from mhubio.modules.convert.DataConverter import DataConverter -from mhubio.core import Instance, InstanceData, DataType, FileType - -import os -from pathlib import Path -import shutil - -from panimg.exceptions import UnconsumedFilesException -from panimg.image_builders.dicom import image_builder_dicom -from panimg.image_builders.tiff import image_builder_tiff -from panimg.image_builders.metaio_nrrd import image_builder_nrrd - -import SimpleITK - - -class MhaPanImgConverter(DataConverter): - """ - Conversion module. - Convert instance data from dicom or nrrd to mha. - """ - - def convert(self, instance: Instance) -> Optional[InstanceData]: - - # create a converted instance - has_instance_dicom = instance.hasType(DataType(FileType.DICOM)) - has_instance_nrrd = instance.hasType(DataType(FileType.NRRD)) - - assert has_instance_dicom or has_instance_nrrd, f"CONVERT ERROR: required datatype (dicom or nrrd) not available in instance {str(instance)}." - - # select input data, dicom has priority over nrrd - input_data = instance.data.filter(DataType(FileType.DICOM) if has_instance_dicom else DataType(FileType.NRRD)).first() - - # out data - mha_data = InstanceData("image.mha", DataType(FileType.MHA, input_data.type.meta)) - mha_data.instance = instance - - # paths - inp_data_dir = Path(input_data.abspath) - out_mha_file = Path(mha_data.abspath) - - # sanity check - assert(inp_data_dir.is_dir()) - - # DICOM CT to MHA conversion (if the file doesn't exist yet) - if out_mha_file.is_file(): - print("CONVERT ERROR: File already exists: ", out_mha_file) - return None - else: - # run conversion using panimg - input_files = {f for f in inp_data_dir.glob(["*.nrrd", "*.dcm"][has_instance_dicom]) if f.is_file()} - img_builder = image_builder_dicom if has_instance_dicom else image_builder_nrrd - try: - for result in img_builder(files=input_files): - sitk_image = result.image # SimpleITK image - SimpleITK.WriteImage(sitk_image, str(out_mha_file)) - except UnconsumedFilesException as e: - # e.file_errors is keyed with a Path to a file that could not be consumed, - # with a list of all the errors found with loading it, - # the user can then choose what to do with that information - print("CONVERT ERROR: UnconsumedFilesException during PanImg conversion: ", e.file_errors) - return None - - return mha_data - - -class TiffPanImgConverter(DataConverter): - """ - Conversion module. - Convert instance data from WSI-dicom to tiff. - """ - - def convert(self, instance: Instance) -> Optional[InstanceData]: - - # create a converted instance - assert instance.hasType(DataType(FileType.DICOM)), f"CONVERT ERROR: required datatype (dicom) not available in instance {str(instance)}." - dicom_data = instance.data.filter(DataType(FileType.DICOM)).first() - - # out data - tiff_data = InstanceData("image.tiff", DataType(FileType.TIFF, dicom_data.type.meta)) - tiff_data.instance = instance - - # paths - inp_dicom_dir = Path(dicom_data.abspath) - out_tiff_file = Path(tiff_data.abspath) - - # sanity check - assert(inp_dicom_dir.is_dir()) - - # WSI-DICOM to TIFF conversion (if the file doesn't exist yet) - if out_tiff_file.is_file(): - print("CONVERT ERROR: File already exists: ", out_tiff_file) - return None - else: - # run conversion using panimg - dcm_input_files = {f for f in inp_dicom_dir.glob("*.dcm") if f.is_file()} - - try: - for result in image_builder_tiff(files=dcm_input_files): - tiff_image = result.file # Path to the tiff file - shutil.move(str(tiff_image), str(out_tiff_file)) - except UnconsumedFilesException as e: - # e.file_errors is keyed with a Path to a file that could not be consumed, - # with a list of all the errors found with loading it, - # the user can then choose what to do with that information - print("CONVERT ERROR: UnconsumedFilesException during PanImg conversion: ", e.file_errors) - return None - - return tiff_data diff --git a/models/gc_nnunet_pancreas/utils/__init__.py b/models/gc_nnunet_pancreas/utils/__init__.py index 7fc72114..683c17d1 100644 --- a/models/gc_nnunet_pancreas/utils/__init__.py +++ b/models/gc_nnunet_pancreas/utils/__init__.py @@ -1,2 +1 @@ -from .GCnnUnetPancreasRunner import * -from .PanImgConverters import * +from .GCNNUnetPancreasRunner import * From 19d29d89e493011cb3e6f2f94d372bb9b9a47373 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Thu, 14 Sep 2023 13:00:49 +0200 Subject: [PATCH 06/21] Updated and cleaned Dockerfile and Runner and added some comments --- .../gc_nnunet_pancreas/dockerfiles/Dockerfile | 23 +++++++++---------- .../utils/GCNNUnetPancreasRunner.py | 11 +++++---- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/models/gc_nnunet_pancreas/dockerfiles/Dockerfile b/models/gc_nnunet_pancreas/dockerfiles/Dockerfile index c19efbc6..3fe9a951 100644 --- a/models/gc_nnunet_pancreas/dockerfiles/Dockerfile +++ b/models/gc_nnunet_pancreas/dockerfiles/Dockerfile @@ -4,22 +4,14 @@ FROM mhubai/base:latest # Specify/override authors label LABEL authors="sil.vandeleemput@radboudumc.nl" -# Clone MHub model (m-gc-nnunet-pancreas branch, fixed to commit 407f1f884f09898bef9a9173e6434d681a50d399) # TODO -#RUN git init \ -# && git sparse-checkout set "models/gc_nnunet_pancreas" \ -# && git fetch https://github.com/MHubAI/models.git m-gc-nnunet-pancreas \ -# && git merge TODO - # Install git-lfs (required for downloading the model weights) -RUN apt update && apt install -y --no-install-recommends \ - git-lfs \ - && rm -rf /var/lib/apt/lists/* +RUN apt update && apt install -y --no-install-recommends git-lfs && rm -rf /var/lib/apt/lists/* # Install the model weights and the algorithm files -# * Pull algorithm from repo into /opt/algorithm for commit e4f4008c6e18e60a79f693448562a340a9252aa8 +# * Pull algorithm from repo into /opt/algorithm (main branch, commit e4f4008c6e18e60a79f693448562a340a9252aa8) # * Remove .git folder to keep docker layer small # * Replace input images path in process.py with an existing folder to avoid errors -RUN git clone --depth 1 https://github.com/DIAGNijmegen/CE-CT_PDAC_AutomaticDetection_nnUnet.git /opt/algorithm && \ +RUN git clone https://github.com/DIAGNijmegen/CE-CT_PDAC_AutomaticDetection_nnUnet.git /opt/algorithm && \ cd /opt/algorithm && \ git reset --hard e4f4008c6e18e60a79f693448562a340a9252aa8 && \ rm -rf /opt/algorithm/.git && \ @@ -39,9 +31,16 @@ RUN pip3 install --no-cache-dir -r /opt/algorithm/requirements.txt RUN SITE_PKG=`pip3 show nnunet | grep "Location:" | awk '{print $2}'` && \ mv /opt/algorithm/nnUNetTrainerV2_Loss_CE_checkpoints.py "$SITE_PKG/nnunet/training/network_training/nnUNetTrainerV2_Loss_CE_checkpoints.py" +# Clone the main branch of MHubAI/models TODO check if ok +RUN git stash \ + && git fetch https://github.com/MHubAI/models.git main \ + && git merge FETCH_HEAD \ + && git sparse-checkout set "models/gc_nnunet_pancreas" \ + && git fetch https://github.com/MHubAI/models.git main + # Add algorithm files to python path ENV PYTHONPATH=/opt/algorithm:/app # Configure main entrypoint ENTRYPOINT ["python3", "-m", "mhubio.run"] -CMD ["--config", "/app/models/gc_nnunet_pancreas/config/default.yml"] \ No newline at end of file +CMD ["--config", "/app/models/gc_nnunet_pancreas/config/default.yml"] diff --git a/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py b/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py index 0b9a1d3f..e57fac33 100644 --- a/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py +++ b/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py @@ -13,10 +13,12 @@ from pathlib import Path +# Import the algorithm pipeline class from the CE-CT_PDAC_AutomaticDetection_nnUnet repository from process import PDACDetectionContainer # TODO should move to MHubio/core/templates.py -HEATMAP = Meta(mod="heatmap") +HEATMAP = Meta(mod="heatmap") + class GCNNUnetPancreasRunner(Module): @IO.Instance() @@ -27,8 +29,9 @@ class GCNNUnetPancreasRunner(Module): the="segmentation of the pancreas, with the following classes: " "1-veins, 2-arteries, 3-pancreas, 4-pancreatic duct, 5-bile duct, 6-cysts, 7-renal vein") def task(self, instance: Instance, in_data: InstanceData, heatmap: InstanceData, segmentation: InstanceData, **kwargs) -> None: + # Configure the algorithm pipeline class and run it algorithm = PDACDetectionContainer() - algorithm.ct_image = in_data.abspath # set as str not Path - algorithm.heatmap = Path(heatmap.abspath) - algorithm.segmentation = Path(segmentation.abspath) + algorithm.ct_image = in_data.abspath # set as str not Path + algorithm.heatmap = Path(heatmap.abspath) + algorithm.segmentation = Path(segmentation.abspath) algorithm.process() From 737a5d2c210138876b8f59391d0492d4856bbc1c Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Tue, 10 Oct 2023 15:34:37 +0200 Subject: [PATCH 07/21] add meta.json --- models/gc_nnunet_pancreas/meta.json | 129 ++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 models/gc_nnunet_pancreas/meta.json diff --git a/models/gc_nnunet_pancreas/meta.json b/models/gc_nnunet_pancreas/meta.json new file mode 100644 index 00000000..bce7005b --- /dev/null +++ b/models/gc_nnunet_pancreas/meta.json @@ -0,0 +1,129 @@ +{ + "id": "bf7ae4bb-c6f5-4b1e-89aa-a8de246def57", + "name": "pdac_detection_in_ct", + "title": "Pancreatic Ductal Adenocarcinoma Detection in CT", + "summary": { + "description": "This algorithm produces a tumor likelihood heatmap for the presence of pancreatic ductal adenocarcinoma (PDAC) in an input venous-phase contrast-enhanced computed tomography scan (CECT). Additionally, the algorithm provides the segmentation of multiple surrounding anatomical structures such as the pancreatic duct, common bile duct, veins and arteries. The heatmap and segmentations are resampled to the same spatial resolution and physical dimensions as the input CECT image for easier visualisation.", + "inputs": [ + { + "label": "Venous phase CT scan", + "description": "A contrast-enhanced CT scan in the venous phase and axial reconstruction", + "format": "DICOM", + "modality": "CT", + "bodypartexamined": "Abdomen", + "slicethickness": "2.5mm", + "non-contrast": false, + "contrast": false + } + ], + "outputs": [ + { + "type": "Segmentation", + "classes": [ + "veins", + "arteries", + "pancreas", + "pancreatic duct", + "bile duct", + "cysts", + "renal vein" + ] + }, + { + "type": "Prediction", + "valueType": "number", + "label": "Pancreatic tumor likelihood", + "description": "Pancreatic tumor likelihood map with values between 0 and 1", + "classes": [] + } + ], + "model": { + "architecture": "nnUnet ", + "training": "supervised", + "cmpapproach": "3D" + }, + "data": { + "training": { + "vol_samples": 242 + }, + "evaluation": { + "vol_samples": 361 + }, + "public": true, + "external": false + } + }, + "details": { + "name": "Fully Automatic Deep Learning Framework for Pancreatic Ductal Adenocarcinoma Detection on Computed Tomography", + "version": "", + "devteam": "DIAGNijmegen (Diagnostic Image Analysis Group, Radboud UMC, The Netherlands)", + "type": "The models were developed using nnUnet. All models employed a 3D U-Net as the base architecture and were trained for 250.000 training steps with five-fold cross-validation.", + "date": { + "weights": "2023-06-28", + "code": "2022-07-19", + "pub": "2022-01-13" + }, + "cite": "Alves N, Schuurmans M, Litjens G, Bosma JS, Hermans J, Huisman H. Fully Automatic Deep Learning Framework for Pancreatic Ductal Adenocarcinoma Detection on Computed Tomography. Cancers (Basel). 2022 Jan 13;14(2):376. doi: 10.3390/cancers14020376. PMID: 35053538; PMCID: PMC8774174.", + "license": { + "code": "Apache 2.0", + "weights": "Apache 2.0" + }, + "publications": [ + { + "title": "Fully Automatic Deep Learning Framework for Pancreatic Ductal Adenocarcinoma Detection on Computed Tomography ", + "uri": "https://www.mdpi.com/2072-6694/14/2/376" + } + ], + "github": "https://github.com/DIAGNijmegen/CE-CT_PDAC_AutomaticDetection_nnUnet", + "zenodo": "", + "colab": "", + "slicer": false + }, + "info": { + "use": { + "title": "Intended Use", + "text": "This algorithm is intended to be used only on venous-phase CECT examinations of patients with clinical suspicion of PDAC. This algorithm should not be used in different patient demographics.", + "references": [], + "tables": [] + }, + "analyses": { + "title": "Analysis", + "text": "The study evaluated a medical model's performance for tumor detection by analyzing receiver operating characteristic (ROC) and free-response receiver operating characteristic (FROC) curves, assessing both tumor presence and lesion localization, and compared three configurations using statistical tests and ensemble modeling.", + "references": [], + "tables": [] + }, + "evaluation": { + "title": "Evaluation Data", + "text": "This framework was tested in an independent, external cohort consisting of two publicly available datasets.", + "references": [ + { + "label": "The Medical Segmentation Decathlon pancreas dataset (training portion) consisting of 281 patients with pancreatic malignancies (including lesions in the head, neck, body, and tail of the pancreas) and voxel-level annotations for the pancreas and lesion.", + "uri": "http://medicaldecathlon.com/" + }, + { + "label": "The Cancer Imaging Archive dataset from the US National Institutes of Health Clinical Center, containing 80 patients with normal pancreas and respective voxel-level annotations.", + "uri": "https://wiki.cancerimagingarchive.net/display/Public/Pancreas-CT" + } + ], + "tables": [] + }, + "training": { + "title": "Training data", + "text": "CE-CT scans in the portal venous phase from 119 patients with pathology-proven PDAC in the pancreatic head (PDAC cohort) and 123 patients with normal pancreas (non-PDAC cohort), acquired between 1 January 2013 and 1 June 2020, were selected for model development.", + "references": [], + "tables": [] + }, + "ethics": { + "title": "", + "text": "", + "references": [], + "tables": [] + }, + "limitations": { + "title": "Before using this model", + "text": "Test the model retrospectively and prospectively on a diagnostic cohort that reflects the target population that the model will be used upon to confirm the validity of the model within a local setting.", + "references": [], + "tables": [] + } + } +} \ No newline at end of file From 334d5738575023698898c3e8cdffa8eee0ca656d Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Thu, 23 Nov 2023 21:42:08 +0100 Subject: [PATCH 08/21] update mhub model definition import Dockerfile --- models/gc_nnunet_pancreas/dockerfiles/Dockerfile | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/models/gc_nnunet_pancreas/dockerfiles/Dockerfile b/models/gc_nnunet_pancreas/dockerfiles/Dockerfile index 3fe9a951..9f6b56a5 100644 --- a/models/gc_nnunet_pancreas/dockerfiles/Dockerfile +++ b/models/gc_nnunet_pancreas/dockerfiles/Dockerfile @@ -31,12 +31,9 @@ RUN pip3 install --no-cache-dir -r /opt/algorithm/requirements.txt RUN SITE_PKG=`pip3 show nnunet | grep "Location:" | awk '{print $2}'` && \ mv /opt/algorithm/nnUNetTrainerV2_Loss_CE_checkpoints.py "$SITE_PKG/nnunet/training/network_training/nnUNetTrainerV2_Loss_CE_checkpoints.py" -# Clone the main branch of MHubAI/models TODO check if ok -RUN git stash \ - && git fetch https://github.com/MHubAI/models.git main \ - && git merge FETCH_HEAD \ - && git sparse-checkout set "models/gc_nnunet_pancreas" \ - && git fetch https://github.com/MHubAI/models.git main +# Import the MHub model definiton +ARG MHUB_MODELS_REPO +RUN buildutils/import_mhub_model.sh gc_nnunet_pancreas ${MHUB_MODELS_REPO} # Add algorithm files to python path ENV PYTHONPATH=/opt/algorithm:/app From 4466e56ec79da9a7dc581227ecccea4d684174f5 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Thu, 23 Nov 2023 21:45:15 +0100 Subject: [PATCH 09/21] removed first comment line in Dockerfile --- models/gc_nnunet_pancreas/dockerfiles/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/models/gc_nnunet_pancreas/dockerfiles/Dockerfile b/models/gc_nnunet_pancreas/dockerfiles/Dockerfile index 9f6b56a5..68577762 100644 --- a/models/gc_nnunet_pancreas/dockerfiles/Dockerfile +++ b/models/gc_nnunet_pancreas/dockerfiles/Dockerfile @@ -1,4 +1,3 @@ -# Specify the base image for the environment FROM mhubai/base:latest # Specify/override authors label From 2b035fee5fa473c4d2f173638101df3808ebf43f Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Thu, 7 Dec 2023 12:29:55 +0100 Subject: [PATCH 10/21] Added segdb export, removed dseg.json, added remapped output to runner, updated Dockerfile and config --- models/gc_nnunet_pancreas/config/default.yml | 8 +- models/gc_nnunet_pancreas/config/dseg.json | 168 ------------------ .../gc_nnunet_pancreas/dockerfiles/Dockerfile | 21 ++- .../utils/GCNNUnetPancreasRunner.py | 33 +++- 4 files changed, 46 insertions(+), 184 deletions(-) delete mode 100644 models/gc_nnunet_pancreas/config/dseg.json diff --git a/models/gc_nnunet_pancreas/config/default.yml b/models/gc_nnunet_pancreas/config/default.yml index 5ae2cae2..b13691ac 100644 --- a/models/gc_nnunet_pancreas/config/default.yml +++ b/models/gc_nnunet_pancreas/config/default.yml @@ -16,20 +16,20 @@ modules: import_dir: sorted_data sort_data: true meta: - mod: ct + mod: '%Modality' MhaConverter: engine: panimg + targets: [dicom:mod=ct] DsegConverter: model_name: 'GC NNUnet Pancreas' - source_segs: ['mha:mod=seg'] + source_segs: ['mha:mod=seg:type=remapped'] target_dicom: dicom:mod=ct skip_empty_slices: True - json_config_path: /app/models/gc_nnunet_pancreas/config/dseg.json DataOrganizer: targets: - mha:mod=heatmap-->[i:sid]/nnunet_pancreas_heatmap.mha - - mha:mod=seg-->[i:sid]/nnunet_pancreas.seg.mha + - mha:mod=seg:type=original-->[i:sid]/nnunet_pancreas.seg.mha - dicomseg:mod=seg-->[i:sid]/nnunet_pancreas.seg.dcm diff --git a/models/gc_nnunet_pancreas/config/dseg.json b/models/gc_nnunet_pancreas/config/dseg.json deleted file mode 100644 index 1e52a967..00000000 --- a/models/gc_nnunet_pancreas/config/dseg.json +++ /dev/null @@ -1,168 +0,0 @@ -{ - "ContentCreatorName": "IDC", - "ClinicalTrialSeriesID": "0", - "ClinicalTrialTimePointID": "1", - "SeriesDescription": "Segmentation", - "SeriesNumber": "42", - "InstanceNumber": "1", - "BodyPartExamined": "ABDOMEN", - "segmentAttributes": [ - [ - { - "labelID": 1, - "SegmentDescription": "Veins", - "SegmentAlgorithmType": "AUTOMATIC", - "SegmentAlgorithmName": "GC nnUNet Pancreas", - "SegmentedPropertyCategoryCodeSequence": { - "CodeValue": "123037004", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Anatomical Structure" - }, - "SegmentedPropertyTypeCodeSequence": { - "CodeValue": "29092000", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Vein" - }, - "SegmentedPropertyTypeModifierCodeSequence": { - "CodeValue": "51440002", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Right and left" - }, - "recommendedDisplayRGBValue": [ - 0, - 151, - 206 - ] - }, - { - "labelID": 2, - "SegmentDescription": "Artery", - "SegmentAlgorithmType": "AUTOMATIC", - "SegmentAlgorithmName": "GC nnUNet Pancreas", - "SegmentedPropertyCategoryCodeSequence": { - "CodeValue": "123037004", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Anatomical Structure" - }, - "SegmentedPropertyTypeCodeSequence": { - "CodeValue": "51114001", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Artery" - }, - "SegmentedPropertyTypeModifierCodeSequence": { - "CodeValue": "51440002", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Right and left" - }, - "recommendedDisplayRGBValue": [ - 216, - 101, - 79 - ] - }, - { - "labelID": 3, - "SegmentDescription": "Pancreas", - "SegmentAlgorithmType": "AUTOMATIC", - "SegmentAlgorithmName": "GC nnUNet Pancreas", - "SegmentedPropertyCategoryCodeSequence": { - "CodeValue": "123037004", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Anatomical Structure" - }, - "SegmentedPropertyTypeCodeSequence": { - "CodeValue": "15776009", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Pancreas" - }, - "recommendedDisplayRGBValue": [ - 249, - 180, - 111 - ] - }, - { - "labelID": 4, - "SegmentDescription": "Pancreatic duct", - "SegmentAlgorithmType": "AUTOMATIC", - "SegmentAlgorithmName": "GC nnUNet Pancreas", - "SegmentedPropertyCategoryCodeSequence": { - "CodeValue": "123037004", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Anatomical Structure" - }, - "SegmentedPropertyTypeCodeSequence": { - "CodeValue": "69930009", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Pancreatic duct" - } - }, - { - "labelID": 5, - "SegmentDescription": "Bile duct", - "SegmentAlgorithmType": "AUTOMATIC", - "SegmentAlgorithmName": "GC nnUNet Pancreas", - "SegmentedPropertyCategoryCodeSequence": { - "CodeValue": "123037004", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Anatomical Structure" - }, - "SegmentedPropertyTypeCodeSequence": { - "CodeValue": "28273000", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Bile duct" - }, - "SegmentedPropertyTypeModifierCodeSequence": { - "CodeValue": "51440002", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Right and left" - }, - "recommendedDisplayRGBValue": [ - 0, - 145, - 30 - ] - }, - { - "labelID": 6, - "SegmentDescription": "Cysts", - "SegmentAlgorithmType": "AUTOMATIC", - "SegmentAlgorithmName": "GC nnUNet Pancreas", - "SegmentedPropertyCategoryCodeSequence": { - "CodeValue": "49755003", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Morphologically Altered Structure" - }, - "SegmentedPropertyTypeCodeSequence": { - "CodeValue": "367643001", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Cyst" - }, - "recommendedDisplayRGBValue": [ - 205, - 205, - 100 - ] - }, - { - "labelID": 7, - "SegmentDescription": "Renal vein", - "SegmentAlgorithmType": "AUTOMATIC", - "SegmentAlgorithmName": "GC nnUNet Pancreas", - "SegmentedPropertyCategoryCodeSequence": { - "CodeValue": "123037004", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Anatomical Structure" - }, - "SegmentedPropertyTypeCodeSequence": { - "CodeValue": "56400007", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Renal vein" - } - } - ] - ], - "ContentLabel": "SEGMENTATION", - "ContentDescription": "Image segmentation", - "ClinicalTrialCoordinatingCenterName": "dcmqi" -} \ No newline at end of file diff --git a/models/gc_nnunet_pancreas/dockerfiles/Dockerfile b/models/gc_nnunet_pancreas/dockerfiles/Dockerfile index 68577762..74cd3946 100644 --- a/models/gc_nnunet_pancreas/dockerfiles/Dockerfile +++ b/models/gc_nnunet_pancreas/dockerfiles/Dockerfile @@ -3,27 +3,38 @@ FROM mhubai/base:latest # Specify/override authors label LABEL authors="sil.vandeleemput@radboudumc.nl" +# Install PyTorch 2.0.1 (CUDA enabled) +RUN pip3 install --no-cache-dir torch==2.0.1+cu118 -f https://download.pytorch.org/whl/torch_stable.html + # Install git-lfs (required for downloading the model weights) -RUN apt update && apt install -y --no-install-recommends git-lfs && rm -rf /var/lib/apt/lists/* +RUN apt update && \ + apt install -y --no-install-recommends git-lfs && \ + rm -rf /var/lib/apt/lists/* # Install the model weights and the algorithm files # * Pull algorithm from repo into /opt/algorithm (main branch, commit e4f4008c6e18e60a79f693448562a340a9252aa8) # * Remove .git folder to keep docker layer small # * Replace input images path in process.py with an existing folder to avoid errors +# * Add specific data types and compression options to output data structures in process.py to reduce generated output footprint RUN git clone https://github.com/DIAGNijmegen/CE-CT_PDAC_AutomaticDetection_nnUnet.git /opt/algorithm && \ cd /opt/algorithm && \ git reset --hard e4f4008c6e18e60a79f693448562a340a9252aa8 && \ rm -rf /opt/algorithm/.git && \ - sed -i 's/Path("\/input\/images\/")/Path("\/app")/g' /opt/algorithm/process.py - -# FIXME: set this environment variable as a shortcut to avoid nnunet crashing the build + sed -i 's/Path("\/input\/images\/")/Path("\/app")/g' /opt/algorithm/process.py && \ + sed -i 's/pred_2_np = sitk\.GetArrayFromImage(pred_2_nii)/pred_2_np = sitk\.GetArrayFromImage(pred_2_nii)\.astype(np\.uint8)/g' /opt/algorithm/process.py && \ + sed -i 's/pm_image = np\.zeros(image_np\.shape)/pm_image = np\.zeros(image_np\.shape, dtype=np\.float32)/g' /opt/algorithm/process.py && \ + sed -i 's/segmentation_np = np\.zeros(image_np\.shape)/segmentation_np = np\.zeros(image_np\.shape, dtype=np\.uint8)/g' /opt/algorithm/process.py && \ + sed -i 's/sitk\.WriteImage(segmentation_image, str(self\.segmentation))/sitk\.WriteImage(segmentation_image, str(self\.segmentation), True)/g' /opt/algorithm/process.py && \ + sed -i 's/sitk\.WriteImage(pred_itk_resampled, str(self\.heatmap))/sitk\.WriteImage(pred_itk_resampled, str(self\.heatmap), True)/g' /opt/algorithm/process.py + +# Set this environment variable as a shortcut to avoid nnunet 1.7.0 crashing the build # by pulling sklearn instead of scikit-learn # N.B. this is a known issue: # https://github.com/MIC-DKFZ/nnUNet/issues/1281 # https://github.com/MIC-DKFZ/nnUNet/pull/1209 ENV SKLEARN_ALLOW_DEPRECATED_SKLEARN_PACKAGE_INSTALL=True -# Install nnUNet and other requirements (should install PyTorch as well...) +# Install nnUNet 1.7.0 and other requirements RUN pip3 install --no-cache-dir -r /opt/algorithm/requirements.txt # Extend the nnUNet installation with custom trainers diff --git a/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py b/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py index e57fac33..65d9ff29 100644 --- a/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py +++ b/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py @@ -12,26 +12,45 @@ from mhubio.core import Module, Instance, InstanceData, DataType, Meta, IO from pathlib import Path +import SimpleITK +import numpy as np # Import the algorithm pipeline class from the CE-CT_PDAC_AutomaticDetection_nnUnet repository from process import PDACDetectionContainer -# TODO should move to MHubio/core/templates.py -HEATMAP = Meta(mod="heatmap") - class GCNNUnetPancreasRunner(Module): @IO.Instance() @IO.Input('in_data', 'mha:mod=ct', the="input data") @IO.Output('heatmap', 'heatmap.mha', 'mha:mod=heatmap:model=GCNNUnetPancreas', data="in_data", the="heatmap of the pancreatic tumor likelihood") - @IO.Output('segmentation', 'segmentation.mha', 'mha:mod=seg:model=GCNNUnetPancreas', data="in_data", - the="segmentation of the pancreas, with the following classes: " - "1-veins, 2-arteries, 3-pancreas, 4-pancreatic duct, 5-bile duct, 6-cysts, 7-renal vein") - def task(self, instance: Instance, in_data: InstanceData, heatmap: InstanceData, segmentation: InstanceData, **kwargs) -> None: + @IO.Output('segmentation', 'segmentation.mha', 'mha:mod=seg:type=original:model=GCNNUnetPancreas', data="in_data", + the="original segmentation of the pancreas, with the following classes: " + "0-background, 1-veins, 2-arteries, 3-pancreas, 4-pancreatic duct, 5-bile duct, 6-cysts, 7-renal vein") + @IO.Output('segmentation_remapped', 'segmentation_remapped.mha', 'mha:mod=seg:type=remapped:model=GCNNUnetPancreas:roi=PANCREAS,PANCREATIC_DUCT,BILE_DUCT,PANCREAS+CYST,RENAL_VEIN', data="in_data", + the="remapped segmentation of the pancreas (without the veins and arteries), with the following classes: " + "0-background, 1-pancreas, 2-pancreatic duct, 3-bile duct, 4-cysts, 5-renal vein") + def task(self, instance: Instance, in_data: InstanceData, heatmap: InstanceData, segmentation: InstanceData, segmentation_remapped: InstanceData, **kwargs) -> None: # Configure the algorithm pipeline class and run it algorithm = PDACDetectionContainer() algorithm.ct_image = in_data.abspath # set as str not Path algorithm.heatmap = Path(heatmap.abspath) algorithm.segmentation = Path(segmentation.abspath) algorithm.process() + + # Generate remapped segmentation + self.remap_segementation( + segmentation=segmentation, + segmentation_remapped=segmentation_remapped + ) + + def remap_segementation(self, segmentation: InstanceData, segmentation_remapped: InstanceData): + mapping = {0:0, 1:0, 2:0, 3:1, 4:2, 5:3, 6:4, 7:5} + mapping_numpy = np.array(list(mapping.values()), dtype=np.uint8) + self.log("Creating remapped segmentation", level="NOTICE") + seg_sitk = SimpleITK.ReadImage(segmentation.abspath) + seg_numpy = SimpleITK.GetArrayFromImage(seg_sitk) + remapped_numpy = mapping_numpy[seg_numpy] + remapped_sitk = SimpleITK.GetImageFromArray(remapped_numpy) + remapped_sitk.CopyInformation(seg_sitk) + SimpleITK.WriteImage(remapped_sitk, segmentation_remapped.abspath, True) From a56e86002f68b9ff50a5c2a08936957211d842db Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Thu, 7 Dec 2023 17:34:06 +0100 Subject: [PATCH 11/21] add cli for running the pdac_detection model --- .../utils/GCNNUnetPancreasRunner.py | 20 ++++--- models/gc_nnunet_pancreas/utils/cli.py | 57 +++++++++++++++++++ 2 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 models/gc_nnunet_pancreas/utils/cli.py diff --git a/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py b/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py index 65d9ff29..1142d8b2 100644 --- a/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py +++ b/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py @@ -14,9 +14,10 @@ from pathlib import Path import SimpleITK import numpy as np +import sys -# Import the algorithm pipeline class from the CE-CT_PDAC_AutomaticDetection_nnUnet repository -from process import PDACDetectionContainer + +CLI_PATH = Path(__file__).parent / "cli.py" class GCNNUnetPancreasRunner(Module): @@ -31,12 +32,15 @@ class GCNNUnetPancreasRunner(Module): the="remapped segmentation of the pancreas (without the veins and arteries), with the following classes: " "0-background, 1-pancreas, 2-pancreatic duct, 3-bile duct, 4-cysts, 5-renal vein") def task(self, instance: Instance, in_data: InstanceData, heatmap: InstanceData, segmentation: InstanceData, segmentation_remapped: InstanceData, **kwargs) -> None: - # Configure the algorithm pipeline class and run it - algorithm = PDACDetectionContainer() - algorithm.ct_image = in_data.abspath # set as str not Path - algorithm.heatmap = Path(heatmap.abspath) - algorithm.segmentation = Path(segmentation.abspath) - algorithm.process() + # Call the PDAC CLI + cmd = [ + sys.executable, + str(CLI_PATH), + in_data.abspath, + heatmap.abspath, + segmentation.abspath + ] + self.subprocess(cmd, text=True) # Generate remapped segmentation self.remap_segementation( diff --git a/models/gc_nnunet_pancreas/utils/cli.py b/models/gc_nnunet_pancreas/utils/cli.py new file mode 100644 index 00000000..460b5a64 --- /dev/null +++ b/models/gc_nnunet_pancreas/utils/cli.py @@ -0,0 +1,57 @@ +""" +---------------------------------------------------- +GC / MHub - CLI for the GC nnUnet Pancreas Algorithm +---------------------------------------------------- + +---------------------------------------------------- +Author: Sil van de Leemput +Email: sil.vandeleemput@radboudumc.nl +---------------------------------------------------- +""" +import argparse +from pathlib import Path + +# Import the algorithm pipeline class from the CE-CT_PDAC_AutomaticDetection_nnUnet repository +from process import PDACDetectionContainer + + +def run_pdac_detection( + input_ct_image: Path, output_heatmap: Path, output_segmentation: Path +): + # Configure the algorithm pipeline class and run it + algorithm = PDACDetectionContainer() + algorithm.ct_image = str(input_ct_image) # set as str not Path + algorithm.heatmap = output_heatmap + algorithm.segmentation = output_segmentation + algorithm.process() + + +def run_pdac_detection_cli(): + parser = argparse.ArgumentParser("CLI for the GC nnUNet Pancreas Algorithm") + parser.add_argument( + "input_ct_image", + type=str, + help="input CT scan (MHA)" + ) + parser.add_argument( + "output_heatmap", + type=str, + help="heatmap of the pancreatic tumor likelihood (MHA)", + ) + parser.add_argument( + "output_segmentation", + type=str, + help="segmentation map of the pancreas (MHA), with the following classes: " + "0-background, 1-veins, 2-arteries, 3-pancreas, 4-pancreatic duct, 5-bile duct, " + "6-cysts, 7-renal vein", + ) + args = parser.parse_args() + run_pdac_detection( + input_ct_image=Path(args.input_ct_image), + output_heatmap=Path(args.output_heatmap), + output_segmentation=Path(args.output_segmentation), + ) + + +if __name__ == "__main__": + run_pdac_detection_cli() From 713b0f3ded2126348fb2e7a15506b28966f5e3fe Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Tue, 12 Dec 2023 11:19:13 +0100 Subject: [PATCH 12/21] added VEIN,ARTERY rois to output segmentation, cleaned config and runner code --- models/gc_nnunet_pancreas/config/default.yml | 4 ++-- .../utils/GCNNUnetPancreasRunner.py | 24 ++----------------- 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/models/gc_nnunet_pancreas/config/default.yml b/models/gc_nnunet_pancreas/config/default.yml index b13691ac..526099e1 100644 --- a/models/gc_nnunet_pancreas/config/default.yml +++ b/models/gc_nnunet_pancreas/config/default.yml @@ -24,12 +24,12 @@ modules: DsegConverter: model_name: 'GC NNUnet Pancreas' - source_segs: ['mha:mod=seg:type=remapped'] + source_segs: ['mha:mod=seg'] target_dicom: dicom:mod=ct skip_empty_slices: True DataOrganizer: targets: - mha:mod=heatmap-->[i:sid]/nnunet_pancreas_heatmap.mha - - mha:mod=seg:type=original-->[i:sid]/nnunet_pancreas.seg.mha + - mha:mod=seg-->[i:sid]/nnunet_pancreas.seg.mha - dicomseg:mod=seg-->[i:sid]/nnunet_pancreas.seg.dcm diff --git a/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py b/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py index 1142d8b2..6ffa6844 100644 --- a/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py +++ b/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py @@ -25,13 +25,10 @@ class GCNNUnetPancreasRunner(Module): @IO.Input('in_data', 'mha:mod=ct', the="input data") @IO.Output('heatmap', 'heatmap.mha', 'mha:mod=heatmap:model=GCNNUnetPancreas', data="in_data", the="heatmap of the pancreatic tumor likelihood") - @IO.Output('segmentation', 'segmentation.mha', 'mha:mod=seg:type=original:model=GCNNUnetPancreas', data="in_data", + @IO.Output('segmentation', 'segmentation.mha', 'mha:mod=seg:model=GCNNUnetPancreas:roi=VEIN,ARTERY,PANCREAS,PANCREATIC_DUCT,BILE_DUCT,PANCREAS+CYST,RENAL_VEIN', data="in_data", the="original segmentation of the pancreas, with the following classes: " "0-background, 1-veins, 2-arteries, 3-pancreas, 4-pancreatic duct, 5-bile duct, 6-cysts, 7-renal vein") - @IO.Output('segmentation_remapped', 'segmentation_remapped.mha', 'mha:mod=seg:type=remapped:model=GCNNUnetPancreas:roi=PANCREAS,PANCREATIC_DUCT,BILE_DUCT,PANCREAS+CYST,RENAL_VEIN', data="in_data", - the="remapped segmentation of the pancreas (without the veins and arteries), with the following classes: " - "0-background, 1-pancreas, 2-pancreatic duct, 3-bile duct, 4-cysts, 5-renal vein") - def task(self, instance: Instance, in_data: InstanceData, heatmap: InstanceData, segmentation: InstanceData, segmentation_remapped: InstanceData, **kwargs) -> None: + def task(self, instance: Instance, in_data: InstanceData, heatmap: InstanceData, segmentation: InstanceData, **kwargs) -> None: # Call the PDAC CLI cmd = [ sys.executable, @@ -41,20 +38,3 @@ def task(self, instance: Instance, in_data: InstanceData, heatmap: InstanceData, segmentation.abspath ] self.subprocess(cmd, text=True) - - # Generate remapped segmentation - self.remap_segementation( - segmentation=segmentation, - segmentation_remapped=segmentation_remapped - ) - - def remap_segementation(self, segmentation: InstanceData, segmentation_remapped: InstanceData): - mapping = {0:0, 1:0, 2:0, 3:1, 4:2, 5:3, 6:4, 7:5} - mapping_numpy = np.array(list(mapping.values()), dtype=np.uint8) - self.log("Creating remapped segmentation", level="NOTICE") - seg_sitk = SimpleITK.ReadImage(segmentation.abspath) - seg_numpy = SimpleITK.GetArrayFromImage(seg_sitk) - remapped_numpy = mapping_numpy[seg_numpy] - remapped_sitk = SimpleITK.GetImageFromArray(remapped_numpy) - remapped_sitk.CopyInformation(seg_sitk) - SimpleITK.WriteImage(remapped_sitk, segmentation_remapped.abspath, True) From 55e34cfe2c6417043222dfe3521546db26295f44 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Mon, 15 Jan 2024 13:22:06 +0100 Subject: [PATCH 13/21] add comments to CLI, add clean method and case-level likelihood extraction to runner, add case-level likelihood to config and meta #39 --- models/gc_nnunet_pancreas/config/default.yml | 15 ++++- models/gc_nnunet_pancreas/meta.json | 15 +++-- .../utils/GCNNUnetPancreasRunner.py | 55 +++++++++++++++++-- models/gc_nnunet_pancreas/utils/cli.py | 11 ++-- 4 files changed, 79 insertions(+), 17 deletions(-) diff --git a/models/gc_nnunet_pancreas/config/default.yml b/models/gc_nnunet_pancreas/config/default.yml index 526099e1..ca3a2a3e 100644 --- a/models/gc_nnunet_pancreas/config/default.yml +++ b/models/gc_nnunet_pancreas/config/default.yml @@ -1,13 +1,14 @@ general: version: 1.0 data_base_dir: /app/data - description: base configuration for GC NNUnet Pancreas model (dicom to dicom) + description: base configuration for GC NNUnet Pancreas model (dicom to dicom, and json output) execute: - DicomImporter - MhaConverter - GCNNUnetPancreasRunner - DsegConverter +- ReportExporter - DataOrganizer modules: @@ -24,12 +25,20 @@ modules: DsegConverter: model_name: 'GC NNUnet Pancreas' - source_segs: ['mha:mod=seg'] + source_segs: ['mha:mod=seg:src=cleaned'] target_dicom: dicom:mod=ct skip_empty_slices: True + ReportExporter: + format: compact + includes: + - data: prostate_cancer_likelihood + label: prostate_cancer_likelihood + value: value + DataOrganizer: targets: - mha:mod=heatmap-->[i:sid]/nnunet_pancreas_heatmap.mha - - mha:mod=seg-->[i:sid]/nnunet_pancreas.seg.mha + - mha:mod=seg:src=cleaned-->[i:sid]/nnunet_pancreas.seg.mha - dicomseg:mod=seg-->[i:sid]/nnunet_pancreas.seg.dcm + - json:mod=report-->[i:sid]/nnunet_pancreas_case_level_likelihood.json diff --git a/models/gc_nnunet_pancreas/meta.json b/models/gc_nnunet_pancreas/meta.json index bce7005b..b24823be 100644 --- a/models/gc_nnunet_pancreas/meta.json +++ b/models/gc_nnunet_pancreas/meta.json @@ -24,16 +24,21 @@ "arteries", "pancreas", "pancreatic duct", - "bile duct", - "cysts", - "renal vein" + "bile duct" ] }, { "type": "Prediction", - "valueType": "number", + "valueType": "Likelihood map", + "label": "Pancreatic tumor likelihood heatmap", + "description": "Pancreatic tumor likelihood heatmap, where each voxel represents a floating point in range [0,1].", + "classes": [] + }, + { + "type": "Prediction", + "valueType": "Likelihood", "label": "Pancreatic tumor likelihood", - "description": "Pancreatic tumor likelihood map with values between 0 and 1", + "description": "Case-level pancreatic tumor likelihood value with a value in range [0,1].", "classes": [] } ], diff --git a/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py b/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py index 6ffa6844..9942705c 100644 --- a/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py +++ b/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py @@ -9,32 +9,77 @@ ----------------------------------------------------------- """ -from mhubio.core import Module, Instance, InstanceData, DataType, Meta, IO +from mhubio.core import Module, Instance, InstanceData, DataType, Meta, IO, ValueOutput from pathlib import Path import SimpleITK -import numpy as np import sys CLI_PATH = Path(__file__).parent / "cli.py" +@ValueOutput.Name('prostate_cancer_likelihood') +@ValueOutput.Label('ProstateCancerLikelihood') +@ValueOutput.Meta(Meta(min=0.0, max=1.0, type="likelihood")) +@ValueOutput.Type(float) +@ValueOutput.Description('Likelihood of case-level prostate cancer.') +class ProstateCancerLikelihood(ValueOutput): + pass + + class GCNNUnetPancreasRunner(Module): @IO.Instance() @IO.Input('in_data', 'mha:mod=ct', the="input data") @IO.Output('heatmap', 'heatmap.mha', 'mha:mod=heatmap:model=GCNNUnetPancreas', data="in_data", the="heatmap of the pancreatic tumor likelihood") - @IO.Output('segmentation', 'segmentation.mha', 'mha:mod=seg:model=GCNNUnetPancreas:roi=VEIN,ARTERY,PANCREAS,PANCREATIC_DUCT,BILE_DUCT,PANCREAS+CYST,RENAL_VEIN', data="in_data", + @IO.Output('segmentation_raw', 'segmentation_raw.mha', 'mha:mod=seg:src=original:model=GCNNUnetPancreas:roi=VEIN,ARTERY,PANCREAS,PANCREATIC_DUCT,BILE_DUCT,PANCREAS+CYST,RENAL_VEIN', data="in_data", the="original segmentation of the pancreas, with the following classes: " "0-background, 1-veins, 2-arteries, 3-pancreas, 4-pancreatic duct, 5-bile duct, 6-cysts, 7-renal vein") - def task(self, instance: Instance, in_data: InstanceData, heatmap: InstanceData, segmentation: InstanceData, **kwargs) -> None: + @IO.Output('segmentation', 'segmentation.mha', 'mha:mod=seg:src=cleaned:model=GCNNUnetPancreas:roi=VEIN,ARTERY,PANCREAS,PANCREATIC_DUCT,BILE_DUCT', data="in_data", + the="cleaned segmentation of the pancreas, with the following classes: " + "0-background, 1-veins, 2-arteries, 3-pancreas, 4-pancreatic duct, 5-bile duct") + @IO.OutputData('cancer_likelihood', ProstateCancerLikelihood, the='Case-level pancreatic tumor likelihood. This is equivalent to the maximum of the pancreatic tumor likelihood heatmap.') + def task(self, instance: Instance, in_data: InstanceData, heatmap: InstanceData, segmentation_raw: InstanceData, segmentation: InstanceData, cancer_likelihood: ProstateCancerLikelihood, **kwargs) -> None: # Call the PDAC CLI + # A CLI was used here to ensure the mhub framework properly captures the nnUNet stdout output cmd = [ sys.executable, str(CLI_PATH), in_data.abspath, heatmap.abspath, - segmentation.abspath + segmentation_raw.abspath ] self.subprocess(cmd, text=True) + + # Remove cysts and renal vein classes from the original segmentation. + # Insufficient training samples were present in the training data for these classes. + # Hence, these classes should be omitted from the final output, since these are not + # expected to produce reliable segmentations. + cancer_likelihood.value = self.clean_segementation( + segmentation_in=segmentation_raw, + segmentation_out=segmentation + ) + + # Extract case-level cancer likelihood + cancer_likelihood.value = self.extract_case_level_cancer_likelihood( + heatmap=heatmap + ) + + def clean_segementation(self, segmentation_in: InstanceData, segmentation_out: InstanceData): + self.log("Cleaning output segmentation", level="NOTICE") + seg_sitk = SimpleITK.ReadImage(segmentation_in.abspath) + seg_numpy = SimpleITK.GetArrayFromImage(seg_sitk) + seg_numpy[seg_numpy >= 6] = 0 # remove cysts and renal vein segmentation from original segmentation + remapped_sitk = SimpleITK.GetImageFromArray(seg_numpy) + remapped_sitk.CopyInformation(seg_sitk) + SimpleITK.WriteImage(remapped_sitk, segmentation_out.abspath, True) + + def extract_case_level_cancer_likelihood(self, heatmap: InstanceData): + self.log("Extracting case-level cancer likelihood", level="NOTICE") + heatmap_sitk = SimpleITK.ReadImage(heatmap.abspath) + f = SimpleITK.MinimumMaximumImageFilter() + f.Execute(heatmap_sitk) + cancer_likelihood = f.GetMaximum() + assert 0.0 <= cancer_likelihood <= 1.0, "Cancer likelihood value must be in range [0.0, 1.0]" + return cancer_likelihood diff --git a/models/gc_nnunet_pancreas/utils/cli.py b/models/gc_nnunet_pancreas/utils/cli.py index 460b5a64..b12ef31f 100644 --- a/models/gc_nnunet_pancreas/utils/cli.py +++ b/models/gc_nnunet_pancreas/utils/cli.py @@ -1,12 +1,15 @@ """ ----------------------------------------------------- +------------------------------------------------------------- GC / MHub - CLI for the GC nnUnet Pancreas Algorithm ----------------------------------------------------- + The model algorith was wrapped in a CLI to ensure + the mhub framework is able to properly capture the nnUNet + stdout/stderr outputs +------------------------------------------------------------- ----------------------------------------------------- +------------------------------------------------------------- Author: Sil van de Leemput Email: sil.vandeleemput@radboudumc.nl ----------------------------------------------------- +------------------------------------------------------------- """ import argparse from pathlib import Path From 9c9508dd74cb64106197ea7604b1d80251e39d36 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Mon, 15 Jan 2024 22:23:34 +0100 Subject: [PATCH 14/21] meta.json - update analysis section and evaluation data section #39 --- models/gc_nnunet_pancreas/meta.json | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/models/gc_nnunet_pancreas/meta.json b/models/gc_nnunet_pancreas/meta.json index b24823be..924857d5 100644 --- a/models/gc_nnunet_pancreas/meta.json +++ b/models/gc_nnunet_pancreas/meta.json @@ -93,20 +93,33 @@ }, "analyses": { "title": "Analysis", - "text": "The study evaluated a medical model's performance for tumor detection by analyzing receiver operating characteristic (ROC) and free-response receiver operating characteristic (FROC) curves, assessing both tumor presence and lesion localization, and compared three configurations using statistical tests and ensemble modeling.", - "references": [], - "tables": [] + "text": "The study evaluated a medical model's performance for tumor detection by analyzing receiver operating characteristic (ROC) and free-response receiver operating characteristic (FROC) curves, assessing both tumor presence and lesion localization, and compared three configurations using statistical tests and ensemble modeling. The table below lists the model's performance on an external evaluation dataset of 361 cases. Additional analysis details and results can be found in the original paper [1].", + "references": [ + { + "label": "Fully Automatic Deep Learning Framework for Pancreatic Ductal Adenocarcinoma Detection on Computed Tomography", + "uri": "https://www.mdpi.com/2072-6694/14/2/376" + } + ], + "tables": [ + { + "label": "Evaluation results of the nnUnet_MS model on the external test set of 361 cases.", + "entries": { + "Mean AUC-ROC (95% CI)": "0.991 (0.970-1.0)", + "Mean pAUC-FROC (95% CI)": "3.996 (3.027-4.965)" + } + } + ] }, "evaluation": { "title": "Evaluation Data", - "text": "This framework was tested in an independent, external cohort consisting of two publicly available datasets.", + "text": "This framework was tested in an independent, external cohort consisting of two publicly available datasets of respectively 281 and 80 patients each. The Medical Segmentation Decathlon pancreas dataset (training portion) [1] consisting of 281 patients with pancreatic malignancies (including lesions in the head, neck, body, and tail of the pancreas) and voxel-level annotations for the pancreas and lesion. The Cancer Imaging Archive dataset from the US National Institutes of Health Clinical Center [2], containing 80 patients with normal pancreas and respective voxel-level annotations.", "references": [ { - "label": "The Medical Segmentation Decathlon pancreas dataset (training portion) consisting of 281 patients with pancreatic malignancies (including lesions in the head, neck, body, and tail of the pancreas) and voxel-level annotations for the pancreas and lesion.", + "label": "The Medical Segmentation Decathlon pancreas dataset (training portion)", "uri": "http://medicaldecathlon.com/" }, { - "label": "The Cancer Imaging Archive dataset from the US National Institutes of Health Clinical Center, containing 80 patients with normal pancreas and respective voxel-level annotations.", + "label": "The Cancer Imaging Archive dataset from the US National Institutes of Health Clinical Center", "uri": "https://wiki.cancerimagingarchive.net/display/Public/Pancreas-CT" } ], From 4969367701b8c3dc17140535b77bd3bb292d8402 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Tue, 16 Jan 2024 14:26:35 +0100 Subject: [PATCH 15/21] updated model/algorithm version to latest commit, removed manual code patches #39 --- models/gc_nnunet_pancreas/dockerfiles/Dockerfile | 14 +++----------- models/gc_nnunet_pancreas/utils/cli.py | 2 +- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/models/gc_nnunet_pancreas/dockerfiles/Dockerfile b/models/gc_nnunet_pancreas/dockerfiles/Dockerfile index 74cd3946..4b84e0f3 100644 --- a/models/gc_nnunet_pancreas/dockerfiles/Dockerfile +++ b/models/gc_nnunet_pancreas/dockerfiles/Dockerfile @@ -12,20 +12,12 @@ RUN apt update && \ rm -rf /var/lib/apt/lists/* # Install the model weights and the algorithm files -# * Pull algorithm from repo into /opt/algorithm (main branch, commit e4f4008c6e18e60a79f693448562a340a9252aa8) +# * Pull algorithm from repo into /opt/algorithm (main branch, commit 2d74e98f66f0a57da66ed26e97448e53571199db) # * Remove .git folder to keep docker layer small -# * Replace input images path in process.py with an existing folder to avoid errors -# * Add specific data types and compression options to output data structures in process.py to reduce generated output footprint RUN git clone https://github.com/DIAGNijmegen/CE-CT_PDAC_AutomaticDetection_nnUnet.git /opt/algorithm && \ cd /opt/algorithm && \ - git reset --hard e4f4008c6e18e60a79f693448562a340a9252aa8 && \ - rm -rf /opt/algorithm/.git && \ - sed -i 's/Path("\/input\/images\/")/Path("\/app")/g' /opt/algorithm/process.py && \ - sed -i 's/pred_2_np = sitk\.GetArrayFromImage(pred_2_nii)/pred_2_np = sitk\.GetArrayFromImage(pred_2_nii)\.astype(np\.uint8)/g' /opt/algorithm/process.py && \ - sed -i 's/pm_image = np\.zeros(image_np\.shape)/pm_image = np\.zeros(image_np\.shape, dtype=np\.float32)/g' /opt/algorithm/process.py && \ - sed -i 's/segmentation_np = np\.zeros(image_np\.shape)/segmentation_np = np\.zeros(image_np\.shape, dtype=np\.uint8)/g' /opt/algorithm/process.py && \ - sed -i 's/sitk\.WriteImage(segmentation_image, str(self\.segmentation))/sitk\.WriteImage(segmentation_image, str(self\.segmentation), True)/g' /opt/algorithm/process.py && \ - sed -i 's/sitk\.WriteImage(pred_itk_resampled, str(self\.heatmap))/sitk\.WriteImage(pred_itk_resampled, str(self\.heatmap), True)/g' /opt/algorithm/process.py + git reset --hard 2d74e98f66f0a57da66ed26e97448e53571199db && \ + rm -rf /opt/algorithm/.git # Set this environment variable as a shortcut to avoid nnunet 1.7.0 crashing the build # by pulling sklearn instead of scikit-learn diff --git a/models/gc_nnunet_pancreas/utils/cli.py b/models/gc_nnunet_pancreas/utils/cli.py index b12ef31f..67181709 100644 --- a/models/gc_nnunet_pancreas/utils/cli.py +++ b/models/gc_nnunet_pancreas/utils/cli.py @@ -23,7 +23,7 @@ def run_pdac_detection( ): # Configure the algorithm pipeline class and run it algorithm = PDACDetectionContainer() - algorithm.ct_image = str(input_ct_image) # set as str not Path + algorithm.ct_image = input_ct_image algorithm.heatmap = output_heatmap algorithm.segmentation = output_segmentation algorithm.process() From 1b248256ba62d17124a702f518e34e5b4e967dfb Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Tue, 16 Jan 2024 14:30:00 +0100 Subject: [PATCH 16/21] meta.json - match model name #39 --- models/gc_nnunet_pancreas/meta.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/gc_nnunet_pancreas/meta.json b/models/gc_nnunet_pancreas/meta.json index 924857d5..9fdba73a 100644 --- a/models/gc_nnunet_pancreas/meta.json +++ b/models/gc_nnunet_pancreas/meta.json @@ -1,6 +1,6 @@ { "id": "bf7ae4bb-c6f5-4b1e-89aa-a8de246def57", - "name": "pdac_detection_in_ct", + "name": "gc_nnunet_pancreas", "title": "Pancreatic Ductal Adenocarcinoma Detection in CT", "summary": { "description": "This algorithm produces a tumor likelihood heatmap for the presence of pancreatic ductal adenocarcinoma (PDAC) in an input venous-phase contrast-enhanced computed tomography scan (CECT). Additionally, the algorithm provides the segmentation of multiple surrounding anatomical structures such as the pancreatic duct, common bile duct, veins and arteries. The heatmap and segmentations are resampled to the same spatial resolution and physical dimensions as the input CECT image for easier visualisation.", From 561883ba1e65fc5be2dae57dd37a2ed82de263bb Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Thu, 18 Jan 2024 14:28:16 +0100 Subject: [PATCH 17/21] meta.json - added disclaimer for output segmentation map --- models/gc_nnunet_pancreas/meta.json | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/models/gc_nnunet_pancreas/meta.json b/models/gc_nnunet_pancreas/meta.json index 9fdba73a..96b8ccbc 100644 --- a/models/gc_nnunet_pancreas/meta.json +++ b/models/gc_nnunet_pancreas/meta.json @@ -17,16 +17,6 @@ } ], "outputs": [ - { - "type": "Segmentation", - "classes": [ - "veins", - "arteries", - "pancreas", - "pancreatic duct", - "bile duct" - ] - }, { "type": "Prediction", "valueType": "Likelihood map", @@ -40,6 +30,17 @@ "label": "Pancreatic tumor likelihood", "description": "Case-level pancreatic tumor likelihood value with a value in range [0,1].", "classes": [] + }, + { + "type": "Segmentation", + "label": "Segmentation of pancreas related tissues. These segmentation classes were not thoroughly validated, use them on your own risk!", + "classes": [ + "veins", + "arteries", + "pancreas", + "pancreatic duct", + "bile duct" + ] } ], "model": { From 2df25f31616977f071b5ba3807f4a5e194c50880 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Thu, 1 Feb 2024 13:19:12 +0100 Subject: [PATCH 18/21] updated to lastest version of algorithm, changed to output the raw heatmap --- models/gc_nnunet_pancreas/dockerfiles/Dockerfile | 4 ++-- models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py | 4 ++-- models/gc_nnunet_pancreas/utils/cli.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/models/gc_nnunet_pancreas/dockerfiles/Dockerfile b/models/gc_nnunet_pancreas/dockerfiles/Dockerfile index 4b84e0f3..77cc05b9 100644 --- a/models/gc_nnunet_pancreas/dockerfiles/Dockerfile +++ b/models/gc_nnunet_pancreas/dockerfiles/Dockerfile @@ -12,11 +12,11 @@ RUN apt update && \ rm -rf /var/lib/apt/lists/* # Install the model weights and the algorithm files -# * Pull algorithm from repo into /opt/algorithm (main branch, commit 2d74e98f66f0a57da66ed26e97448e53571199db) +# * Pull algorithm from repo into /opt/algorithm (main branch, commit 15dd550beada43a8a55b81a32d9b3904a1cf8d30) # * Remove .git folder to keep docker layer small RUN git clone https://github.com/DIAGNijmegen/CE-CT_PDAC_AutomaticDetection_nnUnet.git /opt/algorithm && \ cd /opt/algorithm && \ - git reset --hard 2d74e98f66f0a57da66ed26e97448e53571199db && \ + git reset --hard 15dd550beada43a8a55b81a32d9b3904a1cf8d30 && \ rm -rf /opt/algorithm/.git # Set this environment variable as a shortcut to avoid nnunet 1.7.0 crashing the build diff --git a/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py b/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py index 9942705c..770c203d 100644 --- a/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py +++ b/models/gc_nnunet_pancreas/utils/GCNNUnetPancreasRunner.py @@ -32,7 +32,7 @@ class GCNNUnetPancreasRunner(Module): @IO.Instance() @IO.Input('in_data', 'mha:mod=ct', the="input data") @IO.Output('heatmap', 'heatmap.mha', 'mha:mod=heatmap:model=GCNNUnetPancreas', data="in_data", - the="heatmap of the pancreatic tumor likelihood") + the="raw heatmap of the pancreatic tumor likelihood (not masked with any pancreas segmentations).") @IO.Output('segmentation_raw', 'segmentation_raw.mha', 'mha:mod=seg:src=original:model=GCNNUnetPancreas:roi=VEIN,ARTERY,PANCREAS,PANCREATIC_DUCT,BILE_DUCT,PANCREAS+CYST,RENAL_VEIN', data="in_data", the="original segmentation of the pancreas, with the following classes: " "0-background, 1-veins, 2-arteries, 3-pancreas, 4-pancreatic duct, 5-bile duct, 6-cysts, 7-renal vein") @@ -56,7 +56,7 @@ def task(self, instance: Instance, in_data: InstanceData, heatmap: InstanceData, # Insufficient training samples were present in the training data for these classes. # Hence, these classes should be omitted from the final output, since these are not # expected to produce reliable segmentations. - cancer_likelihood.value = self.clean_segementation( + self.clean_segementation( segmentation_in=segmentation_raw, segmentation_out=segmentation ) diff --git a/models/gc_nnunet_pancreas/utils/cli.py b/models/gc_nnunet_pancreas/utils/cli.py index 67181709..99af524e 100644 --- a/models/gc_nnunet_pancreas/utils/cli.py +++ b/models/gc_nnunet_pancreas/utils/cli.py @@ -22,9 +22,9 @@ def run_pdac_detection( input_ct_image: Path, output_heatmap: Path, output_segmentation: Path ): # Configure the algorithm pipeline class and run it - algorithm = PDACDetectionContainer() + algorithm = PDACDetectionContainer(output_raw_heatmap=True) algorithm.ct_image = input_ct_image - algorithm.heatmap = output_heatmap + algorithm.heatmap_raw = output_heatmap algorithm.segmentation = output_segmentation algorithm.process() @@ -39,7 +39,7 @@ def run_pdac_detection_cli(): parser.add_argument( "output_heatmap", type=str, - help="heatmap of the pancreatic tumor likelihood (MHA)", + help="raw heatmap of the pancreatic tumor likelihood (MHA)", ) parser.add_argument( "output_segmentation", From f189937687c05cbb9143bb632d73faec91a4a47c Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Thu, 1 Feb 2024 13:23:56 +0100 Subject: [PATCH 19/21] meta.json - moved segmentation disclaimer to description and modified labels #39 --- models/gc_nnunet_pancreas/meta.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/models/gc_nnunet_pancreas/meta.json b/models/gc_nnunet_pancreas/meta.json index 96b8ccbc..c751bfcf 100644 --- a/models/gc_nnunet_pancreas/meta.json +++ b/models/gc_nnunet_pancreas/meta.json @@ -33,7 +33,8 @@ }, { "type": "Segmentation", - "label": "Segmentation of pancreas related tissues. These segmentation classes were not thoroughly validated, use them on your own risk!", + "label": "Pancreas segmentation", + "description": "Segmentation of pancreas related tissues, these segmentation classes were not thoroughly validated, use them on your own risk!", "classes": [ "veins", "arteries", From 726fdcb45e12b3b4c267d357326218cd4b44979a Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Thu, 8 Feb 2024 13:14:40 +0100 Subject: [PATCH 20/21] fix dependencies conflict new base image #39 --- models/gc_nnunet_pancreas/dockerfiles/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/gc_nnunet_pancreas/dockerfiles/Dockerfile b/models/gc_nnunet_pancreas/dockerfiles/Dockerfile index 77cc05b9..22b43174 100644 --- a/models/gc_nnunet_pancreas/dockerfiles/Dockerfile +++ b/models/gc_nnunet_pancreas/dockerfiles/Dockerfile @@ -27,7 +27,7 @@ RUN git clone https://github.com/DIAGNijmegen/CE-CT_PDAC_AutomaticDetection_nnUn ENV SKLEARN_ALLOW_DEPRECATED_SKLEARN_PACKAGE_INSTALL=True # Install nnUNet 1.7.0 and other requirements -RUN pip3 install --no-cache-dir -r /opt/algorithm/requirements.txt +RUN pip3 install --no-cache-dir evalutils==0.3.0 nnunet==1.7.0 # Extend the nnUNet installation with custom trainers RUN SITE_PKG=`pip3 show nnunet | grep "Location:" | awk '{print $2}'` && \ From 343c6ab79ca02478501c8c6d53c26744426dd383 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Mon, 26 Feb 2024 11:49:08 +0100 Subject: [PATCH 21/21] meta.json - added version 0.1.0 to details --- models/gc_nnunet_pancreas/meta.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/gc_nnunet_pancreas/meta.json b/models/gc_nnunet_pancreas/meta.json index c751bfcf..c38dcd9a 100644 --- a/models/gc_nnunet_pancreas/meta.json +++ b/models/gc_nnunet_pancreas/meta.json @@ -62,7 +62,7 @@ }, "details": { "name": "Fully Automatic Deep Learning Framework for Pancreatic Ductal Adenocarcinoma Detection on Computed Tomography", - "version": "", + "version": "0.1.0", "devteam": "DIAGNijmegen (Diagnostic Image Analysis Group, Radboud UMC, The Netherlands)", "type": "The models were developed using nnUnet. All models employed a 3D U-Net as the base architecture and were trained for 250.000 training steps with five-fold cross-validation.", "date": {