From 528f10d878c5ccf99a338f26cf7c2d1089a9c612 Mon Sep 17 00:00:00 2001 From: dbouget Date: Tue, 14 May 2024 12:11:32 +0200 Subject: [PATCH] Added support for BrainGrid features --- .../TumorCharacteristicsWidget.py | 129 ++++++++++++++++++ .../CustomQDialog/SoftwareSettingsDialog.py | 72 ++++++++++ utils/backend_logic.py | 2 + utils/data_structures/AtlasStructure.py | 6 +- .../UserPreferencesStructure.py | 28 +++- utils/logic/PipelineResultsCollector.py | 45 +++++- 6 files changed, 273 insertions(+), 9 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/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')