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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions models/fmcib_radiomics/config/default.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
general:
data_base_dir: /app/data
version: 1.0
description: FMCIB pipeline starting from DICOM files and centroids in json files or slicer exports named by their SeriesInstanceUID
description: Run fmcib radiomics pipeline on dicom data

execute:
- DicomImporter
- FileImporter
- DsegExtractor
- NiftiConverter
- CentroidExtractor
- FMCIBRunner
- DataOrganizer

modules:
DicomImporter:
source_dir: input_data
import_dir: sorted_data
sort_data: true
merge: true
meta:
mod: '%Modality'
desc: '%SeriesDescription'

FileImporter:
instance_id: sid
meta: type=fmcibcoordinates
type: json
# roi can be specified manually but will otherwise extracted form the dicomseg via the meta.json
# DsegExtractor:
# roi:
# - LIVER
# - LIVER+NEOPLASM_MALIGNANT_PRIMARY

DataOrganizer:
targets:
- json:type=fmcibfeatures-->[i:sid]/features.json
- csv-->[i:sid]/features.csv
# - nifit:mod=seg:origin=dicomseg-->[i:sid]/masks/[basename]
# - nifti-->[i:sid]/nifti/[d:mod]/[basename]
2 changes: 1 addition & 1 deletion models/fmcib_radiomics/config/from_centroids.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ modules:

DataOrganizer:
targets:
- json:type=fmcibfeatures-->[i:patientID]/features.json
- csv-->[i:patientID]/features.csv
22 changes: 22 additions & 0 deletions models/fmcib_radiomics/config/from_json.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
general:
data_base_dir: /app/data
version: 1.0
description: FMCIB pipeline starting from DICOM files and centroids in json files or slicer exports named by their SeriesInstanceUID

execute:
- DicomImporter
- FileImporter
- NiftiConverter
- FMCIBRunner
- DataOrganizer

modules:

FileImporter:
instance_id: sid
meta: type=fmcibcoordinates
type: json

DataOrganizer:
targets:
- csv-->[i:sid]/features.csv
21 changes: 21 additions & 0 deletions models/fmcib_radiomics/config/from_nifti_mask.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
general:
data_base_dir: /app/data
version: 1.0
description: "FMCIB pipeline starting from a nii.gz file image and a binary mask of the GTV."

execute:
- FileStructureImporter
- CentroidExtractor
- FMCIBRunner
- DataOrganizer

modules:
FileStructureImporter:
structures:
- $patientID/CT.nii.gz@instance@nifti:mod=ct
- $patientID/masks/GTV.nii.gz@nifti:mod=seg
import_id: patientID

DataOrganizer:
targets:
- csv-->[i:patientID]/features.csv
21 changes: 0 additions & 21 deletions models/fmcib_radiomics/config/from_nrrd_mask.yml

This file was deleted.

33 changes: 33 additions & 0 deletions models/fmcib_radiomics/config/from_rtstruct.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
general:
data_base_dir: /app/data
version: 1.0
description: run pyradiomics pipeline on dicom data

execute:
- DicomImporter
- RTStructExtractor
- NiftiConverter
- CentroidExtractor
- FMCIBRunner
- DataOrganizer

modules:
DicomImporter:
source_dir: input_data
import_dir: sorted_data
sort_data: true
merge: true
meta:
mod: '%Modality'
desc: '%SeriesDescription'

# roi can be specified manually but will otherwise extracted form the rtstruct segemnetation names
# RTStructExtractor:
# roi:
# - LIVER
# - LIVER+NEOPLASM_MALIGNANT_PRIMARY

DataOrganizer:
targets:
- csv-->[i:sid]/features.csv
# - nifti-->[i:sid]/nifti/[d:mod]/[basename]
2 changes: 1 addition & 1 deletion models/fmcib_radiomics/config/from_slicer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ modules:

DataOrganizer:
targets:
- json:type=fmcibfeatures-->[i:patientID]/features.json
- csv-->[i:patientID]/features.csv
41 changes: 19 additions & 22 deletions models/fmcib_radiomics/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,25 @@
"title": "Foundation Model for Cancer Imaging Biomarkers",
"summary": {
"description": "A foundation model for cancer imaging biomarker discovery trained through self-supervised learning using a dataset of 11,467 radiographic lesions. The model features can be used as a data-driven substitute for classical radiomic features",
"inputs": [
{
"label": "Input CT Image",
"description": "CT imaging data containing lesions of interest, such as nodules or tumors",
"format": "DICOM",
"modality": "CT",
"slicethickness": "5mm",
"bodypartexamined": "WHOLEBODY",
"non-contrast": true,
"contrast": true
},
{
"label": "Center of mass",
"description": "Center of mass of the lesion in the CT image",
"format": "JSON",
"modality": "JSON",
"slicethickness": "5mm",
"bodypartexamined": "WHOLEBODY",
"non-contrast": true,
"contrast": true
}
],
"inputs": [ {
"label": "Input Image",
"description": "The input image.",
"format": "DICOM",
"modality": "CT",
"bodypartexamined": "WHOLEBODY",
"slicethickness": "2.5mm",
"non-contrast": true,
"contrast": true
}, {
"label": "Input Segmentation",
"description": "The input segmentation to be analysed.",
"format": "DICOMSEG",
"modality": "SEG",
"bodypartexamined": "WHOLEBODY",
"slicethickness": "2.5mm",
"non-contrast": true,
"contrast": true
} ],
"outputs": [
{
"type": "Prediction",
Expand Down
2 changes: 1 addition & 1 deletion models/fmcib_radiomics/mhub.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[model.deployment]
test = "https://zenodo.org/record/13785615/files/fmcib_radiomics.test.zip"
test = "https://zenodo.org/records/14205464/files/test.zip"
79 changes: 47 additions & 32 deletions models/fmcib_radiomics/utils/CentroidExtractor.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,58 @@
"""
---------------------------------------------------------
Author: Leonard Nürnberg
Email: [email protected]
Author: Leonard Nürnberg, Suraj Pai
Email: [email protected], [email protected]
Date: 06.03.2024
---------------------------------------------------------
"""

import json, jsonschema
from mhubio.core import Instance, InstanceData, IO, Module
from mhubio.core import Instance, InstanceData, InstanceDataCollection, IO, Module
import SimpleITK as sitk
import numpy as np

class CentroidExtractor(Module):

@IO.Instance()
@IO.Input('in_mask', 'nrrd:mod=seg', the='Tumor segmentation mask for the input NRRD file.')
@IO.Output('centroids_json', 'centroids.json', "json:type=fmcibcoordinates", the='JSON file containing 3D coordinates of the centroid of the input mask.')
def task(self, instance: Instance, in_mask: InstanceData, centroids_json: InstanceData) -> None:

# read the input mask
mask = sitk.ReadImage(in_mask.abspath)

# get the center of massk from the mask via ITK
label_shape_filter = sitk.LabelShapeStatisticsImageFilter()
label_shape_filter.Execute(mask)
try:
centroid = label_shape_filter.GetCentroid(255)
except:
centroid = label_shape_filter.GetCentroid(1)

# extract x, y, and z coordinates from the centroid
x, y, z = centroid

# set up the coordinate dictionary
coordinate_dict = {
"coordX": x,
"coordY": y,
"coordZ": z,
}

# write the coordinate dictionary to a json file
with open(centroids_json.abspath, "w") as f:
json.dump(coordinate_dict, f)
@IO.Inputs('in_masks', 'nrrd|nifti:mod=seg', the='Tumor segmentation masks for the input NRRD files')
@IO.Outputs('centroid_jsons', '[filename].json', "json:type=fmcibcoordinates", data='in_masks', the='JSON file containing 3D coordinates of the centroid of the input mask.')
def task(self, instance: Instance, in_masks: InstanceDataCollection, centroid_jsons: InstanceDataCollection) -> None:
for i, in_mask in enumerate(in_masks):
seg_rois = in_mask.type.meta['roi'].split(',')
mask = sitk.ReadImage(in_mask.abspath)
mask_array = sitk.GetArrayFromImage(mask)
unique_values = np.unique(mask_array)
print(f"Unique values: {unique_values}")
Comment on lines +23 to +25
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do this only to check the metadata, right?

label_shape_filter = sitk.LabelShapeStatisticsImageFilter()
seg_roi_coordinates = []

for channel_id, seg_roi in enumerate(seg_rois):
# Check if the label exists in the mask
label = channel_id + 1
if label not in unique_values:
print(f"Warning: Label {label} (ROI: {seg_roi}) not found in the mask. Skipping.")
continue

# Calculate centroid if the label exists
label_shape_filter.Execute(mask)
try:
centroid = label_shape_filter.GetCentroid(label)
# Extract x, y, and z coordinates from the centroid
x, y, z = centroid

# Set up the coordinate dictionary
coordinate_dict = {
"Mhub ROI": seg_roi,
"coordX": x,
"coordY": y,
"coordZ": z,
}

seg_roi_coordinates.append(coordinate_dict)
except Exception as e:
print(f"Error processing label {label} (ROI: {seg_roi}): {e}")

centroid_json = centroid_jsons.get(i)
# Write the coordinate dictionary to a json file
with open(centroid_json.abspath, "w") as f:
json.dump(seg_roi_coordinates, f)
Loading