Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6ca2238
Integration test and code improvement for handling DICOM imports...[s…
dbouget Nov 16, 2024
e41c41f
Deeper integration tests separation. Added single patient DICOM loadi…
dbouget Nov 18, 2024
558e762
Fixing the multiple timestamp loading test[skip ci]
dbouget Nov 18, 2024
0fec17d
SP Dicom test running locally but not on the CI[skip ci]
dbouget Nov 18, 2024
138582c
SP Dicom test running locally but not on the CI[skip ci]
dbouget Nov 19, 2024
d982e45
SP Dicom test running locally but not on the CI[skip ci]
dbouget Nov 19, 2024
d73c4df
SP Dicom test running locally but not on the CI[skip ci]
dbouget Nov 19, 2024
d93552e
SP Dicom test running locally but not on the CI[skip ci]
dbouget Nov 19, 2024
e3ef740
Added option to disable the modal dialogs when integration testing [s…
dbouget Nov 19, 2024
d9a30f3
Raidionics patient reloading and edit test, small fix on volume remov…
dbouget Nov 22, 2024
0ae99ab
Temp commit before moving... [skip ci]
dbouget Dec 6, 2024
f2d082a
Fixing issue related to stripped inputs and brain annotations [skip ci]
dbouget Jan 13, 2025
8ee3bf1
MacOS ARM test line edit in CI [skip ci]
dbouget Jan 13, 2025
4323962
MacOS ARM test line edit in CI [skip ci]
dbouget Jan 13, 2025
7254869
Update macos version [skip ci]
dbouget Jan 13, 2025
bf4005c
Update macos integration test call line [skip ci]
dbouget Jan 13, 2025
ac0814c
Updating integration test to prevent hanging [skip ci]
dbouget Jan 13, 2025
f2d24e1
Updating integration test to prevent hanging [skip ci]
dbouget Jan 13, 2025
a0dcbb9
Updating integration test to prevent hanging [skip ci]
dbouget Jan 13, 2025
a1ca540
Updating integration test to prevent hanging [skip ci]
dbouget Jan 13, 2025
b5f0482
Updating integration test to prevent hanging [skip ci]
dbouget Jan 13, 2025
2e6e1a6
Updating integration test to prevent hanging [skip ci]
dbouget Jan 13, 2025
75e8896
Updating integration test to prevent hanging [skip ci]
dbouget Jan 13, 2025
eda0209
Updating integration test to prevent hanging [skip ci]
dbouget Jan 13, 2025
eb484fe
Updating integration test to prevent hanging [skip ci]
dbouget Jan 13, 2025
d82e90c
Timeout issues on macos [skip ci]
dbouget Jan 13, 2025
af5869a
Timeout issues on macos [skip ci]
dbouget Jan 13, 2025
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
6 changes: 3 additions & 3 deletions .github/workflows/build_macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ env:
jobs:
build:
name: Build packages
runs-on: macos-12
runs-on: macos-13
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.8
Expand Down Expand Up @@ -54,8 +54,8 @@ jobs:
env:
DISPLAY: ':99.0'
run: |
pip install pytest-qt pytest-cov
pytest --cov=gui --cov=utils ${{github.workspace}}/integration_tests
pip install pytest-qt pytest-cov pytest-timeout
pytest -vvv --cov=gui --cov=utils ${{github.workspace}}/integration_tests --timeout=60 --log-cli-level=DEBUG

- name: Build software
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/build_macos_arm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ jobs:
run: |
cd ${{github.workspace}}
source tmp/venv/bin/activate
pip3 install pytest-qt pytest-cov
pytest --cov=gui --cov=utils ${{github.workspace}}/integration_tests
pip3 install pytest-qt pytest-cov pytest-timeout
pytest -vvv --cov=gui --cov=utils ${{github.workspace}}/integration_tests --timeout=60 --log-cli-level=DEBUG
deactivate

- name: Build software
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/build_ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ jobs:
env:
DISPLAY: ':99.0'
run: |
pip install pytest-qt pytest-cov
pip install pytest-qt pytest-cov pytest-timeout
/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX
pytest --cov=gui --cov=utils ${{github.workspace}}/integration_tests --cov-report=xml
pytest -vvv --cov=gui --cov=utils ${{github.workspace}}/integration_tests --cov-report=xml --timeout=60 --log-cli-level=DEBUG

- name: Build software
run: |
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,5 @@ quickpkg/
*.tar.gz
assets/Raidionics_ubuntu/usr/local/bin
.coverage
*.raidionics
integrationtests/
5 changes: 4 additions & 1 deletion gui/RaidionicsMainWindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ def on_process_log_message(self, log_msg: str) -> None:
aware of it (in case they don't have the reflex to check the log file).
"""
cases = ["[Software warning]", "[Software error]", "[Backend warning]", "[Backend error]"]
if True in [x in log_msg for x in cases]:
if True in [x in log_msg for x in cases] and not UserPreferencesStructure.getInstance().disable_modal_warnings:
diag = QErrorMessage(self)
diag.setWindowTitle("Error or warning identified!")
diag.showMessage(log_msg + "<br><br>Please visit the log file (Settings > Logs)")
Expand All @@ -540,6 +540,9 @@ def standardOutputWritten(self, text):
self.batch_mode_widget.standardOutputWritten(text)

def on_clear_scene(self):
"""

"""
logging.info("[RaidionicsMainWindow] Interface clean-up. Removing all loaded patients and studies.")
self.batch_study_widget.on_clear_scene()
self.single_patient_widget.on_clear_scene()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,24 +191,28 @@ def on_volume_layer_toggled(self, volume_uid, state):
if not self.current_patient_parameters:
self.current_patient_parameters = SoftwareConfigResources.getInstance().get_active_patient()

if state:
self.reset_overlay() # Until the time there is a co-registration option between input MRI volumes.
self.displayed_image = self.current_patient_parameters.get_mri_by_uid(volume_uid).get_display_volume()
self.displayed_image_uid = volume_uid

# Reset to the view-point, until the time there's co-registration or MNI space, where we can keep it.
# @FIXME. Is the center of the volume actually correct? Looks fishy
self.point_clicker_position = [int(self.displayed_image.shape[0] / 2),
int(self.displayed_image.shape[1] / 2),
int(self.displayed_image.shape[2] / 2)]
self.update_viewers_image()
else: # If all images have been removed by the user, should display an empty view
self.displayed_image = np.zeros(shape=(150, 150, 150), dtype='uint8')
self.displayed_image_uid = None
self.point_clicker_position = [int(self.displayed_image.shape[0] / 2),
int(self.displayed_image.shape[1] / 2),
int(self.displayed_image.shape[2] / 2)]
self.update_viewers_image()
try:
if state:
self.reset_overlay() # Until the time there is a co-registration option between input MRI volumes.
self.displayed_image = self.current_patient_parameters.get_mri_by_uid(volume_uid).get_display_volume()
self.displayed_image_uid = volume_uid

# Reset to the view-point, until the time there's co-registration or MNI space, where we can keep it.
# @FIXME. Is the center of the volume actually correct? Looks fishy
self.point_clicker_position = [int(self.displayed_image.shape[0] / 2),
int(self.displayed_image.shape[1] / 2),
int(self.displayed_image.shape[2] / 2)]
self.update_viewers_image()
else: # If all images have been removed by the user, should display an empty view
self.displayed_image = np.zeros(shape=(150, 150, 150), dtype='uint8')
self.displayed_image_uid = None
self.point_clicker_position = [int(self.displayed_image.shape[0] / 2),
int(self.displayed_image.shape[1] / 2),
int(self.displayed_image.shape[2] / 2)]
self.update_viewers_image()
except Exception as e:
logging.error("[Software error][CentralDisplayAreaWidget] Issue trying to toggle the view for a volume.")
logging.info("Collected: {}\n {}".format(e, traceback.format_exc()))

def on_volume_contrast_changed(self, volume_uid):
# @TODO. Should group the viewer calls into another function somewhere, since all three above methods use it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from gui.UtilsWidgets.CustomQGroupBox.QCollapsibleGroupBox import QCollapsibleGroupBox

from utils.software_config import SoftwareConfigResources
from utils.data_structures.UserPreferencesStructure import UserPreferencesStructure


class AnnotationSingleLayerWidget(QWidget):
Expand Down Expand Up @@ -534,16 +535,18 @@ def __on_annotation_type_changed(self):
params.set_annotation_class_type(self.annotation_type_combobox.currentText())

def __on_parent_mri_changed(self, index: int) -> None:
code = QMessageBox.warning(self, "Parent MRI change warning.",

if not UserPreferencesStructure.getInstance().disable_modal_warnings:
code = QMessageBox.warning(self, "Parent MRI change warning.",
"Are you sure you want to proceed with the change of parent MRI?",
QMessageBox.Ok | QMessageBox.Cancel, QMessageBox.Ok)
if code == QMessageBox.StandardButton.Cancel: # Change canceled
self.parent_image_combobox.blockSignals(True)
parent_mri_display_name = SoftwareConfigResources.getInstance().get_active_patient().get_mri_by_uid(
SoftwareConfigResources.getInstance().get_active_patient().get_annotation_by_uid(self.uid).get_parent_mri_uid()).display_name
self.parent_image_combobox.setCurrentText(parent_mri_display_name)
self.parent_image_combobox.blockSignals(False)
return
if code == QMessageBox.StandardButton.Cancel: # Change canceled
self.parent_image_combobox.blockSignals(True)
parent_mri_display_name = SoftwareConfigResources.getInstance().get_active_patient().get_mri_by_uid(
SoftwareConfigResources.getInstance().get_active_patient().get_annotation_by_uid(self.uid).get_parent_mri_uid()).display_name
self.parent_image_combobox.setCurrentText(parent_mri_display_name)
self.parent_image_combobox.blockSignals(False)
return

params = SoftwareConfigResources.getInstance().get_active_patient().get_annotation_by_uid(self.uid)
old_mri_parent = params.get_parent_mri_uid()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,17 @@ def reset(self):
self.volumes_widget.pop(w)
self.header.collapse()

def get_layer_widget_length(self) -> int:
return len(self.volumes_widget)

def get_layer_widget_by_index(self, index: int) -> AnnotationSingleLayerWidget:
if index >= len(self.volumes_widget):
raise ValueError("[AnnotationsLayerInteractor] Trying to retrieve an Annotation layer widget with an out-of-bound index value.")
return self.volumes_widget[list(self.volumes_widget.keys())[index]]

def get_layer_widget_by_visible_name(self, name: str) -> AnnotationSingleLayerWidget:
for w in list(self.volumes_widget.keys()):
if self.volumes_widget[w].visible_name == name:
if self.volumes_widget[w].display_name_lineedit.text() == name:
return self.volumes_widget[w]
raise ValueError("[AnnotationsLayerInteractor] Trying to retrieve a non-existing Annotation layer widget by visible name with: {}.".format(name))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,18 @@ def reset(self):
self.volumes_widget.pop(w)
self.header.collapse()


def get_layer_widget_length(self) -> int:
return len(self.volumes_widget)

def get_layer_widget_by_index(self, index: int) -> AtlasSingleLayerWidget:
if index >= len(self.volumes_widget):
raise ValueError("[AtlasesLayerInteractor] Trying to retrieve an Atlas layer widget with an out-of-bound index value.")
return self.volumes_widget[list(self.volumes_widget.keys())[index]]

def get_layer_widget_by_visible_name(self, name: str) -> AtlasSingleLayerWidget:
for w in list(self.volumes_widget.keys()):
if self.volumes_widget[w].visible_name == name:
if self.volumes_widget[w].display_name_lineedit.text() == name:
return self.volumes_widget[w]
raise ValueError("[AtlasesLayerInteractor] Trying to retrieve a non-existing Atlas layer widget by visible name with: {}.".format(name))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from gui.UtilsWidgets.CustomQGroupBox.QCollapsibleGroupBox import QCollapsibleGroupBox
from gui.UtilsWidgets.CustomQDialog.ContrastAdjustmentDialog import ContrastAdjustmentDialog
from gui.UtilsWidgets.CustomQDialog.DisplayDICOMMetadataDialog import DisplayMetadataDICOMDialog
from utils.data_structures.UserPreferencesStructure import UserPreferencesStructure

from utils.software_config import SoftwareConfigResources
from utils.data_structures.MRIVolumeStructure import MRISequenceType
Expand Down Expand Up @@ -294,7 +295,7 @@ def __on_delete_layer(self):
"""
linked_annos = SoftwareConfigResources.getInstance().get_active_patient().get_all_annotations_for_mri(self.uid)
linked_atlases = SoftwareConfigResources.getInstance().get_active_patient().get_all_atlases_for_mri(self.uid)
if (len(linked_annos) + len(linked_atlases)) != 0:
if (len(linked_annos) + len(linked_atlases)) != 0 and not UserPreferencesStructure.getInstance().disable_modal_warnings:
code = QMessageBox.warning(self, "MRI volume layer deletion warning.",
"Deleting an MRI volume will also remove all other files linked to it (e.g., annotations).",
QMessageBox.Ok | QMessageBox.Cancel, QMessageBox.Ok)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,17 @@ def reset(self) -> None:
self.volumes_widget.pop(w)
self.header.collapse()

def get_layer_widget_length(self) -> int:
return len(self.volumes_widget)

def get_layer_widget_by_index(self, index: int) -> MRISeriesLayerWidget:
if index >= len(self.volumes_widget):
raise ValueError("[MRIVolumeLayerInteractor] Trying to retrieve an MRI Layer widget with an out-of-bound index value.")
return self.volumes_widget[list(self.volumes_widget.keys())[index]]

def get_layer_widget_by_visible_name(self, name: str) -> MRISeriesLayerWidget:
for w in list(self.volumes_widget.keys()):
if self.volumes_widget[w].visible_name == name:
if self.volumes_widget[w].display_name_lineedit.text() == name:
return self.volumes_widget[w]
raise ValueError("[TimestampsLayerInteractor] Trying to retrieve a non-existing MRI layer widget by visible name with: {}.".format(name))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@
import logging

from gui.SinglePatientComponent.LayersInteractorSidePanel.TimestampsInteractor.TimestampsLayerInteractor import TimestampsLayerInteractor
from gui.SinglePatientComponent.LayersInteractorSidePanel.MRIVolumesInteractor.MRIVolumesLayerInteractor import MRIVolumesLayerInteractor
from gui.SinglePatientComponent.LayersInteractorSidePanel.AnnotationLayersInteractor.AnnotationsLayersInteractor import AnnotationsLayersInteractor
from gui.SinglePatientComponent.LayersInteractorSidePanel.AtlasLayersInteractor.AtlasesLayersInteractor import AtlasesLayersInteractor
from gui.SinglePatientComponent.LayersInteractorSidePanel.ActionsInteractor.ActionsInteractorWidget import ActionsInteractorWidget
from utils.software_config import SoftwareConfigResources

Expand Down Expand Up @@ -78,10 +75,6 @@ def __set_interface(self):
self.main_tabwidget.addTab(self.execution_actions_widget, "Actions")
self.overall_scrollarea_layout.addWidget(self.main_tabwidget)

self.volumes_collapsiblegroupbox = MRIVolumesLayerInteractor(self)
self.annotations_collapsiblegroupbox = AnnotationsLayersInteractor(self)
self.atlases_collapsiblegroupbox = AtlasesLayersInteractor(self)

self.overall_scrollarea_layout.addStretch(1)
self.overall_scrollarea_dummy_widget.setLayout(self.overall_scrollarea_layout)
self.overall_scrollarea.setWidget(self.overall_scrollarea_dummy_widget)
Expand Down Expand Up @@ -113,10 +106,6 @@ def __set_connections(self):
self.execution_actions_widget.pipeline_execution_requested.connect(self.pipeline_execution_requested)
self.main_tabwidget.currentChanged.connect(self.__on_main_tab_changed)

# @TODO. Can be removed, deprecated?
self.import_data_triggered.connect(self.volumes_collapsiblegroupbox.on_import_data)
self.import_data_triggered.connect(self.annotations_collapsiblegroupbox.on_import_data)

def __set_stylesheets(self):
software_ss = SoftwareConfigResources.getInstance().stylesheet_components
font_color = software_ss["Color7"]
Expand Down Expand Up @@ -186,8 +175,6 @@ def on_mri_volume_removed(self, uid):
objects_uids, error_msg = SoftwareConfigResources.getInstance().get_active_patient().remove_mri_volume(volume_uid=uid)
if SoftwareConfigResources.getInstance().get_active_patient().get_patient_mri_volumes_number() == 0:
self.volume_view_toggled.emit(uid, False)
self.annotations_collapsiblegroupbox.reset()
self.atlases_collapsiblegroupbox.reset()
# logging.info("[SinglePatientLayersWidget] on_mri_volume_removed took {} seconds.".format(time.time() - start))

def on_annotation_volume_import(self, uid):
Expand All @@ -210,9 +197,6 @@ def on_patient_selected(self, patient_uid: str) -> None:
patient_uid : str
The unique identifier of the newly selected active patient.
"""
# self.volumes_collapsiblegroupbox.reset()
# self.annotations_collapsiblegroupbox.reset()
# self.atlases_collapsiblegroupbox.reset()
self.patient_view_toggled.emit(patient_uid)

def on_import_patient(self, patient_uid: str) -> None:
Expand All @@ -237,9 +221,6 @@ def on_reset_interface(self) -> None:
Sets all inner widgets to their default states.
"""
self.timestamp_layer_widget.reset()
self.volumes_collapsiblegroupbox.reset()
self.annotations_collapsiblegroupbox.reset()
self.atlases_collapsiblegroupbox.reset()
self.execution_actions_widget.reset()

def on_batch_process_started(self) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ def adjustSize(self) -> None:
self.patient_list_scrollarea_dummy_widget.setFixedSize(QSize(self.size().width(), actual_height))
self.repaint()

def get_patient_results_widget_size(self) -> int:
return len(self.patient_results_widgets)

def get_patient_results_widget_by_index(self, index: int) -> SinglePatientResultsWidget:
if index >= len(self.patient_results_widgets):
raise ValueError(
Expand Down
4 changes: 3 additions & 1 deletion gui/SinglePatientComponent/SinglePatientWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ def __set_cross_connections(self):
self.import_folder_dialog.patient_imported.connect(self.layers_panel.on_import_patient)
self.import_dicom_dialog.patient_imported.connect(self.results_panel.on_import_patient)
self.import_dicom_dialog.mri_volume_imported.connect(self.layers_panel.on_mri_volume_import)
self.import_dicom_dialog.annotation_volume_imported.connect(self.layers_panel.on_annotation_volume_import)
self.layers_panel.import_data_requested.connect(self.__on_import_file_clicked)
self.layers_panel.import_dicom_requested.connect(self.__on_import_dicom_clicked)

Expand Down Expand Up @@ -337,4 +338,5 @@ def on_atlas_volume_imported(self, uid: str) -> None:

def on_clear_scene(self):
for w in list(self.results_panel.patient_results_widgets.keys()):
self.results_panel.patient_results_widgets[w].patient_closed.emit(w)
self.results_panel.patient_results_widgets[w].patient_closed.emit(w)
self.import_dicom_dialog.reset_interface()
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,24 @@ def adjustSize(self) -> None:
# logging.debug("Study patient listing scroll area size set to {}.\n".format(QSize(self.size().width(),
# actual_height)))

def get_study_patient_widget_length(self) -> int:
return len(self.study_patient_widgetitems)

def get_study_patient_widget_by_index(self, index: int) -> PatientListingWidgetItem:
if index >= len(self.study_patient_widgetitems):
raise ValueError(
"[StudyPatientListingWidget] Trying to retrieve a patient listing widget with an out-of-bound index value.")
return self.study_patient_widgetitems[list(self.study_patient_widgetitems.keys())[index]]

def on_reset_interface(self) -> None:
for wid in reversed(list(self.study_patient_widgetitems.keys())):
self.patients_list_scrollarea_layout.removeWidget(self.study_patient_widgetitems[wid])
self.study_patient_widgetitems[wid].setParent(None)
del self.study_patient_widgetitems[wid]
self.study_patient_widgetitems = {}
self.adjustSize()
self.repaint()

def on_patient_imported(self, patient_uid: str) -> None:
wid = PatientListingWidgetItem(patient_uid=patient_uid, parent=self)
self.study_patient_widgetitems[patient_uid] = wid
Expand Down Expand Up @@ -193,3 +211,10 @@ def on_process_started(self) -> None:

def on_process_finished(self):
self.patients_refresh_pushbutton.setEnabled(True)

def on_study_selected(self, uid: str) -> None:
"""
Working on the active study anyway here, so the uid parameter is not used for now.
"""
self.on_reset_interface()
self.on_study_imported(uid)
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ def __set_stylesheets(self):
def adjustSize(self) -> None:
pass

def on_reset_interface(self) -> None:
self.content_tree_widget.clear()

def on_patients_import(self) -> None:
self.content_tree_widget.clear()
study_patients_uid = SoftwareConfigResources.getInstance().get_active_study().included_patients_uids
Expand Down
Loading
Loading