From 083812df38e5e83d93aa81af789c7bef9e8a6f13 Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Mon, 22 Jan 2024 16:59:38 +0000 Subject: [PATCH 1/3] rotate_backend_functional --- mantidimaging/gui/windows/live_viewer/presenter.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/mantidimaging/gui/windows/live_viewer/presenter.py b/mantidimaging/gui/windows/live_viewer/presenter.py index f6b9168b214..3cbad0f5901 100644 --- a/mantidimaging/gui/windows/live_viewer/presenter.py +++ b/mantidimaging/gui/windows/live_viewer/presenter.py @@ -5,6 +5,7 @@ from pathlib import Path from typing import TYPE_CHECKING from logging import getLogger +import numpy as np from imagecodecs._deflate import DeflateError from tifffile import tifffile, TiffFileError @@ -12,11 +13,14 @@ from mantidimaging.gui.mvp_base import BasePresenter from mantidimaging.gui.windows.live_viewer.model import LiveViewerWindowModel, Image_Data +from mantidimaging.core.operations.rotate_stack import RotateFilter +from mantidimaging.core.data import ImageStack if TYPE_CHECKING: from mantidimaging.gui.windows.live_viewer.view import LiveViewerWindowView # pragma: no cover from mantidimaging.gui.windows.main.view import MainWindowView # pragma: no cover + logger = getLogger(__name__) @@ -84,6 +88,8 @@ def load_and_display_image(self, image_path: Path): elif image_path.suffix.lower() == ".fits": with fits.open(image_path.__str__()) as fit: image_data = fit[0].data + + image_data = self.rotate_image(image_data, 90) except (IOError, KeyError, ValueError, TiffFileError, DeflateError) as error: message = f"{type(error).__name__} reading image: {image_path}: {error}" logger.error(message) @@ -106,3 +112,11 @@ def update_image_modified(self, image_path: Path): """ if self.selected_image and image_path == self.selected_image.image_path: self.load_and_display_image(image_path) + + def rotate_image(self, image_data, ang: int): + image_data_shape = image_data.shape + image_data_temp = np.zeros(shape=(1, image_data_shape[0], image_data_shape[1])) + image_data_temp[0] = image_data + image_stack_temp = ImageStack(image_data_temp) + rotated_imaged = RotateFilter().filter_func(image_stack_temp, angle=ang) + return rotated_imaged.data[0].astype(int) From bf5b1e9d9f41b0656c332d8cd01e88e5ba565c1e Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Tue, 23 Jan 2024 16:05:08 +0000 Subject: [PATCH 2/3] Live Viewer rotate fully functional --- .../gui/windows/live_viewer/presenter.py | 10 ++++--- mantidimaging/gui/windows/live_viewer/view.py | 27 ++++++++++++++++++- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/mantidimaging/gui/windows/live_viewer/presenter.py b/mantidimaging/gui/windows/live_viewer/presenter.py index 3cbad0f5901..ec31f631852 100644 --- a/mantidimaging/gui/windows/live_viewer/presenter.py +++ b/mantidimaging/gui/windows/live_viewer/presenter.py @@ -20,7 +20,6 @@ from mantidimaging.gui.windows.live_viewer.view import LiveViewerWindowView # pragma: no cover from mantidimaging.gui.windows.main.view import MainWindowView # pragma: no cover - logger = getLogger(__name__) @@ -89,7 +88,7 @@ def load_and_display_image(self, image_path: Path): with fits.open(image_path.__str__()) as fit: image_data = fit[0].data - image_data = self.rotate_image(image_data, 90) + image_data = self.rotate_image(image_data) except (IOError, KeyError, ValueError, TiffFileError, DeflateError) as error: message = f"{type(error).__name__} reading image: {image_path}: {error}" logger.error(message) @@ -113,10 +112,15 @@ def update_image_modified(self, image_path: Path): if self.selected_image and image_path == self.selected_image.image_path: self.load_and_display_image(image_path) - def rotate_image(self, image_data, ang: int): + def rotate_image(self, image_data): + ang = self.view.image_rotation_angle image_data_shape = image_data.shape image_data_temp = np.zeros(shape=(1, image_data_shape[0], image_data_shape[1])) image_data_temp[0] = image_data image_stack_temp = ImageStack(image_data_temp) rotated_imaged = RotateFilter().filter_func(image_stack_temp, angle=ang) return rotated_imaged.data[0].astype(int) + + def update_image_operation(self): + """ Reload the current image if an operation has been performed on the current image""" + self.load_and_display_image(self.selected_image.image_path) diff --git a/mantidimaging/gui/windows/live_viewer/view.py b/mantidimaging/gui/windows/live_viewer/view.py index 54a13d6a0b9..d47757788bd 100644 --- a/mantidimaging/gui/windows/live_viewer/view.py +++ b/mantidimaging/gui/windows/live_viewer/view.py @@ -4,8 +4,9 @@ from pathlib import Path from typing import TYPE_CHECKING -from PyQt5.QtCore import QSignalBlocker +from PyQt5.QtCore import QSignalBlocker, QCoreApplication from PyQt5.QtWidgets import QVBoxLayout +from PyQt5.Qt import QAction, QActionGroup from mantidimaging.gui.mvp_base import BaseMainWindowView from .live_view_widget import LiveViewWidget @@ -16,6 +17,8 @@ if TYPE_CHECKING: from mantidimaging.gui.windows.main import MainWindowView # noqa:F401 # pragma: no cover +translate = QCoreApplication.translate + class LiveViewerWindowView(BaseMainWindowView): """ @@ -34,6 +37,23 @@ def __init__(self, main_window: 'MainWindowView', live_dir_path: Path) -> None: self.live_viewer = LiveViewWidget() self.imageLayout.addWidget(self.live_viewer) self.live_viewer.z_slider.valueChanged.connect(self.presenter.select_image) + self.image_rotation_angle = 0 + self.right_click_menu = self.live_viewer.image.vb.menu + rotate_menu = self.right_click_menu.addMenu(translate("ViewBox", "Rotate Image")) + rotate_angles_group = QActionGroup(self) + rotate_0 = QAction(translate("ViewBox", "0" + u'\N{DEGREE SIGN}'), rotate_angles_group) + rotate_90 = QAction(translate("ViewBox", "90" + u'\N{DEGREE SIGN}'), rotate_angles_group) + rotate_180 = QAction(translate("ViewBox", "180" + u'\N{DEGREE SIGN}'), rotate_angles_group) + rotate_270 = QAction(translate("ViewBox", "270" + u'\N{DEGREE SIGN}'), rotate_angles_group) + rotate_0.setCheckable(True) + rotate_90.setCheckable(True) + rotate_180.setCheckable(True) + rotate_270.setCheckable(True) + rotate_menu.addActions(rotate_angles_group.actions()) + rotate_0.triggered.connect(lambda: self.set_image_rotation_angle(0)) + rotate_90.triggered.connect(lambda: self.set_image_rotation_angle(90)) + rotate_180.triggered.connect(lambda: self.set_image_rotation_angle(180)) + rotate_270.triggered.connect(lambda: self.set_image_rotation_angle(270)) def show(self) -> None: """Show the window""" @@ -74,3 +94,8 @@ def closeEvent(self, e) -> None: self.live_viewer.handle_deleted() super().closeEvent(e) self.presenter = None # type: ignore # View instance to be destroyed -type can be inconsistent + + def set_image_rotation_angle(self, angle: int): + """Set the image rotation angle which will be read in by the presenter""" + self.image_rotation_angle = angle + self.presenter.update_image_operation() From 6f90f577bceeaf7d30ce91373978b9f4e8beefca Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Wed, 24 Jan 2024 12:22:48 +0000 Subject: [PATCH 3/3] removed lambda functions and clean up --- mantidimaging/gui/windows/live_viewer/view.py | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/mantidimaging/gui/windows/live_viewer/view.py b/mantidimaging/gui/windows/live_viewer/view.py index d47757788bd..988f0539db5 100644 --- a/mantidimaging/gui/windows/live_viewer/view.py +++ b/mantidimaging/gui/windows/live_viewer/view.py @@ -4,7 +4,7 @@ from pathlib import Path from typing import TYPE_CHECKING -from PyQt5.QtCore import QSignalBlocker, QCoreApplication +from PyQt5.QtCore import QSignalBlocker from PyQt5.QtWidgets import QVBoxLayout from PyQt5.Qt import QAction, QActionGroup @@ -17,8 +17,6 @@ if TYPE_CHECKING: from mantidimaging.gui.windows.main import MainWindowView # noqa:F401 # pragma: no cover -translate = QCoreApplication.translate - class LiveViewerWindowView(BaseMainWindowView): """ @@ -39,21 +37,16 @@ def __init__(self, main_window: 'MainWindowView', live_dir_path: Path) -> None: self.live_viewer.z_slider.valueChanged.connect(self.presenter.select_image) self.image_rotation_angle = 0 self.right_click_menu = self.live_viewer.image.vb.menu - rotate_menu = self.right_click_menu.addMenu(translate("ViewBox", "Rotate Image")) - rotate_angles_group = QActionGroup(self) - rotate_0 = QAction(translate("ViewBox", "0" + u'\N{DEGREE SIGN}'), rotate_angles_group) - rotate_90 = QAction(translate("ViewBox", "90" + u'\N{DEGREE SIGN}'), rotate_angles_group) - rotate_180 = QAction(translate("ViewBox", "180" + u'\N{DEGREE SIGN}'), rotate_angles_group) - rotate_270 = QAction(translate("ViewBox", "270" + u'\N{DEGREE SIGN}'), rotate_angles_group) - rotate_0.setCheckable(True) - rotate_90.setCheckable(True) - rotate_180.setCheckable(True) - rotate_270.setCheckable(True) - rotate_menu.addActions(rotate_angles_group.actions()) - rotate_0.triggered.connect(lambda: self.set_image_rotation_angle(0)) - rotate_90.triggered.connect(lambda: self.set_image_rotation_angle(90)) - rotate_180.triggered.connect(lambda: self.set_image_rotation_angle(180)) - rotate_270.triggered.connect(lambda: self.set_image_rotation_angle(270)) + rotate_menu = self.right_click_menu.addMenu("Rotate Image") + self.rotate_angles_group = QActionGroup(self) + allowed_angles = [0, 90, 180, 270] + for angle in allowed_angles: + action = QAction(str(angle) + "°", self.rotate_angles_group) + action.setCheckable(True) + rotate_menu.addAction(action) + action.triggered.connect(self.set_image_rotation_angle) + if angle == 0: + action.setChecked(True) def show(self) -> None: """Show the window""" @@ -95,7 +88,7 @@ def closeEvent(self, e) -> None: super().closeEvent(e) self.presenter = None # type: ignore # View instance to be destroyed -type can be inconsistent - def set_image_rotation_angle(self, angle: int): + def set_image_rotation_angle(self): """Set the image rotation angle which will be read in by the presenter""" - self.image_rotation_angle = angle + self.image_rotation_angle = int(self.rotate_angles_group.checkedAction().text().replace('°', '')) self.presenter.update_image_operation()