From 1b4264ac5d7b089f8769d3106ccadc4b15122498 Mon Sep 17 00:00:00 2001 From: ashmeigh Date: Wed, 3 Jul 2024 14:59:21 +0100 Subject: [PATCH 1/8] added type checking to the liveviewer and recon --- .../gui/windows/live_viewer/model.py | 10 ++-- .../gui/windows/live_viewer/presenter.py | 13 ++--- mantidimaging/gui/windows/live_viewer/view.py | 2 +- mantidimaging/gui/windows/recon/image_view.py | 32 ++++++------- mantidimaging/gui/windows/recon/model.py | 10 ++-- .../gui/windows/recon/point_table_model.py | 48 +++++++++++-------- mantidimaging/gui/windows/recon/presenter.py | 35 ++++++++------ mantidimaging/gui/windows/recon/view.py | 22 ++++----- 8 files changed, 93 insertions(+), 79 deletions(-) diff --git a/mantidimaging/gui/windows/live_viewer/model.py b/mantidimaging/gui/windows/live_viewer/model.py index ad984214334..dd8fe2c96e4 100644 --- a/mantidimaging/gui/windows/live_viewer/model.py +++ b/mantidimaging/gui/windows/live_viewer/model.py @@ -277,7 +277,7 @@ def _is_image_file(file_name: str) -> bool: image_extensions = ('tif', 'tiff', 'fits') return file_name.rpartition(".")[2].lower() in image_extensions - def remove_path(self): + def remove_path(self) -> None: """ Remove the currently set path """ @@ -292,22 +292,22 @@ def update_recent_watcher(self, images: list[Image_Data]) -> None: self.recent_file_watcher.removePaths(self.recent_file_watcher.files()) self.recent_file_watcher.addPaths([str(image.image_path) for image in images]) - def handle_image_modified(self, file_path): + def handle_image_modified(self, file_path) -> None: self.recent_image_changed.emit(Path(file_path)) - def add_sub_directory(self, sub_dir: SubDirectory): + def add_sub_directory(self, sub_dir: SubDirectory) -> None: if sub_dir.path not in self.sub_directories: self.watcher.addPath(str(sub_dir.path)) self.sub_directories[sub_dir.path] = sub_dir - def remove_sub_directory(self, sub_dir: Path): + def remove_sub_directory(self, sub_dir: Path) -> None: if sub_dir in self.sub_directories: self.watcher.removePath(str(sub_dir)) del self.sub_directories[sub_dir] - def clear_deleted_sub_directories(self, directory: Path): + def clear_deleted_sub_directories(self, directory: Path) -> None: for sub_dir in list(self.sub_directories): if sub_dir.is_relative_to(directory) and not sub_dir.exists(): self.remove_sub_directory(sub_dir) diff --git a/mantidimaging/gui/windows/live_viewer/presenter.py b/mantidimaging/gui/windows/live_viewer/presenter.py index 2c1294ab2db..8945f4194bd 100644 --- a/mantidimaging/gui/windows/live_viewer/presenter.py +++ b/mantidimaging/gui/windows/live_viewer/presenter.py @@ -82,7 +82,7 @@ def select_image(self, index: int) -> None: self.display_image(self.selected_image.image_path) - def display_image(self, image_path: Path): + def display_image(self, image_path: Path) -> None: """ Display image in the view after validating contents """ @@ -119,20 +119,21 @@ def load_image(image_path: Path) -> np.ndarray: image_data = fit[0].data return image_data - def update_image_modified(self, image_path: Path): + def update_image_modified(self, image_path: Path) -> None: """ Update the displayed image when the file is modified """ if self.selected_image and image_path == self.selected_image.image_path: self.display_image(image_path) - def update_image_operation(self): + def update_image_operation(self) -> None: """ Reload the current image if an operation has been performed on the current image """ - self.display_image(self.selected_image.image_path) + if self.selected_image is not None: + self.display_image(self.selected_image.image_path) - def convert_image_to_imagestack(self, image_data): + def convert_image_to_imagestack(self, image_data) -> ImageStack: """ Convert the single image to an imagestack so the Operations framework can be used """ @@ -141,7 +142,7 @@ def convert_image_to_imagestack(self, image_data): image_data_temp[0] = image_data return ImageStack(image_data_temp) - def perform_operations(self, image_data): + def perform_operations(self, image_data) -> np.ndarray: if not self.view.filter_params: return image_data image_stack = self.convert_image_to_imagestack(image_data) diff --git a/mantidimaging/gui/windows/live_viewer/view.py b/mantidimaging/gui/windows/live_viewer/view.py index ee4ec458ecf..be963295899 100644 --- a/mantidimaging/gui/windows/live_viewer/view.py +++ b/mantidimaging/gui/windows/live_viewer/view.py @@ -91,7 +91,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): + def set_image_rotation_angle(self) -> None: """Set the image rotation angle which will be read in by the presenter""" if self.rotate_angles_group.checkedAction().text() == "0°": if "Rotate Stack" in self.filter_params: diff --git a/mantidimaging/gui/windows/recon/image_view.py b/mantidimaging/gui/windows/recon/image_view.py index 4683c14c880..a57aea68e3a 100644 --- a/mantidimaging/gui/windows/recon/image_view.py +++ b/mantidimaging/gui/windows/recon/image_view.py @@ -51,7 +51,7 @@ def __init__(self, parent): self.imageview_projection.enable_nonpositive_check() self.imageview_sinogram.enable_nonpositive_check() - def cleanup(self): + def cleanup(self) -> None: self.imageview_projection.cleanup() self.imageview_sinogram.cleanup() self.imageview_recon.cleanup() @@ -60,10 +60,10 @@ def cleanup(self): del self.imageview_sinogram del self.imageview_recon - def slice_line_moved(self): + def slice_line_moved(self) -> None: self.slice_changed(int(self.slice_line.value())) - def update_projection(self, image_data: np.ndarray, preview_slice_index: int, tilt_angle: Degrees | None): + def update_projection(self, image_data: np.ndarray, preview_slice_index: int, tilt_angle: Degrees | None) -> None: self.imageview_projection.clear() self.imageview_projection.setImage(image_data) self.imageview_projection.histogram.imageChanged(autoLevel=True, autoRange=True) @@ -75,13 +75,13 @@ def update_projection(self, image_data: np.ndarray, preview_slice_index: int, ti self.hide_tilt() set_histogram_log_scale(self.imageview_projection.histogram) - def update_sinogram(self, image): + def update_sinogram(self, image) -> None: self.imageview_sinogram.clear() self.imageview_sinogram.setImage(image) self.imageview_sinogram.histogram.imageChanged(autoLevel=True, autoRange=True) set_histogram_log_scale(self.imageview_sinogram.histogram) - def update_recon(self, image_data, reset_roi: bool = False): + def update_recon(self, image_data, reset_roi: bool = False) -> None: self.imageview_recon.clear() self.imageview_recon.setImage(image_data, autoLevels=False) set_histogram_log_scale(self.imageview_recon.histogram) @@ -90,34 +90,34 @@ def update_recon(self, image_data, reset_roi: bool = False): else: self.recon_line_profile.update() - def update_recon_hist(self): + def update_recon_hist(self) -> None: self.imageview_recon.histogram.imageChanged(autoLevel=True, autoRange=True) - def mouse_click(self, ev, line: InfiniteLine): + def mouse_click(self, ev, line: InfiniteLine) -> None: line.setPos(ev.pos()) self.slice_changed(CloseEnoughPoint(ev.pos()).y) - def slice_changed(self, slice_index): + def slice_changed(self, slice_index) -> None: self.parent.presenter.do_preview_reconstruct_slice(slice_idx=slice_index) self.sigSliceIndexChanged.emit(slice_index) - def clear_recon(self): + def clear_recon(self) -> None: self.imageview_recon.clear() - def clear_recon_line_profile(self): + def clear_recon_line_profile(self) -> None: self.recon_line_profile.clear_plot() - def clear_sinogram(self): + def clear_sinogram(self) -> None: self.imageview_sinogram.clear() - def clear_projection(self): + def clear_projection(self) -> None: self.imageview_projection.clear() - def reset_slice_and_tilt(self, slice_index): + def reset_slice_and_tilt(self, slice_index) -> None: self.slice_line.setPos(slice_index) self.hide_tilt() - def hide_tilt(self): + def hide_tilt(self) -> None: """ Hides the tilt line. This stops infinite zooming out loop that messes up the image view (the line likes to be unbound when the degree isn't a multiple o 90 - and the tilt never is) @@ -126,7 +126,7 @@ def hide_tilt(self): if self.tilt_line.scene() is not None: self.imageview_projection.viewbox.removeItem(self.tilt_line) - def set_tilt(self, tilt: Degrees, pos: int | None = None): + def set_tilt(self, tilt: Degrees, pos: int | None = None) -> None: if not isnan(tilt.value): # is isnan it means there is no tilt, i.e. the line is vertical if pos is not None: self.tilt_line.setAngle(90) @@ -134,5 +134,5 @@ def set_tilt(self, tilt: Degrees, pos: int | None = None): self.tilt_line.setAngle(90 + tilt.value) self.imageview_projection.viewbox.addItem(self.tilt_line) - def reset_recon_histogram(self): + def reset_recon_histogram(self) -> None: self.imageview_recon.histogram.autoHistogramRange() diff --git a/mantidimaging/gui/windows/recon/model.py b/mantidimaging/gui/windows/recon/model.py index 7fb7cdcf7fb..9cb2f5af815 100644 --- a/mantidimaging/gui/windows/recon/model.py +++ b/mantidimaging/gui/windows/recon/model.py @@ -82,7 +82,7 @@ def images(self): def num_points(self) -> int: return self.data_model.num_points - def initial_select_data(self, images: ImageStack | None): + def initial_select_data(self, images: ImageStack | None) -> None: self._images = images self.reset_cor_model() @@ -137,7 +137,7 @@ def run_preview_recon(self, recon = self._apply_pixel_size(recon, recon_params) return recon - def run_full_recon(self, recon_params: ReconstructionParameters, progress: Progress) -> ImageStack | None: + def run_full_recon(self, recon_params: ReconstructionParameters, progress: Progress) -> ReconstructedObject: # Ensure we have some sample data images = self.images if images is None: @@ -151,7 +151,7 @@ def run_full_recon(self, recon_params: ReconstructionParameters, progress: Progr return recon @staticmethod - def _apply_pixel_size(recon, recon_params: ReconstructionParameters, progress=None): + def _apply_pixel_size(recon, recon_params: ReconstructionParameters, progress=None) -> ImageStack | None: if recon_params.pixel_size > 0.: recon = DivideFilter.filter_func(recon, value=recon_params.pixel_size, unit="micron", progress=progress) # update the reconstructed stack pixel size with the value actually used for division @@ -255,7 +255,7 @@ def auto_find_correlation(self, progress: Progress) -> tuple[ScalarCoR, Degrees] return find_center(self.images, progress) @staticmethod - def proj_180_degree_shape_matches_images(images): + def proj_180_degree_shape_matches_images(images) -> None: return images.has_proj180deg() and images.height == images.proj180deg.height \ and images.width == images.proj180deg.width @@ -269,7 +269,7 @@ def stack_contains_negative_values(self) -> bool: return bool(np.any(self.images.data < 0)) @property - def stack_id(self): + def stack_id(self) -> uuid.UUID | None: if self.images is not None: return self.images.id return None diff --git a/mantidimaging/gui/windows/recon/point_table_model.py b/mantidimaging/gui/windows/recon/point_table_model.py index e0578357199..2516681d6df 100644 --- a/mantidimaging/gui/windows/recon/point_table_model.py +++ b/mantidimaging/gui/windows/recon/point_table_model.py @@ -3,6 +3,7 @@ from __future__ import annotations from enum import Enum +from typing import Any, Optional, Union from PyQt5.QtCore import QAbstractTableModel, QModelIndex, Qt @@ -29,54 +30,59 @@ class CorTiltPointQtModel(QAbstractTableModel, CorTiltDataModel): def __init__(self, parent=None): super().__init__(parent) - def populate_slice_indices(self, begin, end, count, cor=0.0): + def populate_slice_indices(self, begin: int, end: int, count: int, cor: float = 0.0) -> None: self.beginResetModel() super().populate_slice_indices(begin, end, count, cor) self.endResetModel() - def sort_points(self): + def sort_points(self) -> None: self.layoutAboutToBeChanged.emit() super().sort_points() self.layoutChanged.emit() - def set_point(self, idx, slice_idx: int | None = None, cor: float | None = None, reset_results=True): + def set_point(self, + idx: int, + slice_idx: int | None = None, + cor: float | None = None, + reset_results: bool = True) -> None: super().set_point(idx, slice_idx, cor, reset_results) self.dataChanged.emit(self.index(idx, 0), self.index(idx, 1)) - def columnCount(self, parent=None): + def columnCount(self, parent: QModelIndex | None = None) -> int: return 2 - def rowCount(self, parent): + def rowCount(self, parent: QModelIndex) -> int: if parent.isValid(): return 0 return self.num_points - def flags(self, index): + def flags(self, index: QModelIndex) -> Qt.ItemFlags: flags = super().flags(index) flags |= Qt.ItemFlag.ItemIsEditable return flags - def data(self, index, role=Qt.ItemDataRole.DisplayRole): - if not index.isValid(): + def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole) -> int | str | float | None: + if not index.isValid() or index.row() >= len(self._points): return None - col = index.column() - col_field = Column(col) + point: Point = self._points[index.row()] + col_field: Column = Column(index.column()) if role == Qt.ItemDataRole.DisplayRole: if col_field == Column.SLICE_INDEX: - return self._points[index.row()].slice_index - if col_field == Column.CENTRE_OF_ROTATION: - return self._points[index.row()].cor + return point.slice_index + elif col_field == Column.CENTRE_OF_ROTATION: + return point.cor elif role == Qt.ItemDataRole.ToolTipRole: if col_field == Column.SLICE_INDEX: return 'Slice index (y coordinate of projection)' elif col_field == Column.CENTRE_OF_ROTATION: return 'Centre of rotation for specific slice' - return '' - def getColumn(self, column_index) -> list[int]: + return None + + def getColumn(self, column_index: int) -> list[int]: if column_index != 0 and column_index != 1: return [] else: @@ -85,7 +91,7 @@ def getColumn(self, column_index) -> list[int]: column.append(point.slice_index) return column - def setData(self, index, val, role=Qt.ItemDataRole.EditRole): + def setData(self, index: QModelIndex, val: Any, role: Qt.ItemDataRole = Qt.ItemDataRole.EditRole) -> bool: if role != Qt.ItemDataRole.EditRole: return False @@ -107,7 +113,7 @@ def setData(self, index, val, role=Qt.ItemDataRole.EditRole): return True - def insertRows(self, row, count, parent=None, slice_idx: int | None = None, cor: float | None = None): + def insertRows(self, row, count, parent=None, slice_idx: int | None = None, cor: float | None = None) -> None: self.beginInsertRows(parent if parent is not None else QModelIndex(), row, row + count - 1) for _ in range(count): @@ -115,7 +121,7 @@ def insertRows(self, row, count, parent=None, slice_idx: int | None = None, cor: self.endInsertRows() - def removeRows(self, row, count, parent=None): + def removeRows(self, row, count, parent=None) -> None: if self.empty: return @@ -126,7 +132,7 @@ def removeRows(self, row, count, parent=None): self.endRemoveRows() - def removeAllRows(self, parent=None): + def removeAllRows(self, parent: QModelIndex | None = None) -> None: if self.empty: return @@ -134,12 +140,12 @@ def removeAllRows(self, parent=None): self.clear_points() self.endRemoveRows() - def appendNewRow(self, row: int, slice_idx: int, cor: float = 0.0): + def appendNewRow(self, row: int, slice_idx: int, cor: float = 0.0) -> None: self.insertRows(row, 1, slice_idx=slice_idx, cor=cor) self.set_point(row, slice_idx, cor) self.sort_points() - def headerData(self, section, orientation, role): + def headerData(self, section, orientation, role) -> str | None: if orientation != Qt.Orientation.Horizontal: return None diff --git a/mantidimaging/gui/windows/recon/presenter.py b/mantidimaging/gui/windows/recon/presenter.py index 2c524d2b42f..b2e0fb30c29 100644 --- a/mantidimaging/gui/windows/recon/presenter.py +++ b/mantidimaging/gui/windows/recon/presenter.py @@ -251,7 +251,7 @@ def do_preview_reconstruct_slice(self, cor=None, slice_idx: int | None = None, force_update: bool = False, - reset_roi: bool = False): + reset_roi: bool = False) -> None: if self.model.images is None: self.view.reset_recon_and_sino_previews() return @@ -262,7 +262,7 @@ def do_preview_reconstruct_slice(self, on_preview_complete = partial(self._on_preview_reconstruct_slice_done, reset_roi=reset_roi) self._get_reconstruct_slice(cor, slice_idx, on_preview_complete) - def _on_preview_reconstruct_slice_done(self, task: TaskWorkerThread, reset_roi: bool = False): + def _on_preview_reconstruct_slice_done(self, task: TaskWorkerThread, reset_roi: bool = False) -> None: if task.error is not None: self.view.show_error_dialog(f"Encountered error while trying to reconstruct: {str(task.error)}") return @@ -273,12 +273,12 @@ def _on_preview_reconstruct_slice_done(self, task: TaskWorkerThread, reset_roi: # will still be available after this function ends self.view.update_recon_preview(np.copy(images.data[0]), reset_roi) - def do_stack_reconstruct_slice(self, cor=None, slice_idx: int | None = None): + def do_stack_reconstruct_slice(self, cor=None, slice_idx: int | None = None) -> None: self.view.set_recon_buttons_enabled(False) slice_idx = self._get_slice_index(slice_idx) self._get_reconstruct_slice(cor, slice_idx, self._on_stack_reconstruct_slice_done) - def _on_stack_reconstruct_slice_done(self, task: TaskWorkerThread): + def _on_stack_reconstruct_slice_done(self, task: TaskWorkerThread) -> None: if task.error is not None: self.view.show_error_dialog(f"Encountered error while trying to reconstruct: {str(task.error)}") self.view.set_recon_buttons_enabled(True) @@ -291,7 +291,14 @@ def _on_stack_reconstruct_slice_done(self, task: TaskWorkerThread): assert self.model.images is not None images.name = self.create_recon_output_filename("Recon_Slice") self._replace_inf_nan(images) # pyqtgraph workaround - self.view.show_recon_volume(images, self.model.stack_id) + + stack_id = self.model.stack_id # This might be a UUID or None + if stack_id is None: + self.view.show_error_dialog("Stack ID is missing. Cannot display reconstruction.") + return + + self.view.show_recon_volume(images, stack_id) + images.record_operation('AstraRecon.single_sino', 'Slice Reconstruction', slice_idx=slice_idx, @@ -299,7 +306,7 @@ def _on_stack_reconstruct_slice_done(self, task: TaskWorkerThread): finally: self.view.set_recon_buttons_enabled(True) - def _do_refine_selected_cor(self): + def _do_refine_selected_cor(self) -> None: selected_rows = self.view.get_cor_table_selected_rows() if len(selected_rows): slice_idx = self.model.slices[selected_rows[0]] @@ -339,7 +346,7 @@ def do_cor_fit(self) -> None: self.do_update_projection() self.do_preview_reconstruct_slice() - def _on_volume_recon_done(self, task) -> None: + def _on_volume_recon_done(self, task: TaskWorkerThread) -> None: self.recon_is_running = False if task.error is not None: self.view.show_error_dialog(f"Encountered error while trying to reconstruct: {str(task.error)}") @@ -354,22 +361,22 @@ def _on_volume_recon_done(self, task) -> None: finally: self.view.set_recon_buttons_enabled(True) - def do_clear_all_cors(self): + def do_clear_all_cors(self) -> None: self.view.clear_cor_table() self.model.reset_selected_row() - def do_remove_selected_cor(self): + def do_remove_selected_cor(self) -> None: self.view.remove_selected_cor() - def set_last_cor(self, cor): + def set_last_cor(self, cor: ScalarCoR) -> None: self.model.last_cor = ScalarCoR(cor) - def do_calculate_cors_from_manual_tilt(self): + def do_calculate_cors_from_manual_tilt(self) -> None: cor = ScalarCoR(self.view.rotation_centre) tilt = Degrees(self.view.tilt) self._set_precalculated_cor_tilt(cor, tilt) - def _set_precalculated_cor_tilt(self, cor: ScalarCoR, tilt: Degrees): + def _set_precalculated_cor_tilt(self, cor: ScalarCoR, tilt: Degrees) -> None: self.model.set_precalculated(cor, tilt) self.view.set_results(*self.model.get_results()) for idx, point in enumerate(self.model.data_model.iter_points()): @@ -385,7 +392,7 @@ def _auto_find_correlation(self) -> None: self.recon_is_running = True - def completed(task: TaskWorkerThread): + def completed(task: TaskWorkerThread) -> None: if task.result is None and task.error is not None: selected_stack = self.view.main_window.get_images_from_stack_uuid(self.view.stackSelector.current()) self.view.show_error_dialog( @@ -439,7 +446,7 @@ def _completed_finding_cors(task: TaskWorkerThread) -> None: }, tracker=self.async_tracker) - def proj_180_degree_shape_matches_images(self, images) -> bool: + def proj_180_degree_shape_matches_images(self, images) -> None: return self.model.proj_180_degree_shape_matches_images(images) def _do_nan_zero_negative_check(self) -> None: diff --git a/mantidimaging/gui/windows/recon/view.py b/mantidimaging/gui/windows/recon/view.py index df043a5756f..3a14b216882 100644 --- a/mantidimaging/gui/windows/recon/view.py +++ b/mantidimaging/gui/windows/recon/view.py @@ -208,7 +208,7 @@ def on_row_change(item, _): self.lbhc_enabled.toggled.connect(spinbox.setEnabled) self.lbhc_enabled.toggled.connect(lambda: self.presenter.notify(PresN.RECONSTRUCT_PREVIEW_SLICE)) - def showEvent(self, e): + def showEvent(self, e) -> None: super().showEvent(e) if self.presenter.stack_selection_change_pending: self.presenter.set_stack_uuid(self.stackSelector.current()) @@ -219,13 +219,13 @@ def showEvent(self, e): self.presenter.stack_changed_pending = False self.activateWindow() - def closeEvent(self, e): + def closeEvent(self, e) -> None: if self.presenter.recon_is_running: e.ignore() else: self.hide() - def check_stack_for_invalid_180_deg_proj(self, uuid: UUID): + def check_stack_for_invalid_180_deg_proj(self, uuid: UUID) -> None: try: selected_images = self.main_window.get_images_from_stack_uuid(uuid) except KeyError: @@ -275,7 +275,7 @@ def preview_image_on_button_press(self, event) -> None: if event.button == 1 and event.ydata is not None: self.presenter.set_preview_slice_idx(int(event.ydata)) - def update_projection(self, image_data, preview_slice_index: int, tilt_angle: Degrees | None): + def update_projection(self, image_data, preview_slice_index: int, tilt_angle: Degrees | None) -> None: """ Updates the preview projection image and associated annotations. @@ -294,17 +294,17 @@ def update_projection(self, image_data, preview_slice_index: int, tilt_angle: De self.image_view.update_projection(image_data, preview_slice_index, tilt_angle) - def update_sinogram(self, image_data): + def update_sinogram(self, image_data) -> None: self.image_view.update_sinogram(image_data) def is_auto_update_preview(self) -> bool: return self.previewAutoUpdate.isChecked() - def handle_auto_update_preview_selection(self): + def handle_auto_update_preview_selection(self) -> None: if self.previewAutoUpdate.isChecked(): self.presenter.notify(PresN.RECONSTRUCT_PREVIEW_SLICE) - def update_recon_preview(self, image_data: numpy.ndarray, reset_roi: bool = False): + def update_recon_preview(self, image_data: numpy.ndarray, reset_roi: bool = False) -> None: """ Updates the reconstruction preview image with new data. """ @@ -314,7 +314,7 @@ def update_recon_preview(self, image_data: numpy.ndarray, reset_roi: bool = Fals self.image_view.update_recon_hist() self.update_recon_hist_needed = False - def reset_recon_and_sino_previews(self): + def reset_recon_and_sino_previews(self) -> None: """ Resets the recon and sinogram preview images, forcing a complete redraw next time they are updated. @@ -331,7 +331,7 @@ def reset_projection_preview(self) -> None: def reset_slice_and_tilt(self, slice_index: int) -> None: self.image_view.reset_slice_and_tilt(slice_index) - def on_table_row_count_change(self, _=None, __=None): + def on_table_row_count_change(self, _=None, __=None) -> None: """ Called when rows have been added or removed from the point table. @@ -343,7 +343,7 @@ def on_table_row_count_change(self, _=None, __=None): self.removeBtn.setEnabled(not empty) self.clearAllBtn.setEnabled(not empty) - def add_cor_table_row(self, row: int, slice_index: int, cor: float): + def add_cor_table_row(self, row: int, slice_index: int, cor: float) -> None: """ Adds a row to the manual COR table with a specified slice index. """ @@ -464,7 +464,7 @@ def recon_params(self) -> ReconstructionParameters: regulariser=self.regulariser, beam_hardening_coefs=self.beam_hardening_coefs) - def set_table_point(self, idx, slice_idx, cor): + def set_table_point(self, idx, slice_idx, cor) -> None: # reset_results=False stops the resetting of the data model on # changing a point from here - otherwise calculating the CoR # for each slice from tilt ends up resetting the model afterwards From 20c9645d591f298a27277a163b62b08259075e7b Mon Sep 17 00:00:00 2001 From: ashmeigh Date: Mon, 8 Jul 2024 09:29:15 +0100 Subject: [PATCH 2/8] update --- mantidimaging/gui/windows/recon/model.py | 3 +-- mantidimaging/gui/windows/recon/point_table_model.py | 2 +- mantidimaging/gui/windows/recon/presenter.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/mantidimaging/gui/windows/recon/model.py b/mantidimaging/gui/windows/recon/model.py index 9cb2f5af815..9654bad8b1d 100644 --- a/mantidimaging/gui/windows/recon/model.py +++ b/mantidimaging/gui/windows/recon/model.py @@ -137,7 +137,7 @@ def run_preview_recon(self, recon = self._apply_pixel_size(recon, recon_params) return recon - def run_full_recon(self, recon_params: ReconstructionParameters, progress: Progress) -> ReconstructedObject: + def run_full_recon(self, recon_params: ReconstructionParameters, progress: Progress) -> ImageStack | None: # Ensure we have some sample data images = self.images if images is None: @@ -146,7 +146,6 @@ def run_full_recon(self, recon_params: ReconstructionParameters, progress: Progr # get the image height based on the current ROI recon = reconstructor.full(images, self.data_model.get_all_cors_from_regression(images.height), recon_params, progress) - recon = self._apply_pixel_size(recon, recon_params, progress) return recon diff --git a/mantidimaging/gui/windows/recon/point_table_model.py b/mantidimaging/gui/windows/recon/point_table_model.py index 2516681d6df..ea48247e5c7 100644 --- a/mantidimaging/gui/windows/recon/point_table_model.py +++ b/mantidimaging/gui/windows/recon/point_table_model.py @@ -3,7 +3,7 @@ from __future__ import annotations from enum import Enum -from typing import Any, Optional, Union +from typing import Any from PyQt5.QtCore import QAbstractTableModel, QModelIndex, Qt diff --git a/mantidimaging/gui/windows/recon/presenter.py b/mantidimaging/gui/windows/recon/presenter.py index b2e0fb30c29..a838b0f1987 100644 --- a/mantidimaging/gui/windows/recon/presenter.py +++ b/mantidimaging/gui/windows/recon/presenter.py @@ -368,7 +368,7 @@ def do_clear_all_cors(self) -> None: def do_remove_selected_cor(self) -> None: self.view.remove_selected_cor() - def set_last_cor(self, cor: ScalarCoR) -> None: + def set_last_cor(self, cor) -> None: self.model.last_cor = ScalarCoR(cor) def do_calculate_cors_from_manual_tilt(self) -> None: From 2ca8fad7aeaa1eb0ef7afe265a021f54b3238714 Mon Sep 17 00:00:00 2001 From: ashmeigh Date: Mon, 8 Jul 2024 11:58:33 +0100 Subject: [PATCH 3/8] fixed errors --- mantidimaging/gui/windows/recon/model.py | 7 ++++++- mantidimaging/gui/windows/recon/presenter.py | 9 +++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/mantidimaging/gui/windows/recon/model.py b/mantidimaging/gui/windows/recon/model.py index 9654bad8b1d..a41ba4bc1bc 100644 --- a/mantidimaging/gui/windows/recon/model.py +++ b/mantidimaging/gui/windows/recon/model.py @@ -150,7 +150,12 @@ def run_full_recon(self, recon_params: ReconstructionParameters, progress: Progr return recon @staticmethod - def _apply_pixel_size(recon, recon_params: ReconstructionParameters, progress=None) -> ImageStack | None: + def _apply_pixel_size(recon: ImageStack | None, + recon_params: ReconstructionParameters, + progress: Progress | None = None) -> ImageStack | None: + if recon is None: + return None + if recon_params.pixel_size > 0.: recon = DivideFilter.filter_func(recon, value=recon_params.pixel_size, unit="micron", progress=progress) # update the reconstructed stack pixel size with the value actually used for division diff --git a/mantidimaging/gui/windows/recon/presenter.py b/mantidimaging/gui/windows/recon/presenter.py index a838b0f1987..0ce699ec6e2 100644 --- a/mantidimaging/gui/windows/recon/presenter.py +++ b/mantidimaging/gui/windows/recon/presenter.py @@ -356,6 +356,7 @@ def _on_volume_recon_done(self, task: TaskWorkerThread) -> None: try: self._replace_inf_nan(task.result) # pyqtgraph workaround assert self.model.images is not None + assert self.model.stack_id is not None task.result.name = self.create_recon_output_filename("Recon_Vol") self.view.show_recon_volume(task.result, self.model.stack_id) finally: @@ -446,8 +447,12 @@ def _completed_finding_cors(task: TaskWorkerThread) -> None: }, tracker=self.async_tracker) - def proj_180_degree_shape_matches_images(self, images) -> None: - return self.model.proj_180_degree_shape_matches_images(images) + def proj_180_degree_shape_matches_images(self, images: ImageStack) -> bool: + result = self.model.proj_180_degree_shape_matches_images(images) + if isinstance(result, bool): + return result + else: + raise ValueError("Expected a boolean from proj_180_degree_shape_matches_images") def _do_nan_zero_negative_check(self) -> None: """ From 91e564f0f51205c208f81f2c68be2c1837220833 Mon Sep 17 00:00:00 2001 From: ashmeigh <56345053+ashmeigh@users.noreply.github.com> Date: Tue, 16 Jul 2024 14:07:20 +0100 Subject: [PATCH 4/8] Using # type: ignore to suppress mypy errors Signed-off-by: ashmeigh --- mantidimaging/gui/windows/recon/model.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mantidimaging/gui/windows/recon/model.py b/mantidimaging/gui/windows/recon/model.py index a41ba4bc1bc..735829ecd48 100644 --- a/mantidimaging/gui/windows/recon/model.py +++ b/mantidimaging/gui/windows/recon/model.py @@ -134,7 +134,9 @@ def run_preview_recon(self, images.projection_angles(recon_params.max_projection_angle), recon_params, progress=progress) - recon = self._apply_pixel_size(recon, recon_params) + + recon = self._apply_pixel_size(recon, recon_params) # type: ignore + return recon def run_full_recon(self, recon_params: ReconstructionParameters, progress: Progress) -> ImageStack | None: @@ -146,7 +148,7 @@ def run_full_recon(self, recon_params: ReconstructionParameters, progress: Progr # get the image height based on the current ROI recon = reconstructor.full(images, self.data_model.get_all_cors_from_regression(images.height), recon_params, progress) - recon = self._apply_pixel_size(recon, recon_params, progress) + recon = self._apply_pixel_size(recon, recon_params, progress) # type: ignore return recon @staticmethod From 353f90d7a673e2ff392bb0bd910db2d73a2ae26f Mon Sep 17 00:00:00 2001 From: ashmeigh Date: Thu, 18 Jul 2024 08:15:09 +0100 Subject: [PATCH 5/8] made suggested changes Signed-off-by: ashmeigh --- mantidimaging/gui/windows/recon/model.py | 9 ++------- mantidimaging/gui/windows/recon/presenter.py | 12 ++---------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/mantidimaging/gui/windows/recon/model.py b/mantidimaging/gui/windows/recon/model.py index 735829ecd48..55c4a88ca6d 100644 --- a/mantidimaging/gui/windows/recon/model.py +++ b/mantidimaging/gui/windows/recon/model.py @@ -152,12 +152,7 @@ def run_full_recon(self, recon_params: ReconstructionParameters, progress: Progr return recon @staticmethod - def _apply_pixel_size(recon: ImageStack | None, - recon_params: ReconstructionParameters, - progress: Progress | None = None) -> ImageStack | None: - if recon is None: - return None - + def _apply_pixel_size(recon: ImageStack, recon_params: ReconstructionParameters, progress=None) -> ImageStack: if recon_params.pixel_size > 0.: recon = DivideFilter.filter_func(recon, value=recon_params.pixel_size, unit="micron", progress=progress) # update the reconstructed stack pixel size with the value actually used for division @@ -261,7 +256,7 @@ def auto_find_correlation(self, progress: Progress) -> tuple[ScalarCoR, Degrees] return find_center(self.images, progress) @staticmethod - def proj_180_degree_shape_matches_images(images) -> None: + def proj_180_degree_shape_matches_images(images) -> bool: return images.has_proj180deg() and images.height == images.proj180deg.height \ and images.width == images.proj180deg.width diff --git a/mantidimaging/gui/windows/recon/presenter.py b/mantidimaging/gui/windows/recon/presenter.py index 0ce699ec6e2..e9da077365d 100644 --- a/mantidimaging/gui/windows/recon/presenter.py +++ b/mantidimaging/gui/windows/recon/presenter.py @@ -278,12 +278,11 @@ def do_stack_reconstruct_slice(self, cor=None, slice_idx: int | None = None) -> slice_idx = self._get_slice_index(slice_idx) self._get_reconstruct_slice(cor, slice_idx, self._on_stack_reconstruct_slice_done) - def _on_stack_reconstruct_slice_done(self, task: TaskWorkerThread) -> None: + def _on_stack_reconstruct_slice_done(self, task: TaskWorkerThread): if task.error is not None: self.view.show_error_dialog(f"Encountered error while trying to reconstruct: {str(task.error)}") self.view.set_recon_buttons_enabled(True) return - try: images: ImageStack = task.result slice_idx = self._get_slice_index(None) @@ -291,14 +290,7 @@ def _on_stack_reconstruct_slice_done(self, task: TaskWorkerThread) -> None: assert self.model.images is not None images.name = self.create_recon_output_filename("Recon_Slice") self._replace_inf_nan(images) # pyqtgraph workaround - - stack_id = self.model.stack_id # This might be a UUID or None - if stack_id is None: - self.view.show_error_dialog("Stack ID is missing. Cannot display reconstruction.") - return - - self.view.show_recon_volume(images, stack_id) - + self.view.show_recon_volume(images, self.model.stack_id) images.record_operation('AstraRecon.single_sino', 'Slice Reconstruction', slice_idx=slice_idx, From d8d2fb1c3f1cc227855221d7a086904719d4772c Mon Sep 17 00:00:00 2001 From: ashmeigh Date: Fri, 19 Jul 2024 12:09:27 +0100 Subject: [PATCH 6/8] fixed error on _on_stack_reconstruct_slice_done --- mantidimaging/gui/windows/recon/presenter.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mantidimaging/gui/windows/recon/presenter.py b/mantidimaging/gui/windows/recon/presenter.py index e9da077365d..c55dd236219 100644 --- a/mantidimaging/gui/windows/recon/presenter.py +++ b/mantidimaging/gui/windows/recon/presenter.py @@ -287,10 +287,11 @@ def _on_stack_reconstruct_slice_done(self, task: TaskWorkerThread): images: ImageStack = task.result slice_idx = self._get_slice_index(None) if images is not None: - assert self.model.images is not None + source_id = self.model.stack_id + assert source_id is not None images.name = self.create_recon_output_filename("Recon_Slice") self._replace_inf_nan(images) # pyqtgraph workaround - self.view.show_recon_volume(images, self.model.stack_id) + self.view.show_recon_volume(images, source_id) images.record_operation('AstraRecon.single_sino', 'Slice Reconstruction', slice_idx=slice_idx, From 986fc84386fb5c46b9dc48c93de9e8d7f115b7c4 Mon Sep 17 00:00:00 2001 From: ashmeigh Date: Tue, 23 Jul 2024 08:24:40 +0100 Subject: [PATCH 7/8] chnage 180 back to orgin and added # type: ignore in the apply pixel size --- mantidimaging/gui/windows/recon/presenter.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/mantidimaging/gui/windows/recon/presenter.py b/mantidimaging/gui/windows/recon/presenter.py index c55dd236219..44804a4b83a 100644 --- a/mantidimaging/gui/windows/recon/presenter.py +++ b/mantidimaging/gui/windows/recon/presenter.py @@ -440,12 +440,8 @@ def _completed_finding_cors(task: TaskWorkerThread) -> None: }, tracker=self.async_tracker) - def proj_180_degree_shape_matches_images(self, images: ImageStack) -> bool: - result = self.model.proj_180_degree_shape_matches_images(images) - if isinstance(result, bool): - return result - else: - raise ValueError("Expected a boolean from proj_180_degree_shape_matches_images") + def proj_180_degree_shape_matches_images(self, images) -> bool: + return self.model.proj_180_degree_shape_matches_images(images) def _do_nan_zero_negative_check(self) -> None: """ From 9b7232c32f1c2dbe40a405b48bb89efb80d7f310 Mon Sep 17 00:00:00 2001 From: Sam Tygier Date: Wed, 24 Jul 2024 16:11:10 +0100 Subject: [PATCH 8/8] Remove unnecessary ignores --- mantidimaging/gui/windows/recon/model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mantidimaging/gui/windows/recon/model.py b/mantidimaging/gui/windows/recon/model.py index 55c4a88ca6d..b7dbb648b9b 100644 --- a/mantidimaging/gui/windows/recon/model.py +++ b/mantidimaging/gui/windows/recon/model.py @@ -135,7 +135,7 @@ def run_preview_recon(self, recon_params, progress=progress) - recon = self._apply_pixel_size(recon, recon_params) # type: ignore + recon = self._apply_pixel_size(recon, recon_params) return recon @@ -148,7 +148,7 @@ def run_full_recon(self, recon_params: ReconstructionParameters, progress: Progr # get the image height based on the current ROI recon = reconstructor.full(images, self.data_model.get_all_cors_from_regression(images.height), recon_params, progress) - recon = self._apply_pixel_size(recon, recon_params, progress) # type: ignore + recon = self._apply_pixel_size(recon, recon_params, progress) return recon @staticmethod