From 1fa6d724374fc0f13b479a5cfc1f2d6676242129 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Thu, 12 Oct 2023 13:42:57 +0200 Subject: [PATCH 01/15] initial functional implementation of the PICAI baseline algorithm model --- models/gc_picai_baseline/config/default.yml | 35 ++++++++++ .../gc_picai_baseline/config/mha-pipeline.yml | 31 +++++++++ .../gc_picai_baseline/dockerfiles/Dockerfile | 51 ++++++++++++++ .../utils/PicaiBaselineRunner.py | 68 +++++++++++++++++++ models/gc_picai_baseline/utils/__init__.py | 1 + 5 files changed, 186 insertions(+) create mode 100644 models/gc_picai_baseline/config/default.yml create mode 100644 models/gc_picai_baseline/config/mha-pipeline.yml create mode 100644 models/gc_picai_baseline/dockerfiles/Dockerfile create mode 100644 models/gc_picai_baseline/utils/PicaiBaselineRunner.py create mode 100644 models/gc_picai_baseline/utils/__init__.py diff --git a/models/gc_picai_baseline/config/default.yml b/models/gc_picai_baseline/config/default.yml new file mode 100644 index 00000000..c86566be --- /dev/null +++ b/models/gc_picai_baseline/config/default.yml @@ -0,0 +1,35 @@ +general: + data_base_dir: /app/data + version: 1.0 + description: Prostate MRI classification default (dicom to json) + +execute: +- FileStructureImporter +- MhaConverter +- PicaiBaselineRunner +- ReportExporter +- DataOrganizer + +modules: + FileStructureImporter: + input_dir: input_data + structures: + - $sid@instance/images/transverse-adc-prostate-mri@dicom:mod=mradc + - $sid/images/transverse-t2-prostate-mri@dicom:mod=mrt2 + - $sid/images/transverse-hbv-prostate-mri@dicom:mod=mrhbv + import_id: sid + + MhaConverter: + engine: panimg + + ReportExporter: + format: compact + includes: + - data: prostate_cancer_probability + label: prostate_cancer_probability + value: value + + DataOrganizer: + targets: + - json:mod=report-->[i:sid]/cspca-case-level-likelihood.json + - mha:mod=hm-->[i:sid]/cspca-detection-map.mha diff --git a/models/gc_picai_baseline/config/mha-pipeline.yml b/models/gc_picai_baseline/config/mha-pipeline.yml new file mode 100644 index 00000000..f70fac4b --- /dev/null +++ b/models/gc_picai_baseline/config/mha-pipeline.yml @@ -0,0 +1,31 @@ +general: + data_base_dir: /app/data + version: 1.0 + description: Prostate MRI classification MHA pipeline (mha to json) + +execute: +- FileStructureImporter +- PicaiBaselineRunner +- ReportExporter +- DataOrganizer + +modules: + FileStructureImporter: + input_dir: input_data + structures: + - $sid@instance/images/transverse-adc-prostate-mri/adc.mha@mha:mod=mradc + - $sid/images/transverse-t2-prostate-mri/t2w.mha@mha:mod=mrt2 + - $sid/images/transverse-hbv-prostate-mri/hbv.mha@mha:mod=mrhbv + import_id: sid + + ReportExporter: + format: compact + includes: + - data: prostate_cancer_probability + label: prostate_cancer_probability + value: value + + DataOrganizer: + targets: + - json:mod=report-->[i:sid]/cspca-case-level-likelihood.json + - mha:mod=hm-->[i:sid]/cspca-detection-map.mha diff --git a/models/gc_picai_baseline/dockerfiles/Dockerfile b/models/gc_picai_baseline/dockerfiles/Dockerfile new file mode 100644 index 00000000..a1bff797 --- /dev/null +++ b/models/gc_picai_baseline/dockerfiles/Dockerfile @@ -0,0 +1,51 @@ +# Specify the base image for the environment +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 unpacking model weights) +RUN apt update && apt install -y --no-install-recommends git-lfs && rm -rf /var/lib/apt/lists/* + +# Install PICAI baseline algorithm and model weights +# - Git clone the algorithm repository for v2.1.1 (fixed to v2.1.1 tag) +# - We remove unnecessary files for a compacter docker layer +# - Subsequently we remove the .git directory to procuce a compacter docker layer +RUN git clone --depth 1 --branch v2.1.1 https://github.com/DIAGNijmegen/picai_nnunet_semi_supervised_gc_algorithm.git /opt/algorithm && \ + rm -rf /opt/algorithm/test && \ + rm -rf /opt/algorithm/.git + +# Install additional PICAI 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_focalLoss.py "$SITE_PKG/nnunet/training/network_training/nnUNet_variants/loss_function/nnUNetTrainerV2_focalLoss.py" +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" +RUN SITE_PKG=`pip3 show nnunet | grep "Location:" | awk '{print $2}'` && \ + mv /opt/algorithm/nnUNetTrainerV2_Loss_FL_and_CE.py "$SITE_PKG/nnunet/training/network_training/nnUNetTrainerV2_Loss_FL_and_CE.py" + +# Two code edits to the __init__ method of the algorithm class in process.py to prevent some of its default behavior +# 1. Skip forced error caused by using a different input locations than expected (we don't use the GC dirs) +# 2. Prevent unnecessary folder creation before input directories have been set (we will set the correct directory later) +RUN sed -i "s|file_paths = list(Path(folder).glob(scan_glob_format))|return|g" /opt/algorithm/process.py && \ + sed -i "s|self.cspca_detection_map_path.parent.mkdir(exist_ok=True, parents=True)||g" /opt/algorithm/process.py + +# FIXME: temporary fix waiting for the latest base image update +# Clone the main branch of MHubAI/models +#RUN git stash \ +# && git fetch https://github.com/MHubAI/models.git main \ +# && git merge FETCH_HEAD \ +# && git sparse-checkout set "models/gc_picai_baseline" \ +# && git fetch https://github.com/MHubAI/models.git main + +# Add lobe segmentation code base to python path +ENV PYTHONPATH="/app:/opt/algorithm" + +# Default entrypoint +ENTRYPOINT ["python3", "-m", "mhubio.run"] +CMD ["--config", "/app/models/gc_picai_baseline/config/default.yml"] diff --git a/models/gc_picai_baseline/utils/PicaiBaselineRunner.py b/models/gc_picai_baseline/utils/PicaiBaselineRunner.py new file mode 100644 index 00000000..62b88e30 --- /dev/null +++ b/models/gc_picai_baseline/utils/PicaiBaselineRunner.py @@ -0,0 +1,68 @@ +""" +--------------------------------------------------------- +Mhub / DIAG - Run Module for the PICAI baseline Algorithm +--------------------------------------------------------- + +--------------------------------------------------------- +Author: Sil van de Leemput +Email: sil.vandeleemput@radboudumc.nl +--------------------------------------------------------- +""" + +import json +from pathlib import Path + +from mhubio.core import Instance, InstanceData, IO, Module, ValueOutput, ClassOutput, Meta + +# Import the PICAI Classifier algorithm class from /opt/algorithm +from process import csPCaAlgorithm as PicaiClassifier + + +@ValueOutput.Name('prostate_cancer_probability') +@ValueOutput.Meta(Meta(key="value")) +@ValueOutput.Label('ProstateCancerProbability') +@ValueOutput.Type(float) +@ValueOutput.Description('Probability of case-level prostate cancer.') +class ProstateCancerProbability(ValueOutput): + pass + + +class PicaiBaselineRunner(Module): + + @IO.Instance() + @IO.Input('in_data_t2', 'mha:mod=mrt2', the='input T2 weighted prostate MR image') + @IO.Input('in_data_adc', 'mha:mod=mradc', the='input ADC prostate MR image') + @IO.Input('in_data_hbv', 'mha:mod=mrhbv', the='input HBV prostate MR image') + @IO.Output('cancer_probability_json', 'cspca-case-level-likelihood.json', "json", bundle='model', the='output JSON file with PICAI baseline prostate cancer probability') + @IO.Output('cancer_detection_heatmap', 'cspca_detection_map.mha', "mha:mod=hm", bundle='model', the='output heatmap indicating prostate cancer probability') + @IO.OutputData('cancer_probability', ProstateCancerProbability, the='PICAI baseline prostate cancer probability') + def task(self, instance: Instance, in_data_t2: InstanceData, in_data_adc: InstanceData, in_data_hbv: InstanceData, cancer_probability_json: InstanceData, cancer_detection_heatmap: InstanceData, cancer_probability: ProstateCancerProbability) -> None: + # Initialize classifier object + classifier = PicaiClassifier() + + # Specify input files (the order is important!) + classifier.scan_paths = [ + Path(in_data_t2.abspath), + Path(in_data_adc.abspath), + Path(in_data_hbv.abspath), + ] + + # Specify output files + classifier.cspca_detection_map_path = Path(cancer_detection_heatmap.abspath) + classifier.case_confidence_path = Path(cancer_probability_json.abspath) + + # Run the classifier on the input images + classifier.process() + + # Extract cancer probability value from cancer_probability_file + if not Path(cancer_probability_json.abspath).is_file(): + raise FileNotFoundError(f"Output file {cancer_probability_json.abspath} could not be found!") + + with open(cancer_probability_json.abspath, "r") as f: + cancer_prob = float(json.load(f)) + + if not (isinstance(cancer_prob, (float, int)) and (0.0 <= cancer_prob <= 1.0)): + raise ValueError(f"Cancer probability value should be a probability value, found: {cancer_prob}") + + # Output the predicted values + cancer_probability.value = cancer_prob diff --git a/models/gc_picai_baseline/utils/__init__.py b/models/gc_picai_baseline/utils/__init__.py new file mode 100644 index 00000000..a0ec22bc --- /dev/null +++ b/models/gc_picai_baseline/utils/__init__.py @@ -0,0 +1 @@ +from .PicaiBaselineRunner import * From 954b4a2c0ced7a903a5de29a599164fbdc5ceacc Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Tue, 24 Oct 2023 15:13:10 +0200 Subject: [PATCH 02/15] add meta.json --- models/gc_picai_baseline/meta.json | 133 +++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 models/gc_picai_baseline/meta.json diff --git a/models/gc_picai_baseline/meta.json b/models/gc_picai_baseline/meta.json new file mode 100644 index 00000000..312743c5 --- /dev/null +++ b/models/gc_picai_baseline/meta.json @@ -0,0 +1,133 @@ +{ + "id": "c5f886fb-9f54-4555-a954-da02b22d6d3f", + "name": "picai_baseline", + "title": "PI-CAI challenge baseline", + "summary": { + "description": "The PI-CAI challenge is to validate modern AI algorithms at clinically significant prostate cancer (csPCa) detection and diagnosis. This model algorithm provides the baseline for the challenge.", + "inputs": [ + { + "label": "Prostate biparametric MRI", + "description": "Prostate biparametric MRI exam", + "format": "DICOM", + "modality": "MR", + "bodypartexamined": "Prostate", + "slicethickness": "", + "non-contrast": false, + "contrast": false + } + ], + "outputs": [ + { + "type": "Prediction", + "valueType": "Probability", + "label": "Prostate cancer probability", + "description": "Case-level likelihood of harboring clinically significant prostate cancer, in range [0,1]", + "classes": [] + }, + { + "type": "Prediction", + "valueType": "Probability map", + "label": "Transverse cancer detection map", + "description": "Detection map of clinically significant prostate cancer lesions in 3D, where each voxel represents a floating point in range [0,1]", + "classes": [] + } + ], + "model": { + "architecture": "3d fullres nnUNet", + "training": "supervised", + "cmpapproach": "3D" + }, + "data": { + "training": { + "vol_samples": 1200 + }, + "evaluation": { + "vol_samples": 300 + }, + "public": false, + "external": false + } + }, + "details": { + "name": "PI-CAI challenge baseline", + "version": "v2.1.1", + "devteam": "Diagnostic Image Analysis Group, Radboud University Medical Center, Nijmegen, The Netherlands", + "type": "Prediction", + "date": { + "weights": "2022-06-22", + "code": "2022-09-05", + "pub": "" + }, + "cite": "", + "license": { + "code": "Apache 2.0", + "weights": "Apache 2.0" + }, + "publications": [], + "github": "https://github.com/DIAGNijmegen/picai_nnunet_semi_supervised_gc_algorithm", + "zenodo": "", + "colab": "", + "slicer": false + }, + "info": { + "use": { + "title": "Intended use", + "text": "Prediction of the likelihood of harboring clinically significant prostate cancer (csPCa) in prostate biparametric MRI exams.", + "references": [], + "tables": [] + }, + "analyses": { + "title": "Evaluation", + "text": "Patient-level diagnosis performance is evaluated using the Area Under Receiver Operating Characteristic (AUROC) metric. Lesion-level detection performance is evaluated using the Average Precision (AP) metric. Overall score used to rank each AI algorithm is the average of both task-specific metrics: Overall Ranking Score = (AP + AUROC) / 2", + "references": [ + { + "label": "PI-CAI AI challenge details", + "uri": "https://pi-cai.grand-challenge.org/AI/" + } + ], + "tables": [] + }, + "evaluation": { + "title": "Evaluation data", + "text": "The test sets are two private cohorts of 100 and 1000 biparametric MRI exams respectively. The first was used to tune the algorithms in a public leaderboard, the second was used to determine the top 5 AI algorithms.", + "references": [ + { + "label": "PI-CAI data section", + "uri": "https://pi-cai.grand-challenge.org/DATA/" + } + ], + "tables": [] + }, + "training": { + "title": "Training data", + "text": "For the PI-CAI a publicly available training datasets of 1500 biparametric MRI exams including 328 cases from the ProstateX challenge were made available.", + "references": [ + { + "label": "PI-CAI publicly available training data", + "uri": "https://zenodo.org/record/6624726" + }, + { + "label": "PI-CAI publicly available training data annotations", + "uri": "https://github.com/DIAGNijmegen/picai_labels" + }, + { + "label": "ProstateX challenge", + "uri": "https://prostatex.grand-challenge.org/" + } + ], + "tables": [] + }, + "ethics": { + "title": "", + "text": "", + "references": [], + "tables": [] + }, + "limitations": { + "title": "Limitations", + "text": "This algorithm was developed for research purposes only.", + "references": [], + "tables": [] + } + } +} \ No newline at end of file From 47ce41819b87bf6bb0833defbc60505a3d2e243a Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Mon, 6 Nov 2023 13:51:42 +0100 Subject: [PATCH 03/15] changed input structure and added allow_multi_input on MHAConverter --- models/gc_picai_baseline/config/default.yml | 5 ++--- models/gc_picai_baseline/utils/PicaiBaselineRunner.py | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/models/gc_picai_baseline/config/default.yml b/models/gc_picai_baseline/config/default.yml index c86566be..fe257f9d 100644 --- a/models/gc_picai_baseline/config/default.yml +++ b/models/gc_picai_baseline/config/default.yml @@ -14,13 +14,12 @@ modules: FileStructureImporter: input_dir: input_data structures: - - $sid@instance/images/transverse-adc-prostate-mri@dicom:mod=mradc - - $sid/images/transverse-t2-prostate-mri@dicom:mod=mrt2 - - $sid/images/transverse-hbv-prostate-mri@dicom:mod=mrhbv + - $sid@instance/$type@dicom:mod=mr import_id: sid MhaConverter: engine: panimg + allow_multi_input: true ReportExporter: format: compact diff --git a/models/gc_picai_baseline/utils/PicaiBaselineRunner.py b/models/gc_picai_baseline/utils/PicaiBaselineRunner.py index 62b88e30..d4a122f2 100644 --- a/models/gc_picai_baseline/utils/PicaiBaselineRunner.py +++ b/models/gc_picai_baseline/utils/PicaiBaselineRunner.py @@ -30,9 +30,9 @@ class ProstateCancerProbability(ValueOutput): class PicaiBaselineRunner(Module): @IO.Instance() - @IO.Input('in_data_t2', 'mha:mod=mrt2', the='input T2 weighted prostate MR image') - @IO.Input('in_data_adc', 'mha:mod=mradc', the='input ADC prostate MR image') - @IO.Input('in_data_hbv', 'mha:mod=mrhbv', the='input HBV prostate MR image') + @IO.Input('in_data_t2', 'mha:mod=mr:type=t2w', the='input T2 weighted prostate MR image') + @IO.Input('in_data_adc', 'mha:mod=mr:type=adc', the='input ADC prostate MR image') + @IO.Input('in_data_hbv', 'mha:mod=mr:type=hbv', the='input HBV prostate MR image') @IO.Output('cancer_probability_json', 'cspca-case-level-likelihood.json', "json", bundle='model', the='output JSON file with PICAI baseline prostate cancer probability') @IO.Output('cancer_detection_heatmap', 'cspca_detection_map.mha', "mha:mod=hm", bundle='model', the='output heatmap indicating prostate cancer probability') @IO.OutputData('cancer_probability', ProstateCancerProbability, the='PICAI baseline prostate cancer probability') From 0d19c7c5b907b2be6a1ef935fe9e2c01fff8fda8 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Wed, 15 Nov 2023 13:06:34 +0100 Subject: [PATCH 04/15] add link to baseline algorithm on grand-challenge in meta.json --- models/gc_picai_baseline/meta.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/models/gc_picai_baseline/meta.json b/models/gc_picai_baseline/meta.json index 312743c5..59aab4d2 100644 --- a/models/gc_picai_baseline/meta.json +++ b/models/gc_picai_baseline/meta.json @@ -73,7 +73,12 @@ "use": { "title": "Intended use", "text": "Prediction of the likelihood of harboring clinically significant prostate cancer (csPCa) in prostate biparametric MRI exams.", - "references": [], + "references": [ + { + "label": "PI-CAI baseline algorithm on grand-challenge", + "uri": "https://grand-challenge.org/algorithms/pi-cai-baseline-nnu-net-semi-supervised/" + } + ], "tables": [] }, "analyses": { From 483bf910ad845974d79520b2bf69dd37448f2583 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Thu, 23 Nov 2023 17:03:05 +0100 Subject: [PATCH 05/15] removed first line comment and added mhub model definition import lines --- models/gc_picai_baseline/dockerfiles/Dockerfile | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/models/gc_picai_baseline/dockerfiles/Dockerfile b/models/gc_picai_baseline/dockerfiles/Dockerfile index a1bff797..84a1c607 100644 --- a/models/gc_picai_baseline/dockerfiles/Dockerfile +++ b/models/gc_picai_baseline/dockerfiles/Dockerfile @@ -1,4 +1,3 @@ -# Specify the base image for the environment FROM mhubai/base:latest # Specify/override authors label @@ -35,13 +34,9 @@ RUN SITE_PKG=`pip3 show nnunet | grep "Location:" | awk '{print $2}'` && \ RUN sed -i "s|file_paths = list(Path(folder).glob(scan_glob_format))|return|g" /opt/algorithm/process.py && \ sed -i "s|self.cspca_detection_map_path.parent.mkdir(exist_ok=True, parents=True)||g" /opt/algorithm/process.py -# FIXME: temporary fix waiting for the latest base image update -# Clone the main branch of MHubAI/models -#RUN git stash \ -# && git fetch https://github.com/MHubAI/models.git main \ -# && git merge FETCH_HEAD \ -# && git sparse-checkout set "models/gc_picai_baseline" \ -# && 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_picai_baseline ${MHUB_MODELS_REPO} # Add lobe segmentation code base to python path ENV PYTHONPATH="/app:/opt/algorithm" From 4abafd57479aa8d5b562355e2af83f8da8c9f081 Mon Sep 17 00:00:00 2001 From: Miriam Groeneveld Date: Thu, 30 Nov 2023 14:54:05 +0100 Subject: [PATCH 06/15] PR comments on mata.json --- models/gc_picai_baseline/config/default.yml | 4 +- .../gc_picai_baseline/config/mha-pipeline.yml | 4 +- models/gc_picai_baseline/meta.json | 83 ++++++++++++++----- .../utils/PicaiBaselineRunner.py | 35 ++++---- 4 files changed, 83 insertions(+), 43 deletions(-) diff --git a/models/gc_picai_baseline/config/default.yml b/models/gc_picai_baseline/config/default.yml index fe257f9d..c86cac07 100644 --- a/models/gc_picai_baseline/config/default.yml +++ b/models/gc_picai_baseline/config/default.yml @@ -24,8 +24,8 @@ modules: ReportExporter: format: compact includes: - - data: prostate_cancer_probability - label: prostate_cancer_probability + - data: prostate_cancer_likelihood + label: prostate_cancer_likelihood value: value DataOrganizer: diff --git a/models/gc_picai_baseline/config/mha-pipeline.yml b/models/gc_picai_baseline/config/mha-pipeline.yml index f70fac4b..f20d5abc 100644 --- a/models/gc_picai_baseline/config/mha-pipeline.yml +++ b/models/gc_picai_baseline/config/mha-pipeline.yml @@ -21,8 +21,8 @@ modules: ReportExporter: format: compact includes: - - data: prostate_cancer_probability - label: prostate_cancer_probability + - data: prostate_cancer_likelihood + label: prostate_cancer_likelihood value: value DataOrganizer: diff --git a/models/gc_picai_baseline/meta.json b/models/gc_picai_baseline/meta.json index 59aab4d2..c83ce46d 100644 --- a/models/gc_picai_baseline/meta.json +++ b/models/gc_picai_baseline/meta.json @@ -3,11 +3,31 @@ "name": "picai_baseline", "title": "PI-CAI challenge baseline", "summary": { - "description": "The PI-CAI challenge is to validate modern AI algorithms at clinically significant prostate cancer (csPCa) detection and diagnosis. This model algorithm provides the baseline for the challenge.", + "description": "This algorithm predicts a detection map for the likelihood of clinically significant prostate cancer (csPCa) using biparametric MRI (bpMRI). The algorithm ensembles 5-fold cross-validation models that were trained on the PI-CAI: Public Training and Development Dataset v2.0. The detection map is at the same spatial resolution and physical dimensions as the input axial T2-weighted image.", "inputs": [ { - "label": "Prostate biparametric MRI", - "description": "Prostate biparametric MRI exam", + "label": "Transverse T2-weighted Prostate biparametric MRI", + "description": " Transverse T2-weighted Prostate biparametric MRI exam", + "format": "DICOM", + "modality": "MR", + "bodypartexamined": "Prostate", + "slicethickness": "", + "non-contrast": false, + "contrast": false + }, + { + "label": "High b-value diffusion-weighted maps", + "description": "High b-value diffusion-weighted maps, with b-value of 1400 or 2000, either acquired or vendor-calculated.", + "format": "DICOM", + "modality": "MR", + "bodypartexamined": "Prostate", + "slicethickness": "", + "non-contrast": false, + "contrast": false + }, + { + "label": "ADC map", + "description": "ADC map", "format": "DICOM", "modality": "MR", "bodypartexamined": "Prostate", @@ -19,14 +39,14 @@ "outputs": [ { "type": "Prediction", - "valueType": "Probability", - "label": "Prostate cancer probability", + "valueType": "Likelihood", + "label": "Prostate cancer likelihood", "description": "Case-level likelihood of harboring clinically significant prostate cancer, in range [0,1]", "classes": [] }, { "type": "Prediction", - "valueType": "Probability map", + "valueType": "Likelihood map", "label": "Transverse cancer detection map", "description": "Detection map of clinically significant prostate cancer lesions in 3D, where each voxel represents a floating point in range [0,1]", "classes": [] @@ -34,15 +54,15 @@ ], "model": { "architecture": "3d fullres nnUNet", - "training": "supervised", + "training": "semi-supervised", "cmpapproach": "3D" }, "data": { "training": { - "vol_samples": 1200 + "vol_samples": 1500 }, "evaluation": { - "vol_samples": 300 + "vol_samples": 1000 }, "public": false, "external": false @@ -61,9 +81,18 @@ "cite": "", "license": { "code": "Apache 2.0", - "weights": "Apache 2.0" + "weights": "CC-BY-NC-4.0" }, - "publications": [], + "publications": [ + { + "uri": "https://doi.org/10.5281/zenodo.6667655", + "title": "Artificial Intelligence and Radiologists at Prostate Cancer Detection in MRI: The PI-CAI Challenge (Study Protocol)" + }, + { + "uri": "https://pubs.rsna.org/doi/10.1148/ryai.230031", + "title": "Semisupervised Learning with Report-guided Pseudo Labels for Deep Learning–based Prostate Cancer Detection Using Biparametric MRI" + } + ], "github": "https://github.com/DIAGNijmegen/picai_nnunet_semi_supervised_gc_algorithm", "zenodo": "", "colab": "", @@ -72,7 +101,7 @@ "info": { "use": { "title": "Intended use", - "text": "Prediction of the likelihood of harboring clinically significant prostate cancer (csPCa) in prostate biparametric MRI exams.", + "text": "This algorithm is a deep learning-based detection/diagnosis model, which ensembles 5 independent nnU-Net models (5-fold cross-validation). To predict the likelihood of harboring clinically significant prostate cancer (csPCa), the transversal T2-weighted, apparent diffusion coefficient (ADC) and high b-value diffusion maps are required. The input sequences should be co-registered or aligned reasonably well and the prostate gland should be localized within a volume of 460 cm³ from the centre coordinate. To train these models, a total of 1500 prostate biparametric MRI (bpMRI) scans paired with human-annotated or AI-derived ISUP ≥ 2 delineations were used.", "references": [ { "label": "PI-CAI baseline algorithm on grand-challenge", @@ -83,18 +112,30 @@ }, "analyses": { "title": "Evaluation", - "text": "Patient-level diagnosis performance is evaluated using the Area Under Receiver Operating Characteristic (AUROC) metric. Lesion-level detection performance is evaluated using the Average Precision (AP) metric. Overall score used to rank each AI algorithm is the average of both task-specific metrics: Overall Ranking Score = (AP + AUROC) / 2", + "text": "Patient-level diagnosis performance is evaluated using the Area Under Receiver Operating Characteristic (AUROC) metric. Lesion-level detection performance is evaluated using the Average Precision (AP) metric.", "references": [ { "label": "PI-CAI AI challenge details", "uri": "https://pi-cai.grand-challenge.org/AI/" + }, + { + "label": "PI-CAI baseline algorithm evaluation results on grand-challenge.", + "uri": "https://pi-cai.grand-challenge.org/evaluation/fe187cdb-cb61-4cbb-ab63-2de483a52d60/" } ], - "tables": [] + "tables": [ + { + "label": "Evaluation results on the PI-CAI testing cohort of 1000 cases.", + "entries": { + "AUROC": "0.865", + "AP": "0.576" + } + } + ] }, "evaluation": { "title": "Evaluation data", - "text": "The test sets are two private cohorts of 100 and 1000 biparametric MRI exams respectively. The first was used to tune the algorithms in a public leaderboard, the second was used to determine the top 5 AI algorithms.", + "text": "The PI-CAI Hidden Testing Cohort (1000 cases) includes internal testing data (unseen cases from seen centers) and external testing data (unseen cases from an unseen center).", "references": [ { "label": "PI-CAI data section", @@ -105,19 +146,19 @@ }, "training": { "title": "Training data", - "text": "For the PI-CAI a publicly available training datasets of 1500 biparametric MRI exams including 328 cases from the ProstateX challenge were made available.", + "text": "The publicly available PI-CAI training and development dataset of 1500 biparametric MRI exams was used for training [1]. AI-derived annotations were created for cases without manual annotations [2]. This model was trained using a custom preprocessing step followed by the standard nnU-Net pipeline. The default nnU-Net loss-function was changed to Cross-Entropy + Focal loss. [3]", "references": [ { - "label": "PI-CAI publicly available training data", + "label": "PI-CAI publicly available training and development dataset", "uri": "https://zenodo.org/record/6624726" }, { - "label": "PI-CAI publicly available training data annotations", - "uri": "https://github.com/DIAGNijmegen/picai_labels" + "label": "Method to obtain AI-derived annotations", + "uri": "https://fastmri.eu/research/bosma22a.html" }, { - "label": "ProstateX challenge", - "uri": "https://prostatex.grand-challenge.org/" + "label": "Detailed description of training method", + "uri": "https://github.com/DIAGNijmegen/picai_baseline/blob/main/nnunet_baseline.md" } ], "tables": [] diff --git a/models/gc_picai_baseline/utils/PicaiBaselineRunner.py b/models/gc_picai_baseline/utils/PicaiBaselineRunner.py index d4a122f2..4f1d05ca 100644 --- a/models/gc_picai_baseline/utils/PicaiBaselineRunner.py +++ b/models/gc_picai_baseline/utils/PicaiBaselineRunner.py @@ -18,12 +18,11 @@ from process import csPCaAlgorithm as PicaiClassifier -@ValueOutput.Name('prostate_cancer_probability') -@ValueOutput.Meta(Meta(key="value")) -@ValueOutput.Label('ProstateCancerProbability') +@ValueOutput.Name('prostate_cancer_likelihood') +@ValueOutput.Label('ProstateCancerLikelihood') @ValueOutput.Type(float) -@ValueOutput.Description('Probability of case-level prostate cancer.') -class ProstateCancerProbability(ValueOutput): +@ValueOutput.Description('Likelihood of case-level prostate cancer.') +class ProstateCancerLikelihood(ValueOutput): pass @@ -33,10 +32,10 @@ class PicaiBaselineRunner(Module): @IO.Input('in_data_t2', 'mha:mod=mr:type=t2w', the='input T2 weighted prostate MR image') @IO.Input('in_data_adc', 'mha:mod=mr:type=adc', the='input ADC prostate MR image') @IO.Input('in_data_hbv', 'mha:mod=mr:type=hbv', the='input HBV prostate MR image') - @IO.Output('cancer_probability_json', 'cspca-case-level-likelihood.json', "json", bundle='model', the='output JSON file with PICAI baseline prostate cancer probability') - @IO.Output('cancer_detection_heatmap', 'cspca_detection_map.mha', "mha:mod=hm", bundle='model', the='output heatmap indicating prostate cancer probability') - @IO.OutputData('cancer_probability', ProstateCancerProbability, the='PICAI baseline prostate cancer probability') - def task(self, instance: Instance, in_data_t2: InstanceData, in_data_adc: InstanceData, in_data_hbv: InstanceData, cancer_probability_json: InstanceData, cancer_detection_heatmap: InstanceData, cancer_probability: ProstateCancerProbability) -> None: + @IO.Output('cancer_likelihood_json', 'cspca-case-level-likelihood.json', "json", bundle='model', the='output JSON file with PICAI baseline prostate cancer likelihood') + @IO.Output('cancer_detection_heatmap', 'cspca_detection_map.mha', "mha:mod=hm", bundle='model', the='output heatmap indicating prostate cancer likelihood') + @IO.OutputData('cancer_likelihood', ProstateCancerLikelihood, the='PICAI baseline prostate cancer likelihood') + def task(self, instance: Instance, in_data_t2: InstanceData, in_data_adc: InstanceData, in_data_hbv: InstanceData, cancer_likelihood_json: InstanceData, cancer_detection_heatmap: InstanceData, cancer_likelihood: ProstateCancerLikelihood) -> None: # Initialize classifier object classifier = PicaiClassifier() @@ -49,20 +48,20 @@ def task(self, instance: Instance, in_data_t2: InstanceData, in_data_adc: Instan # Specify output files classifier.cspca_detection_map_path = Path(cancer_detection_heatmap.abspath) - classifier.case_confidence_path = Path(cancer_probability_json.abspath) + classifier.case_confidence_path = Path(cancer_likelihood_json.abspath) # Run the classifier on the input images classifier.process() - # Extract cancer probability value from cancer_probability_file - if not Path(cancer_probability_json.abspath).is_file(): - raise FileNotFoundError(f"Output file {cancer_probability_json.abspath} could not be found!") + # Extract cancer likelihood value from cancer_likelihood_file + if not Path(cancer_likelihood_json.abspath).is_file(): + raise FileNotFoundError(f"Output file {cancer_likelihood_json.abspath} could not be found!") - with open(cancer_probability_json.abspath, "r") as f: - cancer_prob = float(json.load(f)) + with open(cancer_likelihood_json.abspath, "r") as f: + cancer_lh = float(json.load(f)) - if not (isinstance(cancer_prob, (float, int)) and (0.0 <= cancer_prob <= 1.0)): - raise ValueError(f"Cancer probability value should be a probability value, found: {cancer_prob}") + if not (isinstance(cancer_lh, (float, int)) and (0.0 <= cancer_lh <= 1.0)): + raise ValueError(f"Cancer likelihood value should be between 0 and 1, found: {cancer_lh}") # Output the predicted values - cancer_probability.value = cancer_prob + cancer_likelihood.value = cancer_lh From 8d7a6aa516cf22498aacaa0e55f0792e797f7ebe Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Thu, 30 Nov 2023 16:08:13 +0100 Subject: [PATCH 07/15] Dockerfile - minor cleanup and add sklearn deprecation environment flag for nnunet==1.7.0 --- models/gc_picai_baseline/dockerfiles/Dockerfile | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/models/gc_picai_baseline/dockerfiles/Dockerfile b/models/gc_picai_baseline/dockerfiles/Dockerfile index 84a1c607..a91146aa 100644 --- a/models/gc_picai_baseline/dockerfiles/Dockerfile +++ b/models/gc_picai_baseline/dockerfiles/Dockerfile @@ -7,7 +7,9 @@ LABEL authors="sil.vandeleemput@radboudumc.nl" 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 unpacking 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 PICAI baseline algorithm and model weights # - Git clone the algorithm repository for v2.1.1 (fixed to v2.1.1 tag) @@ -17,6 +19,13 @@ RUN git clone --depth 1 --branch v2.1.1 https://github.com/DIAGNijmegen/picai_nn rm -rf /opt/algorithm/test && \ 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 +# 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 additional PICAI requirements RUN pip3 install --no-cache-dir -r /opt/algorithm/requirements.txt From 3b72e0e31002762da35f6f944debab1b787c80ca Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Thu, 30 Nov 2023 16:12:34 +0100 Subject: [PATCH 08/15] meta.json - add citation to cite field --- models/gc_picai_baseline/meta.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/gc_picai_baseline/meta.json b/models/gc_picai_baseline/meta.json index c83ce46d..b15c65ac 100644 --- a/models/gc_picai_baseline/meta.json +++ b/models/gc_picai_baseline/meta.json @@ -78,7 +78,7 @@ "code": "2022-09-05", "pub": "" }, - "cite": "", + "cite": "J. S. Bosma, A. Saha, M. Hosseinzadeh, I. Slootweg, M. de Rooij, and H. Huisman, \"Semisupervised Learning with Report-guided Pseudo Labels for Deep Learning–based Prostate Cancer Detection Using Biparametric MRI\", Radiology: Artificial Intelligence, 230031, 2023. DOI: 10.1148/ryai.230031", "license": { "code": "Apache 2.0", "weights": "CC-BY-NC-4.0" From 66e88c37647a3b59d5bb9cc489ad04d85dd876e0 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Thu, 30 Nov 2023 16:21:22 +0100 Subject: [PATCH 09/15] meta.json - describe relation to the PI-CAI challenge in description --- models/gc_picai_baseline/meta.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/gc_picai_baseline/meta.json b/models/gc_picai_baseline/meta.json index b15c65ac..cfd8697d 100644 --- a/models/gc_picai_baseline/meta.json +++ b/models/gc_picai_baseline/meta.json @@ -3,7 +3,7 @@ "name": "picai_baseline", "title": "PI-CAI challenge baseline", "summary": { - "description": "This algorithm predicts a detection map for the likelihood of clinically significant prostate cancer (csPCa) using biparametric MRI (bpMRI). The algorithm ensembles 5-fold cross-validation models that were trained on the PI-CAI: Public Training and Development Dataset v2.0. The detection map is at the same spatial resolution and physical dimensions as the input axial T2-weighted image.", + "description": "This algorithm predicts a detection map for the likelihood of clinically significant prostate cancer (csPCa) using biparametric MRI (bpMRI). The algorithm ensembles 5-fold cross-validation models that were trained on the PI-CAI: Public Training and Development Dataset v2.0. The detection map is at the same spatial resolution and physical dimensions as the input axial T2-weighted image. This model algorithm was used as a baseline for the PI-CAI challenge hosted on Grand Challenge.", "inputs": [ { "label": "Transverse T2-weighted Prostate biparametric MRI", From 39734f9a7151f80d63f836adadb7b56cf3e5f7ab Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Tue, 5 Dec 2023 13:54:52 +0100 Subject: [PATCH 10/15] meta.json - update input/output descriptions, extended intended use section --- models/gc_picai_baseline/meta.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/models/gc_picai_baseline/meta.json b/models/gc_picai_baseline/meta.json index cfd8697d..e172b4e1 100644 --- a/models/gc_picai_baseline/meta.json +++ b/models/gc_picai_baseline/meta.json @@ -6,32 +6,32 @@ "description": "This algorithm predicts a detection map for the likelihood of clinically significant prostate cancer (csPCa) using biparametric MRI (bpMRI). The algorithm ensembles 5-fold cross-validation models that were trained on the PI-CAI: Public Training and Development Dataset v2.0. The detection map is at the same spatial resolution and physical dimensions as the input axial T2-weighted image. This model algorithm was used as a baseline for the PI-CAI challenge hosted on Grand Challenge.", "inputs": [ { - "label": "Transverse T2-weighted Prostate biparametric MRI", - "description": " Transverse T2-weighted Prostate biparametric MRI exam", + "label": "Transverse T2-weighted prostate biparametric MRI", + "description": "Transverse T2-weighted prostate biparametric MRI exam.", "format": "DICOM", "modality": "MR", "bodypartexamined": "Prostate", - "slicethickness": "", + "slicethickness": "0.5 x 0.5 x 3.0 mm", "non-contrast": false, "contrast": false }, { - "label": "High b-value diffusion-weighted maps", - "description": "High b-value diffusion-weighted maps, with b-value of 1400 or 2000, either acquired or vendor-calculated.", + "label": "Transverse high b-value diffusion-weighted maps of the prostate", + "description": "Transverse high b-value diffusion-weighted (DWI) maps, with b-value of 1400 or 2000, either acquired or vendor-calculated.", "format": "DICOM", "modality": "MR", "bodypartexamined": "Prostate", - "slicethickness": "", + "slicethickness": "0.5 x 0.5 x 3.0 mm", "non-contrast": false, "contrast": false }, { - "label": "ADC map", - "description": "ADC map", + "label": "Transverse apparent diffusion coefficient map of the prostate", + "description": "Transverse apparent diffusion coefficient (ADC) prostate MRI map.", "format": "DICOM", "modality": "MR", "bodypartexamined": "Prostate", - "slicethickness": "", + "slicethickness": "0.5 x 0.5 x 3.0 mm", "non-contrast": false, "contrast": false } @@ -41,14 +41,14 @@ "type": "Prediction", "valueType": "Likelihood", "label": "Prostate cancer likelihood", - "description": "Case-level likelihood of harboring clinically significant prostate cancer, in range [0,1]", + "description": "Case-level likelihood of harboring clinically significant prostate cancer, in range [0,1].", "classes": [] }, { "type": "Prediction", "valueType": "Likelihood map", "label": "Transverse cancer detection map", - "description": "Detection map of clinically significant prostate cancer lesions in 3D, where each voxel represents a floating point in range [0,1]", + "description": "Detection map of clinically significant prostate cancer lesions in 3D, where each voxel represents a floating point in range [0,1]. This map is at the same spatial resolution and physical dimensions as the input transversal T2-weighted image.", "classes": [] } ], @@ -101,7 +101,7 @@ "info": { "use": { "title": "Intended use", - "text": "This algorithm is a deep learning-based detection/diagnosis model, which ensembles 5 independent nnU-Net models (5-fold cross-validation). To predict the likelihood of harboring clinically significant prostate cancer (csPCa), the transversal T2-weighted, apparent diffusion coefficient (ADC) and high b-value diffusion maps are required. The input sequences should be co-registered or aligned reasonably well and the prostate gland should be localized within a volume of 460 cm³ from the centre coordinate. To train these models, a total of 1500 prostate biparametric MRI (bpMRI) scans paired with human-annotated or AI-derived ISUP ≥ 2 delineations were used.", + "text": "This algorithm is a deep learning-based detection/diagnosis model, which ensembles 5 independent nnU-Net models (5-fold cross-validation). To predict the likelihood of harboring clinically significant prostate cancer (csPCa), the transversal T2-weighted, apparent diffusion coefficient (ADC) and high b-value diffusion weighted maps are required. The input sequences should be co-registered or aligned reasonably well and the prostate gland should be localized within a volume of 460 cm³ from the centre coordinate. The nnU-Net framework will internally resample all input scans to 0.5 x 0.5 x 3.0 mm. Per case the input data should be put into the following folder structure: `case1/adc`, `case1/hbv`, `case1/t2w`, corresponding respectively with the ADC, high b-value DWI, and the T2 weighted MR inputs for a case called `case1`.", "references": [ { "label": "PI-CAI baseline algorithm on grand-challenge", @@ -146,7 +146,7 @@ }, "training": { "title": "Training data", - "text": "The publicly available PI-CAI training and development dataset of 1500 biparametric MRI exams was used for training [1]. AI-derived annotations were created for cases without manual annotations [2]. This model was trained using a custom preprocessing step followed by the standard nnU-Net pipeline. The default nnU-Net loss-function was changed to Cross-Entropy + Focal loss. [3]", + "text": "The publicly available PI-CAI training and development dataset of 1500 biparametric MRI exams was used for training [1]. AI-derived annotations were created for cases without manual annotations [2]. This model was trained using a custom preprocessing step followed by the standard nnU-Net pipeline. The default nnU-Net loss-function was changed to Cross-Entropy + Focal loss [3].", "references": [ { "label": "PI-CAI publicly available training and development dataset", From 1f9dd941c0aa9413b342a801257d261ba61d326c Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Thu, 7 Dec 2023 12:56:51 +0100 Subject: [PATCH 11/15] update model/algorithm tag to 2.1.2 for nnunet pipe fix --- models/gc_picai_baseline/dockerfiles/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/gc_picai_baseline/dockerfiles/Dockerfile b/models/gc_picai_baseline/dockerfiles/Dockerfile index a91146aa..7c2af162 100644 --- a/models/gc_picai_baseline/dockerfiles/Dockerfile +++ b/models/gc_picai_baseline/dockerfiles/Dockerfile @@ -12,10 +12,10 @@ RUN apt update && \ rm -rf /var/lib/apt/lists/* # Install PICAI baseline algorithm and model weights -# - Git clone the algorithm repository for v2.1.1 (fixed to v2.1.1 tag) +# - Git clone the algorithm repository for v2.1.2 (fixed to v2.1.2 tag) # - We remove unnecessary files for a compacter docker layer # - Subsequently we remove the .git directory to procuce a compacter docker layer -RUN git clone --depth 1 --branch v2.1.1 https://github.com/DIAGNijmegen/picai_nnunet_semi_supervised_gc_algorithm.git /opt/algorithm && \ +RUN git clone --depth 1 --branch v2.1.2 https://github.com/DIAGNijmegen/picai_nnunet_semi_supervised_gc_algorithm.git /opt/algorithm && \ rm -rf /opt/algorithm/test && \ rm -rf /opt/algorithm/.git From f3c73f3282b29c576bd0346c2c9e71c3713e94a8 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Tue, 12 Dec 2023 10:51:39 +0100 Subject: [PATCH 12/15] add cli and configure runner to use cli --- .../utils/PicaiBaselineRunner.py | 30 +++++------ models/gc_picai_baseline/utils/cli.py | 54 +++++++++++++++++++ 2 files changed, 68 insertions(+), 16 deletions(-) create mode 100644 models/gc_picai_baseline/utils/cli.py diff --git a/models/gc_picai_baseline/utils/PicaiBaselineRunner.py b/models/gc_picai_baseline/utils/PicaiBaselineRunner.py index 4f1d05ca..f3958222 100644 --- a/models/gc_picai_baseline/utils/PicaiBaselineRunner.py +++ b/models/gc_picai_baseline/utils/PicaiBaselineRunner.py @@ -10,12 +10,13 @@ """ import json +import sys from pathlib import Path from mhubio.core import Instance, InstanceData, IO, Module, ValueOutput, ClassOutput, Meta -# Import the PICAI Classifier algorithm class from /opt/algorithm -from process import csPCaAlgorithm as PicaiClassifier + +CLI_PATH = Path(__file__).parent / "cli.py" @ValueOutput.Name('prostate_cancer_likelihood') @@ -36,22 +37,19 @@ class PicaiBaselineRunner(Module): @IO.Output('cancer_detection_heatmap', 'cspca_detection_map.mha', "mha:mod=hm", bundle='model', the='output heatmap indicating prostate cancer likelihood') @IO.OutputData('cancer_likelihood', ProstateCancerLikelihood, the='PICAI baseline prostate cancer likelihood') def task(self, instance: Instance, in_data_t2: InstanceData, in_data_adc: InstanceData, in_data_hbv: InstanceData, cancer_likelihood_json: InstanceData, cancer_detection_heatmap: InstanceData, cancer_likelihood: ProstateCancerLikelihood) -> None: - # Initialize classifier object - classifier = PicaiClassifier() - - # Specify input files (the order is important!) - classifier.scan_paths = [ - Path(in_data_t2.abspath), - Path(in_data_adc.abspath), - Path(in_data_hbv.abspath), + # build command (order matters!) + cmd = [ + sys.executable, + str(CLI_PATH), + in_data_t2.abspath, + in_data_adc.abspath, + in_data_hbv.abspath, + cancer_likelihood_json.abspath, + cancer_detection_heatmap.abspath, ] - # Specify output files - classifier.cspca_detection_map_path = Path(cancer_detection_heatmap.abspath) - classifier.case_confidence_path = Path(cancer_likelihood_json.abspath) - - # Run the classifier on the input images - classifier.process() + # run the command as subprocess + self.subprocess(cmd, text=True) # Extract cancer likelihood value from cancer_likelihood_file if not Path(cancer_likelihood_json.abspath).is_file(): diff --git a/models/gc_picai_baseline/utils/cli.py b/models/gc_picai_baseline/utils/cli.py new file mode 100644 index 00000000..64d51a73 --- /dev/null +++ b/models/gc_picai_baseline/utils/cli.py @@ -0,0 +1,54 @@ +""" +-------------------------------------------------- +Mhub / DIAG - CLI for the PICAI baseline Algorithm +-------------------------------------------------- + +-------------------------------------------------- +Author: Sil van de Leemput +Email: sil.vandeleemput@radboudumc.nl +-------------------------------------------------- +""" + +import argparse +from pathlib import Path +from process import csPCaAlgorithm as PicaiClassifier + + +def run_classifier(t2: Path, adc: Path, hbv: Path, cancer_likelihood_json: Path, cancer_detection_heatmap: Path): + # Initialize classifier object + classifier = PicaiClassifier() + + # Specify input files (the order is important!) + classifier.scan_paths = [ + t2, + adc, + hbv, + ] + + # Specify output files + classifier.cspca_detection_map_path = cancer_detection_heatmap + classifier.case_confidence_path = cancer_likelihood_json + + # Run the classifier on the input images + classifier.process() + + +def run_classifier_cli(): + parser = argparse.ArgumentParser("CLI to run the PICAI baseline classifier") + parser.add_argument("input_t2", type=str, help="input T2 weighted prostate MR image (MHA)") + parser.add_argument("input_adc", type=str, help="input ADC prostate MR image (MHA") + parser.add_argument("input_hbv", type=str, help="input HBV prostate MR image (MHA)") + parser.add_argument("output_cancer_likelihood_json", type=str, help="output JSON file with PICAI baseline prostate cancer likelihood (JSON)") + parser.add_argument("output_cancer_detection_heatmap", type=str, help="output heatmap indicating prostate cancer likelihood (MHA)") + args = parser.parse_args() + run_classifier( + t2=Path(args.input_t2), + adc=Path(args.input_adc), + hbv=Path(args.input_hbv), + cancer_likelihood_json=Path(args.output_cancer_likelihood_json), + cancer_detection_heatmap=Path(args.output_cancer_detection_heatmap), + ) + + +if __name__ == "__main__": + run_classifier_cli() From 2a8ef5963df1220fd0e35983a7a07d4b2fe654b0 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Wed, 13 Dec 2023 11:02:10 +0100 Subject: [PATCH 13/15] meta.json - change name into gc_picai_baseline --- models/gc_picai_baseline/meta.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/gc_picai_baseline/meta.json b/models/gc_picai_baseline/meta.json index e172b4e1..7cd3fec4 100644 --- a/models/gc_picai_baseline/meta.json +++ b/models/gc_picai_baseline/meta.json @@ -1,6 +1,6 @@ { "id": "c5f886fb-9f54-4555-a954-da02b22d6d3f", - "name": "picai_baseline", + "name": "gc_picai_baseline", "title": "PI-CAI challenge baseline", "summary": { "description": "This algorithm predicts a detection map for the likelihood of clinically significant prostate cancer (csPCa) using biparametric MRI (bpMRI). The algorithm ensembles 5-fold cross-validation models that were trained on the PI-CAI: Public Training and Development Dataset v2.0. The detection map is at the same spatial resolution and physical dimensions as the input axial T2-weighted image. This model algorithm was used as a baseline for the PI-CAI challenge hosted on Grand Challenge.", From 7055664293f44dab5b8916d69a1faa911b6d09fe Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Wed, 13 Dec 2023 15:04:11 +0100 Subject: [PATCH 14/15] meta.json - correction for license weights --- models/gc_picai_baseline/meta.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/gc_picai_baseline/meta.json b/models/gc_picai_baseline/meta.json index 7cd3fec4..1dbab230 100644 --- a/models/gc_picai_baseline/meta.json +++ b/models/gc_picai_baseline/meta.json @@ -81,7 +81,7 @@ "cite": "J. S. Bosma, A. Saha, M. Hosseinzadeh, I. Slootweg, M. de Rooij, and H. Huisman, \"Semisupervised Learning with Report-guided Pseudo Labels for Deep Learning–based Prostate Cancer Detection Using Biparametric MRI\", Radiology: Artificial Intelligence, 230031, 2023. DOI: 10.1148/ryai.230031", "license": { "code": "Apache 2.0", - "weights": "CC-BY-NC-4.0" + "weights": "CC-BY-NC-SA-4.0" }, "publications": [ { From ed79ebd134801862eb6e5e2c2810a4a45cb9f6c9 Mon Sep 17 00:00:00 2001 From: silvandeleemput Date: Thu, 14 Dec 2023 13:17:38 +0100 Subject: [PATCH 15/15] runner & cli - changed heatmap output descriptions to detection map and updated descriptions --- models/gc_picai_baseline/config/default.yml | 2 +- models/gc_picai_baseline/utils/PicaiBaselineRunner.py | 6 +++--- models/gc_picai_baseline/utils/cli.py | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/models/gc_picai_baseline/config/default.yml b/models/gc_picai_baseline/config/default.yml index c86cac07..bb5d5deb 100644 --- a/models/gc_picai_baseline/config/default.yml +++ b/models/gc_picai_baseline/config/default.yml @@ -31,4 +31,4 @@ modules: DataOrganizer: targets: - json:mod=report-->[i:sid]/cspca-case-level-likelihood.json - - mha:mod=hm-->[i:sid]/cspca-detection-map.mha + - mha:mod=dm-->[i:sid]/cspca-detection-map.mha diff --git a/models/gc_picai_baseline/utils/PicaiBaselineRunner.py b/models/gc_picai_baseline/utils/PicaiBaselineRunner.py index f3958222..84dc1474 100644 --- a/models/gc_picai_baseline/utils/PicaiBaselineRunner.py +++ b/models/gc_picai_baseline/utils/PicaiBaselineRunner.py @@ -34,9 +34,9 @@ class PicaiBaselineRunner(Module): @IO.Input('in_data_adc', 'mha:mod=mr:type=adc', the='input ADC prostate MR image') @IO.Input('in_data_hbv', 'mha:mod=mr:type=hbv', the='input HBV prostate MR image') @IO.Output('cancer_likelihood_json', 'cspca-case-level-likelihood.json', "json", bundle='model', the='output JSON file with PICAI baseline prostate cancer likelihood') - @IO.Output('cancer_detection_heatmap', 'cspca_detection_map.mha', "mha:mod=hm", bundle='model', the='output heatmap indicating prostate cancer likelihood') + @IO.Output('cancer_lesion_detection_map', 'cspca-detection-map.mha', "mha:mod=dm", bundle='model', the='output detection map of clinically significant prostate cancer lesions in 3D, where each voxel represents a floating point in range [0,1]') @IO.OutputData('cancer_likelihood', ProstateCancerLikelihood, the='PICAI baseline prostate cancer likelihood') - def task(self, instance: Instance, in_data_t2: InstanceData, in_data_adc: InstanceData, in_data_hbv: InstanceData, cancer_likelihood_json: InstanceData, cancer_detection_heatmap: InstanceData, cancer_likelihood: ProstateCancerLikelihood) -> None: + def task(self, instance: Instance, in_data_t2: InstanceData, in_data_adc: InstanceData, in_data_hbv: InstanceData, cancer_likelihood_json: InstanceData, cancer_lesion_detection_map: InstanceData, cancer_likelihood: ProstateCancerLikelihood) -> None: # build command (order matters!) cmd = [ sys.executable, @@ -45,7 +45,7 @@ def task(self, instance: Instance, in_data_t2: InstanceData, in_data_adc: Instan in_data_adc.abspath, in_data_hbv.abspath, cancer_likelihood_json.abspath, - cancer_detection_heatmap.abspath, + cancer_lesion_detection_map.abspath, ] # run the command as subprocess diff --git a/models/gc_picai_baseline/utils/cli.py b/models/gc_picai_baseline/utils/cli.py index 64d51a73..deaf9ecf 100644 --- a/models/gc_picai_baseline/utils/cli.py +++ b/models/gc_picai_baseline/utils/cli.py @@ -14,7 +14,7 @@ from process import csPCaAlgorithm as PicaiClassifier -def run_classifier(t2: Path, adc: Path, hbv: Path, cancer_likelihood_json: Path, cancer_detection_heatmap: Path): +def run_classifier(t2: Path, adc: Path, hbv: Path, cancer_likelihood_json: Path, cancer_lesion_detection_map: Path): # Initialize classifier object classifier = PicaiClassifier() @@ -26,7 +26,7 @@ def run_classifier(t2: Path, adc: Path, hbv: Path, cancer_likelihood_json: Path, ] # Specify output files - classifier.cspca_detection_map_path = cancer_detection_heatmap + classifier.cspca_detection_map_path = cancer_lesion_detection_map classifier.case_confidence_path = cancer_likelihood_json # Run the classifier on the input images @@ -39,14 +39,14 @@ def run_classifier_cli(): parser.add_argument("input_adc", type=str, help="input ADC prostate MR image (MHA") parser.add_argument("input_hbv", type=str, help="input HBV prostate MR image (MHA)") parser.add_argument("output_cancer_likelihood_json", type=str, help="output JSON file with PICAI baseline prostate cancer likelihood (JSON)") - parser.add_argument("output_cancer_detection_heatmap", type=str, help="output heatmap indicating prostate cancer likelihood (MHA)") + parser.add_argument("output_cancer_lesion_detection_map", type=str, help="output detection map of clinically significant prostate cancer lesions in 3D (MHA)") args = parser.parse_args() run_classifier( t2=Path(args.input_t2), adc=Path(args.input_adc), hbv=Path(args.input_hbv), cancer_likelihood_json=Path(args.output_cancer_likelihood_json), - cancer_detection_heatmap=Path(args.output_cancer_detection_heatmap), + cancer_lesion_detection_map=Path(args.output_cancer_lesion_detection_map), )