From de39253a173d322fefbdcc0f82bd8d620a52cd70 Mon Sep 17 00:00:00 2001 From: David Bouget Date: Fri, 7 Jun 2024 11:43:10 +0200 Subject: [PATCH] GBM postop 1p-5p support, added BrainGrid features (#77) * Enabling the use of all 5 GBM postop segmentation models, based on the provided inputs. * Added support for BrainGrid features [skip ci] --- .../TumorCharacteristicsWidget.py | 129 +++++++++ .../CustomQDialog/SoftwareSettingsDialog.py | 72 +++++ utils/backend_logic.py | 2 + utils/data_structures/AtlasStructure.py | 6 +- .../UserPreferencesStructure.py | 28 +- utils/logic/PipelineCreationHandler.py | 262 ++++-------------- utils/logic/PipelineResultsCollector.py | 45 ++- 7 files changed, 323 insertions(+), 221 deletions(-) diff --git a/gui/SinglePatientComponent/PatientResultsSidePanel/TumorCharacteristicsWidget.py b/gui/SinglePatientComponent/PatientResultsSidePanel/TumorCharacteristicsWidget.py index 953c3bc..c94a8ed 100644 --- a/gui/SinglePatientComponent/PatientResultsSidePanel/TumorCharacteristicsWidget.py +++ b/gui/SinglePatientComponent/PatientResultsSidePanel/TumorCharacteristicsWidget.py @@ -7,6 +7,7 @@ from gui.UtilsWidgets.CustomQGroupBox.QCollapsibleWidget import QCollapsibleWidget from utils.software_config import SoftwareConfigResources +from utils.data_structures.UserPreferencesStructure import UserPreferencesStructure from gui.UtilsWidgets.CustomQDialog.SavePatientChangesDialog import SavePatientChangesDialog @@ -35,6 +36,7 @@ def __set_interface(self): self.__set_resectability_part() self.__set_cortical_structures_part() self.__set_subcortical_structures_part() + self.__set_braingrid_structures_part() self.layout.addStretch(1) def __set_volumes_part(self): @@ -179,6 +181,16 @@ def __set_subcortical_structures_part(self): self.subcorticalstructures_collapsiblegroupbox.content_layout.setSpacing(0) self.layout.addWidget(self.subcorticalstructures_collapsiblegroupbox) + def __set_braingrid_structures_part(self): + self.braingridstructures_collapsiblegroupbox = QCollapsibleWidget("BrainGrid structures") + self.braingridstructures_collapsiblegroupbox.set_icon_filenames(expand_fn=os.path.join(os.path.dirname(os.path.realpath(__file__)), + '../../Images/collapsed_icon.png'), + collapse_fn=os.path.join(os.path.dirname(os.path.realpath(__file__)), + '../../Images/uncollapsed_icon.png')) + self.braingridstructures_collapsiblegroupbox.content_layout.setContentsMargins(20, 0, 20, 0) + self.braingridstructures_collapsiblegroupbox.content_layout.setSpacing(0) + self.layout.addWidget(self.braingridstructures_collapsiblegroupbox) + def __set_layout_dimensions(self): self.original_space_volume_header_label.setFixedHeight(20) self.original_space_volume_label.setFixedHeight(20) @@ -232,6 +244,11 @@ def __set_layout_dimensions(self): self.subcorticalstructures_collapsiblegroupbox.header.title_label.setFixedHeight(35) self.subcorticalstructures_collapsiblegroupbox.header.background_label.setFixedHeight(40) + self.braingridstructures_collapsiblegroupbox.header.setFixedHeight(40) + self.braingridstructures_collapsiblegroupbox.header.set_icon_size(QSize(35, 35)) + self.braingridstructures_collapsiblegroupbox.header.title_label.setFixedHeight(35) + self.braingridstructures_collapsiblegroupbox.header.background_label.setFixedHeight(40) + def __set_connections(self): self.volumes_collapsiblegroupbox.toggled.connect(self.on_size_request) self.laterality_collapsiblegroupbox.toggled.connect(self.on_size_request) @@ -239,6 +256,7 @@ def __set_connections(self): self.multifocality_collapsiblegroupbox.toggled.connect(self.on_size_request) self.corticalstructures_collapsiblegroupbox.toggled.connect(self.on_size_request) self.subcorticalstructures_collapsiblegroupbox.toggled.connect(self.on_size_request) + self.braingridstructures_collapsiblegroupbox.toggled.connect(self.on_size_request) def set_stylesheets(self, selected: bool) -> None: software_ss = SoftwareConfigResources.getInstance().stylesheet_components @@ -539,6 +557,33 @@ def set_stylesheets(self, selected: bool) -> None: }""") self.subcorticalstructures_collapsiblegroupbox.content_widget.setStyleSheet("QWidget{background-color:rgb(254,254,254);}") + ###################################### BRAINGRID STRUCTURES GROUPBOX ######################################### + self.braingridstructures_collapsiblegroupbox.header.background_label.setStyleSheet(""" + QLabel{ + background-color:rgb(248, 248, 248); + border-width: 1px; + border-style: solid; + border-color: black rgb(248, 248, 248) black rgb(248, 248, 248); + border-radius: 2px; + }""") + self.braingridstructures_collapsiblegroupbox.header.title_label.setStyleSheet(""" + QLabel{ + background-color:rgb(248, 248, 248); + color: """ + font_color + """; + text-align:left; + font:bold; + font-size:14px; + padding-left:20px; + padding-right:20px; + border: none; + }""") + self.braingridstructures_collapsiblegroupbox.header.icon_label.setStyleSheet(""" + QLabel{ + border: none; + padding-left:20px; + }""") + self.braingridstructures_collapsiblegroupbox.content_widget.setStyleSheet("QWidget{background-color:rgb(254,254,254);}") + def adjustSize(self): pass @@ -746,6 +791,90 @@ def populate_from_report(self) -> None: self.subcorticalstructures_collapsiblegroupbox.content_layout.addLayout(lay) self.subcorticalstructures_collapsiblegroupbox.adjustSize() + # BrainGrid structures + self.braingridstructures_collapsiblegroupbox.clear_content_layout() + if UserPreferencesStructure.getInstance().compute_braingrid_structures: + lay = QHBoxLayout() + label_header = QLabel("Infiltration count:") + label_header.setStyleSheet(""" + QLabel{ + font-size:13px; + color: rgba(67, 88, 90, 1); + border-style: none; + }""") + label = QLabel("{}".format(str(report_json['Main']['Total']['BrainGrid']["Infiltration count"]))) + label_header.setFixedHeight(20) + label_header.setFixedWidth(190) + label.setFixedHeight(20) + label.setFixedWidth(80) + label.setAlignment(Qt.AlignRight) + label.setStyleSheet(""" + QLabel{ + color: rgba(67, 88, 90, 1); + text-align:right; + font:semibold; + font-size:13px; + }""") + lay.addWidget(label_header) + lay.addWidget(label) + lay.addStretch(1) + self.braingridstructures_collapsiblegroupbox.content_layout.addLayout(lay) + for atlas in UserPreferencesStructure.getInstance().braingrid_structures_list: # report_json['Main']['Total']['BrainGrid'] + sorted_overlaps = dict(sorted(report_json['Main']['Total']['BrainGrid'][atlas].items(), key=lambda item: item[1], reverse=True)) + label = QLabel("{} atlas".format(atlas)) + label.setFixedHeight(20) + label.setStyleSheet(""" + QLabel{ + color: """ + software_ss["Color7"] + """; + text-align:left; + font:bold; + font-size:14px; + }""") + line_label = QLabel() + line_label.setFixedHeight(3) + line_label.setStyleSheet("QLabel{background-color: rgb(214, 214, 214);}") + if list(report_json['Main']['Total']['BrainGrid'].keys()).index(atlas) != 0: + upper_line_label = QLabel() + upper_line_label.setFixedHeight(3) + upper_line_label.setStyleSheet("QLabel{background-color: rgb(214, 214, 214);}") + self.braingridstructures_collapsiblegroupbox.content_layout.addWidget(upper_line_label) + self.braingridstructures_collapsiblegroupbox.content_layout.addWidget(label) + self.braingridstructures_collapsiblegroupbox.content_layout.addWidget(line_label) + for struct, val in sorted_overlaps.items(): + if val >= 1.0: + lay = QHBoxLayout() + struct_display_name = struct.replace('_', ' ').replace('-', ' ') + label_header = QLineEdit("{} ".format(struct_display_name)) + label_header.setReadOnly(True) + label_header.setCursorPosition(0) + label_header.home(True) + label_header.setStyleSheet(""" + QLineEdit{ + font-size:13px; + color: rgba(67, 88, 90, 1); + border-style: none; + }""") + label = QLabel("{:.2f} %".format(val)) + label_header.setFixedHeight(20) + label_header.setFixedWidth(190) + label.setFixedHeight(20) + label.setFixedWidth(80) + label.setAlignment(Qt.AlignRight) + label.setStyleSheet(""" + QLabel{ + color: rgba(67, 88, 90, 1); + text-align:right; + font:semibold; + font-size:13px; + }""") + lay.addWidget(label_header) + # lay.addStretch(1) + lay.addWidget(label) + lay.addStretch(1) + self.braingridstructures_collapsiblegroupbox.content_layout.addLayout(lay) + + self.braingridstructures_collapsiblegroupbox.adjustSize() + self.adjustSize() def on_size_request(self): diff --git a/gui/UtilsWidgets/CustomQDialog/SoftwareSettingsDialog.py b/gui/UtilsWidgets/CustomQDialog/SoftwareSettingsDialog.py index 5948503..8a28de3 100644 --- a/gui/UtilsWidgets/CustomQDialog/SoftwareSettingsDialog.py +++ b/gui/UtilsWidgets/CustomQDialog/SoftwareSettingsDialog.py @@ -321,10 +321,44 @@ def __set_processing_reporting_options_interface(self): self.processing_options_subcorticalstructures_selection_layout.addWidget(self.subcorticalstructures_bcb_checkbox) self.processing_options_subcorticalstructures_selection_layout.addWidget(self.subcorticalstructures_bcb_label) self.processing_options_subcorticalstructures_selection_layout.addStretch(1) + self.subcorticalstructures_braingrid_label = QLabel("BrainGrid") + self.subcorticalstructures_braingrid_label.setToolTip("From the BrainGrid research, a total of 20 unique structures with left and right disambiguation.") + self.subcorticalstructures_braingrid_checkbox = QCheckBox() + self.subcorticalstructures_braingrid_checkbox.setChecked("BrainGrid" in UserPreferencesStructure.getInstance().subcortical_structures_list if UserPreferencesStructure.getInstance().subcortical_structures_list != None else False) + self.subcorticalstructures_braingrid_checkbox.setEnabled(UserPreferencesStructure.getInstance().compute_subcortical_structures) + self.processing_options_subcorticalstructures_selection_layout.addWidget(self.subcorticalstructures_braingrid_checkbox) + self.processing_options_subcorticalstructures_selection_layout.addWidget(self.subcorticalstructures_braingrid_label) + self.processing_options_subcorticalstructures_selection_layout.addStretch(1) self.processing_reporting_subcortical_groupboxlayout.addLayout(self.processing_options_subcorticalstructures_selection_layout) self.processing_reporting_subcortical_groupbox.setLayout(self.processing_reporting_subcortical_groupboxlayout) self.processing_reporting_options_base_layout.addWidget(self.processing_reporting_subcortical_groupbox) + self.processing_reporting_braingrid_groupbox = QGroupBox() + self.processing_reporting_braingrid_groupbox.setTitle("BrainGrid structures") + self.processing_reporting_braingrid_groupboxlayout = QVBoxLayout() + + self.processing_options_compute_braingridstructures_layout = QHBoxLayout() + self.processing_options_compute_braingridstructures_label = QLabel("Report BrainGrid structures") + self.processing_options_compute_braingridstructures_label.setToolTip("Tick the box in order to include BrainGrid structures related features in the standardized report.\n") + self.processing_options_compute_braingridstructures_checkbox = QCheckBox() + self.processing_options_compute_braingridstructures_checkbox.setChecked(UserPreferencesStructure.getInstance().compute_braingrid_structures) + self.processing_options_compute_braingridstructures_layout.addWidget(self.processing_options_compute_braingridstructures_checkbox) + self.processing_options_compute_braingridstructures_layout.addWidget(self.processing_options_compute_braingridstructures_label) + self.processing_options_compute_braingridstructures_layout.addStretch(1) + self.processing_reporting_braingrid_groupboxlayout.addLayout(self.processing_options_compute_braingridstructures_layout) + self.processing_options_braingridstructures_selection_layout = QHBoxLayout() + self.braingridstructures_voxels_label = QLabel("Voxels") + self.braingridstructures_voxels_label.setToolTip("From the BrainGrid research, super-voxels brain parcellation.") + self.braingridstructures_voxels_checkbox = QCheckBox() + self.braingridstructures_voxels_checkbox.setChecked("Voxels" in UserPreferencesStructure.getInstance().braingrid_structures_list if UserPreferencesStructure.getInstance().braingrid_structures_list != None else False) + self.braingridstructures_voxels_checkbox.setEnabled(UserPreferencesStructure.getInstance().compute_braingrid_structures) + self.processing_options_braingridstructures_selection_layout.addWidget(self.braingridstructures_voxels_checkbox) + self.processing_options_braingridstructures_selection_layout.addWidget(self.braingridstructures_voxels_label) + self.processing_options_braingridstructures_selection_layout.addStretch(1) + self.processing_reporting_braingrid_groupboxlayout.addLayout(self.processing_options_braingridstructures_selection_layout) + self.processing_reporting_braingrid_groupbox.setLayout(self.processing_reporting_braingrid_groupboxlayout) + self.processing_reporting_options_base_layout.addWidget(self.processing_reporting_braingrid_groupbox) + self.processing_reporting_options_base_layout.addStretch(1) self.processing_reporting_options_widget.setLayout(self.processing_reporting_options_base_layout) self.options_stackedwidget.addWidget(self.processing_reporting_options_widget) @@ -402,6 +436,9 @@ def __set_connections(self): self.corticalstructures_harvardoxford_checkbox.stateChanged.connect(self.__on_corticalstructure_harvardoxford_status_changed) self.processing_options_compute_subcorticalstructures_checkbox.stateChanged.connect(self.__on_compute_subcorticalstructures_status_changed) self.subcorticalstructures_bcb_checkbox.stateChanged.connect(self.__on_subcorticalstructure_bcb_status_changed) + self.subcorticalstructures_braingrid_checkbox.stateChanged.connect(self.__on_subcorticalstructure_braingrid_status_changed) + self.processing_options_compute_braingridstructures_checkbox.stateChanged.connect(self.__on_compute_braingridstructures_status_changed) + self.braingridstructures_voxels_checkbox.stateChanged.connect(self.__on_braingridstructure_voxels_status_changed) self.dark_mode_checkbox.stateChanged.connect(self.__on_dark_mode_status_changed) self.exit_accept_pushbutton.clicked.connect(self.__on_exit_accept_clicked) self.exit_cancel_pushbutton.clicked.connect(self.__on_exit_cancel_clicked) @@ -750,9 +787,13 @@ def __on_compute_subcorticalstructures_status_changed(self, state): if state: self.subcorticalstructures_bcb_checkbox.setEnabled(True) self.subcorticalstructures_bcb_label.setEnabled(True) + self.subcorticalstructures_braingrid_checkbox.setEnabled(True) + self.subcorticalstructures_braingrid_label.setEnabled(True) else: self.subcorticalstructures_bcb_checkbox.setEnabled(False) self.subcorticalstructures_bcb_label.setEnabled(False) + self.subcorticalstructures_braingrid_checkbox.setEnabled(False) + self.subcorticalstructures_braingrid_label.setEnabled(False) def __on_subcorticalstructure_bcb_status_changed(self, state): structs = UserPreferencesStructure.getInstance().subcortical_structures_list @@ -765,6 +806,37 @@ def __on_subcorticalstructure_bcb_status_changed(self, state): structs.remove("BCB") UserPreferencesStructure.getInstance().subcortical_structures_list = structs + def __on_subcorticalstructure_braingrid_status_changed(self, state): + structs = UserPreferencesStructure.getInstance().subcortical_structures_list + if state: + if structs is None: + structs = ["BrainGrid"] + else: + structs.append("BrainGrid") + else: + structs.remove("BrainGrid") + UserPreferencesStructure.getInstance().subcortical_structures_list = structs + + def __on_compute_braingridstructures_status_changed(self, state): + UserPreferencesStructure.getInstance().compute_braingrid_structures = self.processing_options_compute_braingridstructures_checkbox.isChecked() + if state: + self.braingridstructures_voxels_checkbox.setEnabled(True) + self.braingridstructures_voxels_label.setEnabled(True) + else: + self.braingridstructures_voxels_checkbox.setEnabled(False) + self.braingridstructures_voxels_label.setEnabled(False) + + def __on_braingridstructure_voxels_status_changed(self, state): + structs = UserPreferencesStructure.getInstance().braingrid_structures_list + if state: + if structs is None: + structs = ["Voxels"] + else: + structs.append("Voxels") + else: + structs.remove("Voxels") + UserPreferencesStructure.getInstance().braingrid_structures_list = structs + def __on_dark_mode_status_changed(self, state): # @TODO. Would have to bounce back to the QApplication class, to trigger a global setStyleSheet on-the-fly? SoftwareConfigResources.getInstance().set_dark_mode_state(state) diff --git a/utils/backend_logic.py b/utils/backend_logic.py index 2f47fb2..894d53b 100644 --- a/utils/backend_logic.py +++ b/utils/backend_logic.py @@ -116,6 +116,8 @@ def run_pipeline(task: str, model_name: str, patient_parameters: PatientParamete rads_config.set('Neuro', 'cortical_features', ",".join(UserPreferencesStructure.getInstance().cortical_structures_list)) if UserPreferencesStructure.getInstance().compute_subcortical_structures: rads_config.set('Neuro', 'subcortical_features', ",".join(UserPreferencesStructure.getInstance().subcortical_structures_list)) + if UserPreferencesStructure.getInstance().compute_braingrid_structures: + rads_config.set('Neuro', 'braingrid_features', ",".join(UserPreferencesStructure.getInstance().braingrid_structures_list)) rads_config_filename = os.path.join(patient_parameters.output_folder, 'rads_config.ini') with open(rads_config_filename, 'w') as outfile: rads_config.write(outfile) diff --git a/utils/data_structures/AtlasStructure.py b/utils/data_structures/AtlasStructure.py index d5eb8e9..d97b6d2 100644 --- a/utils/data_structures/AtlasStructure.py +++ b/utils/data_structures/AtlasStructure.py @@ -99,7 +99,11 @@ def __init_from_scratch(self): elif "Harvard" in self._unique_id: self._display_name = "Harvard-Oxford" elif "BCB" in self._unique_id: - self._display_name = "BCB group" + self._display_name = "BCB WM" + elif "Voxels" in self._unique_id: + self._display_name = "BrainGrid Voxels" + elif "BrainGrid" in self._unique_id: + self._display_name = "BrainGrid WM" elif "MNI" in self._unique_id: self._display_name = "MNI group" diff --git a/utils/data_structures/UserPreferencesStructure.py b/utils/data_structures/UserPreferencesStructure.py index f47190a..6b8e1f0 100644 --- a/utils/data_structures/UserPreferencesStructure.py +++ b/utils/data_structures/UserPreferencesStructure.py @@ -30,7 +30,9 @@ class UserPreferencesStructure: _compute_cortical_structures = True # True to include cortical features computation in the standardized reporting _cortical_structures_list = ["MNI", "Schaefer7", "Schaefer17", "Harvard-Oxford"] # List of cortical atlases to include _compute_subcortical_structures = True # True to include subcortical features computation in the standardized reporting - _subcortical_structures_list = ["BCB"] # List of subcortical atlases to include + _subcortical_structures_list = ["BCB", "BrainGrid"] # List of subcortical atlases to include + _compute_braingrid_structures = False # True to include braingrid features computation in the standardized reporting + _braingrid_structures_list = ["Voxels"] # List of BrainGrid features to include _use_dark_mode = False # True for dark mode and False for regular mode @staticmethod @@ -214,6 +216,24 @@ def subcortical_structures_list(self, structures: List[str]) -> None: self._subcortical_structures_list = structures self.save_preferences() + @property + def compute_braingrid_structures(self) -> bool: + return self._compute_braingrid_structures + + @compute_braingrid_structures.setter + def compute_braingrid_structures(self, state: bool) -> None: + self._compute_braingrid_structures = state + self.save_preferences() + + @property + def braingrid_structures_list(self) -> List[str]: + return self._braingrid_structures_list + + @braingrid_structures_list.setter + def braingrid_structures_list(self, structures: List[str]) -> None: + self._braingrid_structures_list = structures + self.save_preferences() + def __parse_preferences(self) -> None: """ Loads the saved user preferences from disk (located in raidionics_preferences.json) and updates all internal @@ -256,6 +276,10 @@ def __parse_preferences(self) -> None: self.compute_subcortical_structures = preferences['Processing']['Reporting']['compute_subcortical_structures'] if 'subcortical_structures_list' in preferences['Processing']['Reporting'].keys(): self.subcortical_structures_list = preferences['Processing']['Reporting']['subcortical_structures_list'] + if 'compute_braingrid_structures' in preferences['Processing']['Reporting'].keys(): + self.compute_braingrid_structures = preferences['Processing']['Reporting']['compute_braingrid_structures'] + if 'braingrid_structures_list' in preferences['Processing']['Reporting'].keys(): + self.braingrid_structures_list = preferences['Processing']['Reporting']['braingrid_structures_list'] if 'Appearance' in preferences.keys(): if 'dark_mode' in preferences['Appearance'].keys(): self._use_dark_mode = preferences['Appearance']['dark_mode'] @@ -285,6 +309,8 @@ def save_preferences(self) -> None: preferences['Processing']['Reporting']['cortical_structures_list'] = self._cortical_structures_list preferences['Processing']['Reporting']['compute_subcortical_structures'] = self._compute_subcortical_structures preferences['Processing']['Reporting']['subcortical_structures_list'] = self._subcortical_structures_list + preferences['Processing']['Reporting']['compute_braingrid_structures'] = self._compute_braingrid_structures + preferences['Processing']['Reporting']['braingrid_structures_list'] = self._braingrid_structures_list preferences['Appearance'] = {} preferences['Appearance']['dark_mode'] = self._use_dark_mode diff --git a/utils/logic/PipelineCreationHandler.py b/utils/logic/PipelineCreationHandler.py index 16f5ed9..4d81837 100644 --- a/utils/logic/PipelineCreationHandler.py +++ b/utils/logic/PipelineCreationHandler.py @@ -64,14 +64,16 @@ def create_pipeline(model_name: str, patient_parameters, task: str) -> dict: elif task == 'preop_segmentation': return __create_segmentation_pipeline(model_name, patient_parameters) elif task == 'postop_segmentation': - download_model(model_name='MRI_GBM_Postop_FV_4p') + model_name = select_appropriate_postop_model(patient_parameters) + download_model(model_name=model_name) return __create_postop_segmentation_pipeline(model_name, patient_parameters) elif task == 'other_segmentation': return __create_other_segmentation_pipeline(model_name, patient_parameters) elif task == 'preop_reporting': return __create_preop_reporting_pipeline(model_name, patient_parameters) elif task == 'postop_reporting': - download_model(model_name='MRI_GBM_Postop_FV_4p') + model_name = select_appropriate_postop_model(patient_parameters) + download_model(model_name=model_name) return __create_postop_reporting_pipeline(model_name, patient_parameters) else: return __create_custom_pipeline(task, model_name, patient_parameters) @@ -179,13 +181,8 @@ def __create_postop_segmentation_pipeline(model_name, patient_parameters): """ The default postop segmentation model is the one with four inputs, but based on the loaded images another fitting model could be used. - @TODO. Ideally, in the future, the disambiguation of the best model to use should be done in the backend. In that - case, how to properly retrieve the corresponding pipeline.json? """ - postop_model_name = "MRI_GBM_Postop_FV_4p" - if UserPreferencesStructure.getInstance().use_manual_sequences: - postop_model_name = select_appropriate_postop_model(patient_parameters) - infile = open(os.path.join(SoftwareConfigResources.getInstance().models_path, postop_model_name, 'pipeline.json'), 'rb') + infile = open(os.path.join(SoftwareConfigResources.getInstance().models_path, model_name, 'pipeline.json'), 'rb') raw_pip = json.load(infile) pip = {} @@ -317,6 +314,9 @@ def __create_postop_reporting_pipeline(model_name, patient_parameters): """ """ + infile = open(os.path.join(SoftwareConfigResources.getInstance().models_path, model_name, 'pipeline.json'), 'rb') + raw_pip = json.load(infile) + pip = {} pip_num_int = 0 if not UserPreferencesStructure.getInstance().use_manual_sequences: @@ -329,206 +329,14 @@ def __create_postop_reporting_pipeline(model_name, patient_parameters): pip[pip_num]["description"] = "Classification of the MRI sequence type for all input scans" download_model(model_name='MRI_Sequence_Classifier') - pip_num_int = pip_num_int + 1 - pip_num = str(pip_num_int) - pip[pip_num] = {} - pip[pip_num]["task"] = 'Segmentation' - pip[pip_num]["inputs"] = {} - pip[pip_num]["inputs"]["0"] = {} - pip[pip_num]["inputs"]["0"]["timestamp"] = 0 - pip[pip_num]["inputs"]["0"]["sequence"] = "T1-CE" - pip[pip_num]["inputs"]["0"]["labels"] = None - pip[pip_num]["inputs"]["0"]["space"] = {} - pip[pip_num]["inputs"]["0"]["space"]["timestamp"] = 0 - pip[pip_num]["inputs"]["0"]["space"]["sequence"] = "T1-CE" - pip[pip_num]["target"] = ["Brain"] - pip[pip_num]["model"] = "MRI_Brain" - pip[pip_num]["description"] = "Brain segmentation in T1CE (T0)" - download_model(model_name='MRI_Brain') - - pip_num_int = pip_num_int + 1 - pip_num = str(pip_num_int) - pip[pip_num] = {} - pip[pip_num]["task"] = 'Segmentation' - pip[pip_num]["inputs"] = {} - pip[pip_num]["inputs"]["0"] = {} - pip[pip_num]["inputs"]["0"]["timestamp"] = 0 - pip[pip_num]["inputs"]["0"]["sequence"] = "T1-CE" - pip[pip_num]["inputs"]["0"]["labels"] = None - pip[pip_num]["inputs"]["0"]["space"] = {} - pip[pip_num]["inputs"]["0"]["space"]["timestamp"] = 0 - pip[pip_num]["inputs"]["0"]["space"]["sequence"] = "T1-CE" - pip[pip_num]["target"] = ["Tumor"] - pip[pip_num]["model"] = model_name - pip[pip_num]["description"] = "Tumor segmentation in T1CE (T0)" - download_model(model_name=model_name) - - pip_num_int = pip_num_int + 1 - pip_num = str(pip_num_int) - pip[pip_num] = {} - pip[pip_num]["task"] = 'Segmentation' - pip[pip_num]["inputs"] = {} - pip[pip_num]["inputs"]["0"] = {} - pip[pip_num]["inputs"]["0"]["timestamp"] = 1 - pip[pip_num]["inputs"]["0"]["sequence"] = "T1-CE" - pip[pip_num]["inputs"]["0"]["labels"] = None - pip[pip_num]["inputs"]["0"]["space"] = {} - pip[pip_num]["inputs"]["0"]["space"]["timestamp"] = 1 - pip[pip_num]["inputs"]["0"]["space"]["sequence"] = "T1-CE" - pip[pip_num]["target"] = ["Brain"] - pip[pip_num]["model"] = "MRI_Brain" - pip[pip_num]["description"] = "Brain segmentation in T1CE (T1)" - - pip_num_int = pip_num_int + 1 - pip_num = str(pip_num_int) - pip[pip_num] = {} - pip[pip_num]["task"] = 'Registration' - pip[pip_num]["moving"] = {} - pip[pip_num]["moving"]["timestamp"] = 0 - pip[pip_num]["moving"]["sequence"] = "T1-CE" - pip[pip_num]["fixed"] = {} - pip[pip_num]["fixed"]["timestamp"] = 1 - pip[pip_num]["fixed"]["sequence"] = "T1-CE" - pip[pip_num]["description"] = "Registration from T1CE (T0) to T1CE (T1)" - - pip_num_int = pip_num_int + 1 - pip_num = str(pip_num_int) - pip[pip_num] = {} - pip[pip_num]["task"] = 'Apply registration' - pip[pip_num]["moving"] = {} - pip[pip_num]["moving"]["timestamp"] = 0 - pip[pip_num]["moving"]["sequence"] = "T1-CE" - pip[pip_num]["fixed"] = {} - pip[pip_num]["fixed"]["timestamp"] = 1 - pip[pip_num]["fixed"]["sequence"] = "T1-CE" - pip[pip_num]["direction"] = "forward" - pip[pip_num]["description"] = "Apply registration from T1CE (T0) to T1CE (T1)" - - pip_num_int = pip_num_int + 1 - pip_num = str(pip_num_int) - pip[pip_num] = {} - pip[pip_num]["task"] = 'Segmentation' - pip[pip_num]["inputs"] = {} - pip[pip_num]["inputs"]["0"] = {} - pip[pip_num]["inputs"]["0"]["timestamp"] = 1 - pip[pip_num]["inputs"]["0"]["sequence"] = "FLAIR" - pip[pip_num]["inputs"]["0"]["labels"] = None - pip[pip_num]["inputs"]["0"]["space"] = {} - pip[pip_num]["inputs"]["0"]["space"]["timestamp"] = 1 - pip[pip_num]["inputs"]["0"]["space"]["sequence"] = "FLAIR" - pip[pip_num]["target"] = ["Brain"] - pip[pip_num]["model"] = "MRI_Brain" - pip[pip_num]["description"] = "Brain segmentation in FLAIR (T1)" - - pip_num_int = pip_num_int + 1 - pip_num = str(pip_num_int) - pip[pip_num] = {} - pip[pip_num]["task"] = 'Registration' - pip[pip_num]["moving"] = {} - pip[pip_num]["moving"]["timestamp"] = 1 - pip[pip_num]["moving"]["sequence"] = "FLAIR" - pip[pip_num]["fixed"] = {} - pip[pip_num]["fixed"]["timestamp"] = 1 - pip[pip_num]["fixed"]["sequence"] = "T1-CE" - pip[pip_num]["description"] = "Registration from FLAIR (T1) to T1CE (T1)" - - pip_num_int = pip_num_int + 1 - pip_num = str(pip_num_int) - pip[pip_num] = {} - pip[pip_num]["task"] = 'Apply registration' - pip[pip_num]["moving"] = {} - pip[pip_num]["moving"]["timestamp"] = 1 - pip[pip_num]["moving"]["sequence"] = "FLAIR" - pip[pip_num]["fixed"] = {} - pip[pip_num]["fixed"]["timestamp"] = 1 - pip[pip_num]["fixed"]["sequence"] = "T1-CE" - pip[pip_num]["direction"] = "forward" - pip[pip_num]["description"] = "Apply registration from FLAIR (T1) to T1CE (T1)" - - pip_num_int = pip_num_int + 1 - pip_num = str(pip_num_int) - pip[pip_num] = {} - pip[pip_num]["task"] = 'Segmentation' - pip[pip_num]["inputs"] = {} - pip[pip_num]["inputs"]["0"] = {} - pip[pip_num]["inputs"]["0"]["timestamp"] = 1 - pip[pip_num]["inputs"]["0"]["sequence"] = "T1-w" - pip[pip_num]["inputs"]["0"]["labels"] = None - pip[pip_num]["inputs"]["0"]["space"] = {} - pip[pip_num]["inputs"]["0"]["space"]["timestamp"] = 1 - pip[pip_num]["inputs"]["0"]["space"]["sequence"] = "T1-w" - pip[pip_num]["target"] = ["Brain"] - pip[pip_num]["model"] = "MRI_Brain" - pip[pip_num]["description"] = "Brain segmentation in T1w (T1)" - - pip_num_int = pip_num_int + 1 - pip_num = str(pip_num_int) - pip[pip_num] = {} - pip[pip_num]["task"] = 'Registration' - pip[pip_num]["moving"] = {} - pip[pip_num]["moving"]["timestamp"] = 1 - pip[pip_num]["moving"]["sequence"] = "T1-w" - pip[pip_num]["fixed"] = {} - pip[pip_num]["fixed"]["timestamp"] = 1 - pip[pip_num]["fixed"]["sequence"] = "T1-CE" - pip[pip_num]["description"] = "Registration from T1w (T1) to T1CE (T1)" - - pip_num_int = pip_num_int + 1 - pip_num = str(pip_num_int) - pip[pip_num] = {} - pip[pip_num]["task"] = 'Apply registration' - pip[pip_num]["moving"] = {} - pip[pip_num]["moving"]["timestamp"] = 1 - pip[pip_num]["moving"]["sequence"] = "T1-w" - pip[pip_num]["fixed"] = {} - pip[pip_num]["fixed"]["timestamp"] = 1 - pip[pip_num]["fixed"]["sequence"] = "T1-CE" - pip[pip_num]["direction"] = "forward" - pip[pip_num]["description"] = "Apply registration from T1w (T1) to T1CE (T1)" - - pip_num_int = pip_num_int + 1 - pip_num = str(pip_num_int) - pip[pip_num] = {} - pip[pip_num]["task"] = 'Segmentation' - pip[pip_num]["inputs"] = {} - pip[pip_num]["inputs"]["0"] = {} - pip[pip_num]["inputs"]["0"]["timestamp"] = 1 - pip[pip_num]["inputs"]["0"]["sequence"] = "T1-CE" - pip[pip_num]["inputs"]["0"]["labels"] = None - pip[pip_num]["inputs"]["0"]["space"] = {} - pip[pip_num]["inputs"]["0"]["space"]["timestamp"] = 1 - pip[pip_num]["inputs"]["0"]["space"]["sequence"] = "T1-CE" - pip[pip_num]["inputs"]["1"] = {} - pip[pip_num]["inputs"]["1"]["timestamp"] = 1 - pip[pip_num]["inputs"]["1"]["sequence"] = "T1-w" - pip[pip_num]["inputs"]["1"]["labels"] = None - pip[pip_num]["inputs"]["1"]["space"] = {} - pip[pip_num]["inputs"]["1"]["space"]["timestamp"] = 1 - pip[pip_num]["inputs"]["1"]["space"]["sequence"] = "T1-CE" - pip[pip_num]["inputs"]["2"] = {} - pip[pip_num]["inputs"]["2"]["timestamp"] = 1 - pip[pip_num]["inputs"]["2"]["sequence"] = "FLAIR" - pip[pip_num]["inputs"]["2"]["labels"] = None - pip[pip_num]["inputs"]["2"]["space"] = {} - pip[pip_num]["inputs"]["2"]["space"]["timestamp"] = 1 - pip[pip_num]["inputs"]["2"]["space"]["sequence"] = "T1-CE" - pip[pip_num]["inputs"]["3"] = {} - pip[pip_num]["inputs"]["3"]["timestamp"] = 0 - pip[pip_num]["inputs"]["3"]["sequence"] = "T1-CE" - pip[pip_num]["inputs"]["3"]["labels"] = None - pip[pip_num]["inputs"]["3"]["space"] = {} - pip[pip_num]["inputs"]["3"]["space"]["timestamp"] = 1 - pip[pip_num]["inputs"]["3"]["space"]["sequence"] = "T1-CE" - pip[pip_num]["inputs"]["4"] = {} - pip[pip_num]["inputs"]["4"]["timestamp"] = 0 - pip[pip_num]["inputs"]["4"]["sequence"] = "T1-CE" - pip[pip_num]["inputs"]["4"]["labels"] = "Tumor" - pip[pip_num]["inputs"]["4"]["space"] = {} - pip[pip_num]["inputs"]["4"]["space"]["timestamp"] = 1 - pip[pip_num]["inputs"]["4"]["space"]["sequence"] = "T1-CE" - pip[pip_num]["target"] = ["Tumor"] - pip[pip_num]["model"] = "MRI_GBM_Postop_FV_4p" - pip[pip_num]["description"] = "Tumor segmentation in T1CE (T1)" + for steps in list(raw_pip.keys()): + # Excluding brain segmentation step if the inputs are already skull-stripped + if (UserPreferencesStructure.getInstance().use_stripped_inputs and + (raw_pip[steps]["task"] == "Segmentation" and raw_pip[steps]["model"] == "MRI_Brain")): + continue + pip_num_int = pip_num_int + 1 + pip_num = str(pip_num_int) + pip[pip_num] = raw_pip[steps] pip_num_int = pip_num_int + 1 pip_num = str(pip_num_int) @@ -635,9 +443,39 @@ def __create_custom_pipeline(task, tumor_type, patient_parameters): def select_appropriate_postop_model(patient_parameters) -> str: """ - Temporary method, which will be deported in the backend, for selecting the best postoperative glioblastoma - segmentation model based on available inputs. + Method for selecting the best postoperative glioblastoma segmentation model based on available inputs. + Should it be deported in the RADS backend? """ - model_name = "MRI_GBM_Postop_FV_4p" - + model_name = "MRI_GBM_Postop_FV_1p" + if not UserPreferencesStructure.getInstance().use_manual_sequences: + # Case where the model selection should then be deported to the backend, or the MRI sequence identification + # should happen before calling a segmentation/reporting pipeline? + return "MRI_GBM_Postop_FV_4p" + + exist_preop_t1 = False + exist_postop_t1ce = False + exist_postop_t1w = False + exist_postop_flair = False + + for v in list(patient_parameters.mri_volumes.keys()): + volume_object = patient_parameters.mri_volumes[v] + if volume_object.timestamp_uid == "T0": + if volume_object.get_sequence_type_str() == "T1-CE": + exist_preop_t1 = True + elif volume_object.timestamp_uid == "T1": + if volume_object.get_sequence_type_str() == "T1-CE": + exist_postop_t1ce = True + elif volume_object.get_sequence_type_str() == "T1-w": + exist_postop_t1w = True + elif volume_object.get_sequence_type_str() == "FLAIR": + exist_postop_flair = True + + if exist_postop_t1ce and exist_postop_t1w: + model_name = "MRI_GBM_Postop_FV_2p" + if exist_postop_t1ce and exist_postop_t1w and exist_postop_flair: + model_name = "MRI_GBM_Postop_FV_3p" + if exist_postop_t1ce and exist_postop_t1w and exist_preop_t1: + model_name = "MRI_GBM_Postop_FV_4p" + if exist_postop_t1ce and exist_postop_t1w and exist_postop_flair and exist_preop_t1: + model_name = "MRI_GBM_Postop_FV_5p" return model_name diff --git a/utils/logic/PipelineResultsCollector.py b/utils/logic/PipelineResultsCollector.py index 148630c..b63bdd8 100644 --- a/utils/logic/PipelineResultsCollector.py +++ b/utils/logic/PipelineResultsCollector.py @@ -115,13 +115,13 @@ def collect_results(patient_parameters, pipeline): 'T' + str(pip_step["moving"]["timestamp"]), 'Subcortical-structures') # @TODO. Hardcoded for now, have to improve the RADS backend here if we are to support more atlases. - subcortical_masks = ['MNI_BCB_atlas.nii.gz'] - # subcortical_masks = [] - # for _, _, files in os.walk(subcortical_folder): - # for f in files: - # if '_mask' in f: - # subcortical_masks.append(f) - # break + # subcortical_masks = ['MNI_BCB_atlas.nii.gz'] + subcortical_masks = [] + for _, _, files in os.walk(subcortical_folder): + for f in files: + if '_overall_mask' in f: + subcortical_masks.append(f) + break for m in subcortical_masks: atlas_filename = os.path.join(subcortical_folder, m) @@ -142,6 +142,37 @@ def collect_results(patient_parameters, pipeline): reference='Patient') results['Atlas'].append(data_uid) + + # Collecting the atlas BrainGrid structures + if UserPreferencesStructure.getInstance().compute_braingrid_structures: + braingrid_folder = os.path.join(patient_parameters.output_folder, 'reporting', + 'T' + str(pip_step["moving"]["timestamp"]), 'Braingrid-structures') + braingrid_masks = [] + for _, _, files in os.walk(braingrid_folder): + for f in files: + braingrid_masks.append(f) + break + + for m in braingrid_masks: + atlas_filename = os.path.join(braingrid_folder, m) + dest_atlas_filename = os.path.join(patient_parameters.output_folder, dest_ts_object.folder_name, + 'raw', m) + shutil.move(atlas_filename, dest_atlas_filename) + description_filename = os.path.join(patient_parameters.output_folder, 'reporting', + 'atlas_descriptions', m.split('_')[1] + '_description.csv') + dest_desc_filename = os.path.join(patient_parameters.output_folder, 'atlas_descriptions', + m.split('_')[1] + '_description.csv') + os.makedirs(os.path.dirname(dest_desc_filename), exist_ok=True) + if not os.path.exists(dest_desc_filename): + shutil.move(description_filename, dest_desc_filename) + data_uid, error_msg = patient_parameters.import_atlas_structures(dest_atlas_filename, + parent_mri_uid=parent_mri_uid, + investigation_ts_folder_name=dest_ts_object.folder_name, + description=dest_desc_filename, + reference='Patient') + + results['Atlas'].append(data_uid) + elif pip_step["task"] == "Features computation": report_filename = os.path.join(patient_parameters.output_folder, 'reporting', 'neuro_clinical_report.json')