From 88bba056fb80fa06c9a464a3e1d587a6c43ecdcd Mon Sep 17 00:00:00 2001 From: Angeleene Ang Date: Sun, 4 Sep 2022 10:41:49 +0300 Subject: [PATCH] compiled uncomiited changes --- MRICenterline/app/config/internal_config.py | 2 +- MRICenterline/app/export/centerline.py | 8 ++ MRICenterline/app/export/export.py | 6 +- .../app/file_reader/dicom/DICOMReader.py | 7 +- .../app/gui_data_handling/case_model.py | 7 +- .../app/gui_data_handling/centerline_model.py | 6 ++ .../app/gui_data_handling/sequence_model.py | 2 +- .../slice_loc_based_image_properties.py | 2 +- MRICenterline/app/points/import_from_v3.py | 22 +++-- MRICenterline/gui/display/toolbar.py | 8 +- MRICenterline/gui/display/toolbar_connect.py | 6 +- MRICenterline/gui/export/__init__.py | 0 MRICenterline/gui/export/dialog_box.py | 93 +++++++++++++++++++ MRICenterline/gui/window/MainWindow.py | 4 + MRICenterline/gui/window/Toolbar.py | 11 +++ MRICenterline/gui/window/toolbar_connect.py | 6 +- 16 files changed, 163 insertions(+), 27 deletions(-) create mode 100644 MRICenterline/app/export/centerline.py create mode 100644 MRICenterline/gui/export/__init__.py create mode 100644 MRICenterline/gui/export/dialog_box.py diff --git a/MRICenterline/app/config/internal_config.py b/MRICenterline/app/config/internal_config.py index ad9c1fd..e4c36cb 100644 --- a/MRICenterline/app/config/internal_config.py +++ b/MRICenterline/app/config/internal_config.py @@ -1,6 +1,6 @@ APP_NAME = "ImageUntangler" APP_BYLINE = "TCML" -VER_NUMBER = '4.1.0' +VER_NUMBER = '4.1.1' WINDOW_NAME = f'{APP_NAME} by {APP_BYLINE} v-{VER_NUMBER}' BG_COLOR = (204 / 255, 204 / 255, 204 / 255) diff --git a/MRICenterline/app/export/centerline.py b/MRICenterline/app/export/centerline.py new file mode 100644 index 0000000..7dfafd0 --- /dev/null +++ b/MRICenterline/app/export/centerline.py @@ -0,0 +1,8 @@ +import logging +logging.getLogger(__name__) + + +def export(cl_model, destination, options): + logging.info(f"Exporting CL to {destination} as {options}") + + print(cl_model.nparray.shape) diff --git a/MRICenterline/app/export/export.py b/MRICenterline/app/export/export.py index 5985a53..56f9383 100644 --- a/MRICenterline/app/export/export.py +++ b/MRICenterline/app/export/export.py @@ -12,9 +12,9 @@ class ExportType(Enum): - DICOM = auto() - IPYNB = auto() - PNG = auto() + DICOM = "DICOM" + PNG = "PNG" + NPZ = "NPZ" def export(image_properties: ImageProperties, diff --git a/MRICenterline/app/file_reader/dicom/DICOMReader.py b/MRICenterline/app/file_reader/dicom/DICOMReader.py index a9af319..f9ad912 100644 --- a/MRICenterline/app/file_reader/dicom/DICOMReader.py +++ b/MRICenterline/app/file_reader/dicom/DICOMReader.py @@ -112,8 +112,11 @@ def get_z_coords(self, seq, use_v3=False): return self.get_z_coords(self.sequence_list[seq]) def get_image_orientation(self, item) -> ImageOrientation: - file = self.get_file_list(item)[0] - return DICOMImageOrientation.get_image_orientation(file) + if len(self.get_file_list(item)): + file = self.get_file_list(item)[0] + return DICOMImageOrientation.get_image_orientation(file) + else: + return ImageOrientation.UNKNOWN @staticmethod def generate(file_list, use_v3=False): diff --git a/MRICenterline/app/gui_data_handling/case_model.py b/MRICenterline/app/gui_data_handling/case_model.py index 28572ad..ebc57b9 100644 --- a/MRICenterline/app/gui_data_handling/case_model.py +++ b/MRICenterline/app/gui_data_handling/case_model.py @@ -82,8 +82,11 @@ def save(self): return session_id - def export(self, destination: str): - self.sequence_manager.export(destination) + def export(self, destination: str, display_options: dict, centerline_options: dict): + # self.sequence_manager.export(destination, display_options) + + if self.centerline_model: + self.centerline_model.export(destination, centerline_options) def set_picker_status(self, status: PickerStatus): logging.debug(f"Setting display panel picker status to {status}") diff --git a/MRICenterline/app/gui_data_handling/centerline_model.py b/MRICenterline/app/gui_data_handling/centerline_model.py index c525475..7f8e28c 100644 --- a/MRICenterline/app/gui_data_handling/centerline_model.py +++ b/MRICenterline/app/gui_data_handling/centerline_model.py @@ -31,6 +31,7 @@ def __init__(self, case_model): self.angle = 0 self.vtk_data = vtkImageData() + self.nparray = None self.point_markers = VerticalLineArray() self.has_highlight = False @@ -62,6 +63,10 @@ def save(self): # TODO: save measurements from centerline calculations print("save points") + def export(self, destination, export_options): + from MRICenterline.app.export.centerline import export + export(self, destination, export_options) + def refresh_panel(self, angle_change=None, height_change=None): self.calculate_centerline() self.centerline_viewer.refresh_panel(angle_change, height_change) @@ -145,6 +150,7 @@ def _calculate(): height=self.height, angle_degrees=self.angle) + self.nparray = ppv.MPR_M self.vtk_data = vtk_transform(ppv) self.parallel_scale = self.parallel_scale * ppv.delta * \ (self.vtk_data.GetExtent()[1] - self.vtk_data.GetExtent()[0]) diff --git a/MRICenterline/app/gui_data_handling/sequence_model.py b/MRICenterline/app/gui_data_handling/sequence_model.py index 4fd2b1f..d03a524 100644 --- a/MRICenterline/app/gui_data_handling/sequence_model.py +++ b/MRICenterline/app/gui_data_handling/sequence_model.py @@ -197,7 +197,7 @@ def load_points(self, length_id, mpr_id): logging.info(f"Reading from MPR [{mpr_id}]") - def export(self, destination): + def export(self, destination, export_options): from MRICenterline.app.export import export export(self.current_image_properties, diff --git a/MRICenterline/app/gui_data_handling/slice_loc_based_image_properties.py b/MRICenterline/app/gui_data_handling/slice_loc_based_image_properties.py index 019c3c2..48611be 100644 --- a/MRICenterline/app/gui_data_handling/slice_loc_based_image_properties.py +++ b/MRICenterline/app/gui_data_handling/slice_loc_based_image_properties.py @@ -16,7 +16,7 @@ def __init__(self, np_array, image_orientation, z_coords, file_list, parent=None sitk_image = sitk.ReadImage(file_list) - super().__init__(sitk_image=sitk_image, image_orientation=image_orientation, parent=parent) + super().__init__(sitk_image=sitk_image, image_orientation=image_orientation, parent=parent, z_coords=z_coords) self.vtk_data = self.get_vtk_data_old(np_arr=np_array, origin=sitk_image.GetOrigin(), diff --git a/MRICenterline/app/points/import_from_v3.py b/MRICenterline/app/points/import_from_v3.py index 91194a1..6778012 100644 --- a/MRICenterline/app/points/import_from_v3.py +++ b/MRICenterline/app/points/import_from_v3.py @@ -77,31 +77,35 @@ def parse_points(self, points_from_json, point_array: PointArray): case_id=get_case_id(self.case_name), folder=self.filename.parents[1], is_new_case=False) + image_orientation = dcm_reader.get_image_orientation(self.sequence_name) + z_coords = dcm_reader.get_z_coords(self.sequence_name) if CFG.get_testing_status("use-slice-location"): np_array, file_list = dcm_reader[self.sequence_name] image_properties = SliceLocImageProperties(np_array=np_array, - z_coords=dcm_reader.get_z_coords(self.sequence_name), - file_list=file_list) + z_coords=z_coords, + file_list=file_list, + image_orientation=image_orientation) for pt in points_from_json: parsed = Point.point_from_vtk_coords(pt, image_properties) point_array.add_point(parsed) else: - image_properties = ImageProperties(dcm_reader[self.sequence_name]) + image_properties = ImageProperties(sitk_image=dcm_reader[self.sequence_name], + image_orientation=image_orientation, + z_coords=z_coords) v3_file_list = dcm_reader.get_file_list(self.sequence_name, use_v3=True) v3_np_arr, clean_file_list = DICOMReader.generate(file_list=v3_file_list, use_v3=True) - v3_z_coords = dcm_reader.get_z_coords(seq=self.sequence_name, use_v3=True) v3_image_properties = SliceLocImageProperties(np_array=v3_np_arr, - z_coords=v3_z_coords, - file_list=clean_file_list) - image_orientation = dcm_reader.get_image_orientation(self.sequence_name) + z_coords=z_coords, + file_list=clean_file_list, + image_orientation=image_orientation) if dcm_reader.case_name in ['106', '16']: print(f"SKIPPING {dcm_reader.case_id}") # known problematic cases else: - assert len(v3_z_coords) == v3_image_properties.size[2], "z_coord list must be same as size of the image" + assert len(z_coords) == v3_image_properties.size[2], "z_coord list must be same as size of the image" for pt in points_from_json: parsed = Point.point_from_v3(image_coordinates=pt, @@ -109,7 +113,7 @@ def parse_points(self, points_from_json, point_array: PointArray): v3_image_size=v3_image_properties.size, v3_image_spacing=v3_image_properties.spacing, v3_image_dimensions=v3_image_properties.dimensions, - v3_z_coords=v3_z_coords, + v3_z_coords=z_coords, image_orientation=image_orientation) point_array.add_point(parsed) diff --git a/MRICenterline/gui/display/toolbar.py b/MRICenterline/gui/display/toolbar.py index 125f2e3..1f991e2 100644 --- a/MRICenterline/gui/display/toolbar.py +++ b/MRICenterline/gui/display/toolbar.py @@ -42,7 +42,7 @@ def enable_length_picking(s): else: toolbar_connect.set_picker_status(model, PickerStatus.NOT_PICKING) - length_button = QPushButton(qta.icon('mdi.ruler'), "Length") + length_button = QPushButton(qta.icon('mdi.ruler'), "Add Length Points") length_button.setCheckable(True) length_button.setChecked(False) layout.addWidget(length_button, 0, column, 1, 1) @@ -66,7 +66,7 @@ def enable_mpr_picking(s): else: toolbar_connect.set_picker_status(model, PickerStatus.NOT_PICKING) - mpr_button = QPushButton(qta.icon('mdi.image-filter-center-focus-strong'), "MPR") + mpr_button = QPushButton(qta.icon('mdi.image-filter-center-focus-strong'), "Add MPR points") mpr_button.setCheckable(True) layout.addWidget(mpr_button, 0, column, 1, 1) mpr_button.clicked.connect(enable_mpr_picking) @@ -81,7 +81,7 @@ def enable_mpr_picking(s): # region additional MPR functions column += 1 # NEW COLUMN - select_point_button = QPushButton(qta.icon("mdi.select-search"), "Select MPR point") + select_point_button = QPushButton(qta.icon("mdi.select-search"), "Highlight MPR point") layout.addWidget(select_point_button, 0, column, 1, 1) select_point_button.setFlat(True) select_point_button.clicked.connect(lambda: toolbar_connect.find_point(model)) @@ -140,7 +140,7 @@ def reset_other_picker_buttons(s): length_button.setChecked(not s) mpr_button.setChecked(not s) - window_level_button = QPushButton(qta.icon('mdi.lock-reset'), "Window/level") + window_level_button = QPushButton(qta.icon('mdi.lock-reset'), "Change Window/level") window_level_button.setCheckable(True) window_level_button.setChecked(True) layout.addWidget(window_level_button, 0, column, 1, 1) diff --git a/MRICenterline/gui/display/toolbar_connect.py b/MRICenterline/gui/display/toolbar_connect.py index c23dd46..ffce755 100644 --- a/MRICenterline/gui/display/toolbar_connect.py +++ b/MRICenterline/gui/display/toolbar_connect.py @@ -43,9 +43,9 @@ def find_point(model): def export(model, parent): - from PyQt5.QtWidgets import QFileDialog - destination = str(QFileDialog.getExistingDirectory(parent, "Select destination")) - model.export(destination) + from MRICenterline.gui.export.dialog_box import ExportDialogBox + export_db = ExportDialogBox(model=model, parent=parent) + export_db.show() def toggle_mpr_marker(model, show: bool): diff --git a/MRICenterline/gui/export/__init__.py b/MRICenterline/gui/export/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/MRICenterline/gui/export/dialog_box.py b/MRICenterline/gui/export/dialog_box.py new file mode 100644 index 0000000..e9d437c --- /dev/null +++ b/MRICenterline/gui/export/dialog_box.py @@ -0,0 +1,93 @@ +from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QHBoxLayout, QVBoxLayout, QGroupBox, QRadioButton, QCheckBox, \ + QFileDialog, QPushButton +from PyQt5.Qt import Qt + +from MRICenterline import CFG + +import logging + +from MRICenterline.app.export import ExportType + +logging.getLogger(__name__) + + +class ExportDialogBox(QDialog): + def __init__(self, model, parent=None): + super().__init__(parent) + self.model = model + self.parent = parent + self.destination = None + + layout = QVBoxLayout(self) + self.setLayout(layout) + + # display export + + self.display_export_box = QGroupBox("Export Display image as") + self.display_export_box.setCheckable(True) + layout.addWidget(self.display_export_box) + + display_export_layout = QVBoxLayout() + self.display_export_box.setLayout(display_export_layout) + + self.display_format_buttons = [] + for fmt in ExportType: + radiobutton = QRadioButton(fmt.value) + display_export_layout.addWidget(radiobutton) + self.display_format_buttons.append((fmt, radiobutton)) + + self.display_length = QCheckBox("Include Length Measurements") + display_export_layout.addWidget(self.display_length) + + self.display_annotation = QCheckBox("Include Annotations") + display_export_layout.addWidget(self.display_annotation) + + # centerline export + + self.centerline_export_box = QGroupBox("Export centerline image as") + self.centerline_export_box.setCheckable(True) + layout.addWidget(self.centerline_export_box) + + centerline_export_layout = QVBoxLayout() + self.centerline_export_box.setLayout(centerline_export_layout) + + self.centerline_format_buttons = [] + for fmt in ExportType: + radiobutton = QRadioButton(fmt.value) + centerline_export_layout.addWidget(radiobutton) + self.centerline_format_buttons.append((fmt, radiobutton)) + + self.centerline_length = QCheckBox("Include Length Measurements") + centerline_export_layout.addWidget(self.centerline_length) + + buttons = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel) + buttons.accepted.connect(self.select_destination) + buttons.accepted.connect(self.compile_export_options) + buttons.rejected.connect(self.reject) + + layout.addWidget(buttons) + + def select_destination(self): + self.destination = str(QFileDialog.getExistingDirectory(self.parent, "Select destination")) + logging.info(f"Exporting to {self.destination}") + + def compile_export_options(self): + display_export_options = dict() + centerline_export_options = dict() + + if self.display_export_box.isChecked(): + for fmt, button in self.display_format_buttons: + if button.isChecked(): + display_export_options['format'] = fmt + + display_export_options['length'] = self.display_length.isChecked() + display_export_options['annotation'] = self.display_annotation.isChecked() + + if self.centerline_export_box.isChecked(): + for fmt, button in self.centerline_format_buttons: + if button.isChecked(): + centerline_export_options['format'] = fmt + + centerline_export_options['length'] = self.centerline_length.isChecked() + + self.model.export(self.destination, display_export_options, centerline_export_options) diff --git a/MRICenterline/gui/window/MainWindow.py b/MRICenterline/gui/window/MainWindow.py index fe367b8..a9d0298 100644 --- a/MRICenterline/gui/window/MainWindow.py +++ b/MRICenterline/gui/window/MainWindow.py @@ -1,6 +1,7 @@ from PyQt5.QtCore import QSize from PyQt5.QtWidgets import QMainWindow, QStackedWidget, QWidget, QToolBar +from MRICenterline.gui.splash.connect import open_using_file_dialog from MRICenterline.gui.window.Toolbar import IUToolbar from MRICenterline.gui.splash.InitialWidget import IUInitialWidget @@ -50,3 +51,6 @@ def open_new_case(self): self.addToolBar(self.toolbar) self.setWindowTitle(CONST.WINDOW_NAME) + + def open_new_case_from_folder(self): + open_using_file_dialog(self) diff --git a/MRICenterline/gui/window/Toolbar.py b/MRICenterline/gui/window/Toolbar.py index ec0a2da..ac89973 100644 --- a/MRICenterline/gui/window/Toolbar.py +++ b/MRICenterline/gui/window/Toolbar.py @@ -16,6 +16,11 @@ def __init__(self, parent=None): self.addWidget(new_case_button) new_case_button.clicked.connect(parent.open_new_case) + new_case_folder_button = QPushButton(qta.icon('fa.gear'), "New Case from Folder") + new_case_folder_button.setFlat(True) + self.addWidget(new_case_folder_button) + new_case_folder_button.clicked.connect(parent.open_new_case_from_folder) + setting_button = QPushButton(qta.icon('fa.gear'), "Settings") setting_button.setFlat(True) self.addWidget(setting_button) @@ -26,3 +31,9 @@ def __init__(self, parent=None): self.addWidget(help_button) help_button.clicked.connect(lambda: toolbar_connect.open_help_dialog(self)) help_button.setEnabled(CFG.get_testing_status(testing=None)) # TODO: populate the help dialog + + if CFG.get_testing_status(testing=None): + dialog_test = QPushButton(qta.icon('fa5s.info-circle'), "Test dialog") + dialog_test.setFlat(True) + self.addWidget(dialog_test) + dialog_test.clicked.connect(lambda: toolbar_connect.dialog_box_test(self)) diff --git a/MRICenterline/gui/window/toolbar_connect.py b/MRICenterline/gui/window/toolbar_connect.py index ce69a19..d5a455e 100644 --- a/MRICenterline/gui/window/toolbar_connect.py +++ b/MRICenterline/gui/window/toolbar_connect.py @@ -5,4 +5,8 @@ def open_help_dialog(parent): - pass \ No newline at end of file + pass + + +def dialog_box_test(parent): + pass