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..b7dbb648b9b 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() @@ -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) + return recon def run_full_recon(self, recon_params: ReconstructionParameters, progress: Progress) -> ImageStack | None: @@ -146,12 +148,11 @@ 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 @staticmethod - def _apply_pixel_size(recon, recon_params: ReconstructionParameters, progress=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 @@ -255,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): + 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 @@ -269,7 +270,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..ea48247e5c7 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 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..44804a4b83a 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,7 +273,7 @@ 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) @@ -283,15 +283,15 @@ def _on_stack_reconstruct_slice_done(self, task: TaskWorkerThread): 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) 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, @@ -299,7 +299,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 +339,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)}") @@ -349,27 +349,28 @@ def _on_volume_recon_done(self, task) -> 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: 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) -> 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 +386,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( 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