Skip to content

Commit

Permalink
Add update for dseg/rtstruct [WIP] (#99)
Browse files Browse the repository at this point in the history
@LennyN95 PR'ing early for feedback

---------

Signed-off-by: Suraj <[email protected]>
  • Loading branch information
surajpaib authored Feb 17, 2025
1 parent 0beda94 commit 3294f5d
Show file tree
Hide file tree
Showing 12 changed files with 246 additions and 125 deletions.
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}")
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

0 comments on commit 3294f5d

Please sign in to comment.