From 1d8c6565c34a8014840d51d76a725913a375d5f8 Mon Sep 17 00:00:00 2001 From: Rahul Soni Date: Wed, 30 Aug 2023 01:54:13 +0530 Subject: [PATCH 01/17] mhub resources for bamf-nnunet-mr-prostate model --- .../config/default.yml | 33 ++++++++++++ .../bamf_nnunet_mr_prostate/config/meta.json | 33 ++++++++++++ .../bamf_nnunet_mr_prostate/config/slicer.yml | 30 +++++++++++ .../dockerfiles/Dockerfile | 43 ++++++++++++++++ models/bamf_nnunet_mr_prostate/scripts/run.py | 51 +++++++++++++++++++ .../scripts/slicer_run.py | 37 ++++++++++++++ 6 files changed, 227 insertions(+) create mode 100644 models/bamf_nnunet_mr_prostate/config/default.yml create mode 100644 models/bamf_nnunet_mr_prostate/config/meta.json create mode 100644 models/bamf_nnunet_mr_prostate/config/slicer.yml create mode 100644 models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile create mode 100644 models/bamf_nnunet_mr_prostate/scripts/run.py create mode 100644 models/bamf_nnunet_mr_prostate/scripts/slicer_run.py diff --git a/models/bamf_nnunet_mr_prostate/config/default.yml b/models/bamf_nnunet_mr_prostate/config/default.yml new file mode 100644 index 00000000..971dd9a5 --- /dev/null +++ b/models/bamf_nnunet_mr_prostate/config/default.yml @@ -0,0 +1,33 @@ +general: + data_base_dir: /app/data + version: 1.0 + description: default configuration for Bamf NNUnet MR Prostate segmentation (dicom to dicom) + +execute: +- DicomImporter +- NiftiConverter +- NNUnetRunner +- DsegConverter +- DataOrganizer + +modules: + DicomImporter: + source_dir: input_data + import_dir: sorted_data + sort_data: true + meta: + mod: 'mr' + + NNUnetRunner: + nnunet_task: 'Task788_ProstateX' + nnunet_model: '3d_fullres' + + DsegConverter: + source_segs: 'nifti:mod=seg' + model_name: 'Bamf NNUnet MR Prostate' + skip_empty_slices: True + json_config_path: '/app/models/bamf_nnunet_mr_prostate/config/meta.json' + + DataOrganizer: + targets: + - dicomseg-->[i:sid]/bamf_nnunet_mr_prostate.seg.dcm \ No newline at end of file diff --git a/models/bamf_nnunet_mr_prostate/config/meta.json b/models/bamf_nnunet_mr_prostate/config/meta.json new file mode 100644 index 00000000..53564009 --- /dev/null +++ b/models/bamf_nnunet_mr_prostate/config/meta.json @@ -0,0 +1,33 @@ +{ + "ContentCreatorName": "BAMFHealth^AI", + "ClinicalTrialSeriesID": "Session1", + "ClinicalTrialTimePointID": "1", + "SeriesDescription": "AIMI Prostate Segmentation", + "SeriesNumber": "300", + "InstanceNumber": "1", + "BodyPartExamined": "PROSTATE", + "segmentAttributes": [ + [ + { + "labelID": 1, + "SegmentDescription": "Prostate", + "SegmentAlgorithmType": "AUTOMATIC", + "SegmentAlgorithmName": "BAMF-Prostate-MR", + "SegmentedPropertyCategoryCodeSequence": { + "CodeValue": "123037004", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Anatomical Structure" + }, + "SegmentedPropertyTypeCodeSequence": { + "CodeValue": "41216001", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Prostate" + }, + "recommendedDisplayRGBValue": [230, 158, 140] + } + ] + ], + "ContentLabel": "SEGMENTATION", + "ContentDescription": "Image segmentation", + "ClinicalTrialCoordinatingCenterName": "dcmqi" +} \ No newline at end of file diff --git a/models/bamf_nnunet_mr_prostate/config/slicer.yml b/models/bamf_nnunet_mr_prostate/config/slicer.yml new file mode 100644 index 00000000..77114b4e --- /dev/null +++ b/models/bamf_nnunet_mr_prostate/config/slicer.yml @@ -0,0 +1,30 @@ +general: + data_base_dir: /app/data + version: 1.0 + description: configuration for Bamf NNUnet MR Prostate segmentation in 3D Slicer (nrrd to nifti) + +execute: +- NrrdImporter +- NiftiConverter +- NNUnetRunner +- JsonSegExporter +- DataOrganizer + +modules: + NrrdImporter: + input_dir: 'input_data' + input_file_name: 'image.nrrd' + + JsonSegExporter: + segment_id_meta_key: roi + targets: + - nifti:mod=seg-->[basename] + + NNUnetRunner: + nnunet_task: 'Task778_MR_Prostate' + nnunet_model: '3d_fullres' + + DataOrganizer: + targets: + - nifti:mod=seg-->[basename] + - json:mod=seg-->segdef.json \ No newline at end of file diff --git a/models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile b/models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile new file mode 100644 index 00000000..59216532 --- /dev/null +++ b/models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile @@ -0,0 +1,43 @@ +FROM mhubai/base:latest + +# 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 platipy +RUN pip3 install --no-cache-dir \ + nnunet + +# Clone the main branch of MHubAI/models +RUN git stash \ + && git sparse-checkout set "models/bamf_nnunet_mr_prostate" \ + && git fetch https://github.com/MHubAI/models.git main \ + && git merge FETCH_HEAD + +# Pull weights into the container +ENV WEIGHTS_DIR=/root/.nnunet/nnUNet_models/nnUNet/ +RUN mkdir -p $WEIGHTS_DIR +ENV WEIGHTS_FN=Task788_Prostate.zip +ENV WEIGHTS_URL=https://zenodo.org/record/8290093/files/$WEIGHTS_FN +RUN wget --directory-prefix ${WEIGHTS_DIR} ${WEIGHTS_URL} +RUN unzip ${WEIGHTS_DIR}${WEIGHTS_FN} -d ${WEIGHTS_DIR} +RUN rm ${WEIGHTS_DIR}${WEIGHTS_FN} + +# specify nnunet specific environment variables +ENV WEIGHTS_FOLDER=$WEIGHTS_DIR + +##################################################################################### +# Copy changes to config if you have any +##################################################################################### +RUN mkdir -p /app/models/bamf_nnunet_mr_prostate/config/ +COPY config/* /app/models/bamf_nnunet_mr_prostate/config/ + +RUN mkdir -p /app/models/bamf_nnunet_mr_prostate/scripts/ +COPY scripts/* /app/models/bamf_nnunet_mr_prostate/scripts/ + +# Default run script +ENTRYPOINT ["python3", "-m", "mhubio.run", "--config", "/app/models/bamf_nnunet_mr_prostate/config/default.yml"] +CMD ["--script", "/app/models/bamf_nnunet_mr_prostate/scripts/run.py"] \ No newline at end of file diff --git a/models/bamf_nnunet_mr_prostate/scripts/run.py b/models/bamf_nnunet_mr_prostate/scripts/run.py new file mode 100644 index 00000000..8ec0d13d --- /dev/null +++ b/models/bamf_nnunet_mr_prostate/scripts/run.py @@ -0,0 +1,51 @@ +""" +------------------------------------------------- +MHub - run the NNUnet MR liver segmentation + pipeline +------------------------------------------------- + +------------------------------------------------- +Author: Leonard Nürnberg +Email: leonard.nuernberg@maastrichtuniversity.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/bamf_nnunet_mr_prostate/config/default.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]/liver.nii.gz") +organizer.setTarget(DataType(FileType.DICOMSEG, SEG), "/app/data/output_data/[i:sid]/liver.seg.dcm") +organizer.execute() \ No newline at end of file diff --git a/models/bamf_nnunet_mr_prostate/scripts/slicer_run.py b/models/bamf_nnunet_mr_prostate/scripts/slicer_run.py new file mode 100644 index 00000000..a194c324 --- /dev/null +++ b/models/bamf_nnunet_mr_prostate/scripts/slicer_run.py @@ -0,0 +1,37 @@ +""" +------------------------------------------------- +MHub - run the PP pipeline locally +------------------------------------------------- + +------------------------------------------------- +Author: Leonard Nürnberg +Email: leonard.nuernberg@maastrichtuniversity.nl +------------------------------------------------- +""" + +import sys, os +sys.path.append('.') + +from mhubio.core import Config, DataType, FileType, SEG +from mhubio.modules.importer.NrrdImporter import NrrdImporter +from mhubio.modules.convert.NiftiConverter import NiftiConverter +from mhubio.modules.runner.NNUnetRunner import NNUnetRunner +from mhubio.modules.organizer.DataOrganizer import DataOrganizer + +# config +config = Config('/app/models/bamf_nnunet_mr_prostate/config/slicer.yml') +config.verbose = True + +# import nrrd data provided by slicer +NrrdImporter(config).execute() + +# convert (ct:dicom -> ct:nifti) +NiftiConverter(config).execute() + +# execute model (nnunet) +NNUnetRunner(config).execute() + +# organize data into output folder available to slicer +organizer = DataOrganizer(config) +organizer.setTarget(DataType(FileType.NIFTI, SEG), "/app/data/output_data/prostate.nii.gz") +organizer.execute() \ No newline at end of file From a5a36a27ac79fc37813e2310f356efc381c36212 Mon Sep 17 00:00:00 2001 From: Rahul Soni Date: Sun, 3 Sep 2023 11:07:00 +0530 Subject: [PATCH 02/17] update config annotations --- models/bamf_nnunet_mr_prostate/config/default.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/models/bamf_nnunet_mr_prostate/config/default.yml b/models/bamf_nnunet_mr_prostate/config/default.yml index 971dd9a5..16192bf6 100644 --- a/models/bamf_nnunet_mr_prostate/config/default.yml +++ b/models/bamf_nnunet_mr_prostate/config/default.yml @@ -19,11 +19,14 @@ modules: mod: 'mr' NNUnetRunner: + in_data: 'nifty:mod=mr' nnunet_task: 'Task788_ProstateX' nnunet_model: '3d_fullres' + folds: '5' DsegConverter: source_segs: 'nifti:mod=seg' + target_dicom: 'dicom:mod=mr' model_name: 'Bamf NNUnet MR Prostate' skip_empty_slices: True json_config_path: '/app/models/bamf_nnunet_mr_prostate/config/meta.json' From bd8b03f3ab3b88bce87ea452afc1acfc2aa7d6dc Mon Sep 17 00:00:00 2001 From: Rahul Soni Date: Sun, 3 Sep 2023 19:51:37 +0530 Subject: [PATCH 03/17] fix modality val in runner --- models/bamf_nnunet_mr_prostate/config/default.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/models/bamf_nnunet_mr_prostate/config/default.yml b/models/bamf_nnunet_mr_prostate/config/default.yml index 16192bf6..2cdfc9e0 100644 --- a/models/bamf_nnunet_mr_prostate/config/default.yml +++ b/models/bamf_nnunet_mr_prostate/config/default.yml @@ -19,10 +19,9 @@ modules: mod: 'mr' NNUnetRunner: - in_data: 'nifty:mod=mr' + in_data: 'nifti:mod=mr' nnunet_task: 'Task788_ProstateX' nnunet_model: '3d_fullres' - folds: '5' DsegConverter: source_segs: 'nifti:mod=seg' From c60d1c3312b52bccfe4f7f722a46c1259634888b Mon Sep 17 00:00:00 2001 From: Rahul Soni Date: Fri, 22 Sep 2023 11:35:25 -0700 Subject: [PATCH 04/17] restructure according to new guidelines --- .../config/default.yml | 11 +- .../bamf_nnunet_mr_prostate/config/meta.json | 33 ------ .../dockerfiles/Dockerfile | 23 ++-- models/bamf_nnunet_mr_prostate/meta.json | 100 ++++++++++++++++++ models/bamf_nnunet_mr_prostate/scripts/run.py | 51 --------- .../scripts/slicer_run.py | 37 ------- 6 files changed, 114 insertions(+), 141 deletions(-) delete mode 100644 models/bamf_nnunet_mr_prostate/config/meta.json create mode 100644 models/bamf_nnunet_mr_prostate/meta.json delete mode 100644 models/bamf_nnunet_mr_prostate/scripts/run.py delete mode 100644 models/bamf_nnunet_mr_prostate/scripts/slicer_run.py diff --git a/models/bamf_nnunet_mr_prostate/config/default.yml b/models/bamf_nnunet_mr_prostate/config/default.yml index 2cdfc9e0..ea35191f 100644 --- a/models/bamf_nnunet_mr_prostate/config/default.yml +++ b/models/bamf_nnunet_mr_prostate/config/default.yml @@ -18,17 +18,20 @@ modules: meta: mod: 'mr' + NiftiConverter: + in_datas: dicom:mod=mr + engine: dcm2niix + NNUnetRunner: - in_data: 'nifti:mod=mr' + in_data: nifti:mod=mr nnunet_task: 'Task788_ProstateX' nnunet_model: '3d_fullres' DsegConverter: - source_segs: 'nifti:mod=seg' - target_dicom: 'dicom:mod=mr' + source_segs: nifti:mod=seg + target_dicom: dicom:mod=mr model_name: 'Bamf NNUnet MR Prostate' skip_empty_slices: True - json_config_path: '/app/models/bamf_nnunet_mr_prostate/config/meta.json' DataOrganizer: targets: diff --git a/models/bamf_nnunet_mr_prostate/config/meta.json b/models/bamf_nnunet_mr_prostate/config/meta.json deleted file mode 100644 index 53564009..00000000 --- a/models/bamf_nnunet_mr_prostate/config/meta.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "ContentCreatorName": "BAMFHealth^AI", - "ClinicalTrialSeriesID": "Session1", - "ClinicalTrialTimePointID": "1", - "SeriesDescription": "AIMI Prostate Segmentation", - "SeriesNumber": "300", - "InstanceNumber": "1", - "BodyPartExamined": "PROSTATE", - "segmentAttributes": [ - [ - { - "labelID": 1, - "SegmentDescription": "Prostate", - "SegmentAlgorithmType": "AUTOMATIC", - "SegmentAlgorithmName": "BAMF-Prostate-MR", - "SegmentedPropertyCategoryCodeSequence": { - "CodeValue": "123037004", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Anatomical Structure" - }, - "SegmentedPropertyTypeCodeSequence": { - "CodeValue": "41216001", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Prostate" - }, - "recommendedDisplayRGBValue": [230, 158, 140] - } - ] - ], - "ContentLabel": "SEGMENTATION", - "ContentDescription": "Image segmentation", - "ClinicalTrialCoordinatingCenterName": "dcmqi" -} \ No newline at end of file diff --git a/models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile b/models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile index 59216532..73613636 100644 --- a/models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile +++ b/models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile @@ -11,11 +11,11 @@ ENV SKLEARN_ALLOW_DEPRECATED_SKLEARN_PACKAGE_INSTALL=True RUN pip3 install --no-cache-dir \ nnunet -# Clone the main branch of MHubAI/models -RUN git stash \ - && git sparse-checkout set "models/bamf_nnunet_mr_prostate" \ - && git fetch https://github.com/MHubAI/models.git main \ - && git merge FETCH_HEAD +# # Clone the main branch of MHubAI/models +# RUN git stash \ +# && git sparse-checkout set "models/bamf_nnunet_mr_prostate" \ +# && git fetch https://github.com/MHubAI/models.git main \ +# && git merge FETCH_HEAD # Pull weights into the container ENV WEIGHTS_DIR=/root/.nnunet/nnUNet_models/nnUNet/ @@ -29,15 +29,6 @@ RUN rm ${WEIGHTS_DIR}${WEIGHTS_FN} # specify nnunet specific environment variables ENV WEIGHTS_FOLDER=$WEIGHTS_DIR -##################################################################################### -# Copy changes to config if you have any -##################################################################################### -RUN mkdir -p /app/models/bamf_nnunet_mr_prostate/config/ -COPY config/* /app/models/bamf_nnunet_mr_prostate/config/ - -RUN mkdir -p /app/models/bamf_nnunet_mr_prostate/scripts/ -COPY scripts/* /app/models/bamf_nnunet_mr_prostate/scripts/ - # Default run script -ENTRYPOINT ["python3", "-m", "mhubio.run", "--config", "/app/models/bamf_nnunet_mr_prostate/config/default.yml"] -CMD ["--script", "/app/models/bamf_nnunet_mr_prostate/scripts/run.py"] \ No newline at end of file +ENTRYPOINT ["python3", "-m", "mhubio.run"] +CMD ["--config", "/app/models/bamf_nnunet_mr_prostate/config/default.yml"] \ No newline at end of file diff --git a/models/bamf_nnunet_mr_prostate/meta.json b/models/bamf_nnunet_mr_prostate/meta.json new file mode 100644 index 00000000..21c5cdd2 --- /dev/null +++ b/models/bamf_nnunet_mr_prostate/meta.json @@ -0,0 +1,100 @@ +{ + "id": "", + "name": "bamf_nnunet_mr_prostate", + "title": "Bamf NNunet MR Prostate Model", + "summary": { + "description": "bamf_nnunet_mr_prostate model is a semantic segmentation model where an NNUnet model is finetuned for MR images", + "inputs": [ + { + "label": "Input Image", + "description": "The MR scan of a patient", + "format": "DICOM", + "modality": "MR", + "bodypartexamined": "PROSTATE", + "non-contrast": true, + "contrast": true + } + ], + "outputs": [ + { + "type": "Segmentation", + "classes": [ + "Prostate" + ] + } + ], + "model": { + "architecture": "U-net", + "training": "Supervised", + "cmpapproach": "2D, 3D, ensemble" + } + }, + "details": { + "name": "bamf_nnunet_mr_prostate", + "version": "1.0.0", + "devteam": "BAMF Health Pvt Ltd", + "type": "nnU-Net (U-Net structure, optimized by data-driven heuristics)", + "date": { + "weights": "09\/07\/23", + "code": "n\/a", + "pub": 2023 + }, + "license": { + "code": "Apache 2.0", + "weights": "CC BY-NC 4.0" + }, + "publications": [ + { + "title": "", + "uri": "" + } + ], + "github": "https:\/\/github.com\/bamf-health\/aimi-prostate-mr", + "zenodo": "https:\/\/zenodo.org\/record\/8290093", + "slicer": true + }, + "info": { + "use": { + "title": "Intended Use", + "text": "This model is intended to perform segmentations of prostate region that are relevant for use cases such as organ volumetry, disease characterization, and surgical or radiation therapy planning." + }, + "analyses": { + "title": "Quantitative Analyses", + "text": "The model's performance was assessed using the Dice Coefficient on a held-out portion of the in-house dataset (public). For more information, please refer to the model's publication [1].", + "references": [ + { + "label": "nnUNet: Robust Segmentation of Prostate in MR Images", + "uri": "https:\/\/pubs.rsna.org\/doi\/10.1148\/ryai.230024" + } + ] + }, + "evaluation": { + "title": "Evaluation Data", + "text": "The evaluation dataset consists of 65 MR scans of patients with different ages, presenting various pathologies, and acquired with various imaging protocols. The Nora Imaging Platform was used for manual segmentation or further refinement of generated segmentations, starting from data uniformely resampled to 1.5mm isotropic. Segmentation was supervised by two physicians with three and six years of experience in body imaging. If an existing model for a given structure was publicly available, that model was used to create a first segmentation, which was then validated and refined manually. Furthermore, the authors used an iterative learning approach to speed up the segmentation process [1][2]. The evaluation process was conducted on images resampled to 1.5mm isotropic.", + "references": [ + { + "label": "TotalSegmentator: Robust Segmentation of 104 Anatomic Structures in CT Images", + "uri": "https:\/\/pubs.rsna.org\/doi\/10.1148\/ryai.230024" + }, + { + "label": "TotalSegmentator dataset", + "uri": "https:\/\/doi.org\/10.5281\/zenodo.6802613" + } + ] + }, + "training": { + "title": "Training Data", + "text": "The training dataset consists of 1082 CT scans of patients with different ages, presenting various pathologies, and acquired with various imaging protocols [1][2]. All of the CT images were resampled to a common resolution of 1.5mm isotropic before training. For details regarding the labels the model was trained with, see the section above.", + "references": [ + { + "label": "TotalSegmentator: Robust Segmentation of 104 Anatomic Structures in CT Images", + "uri": "https:\/\/pubs.rsna.org\/doi\/10.1148\/ryai.230024" + }, + { + "label": "TotalSegmentator dataset", + "uri": "https:\/\/doi.org\/10.5281\/zenodo.6802613" + } + ] + } + } +} \ No newline at end of file diff --git a/models/bamf_nnunet_mr_prostate/scripts/run.py b/models/bamf_nnunet_mr_prostate/scripts/run.py deleted file mode 100644 index 8ec0d13d..00000000 --- a/models/bamf_nnunet_mr_prostate/scripts/run.py +++ /dev/null @@ -1,51 +0,0 @@ -""" -------------------------------------------------- -MHub - run the NNUnet MR liver segmentation - pipeline -------------------------------------------------- - -------------------------------------------------- -Author: Leonard Nürnberg -Email: leonard.nuernberg@maastrichtuniversity.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/bamf_nnunet_mr_prostate/config/default.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]/liver.nii.gz") -organizer.setTarget(DataType(FileType.DICOMSEG, SEG), "/app/data/output_data/[i:sid]/liver.seg.dcm") -organizer.execute() \ No newline at end of file diff --git a/models/bamf_nnunet_mr_prostate/scripts/slicer_run.py b/models/bamf_nnunet_mr_prostate/scripts/slicer_run.py deleted file mode 100644 index a194c324..00000000 --- a/models/bamf_nnunet_mr_prostate/scripts/slicer_run.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -------------------------------------------------- -MHub - run the PP pipeline locally -------------------------------------------------- - -------------------------------------------------- -Author: Leonard Nürnberg -Email: leonard.nuernberg@maastrichtuniversity.nl -------------------------------------------------- -""" - -import sys, os -sys.path.append('.') - -from mhubio.core import Config, DataType, FileType, SEG -from mhubio.modules.importer.NrrdImporter import NrrdImporter -from mhubio.modules.convert.NiftiConverter import NiftiConverter -from mhubio.modules.runner.NNUnetRunner import NNUnetRunner -from mhubio.modules.organizer.DataOrganizer import DataOrganizer - -# config -config = Config('/app/models/bamf_nnunet_mr_prostate/config/slicer.yml') -config.verbose = True - -# import nrrd data provided by slicer -NrrdImporter(config).execute() - -# convert (ct:dicom -> ct:nifti) -NiftiConverter(config).execute() - -# execute model (nnunet) -NNUnetRunner(config).execute() - -# organize data into output folder available to slicer -organizer = DataOrganizer(config) -organizer.setTarget(DataType(FileType.NIFTI, SEG), "/app/data/output_data/prostate.nii.gz") -organizer.execute() \ No newline at end of file From 253cc95804d01f938501438d176363dfa9dff32c Mon Sep 17 00:00:00 2001 From: Rahul Soni Date: Fri, 22 Sep 2023 12:02:21 -0700 Subject: [PATCH 05/17] add meta json for DsegConverter --- .../config/default.yml | 1 + .../bamf_nnunet_mr_prostate/config/meta.json | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 models/bamf_nnunet_mr_prostate/config/meta.json diff --git a/models/bamf_nnunet_mr_prostate/config/default.yml b/models/bamf_nnunet_mr_prostate/config/default.yml index ea35191f..6ba0daa9 100644 --- a/models/bamf_nnunet_mr_prostate/config/default.yml +++ b/models/bamf_nnunet_mr_prostate/config/default.yml @@ -32,6 +32,7 @@ modules: target_dicom: dicom:mod=mr model_name: 'Bamf NNUnet MR Prostate' skip_empty_slices: True + json_config_path: /app/models/bamf_nnunet_mr_prostate/config/meta.json DataOrganizer: targets: diff --git a/models/bamf_nnunet_mr_prostate/config/meta.json b/models/bamf_nnunet_mr_prostate/config/meta.json new file mode 100644 index 00000000..9a42e7be --- /dev/null +++ b/models/bamf_nnunet_mr_prostate/config/meta.json @@ -0,0 +1,33 @@ +{ + "ContentCreatorName": "BAMFHealth^AI", + "ClinicalTrialSeriesID": "Session1", + "ClinicalTrialTimePointID": "1", + "SeriesDescription": "AIMI Prostate Segmentation", + "SeriesNumber": "300", + "InstanceNumber": "1", + "BodyPartExamined": "PROSTATE", + "segmentAttributes": [ + [ + { + "labelID": 1, + "SegmentDescription": "Prostate", + "SegmentAlgorithmType": "AUTOMATIC", + "SegmentAlgorithmName": "BAMF-Prostate-MR", + "SegmentedPropertyCategoryCodeSequence": { + "CodeValue": "123037004", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Anatomical Structure" + }, + "SegmentedPropertyTypeCodeSequence": { + "CodeValue": "41216001", + "CodingSchemeDesignator": "SCT", + "CodeMeaning": "Prostate" + }, + "recommendedDisplayRGBValue": [230, 158, 140] + } + ] + ], + "ContentLabel": "SEGMENTATION", + "ContentDescription": "Image segmentation", + "ClinicalTrialCoordinatingCenterName": "dcmqi" + } \ No newline at end of file From 487305b93d655d7fda137e7820aa059b33440bf1 Mon Sep 17 00:00:00 2001 From: Rahul Soni Date: Fri, 22 Sep 2023 12:07:04 -0700 Subject: [PATCH 06/17] add back sparse checkout --- models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile b/models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile index 73613636..7db3d34f 100644 --- a/models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile +++ b/models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile @@ -11,11 +11,11 @@ ENV SKLEARN_ALLOW_DEPRECATED_SKLEARN_PACKAGE_INSTALL=True RUN pip3 install --no-cache-dir \ nnunet -# # Clone the main branch of MHubAI/models -# RUN git stash \ -# && git sparse-checkout set "models/bamf_nnunet_mr_prostate" \ -# && git fetch https://github.com/MHubAI/models.git main \ -# && git merge FETCH_HEAD +# Clone the main branch of MHubAI/models +RUN git stash \ + && git sparse-checkout set "models/bamf_nnunet_mr_prostate" \ + && git fetch https://github.com/MHubAI/models.git main \ + && git merge FETCH_HEAD # Pull weights into the container ENV WEIGHTS_DIR=/root/.nnunet/nnUNet_models/nnUNet/ From d5a905eeeccef3704479134328ca4d29775d4f0e Mon Sep 17 00:00:00 2001 From: Rahul Soni Date: Tue, 14 Nov 2023 15:34:43 -0800 Subject: [PATCH 07/17] reorganise module --- .../config/default.yml | 8 +- .../bamf_nnunet_mr_prostate/config/meta.json | 33 ------- .../bamf_nnunet_mr_prostate/config/slicer.yml | 11 ++- .../dockerfiles/Dockerfile | 10 +-- models/bamf_nnunet_mr_prostate/meta.json | 90 +++++++++++++------ .../utils/BamfProcessorRunner.py | 79 ++++++++++++++++ .../bamf_nnunet_mr_prostate/utils/__init__.py | 1 + 7 files changed, 159 insertions(+), 73 deletions(-) delete mode 100644 models/bamf_nnunet_mr_prostate/config/meta.json create mode 100644 models/bamf_nnunet_mr_prostate/utils/BamfProcessorRunner.py create mode 100644 models/bamf_nnunet_mr_prostate/utils/__init__.py diff --git a/models/bamf_nnunet_mr_prostate/config/default.yml b/models/bamf_nnunet_mr_prostate/config/default.yml index 6ba0daa9..bfe793b3 100644 --- a/models/bamf_nnunet_mr_prostate/config/default.yml +++ b/models/bamf_nnunet_mr_prostate/config/default.yml @@ -7,6 +7,7 @@ execute: - DicomImporter - NiftiConverter - NNUnetRunner +- BamfProcessorRunner - DsegConverter - DataOrganizer @@ -24,15 +25,16 @@ modules: NNUnetRunner: in_data: nifti:mod=mr - nnunet_task: 'Task788_ProstateX' - nnunet_model: '3d_fullres' + nnunet_task: Task788_ProstateX + nnunet_model: 3d_fullres + + BamfProcessorRunner: DsegConverter: source_segs: nifti:mod=seg target_dicom: dicom:mod=mr model_name: 'Bamf NNUnet MR Prostate' skip_empty_slices: True - json_config_path: /app/models/bamf_nnunet_mr_prostate/config/meta.json DataOrganizer: targets: diff --git a/models/bamf_nnunet_mr_prostate/config/meta.json b/models/bamf_nnunet_mr_prostate/config/meta.json deleted file mode 100644 index 9a42e7be..00000000 --- a/models/bamf_nnunet_mr_prostate/config/meta.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "ContentCreatorName": "BAMFHealth^AI", - "ClinicalTrialSeriesID": "Session1", - "ClinicalTrialTimePointID": "1", - "SeriesDescription": "AIMI Prostate Segmentation", - "SeriesNumber": "300", - "InstanceNumber": "1", - "BodyPartExamined": "PROSTATE", - "segmentAttributes": [ - [ - { - "labelID": 1, - "SegmentDescription": "Prostate", - "SegmentAlgorithmType": "AUTOMATIC", - "SegmentAlgorithmName": "BAMF-Prostate-MR", - "SegmentedPropertyCategoryCodeSequence": { - "CodeValue": "123037004", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Anatomical Structure" - }, - "SegmentedPropertyTypeCodeSequence": { - "CodeValue": "41216001", - "CodingSchemeDesignator": "SCT", - "CodeMeaning": "Prostate" - }, - "recommendedDisplayRGBValue": [230, 158, 140] - } - ] - ], - "ContentLabel": "SEGMENTATION", - "ContentDescription": "Image segmentation", - "ClinicalTrialCoordinatingCenterName": "dcmqi" - } \ No newline at end of file diff --git a/models/bamf_nnunet_mr_prostate/config/slicer.yml b/models/bamf_nnunet_mr_prostate/config/slicer.yml index 77114b4e..3b74515c 100644 --- a/models/bamf_nnunet_mr_prostate/config/slicer.yml +++ b/models/bamf_nnunet_mr_prostate/config/slicer.yml @@ -7,13 +7,14 @@ execute: - NrrdImporter - NiftiConverter - NNUnetRunner +- BamfProcessorRunner - JsonSegExporter - DataOrganizer modules: NrrdImporter: - input_dir: 'input_data' - input_file_name: 'image.nrrd' + input_dir: input_data + input_file_name: image.nrrd JsonSegExporter: segment_id_meta_key: roi @@ -21,8 +22,10 @@ modules: - nifti:mod=seg-->[basename] NNUnetRunner: - nnunet_task: 'Task778_MR_Prostate' - nnunet_model: '3d_fullres' + nnunet_task: Task778_MR_Prostate + nnunet_model: 3d_fullres + + BamfProcessorRunner: DataOrganizer: targets: diff --git a/models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile b/models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile index 7db3d34f..3677418e 100644 --- a/models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile +++ b/models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile @@ -12,10 +12,8 @@ RUN pip3 install --no-cache-dir \ nnunet # Clone the main branch of MHubAI/models -RUN git stash \ - && git sparse-checkout set "models/bamf_nnunet_mr_prostate" \ - && git fetch https://github.com/MHubAI/models.git main \ - && git merge FETCH_HEAD +ARG MHUB_MODELS_REPO +RUN buildutils/import_mhub_model.sh bamf_nnunet_mr_prostate ${MHUB_MODELS_REPO} # Pull weights into the container ENV WEIGHTS_DIR=/root/.nnunet/nnUNet_models/nnUNet/ @@ -30,5 +28,5 @@ RUN rm ${WEIGHTS_DIR}${WEIGHTS_FN} ENV WEIGHTS_FOLDER=$WEIGHTS_DIR # Default run script -ENTRYPOINT ["python3", "-m", "mhubio.run"] -CMD ["--config", "/app/models/bamf_nnunet_mr_prostate/config/default.yml"] \ No newline at end of file +ENTRYPOINT ["mhub.run"] +CMD ["--config", "/app/models/bamf_nnunet_mr_liver/config/default.yml"] \ No newline at end of file diff --git a/models/bamf_nnunet_mr_prostate/meta.json b/models/bamf_nnunet_mr_prostate/meta.json index 21c5cdd2..cf606a6e 100644 --- a/models/bamf_nnunet_mr_prostate/meta.json +++ b/models/bamf_nnunet_mr_prostate/meta.json @@ -17,7 +17,9 @@ ], "outputs": [ { + "label": "Segmentation", "type": "Segmentation", + "description": "Segmentation Prostate", "classes": [ "Prostate" ] @@ -26,73 +28,107 @@ "model": { "architecture": "U-net", "training": "Supervised", - "cmpapproach": "2D, 3D, ensemble" - } + "cmpapproach": "3D, ensemble" + }, + "data": { + "training": { + "vol_samples": 439 + }, + "evaluation": { + "vol_samples": 81 + }, + "public": true, + "external": true + } }, "details": { - "name": "bamf_nnunet_mr_prostate", + "name": "AIMI MR Prostate", "version": "1.0.0", - "devteam": "BAMF Health Pvt Ltd", + "devteam": "BAMF Health", "type": "nnU-Net (U-Net structure, optimized by data-driven heuristics)", "date": { - "weights": "09\/07\/23", - "code": "n\/a", - "pub": 2023 + "code": "17.10.2023", + "weights": "28.08.2023", + "pub": "23.10.2023" }, + "cite": "Murugesan, Gowtham Krishnan, Diana McCrumb, Mariam Aboian, Tej Verma, Rahul Soni, Fatima Memon, and Jeff Van Oss. The AIMI Initiative: AI-Generated Annotations for Imaging Data Commons Collections. arXiv preprint arXiv:2310.14897 (2023).", "license": { - "code": "Apache 2.0", + "code": "MIT", "weights": "CC BY-NC 4.0" }, "publications": [ { - "title": "", - "uri": "" + "title": "The AIMI Initiative: AI-Generated Annotations in IDC Collections", + "uri": "https://arxiv.org/abs/2310.14897" } ], - "github": "https:\/\/github.com\/bamf-health\/aimi-prostate-mr", - "zenodo": "https:\/\/zenodo.org\/record\/8290093", - "slicer": true + "github": "https://github.com/bamf-health/aimi-prostate-mr" }, "info": { "use": { "title": "Intended Use", - "text": "This model is intended to perform segmentations of prostate region that are relevant for use cases such as organ volumetry, disease characterization, and surgical or radiation therapy planning." + "text": "This model is intended to perform segmentations of prostate region in MR scans. The model has been trained and tested on scans aquired during clinical care of patients, so it might not be suited for a healthy population. The generalization capabilities of the model on a range of ages, genders, and ethnicities are unknown." }, "analyses": { "title": "Quantitative Analyses", - "text": "The model's performance was assessed using the Dice Coefficient on a held-out portion of the in-house dataset (public). For more information, please refer to the model's publication [1].", + "text": "The model's performance was assessed using the Dice Coefficient and Normalized Surface Distance (NSD) with tolerance 7mm, as specified in the MR prostate segmentation task in the Medical Segmentation Decathlon challenge. The model was used to segment cases from the IDC collection ProstateX [1]. Seven of those cases were reviewed and corrected by a board-certified radiologist and a non-expert. The analysis is published here [2]", + "tables": [ + { + "label": "Label-wise metrics (mean (standard deviation)) between AI derived and manually corrected MR prostate annotations", + "entries": { + "Dice: Radiologist": "1.00 (0.00)", + "NSD: Radiologist": "0.00 (0.00)" + } + } + ], "references": [ { - "label": "nnUNet: Robust Segmentation of Prostate in MR Images", - "uri": "https:\/\/pubs.rsna.org\/doi\/10.1148\/ryai.230024" + "label": "PROSTATEx", + "uri": "https://wiki.cancerimagingarchive.net/pages/viewpage.action?pageId=23691656" + }, + { + "label": "The AIMI Initiative: AI-Generated Annotations for Imaging Data Commons Collections", + "uri": "https://arxiv.org/abs/2310.14897" } ] }, "evaluation": { "title": "Evaluation Data", - "text": "The evaluation dataset consists of 65 MR scans of patients with different ages, presenting various pathologies, and acquired with various imaging protocols. The Nora Imaging Platform was used for manual segmentation or further refinement of generated segmentations, starting from data uniformely resampled to 1.5mm isotropic. Segmentation was supervised by two physicians with three and six years of experience in body imaging. If an existing model for a given structure was publicly available, that model was used to create a first segmentation, which was then validated and refined manually. Furthermore, the authors used an iterative learning approach to speed up the segmentation process [1][2]. The evaluation process was conducted on images resampled to 1.5mm isotropic.", + "text": "The model was used to segment cases from the PROSTATEx dataset. A test/holdout validation split of 81/34 was created from the 115 prostates. These were reviewed and corrected by a board-certified radiologist. The model predictions, and radiologist corrections are published on zenodo [2]", "references": [ { - "label": "TotalSegmentator: Robust Segmentation of 104 Anatomic Structures in CT Images", - "uri": "https:\/\/pubs.rsna.org\/doi\/10.1148\/ryai.230024" + "label": "Imaging Data Collections (IDC)", + "uri": "https://datacommons.cancer.gov/repository/imaging-data-commons" }, { - "label": "TotalSegmentator dataset", - "uri": "https:\/\/doi.org\/10.5281\/zenodo.6802613" + "label": "PROSTATEx", + "uri": "https://wiki.cancerimagingarchive.net/pages/viewpage.action?pageId=23691656" + }, + { + "label": "Image segmentations produced by the AIMI Annotations initiative", + "uri": "https://zenodo.org/records/10009368" } ] }, "training": { "title": "Training Data", - "text": "The training dataset consists of 1082 CT scans of patients with different ages, presenting various pathologies, and acquired with various imaging protocols [1][2]. All of the CT images were resampled to a common resolution of 1.5mm isotropic before training. For details regarding the labels the model was trained with, see the section above.", + "text": "The training dataset consists of 439 T2W MRI prostate annotations taken from IDC [1] (N=98), ProstateX dataset found publicly [2] (N=134), T2W MRI scans from Prostate158 [3] (N=138), and ISBI-MR-Prostate-2013 dataset [4] (N=69)", "references": [ { - "label": "TotalSegmentator: Robust Segmentation of 104 Anatomic Structures in CT Images", - "uri": "https:\/\/pubs.rsna.org\/doi\/10.1148\/ryai.230024" + "label": "Imaging Data Collections (IDC)", + "uri": "https://datacommons.cancer.gov/repository/imaging-data-commons" + }, + { + "label": "ProstateX dataset", + "uri": "https://wiki.cancerimagingarchive.net/pages/viewpage.action?pageId=23691656" + }, + { + "label": "Prostate158 dataset", + "uri": "https://zenodo.org/records/6481141" }, { - "label": "TotalSegmentator dataset", - "uri": "https:\/\/doi.org\/10.5281\/zenodo.6802613" + "label": "ISBI-MR-Prostate-2013 dataset", + "uri": "https://wiki.cancerimagingarchive.net/display/public/nci-isbi+2013+challenge+-+automated+segmentation+of+prostate+structures" } ] } diff --git a/models/bamf_nnunet_mr_prostate/utils/BamfProcessorRunner.py b/models/bamf_nnunet_mr_prostate/utils/BamfProcessorRunner.py new file mode 100644 index 00000000..d8a3bce2 --- /dev/null +++ b/models/bamf_nnunet_mr_prostate/utils/BamfProcessorRunner.py @@ -0,0 +1,79 @@ +""" +------------------------------------------------- +MedicalHub - Run Module for ensembling nnUNet inference. +------------------------------------------------- +------------------------------------------------- +Author: Rahul Soni +Email: rahul.soni@bamfhealth.com +------------------------------------------------- +""" + +from mhubio.core import Instance, InstanceData, DataType, FileType, CT, SEG +from mhubio.core import Module, IO +import os, numpy as np +import SimpleITK as sitk +from skimage import measure, filters +import numpy as np +import shutil + + + +class BamfProcessorRunner(Module): + + @IO.Instance + @IO.Input('in_data', 'nifti:mod=ct|mr', the='input data to run nnunet on') + def task(self, instance: Instance, in_data: InstanceData, out_data: InstanceData) -> None: + + # Log bamf runner info + self.v("Running BamfProcessor on....") + self.v(f" > input data: {in_data.abspath}") + self.v(f" > output data: {out_data.abspath}") + + # read image + self.v(f"Reading image from {in_data.abspath}") + img_itk = sitk.ReadImage(in_data.abspath) + img_np = sitk.GetArrayFromImage(img_itk) + + # apply post-processing + img_bamf_processed = self.n_connected(img_np) + + # store image temporarily + out_file = os.path.join(instance.abspath, f'bamf_processed.nrrd') + self.v(f"Writing tmp image to {out_file}") + img_bamf_processed_itk = sitk.GetImageFromArray(img_bamf_processed) + img_bamf_processed_itk.CopyInformation(img_itk) + sitk.WriteImage(img_bamf_processed_itk, out_file) + + # meta + meta = { + "model": "BamfProcessor" + } + + # create output data + seg_data_type = DataType(FileType.NRRD, SEG + meta) + seg_data = InstanceData(out_file, type=seg_data_type) + instance.addData(seg_data) + seg_data.confirm() + + + def n_connected(self, img_data): + img_data_mask = np.zeros(img_data.shape) + img_data_mask[img_data > 0] = 1 + img_filtered = np.zeros(img_data_mask.shape) + blobs_labels = measure.label(img_data_mask, background=0) + lbl, counts = np.unique(blobs_labels, return_counts=True) + lbl_dict = {} + for i, j in zip(lbl, counts): + lbl_dict[i] = j + sorted_dict = dict(sorted(lbl_dict.items(), key=lambda x: x[1], reverse=True)) + count = 0 + + for key, value in sorted_dict.items(): + if count >= 1: + print(key, value) + img_filtered[blobs_labels == key] = 1 + count += 1 + + img_data[img_filtered != 1] = 0 + return img_data + \ No newline at end of file diff --git a/models/bamf_nnunet_mr_prostate/utils/__init__.py b/models/bamf_nnunet_mr_prostate/utils/__init__.py new file mode 100644 index 00000000..d6522730 --- /dev/null +++ b/models/bamf_nnunet_mr_prostate/utils/__init__.py @@ -0,0 +1 @@ +from .BamfProcessorRunner import * \ No newline at end of file From 7537e6391abd386b2a4550535db2be147f47f145 Mon Sep 17 00:00:00 2001 From: Rahul Soni Date: Tue, 14 Nov 2023 16:35:58 -0800 Subject: [PATCH 08/17] add roi field --- models/bamf_nnunet_mr_prostate/config/default.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/models/bamf_nnunet_mr_prostate/config/default.yml b/models/bamf_nnunet_mr_prostate/config/default.yml index bfe793b3..f6bbb026 100644 --- a/models/bamf_nnunet_mr_prostate/config/default.yml +++ b/models/bamf_nnunet_mr_prostate/config/default.yml @@ -27,6 +27,7 @@ modules: in_data: nifti:mod=mr nnunet_task: Task788_ProstateX nnunet_model: 3d_fullres + roi: PROSTATE BamfProcessorRunner: From 2c4417bd495ef60dce9f41256988b6511ee9b6fe Mon Sep 17 00:00:00 2001 From: Rahul Soni Date: Tue, 14 Nov 2023 17:06:26 -0800 Subject: [PATCH 09/17] update meta --- models/bamf_nnunet_mr_prostate/meta.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/bamf_nnunet_mr_prostate/meta.json b/models/bamf_nnunet_mr_prostate/meta.json index cf606a6e..49ff50d8 100644 --- a/models/bamf_nnunet_mr_prostate/meta.json +++ b/models/bamf_nnunet_mr_prostate/meta.json @@ -71,7 +71,7 @@ }, "analyses": { "title": "Quantitative Analyses", - "text": "The model's performance was assessed using the Dice Coefficient and Normalized Surface Distance (NSD) with tolerance 7mm, as specified in the MR prostate segmentation task in the Medical Segmentation Decathlon challenge. The model was used to segment cases from the IDC collection ProstateX [1]. Seven of those cases were reviewed and corrected by a board-certified radiologist and a non-expert. The analysis is published here [2]", + "text": "The model's performance was assessed using the Dice Coefficient and Normalized Surface Distance (NSD) with tolerance 7mm, as specified in the MR prostate segmentation task in the Medical Segmentation Decathlon challenge. The model was used to segment cases from the IDC collection ProstateX [1]. Thirty four of those cases were reviewed and corrected by a board-certified radiologist and a non-expert. The analysis is published here [2]", "tables": [ { "label": "Label-wise metrics (mean (standard deviation)) between AI derived and manually corrected MR prostate annotations", From ffa10d89a286f407b2716572ec9aa59aa33f5a9d Mon Sep 17 00:00:00 2001 From: Rahul Soni Date: Tue, 14 Nov 2023 17:12:33 -0800 Subject: [PATCH 10/17] rename license to licence --- models/bamf_nnunet_mr_prostate/meta.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/bamf_nnunet_mr_prostate/meta.json b/models/bamf_nnunet_mr_prostate/meta.json index 49ff50d8..aed01d54 100644 --- a/models/bamf_nnunet_mr_prostate/meta.json +++ b/models/bamf_nnunet_mr_prostate/meta.json @@ -52,7 +52,7 @@ "pub": "23.10.2023" }, "cite": "Murugesan, Gowtham Krishnan, Diana McCrumb, Mariam Aboian, Tej Verma, Rahul Soni, Fatima Memon, and Jeff Van Oss. The AIMI Initiative: AI-Generated Annotations for Imaging Data Commons Collections. arXiv preprint arXiv:2310.14897 (2023).", - "license": { + "licence": { "code": "MIT", "weights": "CC BY-NC 4.0" }, From 26d495a3a2ff346c61500b6d58ebc36e3c568392 Mon Sep 17 00:00:00 2001 From: Rahul Soni Date: Tue, 14 Nov 2023 17:32:30 -0800 Subject: [PATCH 11/17] add slicethickness --- models/bamf_nnunet_mr_prostate/meta.json | 1 + 1 file changed, 1 insertion(+) diff --git a/models/bamf_nnunet_mr_prostate/meta.json b/models/bamf_nnunet_mr_prostate/meta.json index aed01d54..6b6f7e15 100644 --- a/models/bamf_nnunet_mr_prostate/meta.json +++ b/models/bamf_nnunet_mr_prostate/meta.json @@ -11,6 +11,7 @@ "format": "DICOM", "modality": "MR", "bodypartexamined": "PROSTATE", + "slicethickness": "2.5mm", "non-contrast": true, "contrast": true } From 005103d23ded5adc133668ea244cf1fe8ab5497b Mon Sep 17 00:00:00 2001 From: Rahul Soni Date: Tue, 14 Nov 2023 18:09:13 -0800 Subject: [PATCH 12/17] - In /summary/model/training Supervised to supervised - In /summary/model/cmpapproach "#D, Emsemble" to "3D" - /details/licence to /details/license --- models/bamf_nnunet_mr_prostate/meta.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/models/bamf_nnunet_mr_prostate/meta.json b/models/bamf_nnunet_mr_prostate/meta.json index 6b6f7e15..8c0fecab 100644 --- a/models/bamf_nnunet_mr_prostate/meta.json +++ b/models/bamf_nnunet_mr_prostate/meta.json @@ -28,8 +28,8 @@ ], "model": { "architecture": "U-net", - "training": "Supervised", - "cmpapproach": "3D, ensemble" + "training": "supervised", + "cmpapproach": "3D" }, "data": { "training": { @@ -53,7 +53,7 @@ "pub": "23.10.2023" }, "cite": "Murugesan, Gowtham Krishnan, Diana McCrumb, Mariam Aboian, Tej Verma, Rahul Soni, Fatima Memon, and Jeff Van Oss. The AIMI Initiative: AI-Generated Annotations for Imaging Data Commons Collections. arXiv preprint arXiv:2310.14897 (2023).", - "licence": { + "license": { "code": "MIT", "weights": "CC BY-NC 4.0" }, From f852d5a329fea286c440f542742b29c08cd9f032 Mon Sep 17 00:00:00 2001 From: Rahul Soni Date: Tue, 14 Nov 2023 18:17:23 -0800 Subject: [PATCH 13/17] update entrypoint --- models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile b/models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile index 3677418e..3cc41aa8 100644 --- a/models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile +++ b/models/bamf_nnunet_mr_prostate/dockerfiles/Dockerfile @@ -29,4 +29,4 @@ ENV WEIGHTS_FOLDER=$WEIGHTS_DIR # Default run script ENTRYPOINT ["mhub.run"] -CMD ["--config", "/app/models/bamf_nnunet_mr_liver/config/default.yml"] \ No newline at end of file +CMD ["--config", "/app/models/bamf_nnunet_mr_prostate/config/default.yml"] From de57b5476e228ce5e7ac85045fb96fec5e34f247 Mon Sep 17 00:00:00 2001 From: Rahul Soni Date: Thu, 16 Nov 2023 04:47:16 -0800 Subject: [PATCH 14/17] update BamfProcessor --- .../utils/BamfProcessorRunner.py | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/models/bamf_nnunet_mr_prostate/utils/BamfProcessorRunner.py b/models/bamf_nnunet_mr_prostate/utils/BamfProcessorRunner.py index d8a3bce2..90d34404 100644 --- a/models/bamf_nnunet_mr_prostate/utils/BamfProcessorRunner.py +++ b/models/bamf_nnunet_mr_prostate/utils/BamfProcessorRunner.py @@ -1,6 +1,6 @@ """ ------------------------------------------------- -MedicalHub - Run Module for ensembling nnUNet inference. +MHub - Run Module for ensembling nnUNet inference. ------------------------------------------------- ------------------------------------------------- Author: Rahul Soni @@ -22,8 +22,8 @@ class BamfProcessorRunner(Module): @IO.Instance @IO.Input('in_data', 'nifti:mod=ct|mr', the='input data to run nnunet on') + @IO.Output('out_data', 'nrrd:mod=seg:processor=bamf', data='in_data', the="keep the two largest connected components of the segmentation and remove all other ones") def task(self, instance: Instance, in_data: InstanceData, out_data: InstanceData) -> None: - # Log bamf runner info self.v("Running BamfProcessor on....") self.v(f" > input data: {in_data.abspath}") @@ -38,22 +38,10 @@ def task(self, instance: Instance, in_data: InstanceData, out_data: InstanceData img_bamf_processed = self.n_connected(img_np) # store image temporarily - out_file = os.path.join(instance.abspath, f'bamf_processed.nrrd') - self.v(f"Writing tmp image to {out_file}") + self.v(f"Writing tmp image to {out_data.abspath}") img_bamf_processed_itk = sitk.GetImageFromArray(img_bamf_processed) img_bamf_processed_itk.CopyInformation(img_itk) - sitk.WriteImage(img_bamf_processed_itk, out_file) - - # meta - meta = { - "model": "BamfProcessor" - } - - # create output data - seg_data_type = DataType(FileType.NRRD, SEG + meta) - seg_data = InstanceData(out_file, type=seg_data_type) - instance.addData(seg_data) - seg_data.confirm() + sitk.WriteImage(img_bamf_processed_itk, out_data.abspath) def n_connected(self, img_data): @@ -76,4 +64,3 @@ def n_connected(self, img_data): img_data[img_filtered != 1] = 0 return img_data - \ No newline at end of file From e986578e51ad9418ac59ed77664e44c7f018d911 Mon Sep 17 00:00:00 2001 From: Rahul Soni Date: Mon, 18 Dec 2023 19:54:40 +0700 Subject: [PATCH 15/17] Update Bamf processor --- models/bamf_nnunet_mr_prostate/utils/BamfProcessorRunner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/bamf_nnunet_mr_prostate/utils/BamfProcessorRunner.py b/models/bamf_nnunet_mr_prostate/utils/BamfProcessorRunner.py index 90d34404..f5e6ea77 100644 --- a/models/bamf_nnunet_mr_prostate/utils/BamfProcessorRunner.py +++ b/models/bamf_nnunet_mr_prostate/utils/BamfProcessorRunner.py @@ -22,7 +22,7 @@ class BamfProcessorRunner(Module): @IO.Instance @IO.Input('in_data', 'nifti:mod=ct|mr', the='input data to run nnunet on') - @IO.Output('out_data', 'nrrd:mod=seg:processor=bamf', data='in_data', the="keep the two largest connected components of the segmentation and remove all other ones") + @IO.Output('out_data', 'bamf_processed.nrrd', 'nrrd:mod=seg:processor=bamf', data='in_data', the="keep the two largest connected components of the segmentation and remove all other ones") def task(self, instance: Instance, in_data: InstanceData, out_data: InstanceData) -> None: # Log bamf runner info self.v("Running BamfProcessor on....") From 924e1b18a3283cc2ae62830ede6da3e1450fb614 Mon Sep 17 00:00:00 2001 From: Rahul Soni Date: Thu, 1 Feb 2024 13:05:48 +0530 Subject: [PATCH 16/17] refactor module --- .../config/default.yml | 4 ++-- .../utils/BamfProcessorRunner.py | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/models/bamf_nnunet_mr_prostate/config/default.yml b/models/bamf_nnunet_mr_prostate/config/default.yml index f6bbb026..d54f9023 100644 --- a/models/bamf_nnunet_mr_prostate/config/default.yml +++ b/models/bamf_nnunet_mr_prostate/config/default.yml @@ -17,7 +17,7 @@ modules: import_dir: sorted_data sort_data: true meta: - mod: 'mr' + mod: '%Modality' NiftiConverter: in_datas: dicom:mod=mr @@ -34,7 +34,7 @@ modules: DsegConverter: source_segs: nifti:mod=seg target_dicom: dicom:mod=mr - model_name: 'Bamf NNUnet MR Prostate' + model_name: Bamf NNUnet MR Prostate skip_empty_slices: True DataOrganizer: diff --git a/models/bamf_nnunet_mr_prostate/utils/BamfProcessorRunner.py b/models/bamf_nnunet_mr_prostate/utils/BamfProcessorRunner.py index f5e6ea77..53fc382a 100644 --- a/models/bamf_nnunet_mr_prostate/utils/BamfProcessorRunner.py +++ b/models/bamf_nnunet_mr_prostate/utils/BamfProcessorRunner.py @@ -8,13 +8,12 @@ ------------------------------------------------- """ -from mhubio.core import Instance, InstanceData, DataType, FileType, CT, SEG +from mhubio.core import Instance, InstanceData from mhubio.core import Module, IO -import os, numpy as np +import numpy as np import SimpleITK as sitk -from skimage import measure, filters +from skimage import measure import numpy as np -import shutil @@ -24,13 +23,14 @@ class BamfProcessorRunner(Module): @IO.Input('in_data', 'nifti:mod=ct|mr', the='input data to run nnunet on') @IO.Output('out_data', 'bamf_processed.nrrd', 'nrrd:mod=seg:processor=bamf', data='in_data', the="keep the two largest connected components of the segmentation and remove all other ones") def task(self, instance: Instance, in_data: InstanceData, out_data: InstanceData) -> None: - # Log bamf runner info - self.v("Running BamfProcessor on....") - self.v(f" > input data: {in_data.abspath}") - self.v(f" > output data: {out_data.abspath}") + + # Log bamf runner info + self.log("Running BamfProcessor on....") + self.log(f" > input data: {in_data.abspath}") + self.log(f" > output data: {out_data.abspath}") # read image - self.v(f"Reading image from {in_data.abspath}") + self.log(f"Reading image from {in_data.abspath}") img_itk = sitk.ReadImage(in_data.abspath) img_np = sitk.GetArrayFromImage(img_itk) @@ -38,7 +38,7 @@ def task(self, instance: Instance, in_data: InstanceData, out_data: InstanceData img_bamf_processed = self.n_connected(img_np) # store image temporarily - self.v(f"Writing tmp image to {out_data.abspath}") + self.log(f"Writing tmp image to {out_data.abspath}") img_bamf_processed_itk = sitk.GetImageFromArray(img_bamf_processed) img_bamf_processed_itk.CopyInformation(img_itk) sitk.WriteImage(img_bamf_processed_itk, out_data.abspath) From 22b5043276c8718085497a09e4cd93c9a5d4fee3 Mon Sep 17 00:00:00 2001 From: Rahul Soni Date: Thu, 1 Feb 2024 15:30:52 +0530 Subject: [PATCH 17/17] change engine to plastimatch --- models/bamf_nnunet_mr_prostate/config/default.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/bamf_nnunet_mr_prostate/config/default.yml b/models/bamf_nnunet_mr_prostate/config/default.yml index d54f9023..7c8f393e 100644 --- a/models/bamf_nnunet_mr_prostate/config/default.yml +++ b/models/bamf_nnunet_mr_prostate/config/default.yml @@ -21,7 +21,7 @@ modules: NiftiConverter: in_datas: dicom:mod=mr - engine: dcm2niix + engine: plastimatch NNUnetRunner: in_data: nifti:mod=mr