Skip to content

Commit

Permalink
Merge branch 'main' into dask_live_viewer
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeSullivan7 authored Aug 9, 2024
2 parents 209facd + ed6cdc8 commit 519228b
Show file tree
Hide file tree
Showing 16 changed files with 69 additions and 68 deletions.
2 changes: 1 addition & 1 deletion .github/actions/publish-package/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ runs:
conda config --set always_yes yes --set changeps1 no
# Install build requirements
# We can't use the makefile target for this because the CONDA_ACTIVATE command is incompatible with GitHub Actions Windows runners
conda create -n build-env --yes boa anaconda-client conda-verify
conda create -n build-env --yes boa anaconda-client
conda activate build-env
# Configure the conda channels
conda config --env $(cat environment.yml | sed -ne '/channels:/,/dependencies:/{//!p}' | grep '^ -' | sed 's/ - / --append channels /g' | tr -d '\n')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Testing with CentOS 7 docker
name: Testing with Rocky docker

on:
push:
Expand All @@ -16,42 +16,42 @@ concurrency:

jobs:
test:
# It is hosted on Ubuntu but the docker image is built on CentOS 7
# It is hosted on Ubuntu but the docker image is built on Rocky
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Pre-load docker image
run: docker pull ghcr.io/mantidproject/mantidimaging:centos7
run: docker pull ghcr.io/mantidproject/mantidimaging:rocky8

- name: List versions
uses: ./.github/actions/test
with:
command: python --version; conda list ; pip list
label: centos7
label: rocky8

- name: yapf
uses: ./.github/actions/test
with:
command: yapf --parallel --diff --recursive .
label: centos7
label: rocky8

- name: ruff
uses: ./.github/actions/test
with:
command: ruff check .
label: centos7
label: rocky8

- name: mypy
uses: ./.github/actions/test
with:
command: mypy --ignore-missing-imports mantidimaging
label: centos7
label: rocky8

- name: pytest
timeout-minutes: 5
uses: ./.github/actions/test
with:
command: xvfb-run pytest -n auto -o log_cli=true --ignore=mantidimaging/eyes_tests --durations=10
label: centos7
label: rocky8
2 changes: 1 addition & 1 deletion conda/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ source:
requirements:
build:
- python=3.12.*
- setuptools=62.*
- setuptools=72.*
run:
- python=3.12.*
- pip
Expand Down
24 changes: 0 additions & 24 deletions docker/Makefile

This file was deleted.

2 changes: 1 addition & 1 deletion docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Requirements
------------

Operating system
- Linux. Tested on Ubuntu 18.04, 20.04, 22.04 and CentOS 7
- Linux. Tested on Ubuntu 18.04, 20.04, 22.04 and Rocky 8
- Windows. Tested on Windows 10

GPU
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#2273: Display the modified image timestamp within the live viewer to allow for easier determination of what scan is being displayed within the live viewer.
1 change: 1 addition & 0 deletions docs/release_notes/next/fix-2285-crop-coord-stack-bounds
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#2285: Resolve operations windows crop co-ordinates ROI size from being larger than stack bounds. Additionally resolve ROI crop co-ordinate dialog window persistence if parent window closed.
34 changes: 23 additions & 11 deletions mantidimaging/eyes_tests/live_viewer_window_test.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
# Copyright (C) 2024 ISIS Rutherford Appleton Laboratory UKRI
# SPDX - License - Identifier: GPL-3.0-or-later
from __future__ import annotations

from __future__ import annotations
from typing import TYPE_CHECKING
from unittest import mock

import numpy as np

import os
from mantidimaging.core.operations.loader import load_filter_packages
from mantidimaging.gui.windows.live_viewer.model import Image_Data
from mantidimaging.test_helpers.unit_test_helper import FakeFSTestCase
from pathlib import Path

from mantidimaging.eyes_tests.base_eyes import BaseEyesTest

if TYPE_CHECKING:
import time # noqa: F401


class LiveViewerWindowTest(FakeFSTestCase, BaseEyesTest):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.initial_time = 4000.0

@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
Expand All @@ -30,26 +36,30 @@ def setUp(self) -> None:
def _generate_image(self):
image = np.zeros((10, 10))
image[5, :] = np.arange(10)
os.utime(self.live_directory, (10, self.initial_time))
self.initial_time += 1000
return image

def _make_simple_dir(self, directory: Path):
file_list = [directory / f"abc_{i:06d}.tif" for i in range(5)]
if not directory.exists():
self.fs.create_dir(directory)

increment = 0
for file in file_list:
self.fs.create_file(file)
os.utime(file, (10, self.initial_time + increment))
increment += 1000

return file_list

@mock.patch('mantidimaging.gui.windows.live_viewer.model.ImageWatcher')
def test_live_view_opens_without_data(self, _mock_image_watcher):
@mock.patch("time.time", return_value=4000.0)
def test_live_view_opens_without_data(self, _mock_time, _mock_image_watcher):
self.imaging.show_live_viewer(self.live_directory)
self.check_target(widget=self.imaging.live_viewer)

@mock.patch('mantidimaging.gui.windows.live_viewer.presenter.LiveViewerWindowPresenter.load_image')
@mock.patch('mantidimaging.gui.windows.live_viewer.model.ImageWatcher')
def test_live_view_opens_with_data(self, _mock_image_watcher, mock_load_image):
@mock.patch("time.time", return_value=4000.0)
def test_live_view_opens_with_data(self, _mock_time, _mock_image_watcher, mock_load_image):
file_list = self._make_simple_dir(self.live_directory)
image_list = [Image_Data(path) for path in file_list]
mock_load_image.return_value = self._generate_image()
Expand All @@ -59,7 +69,8 @@ def test_live_view_opens_with_data(self, _mock_image_watcher, mock_load_image):

@mock.patch('mantidimaging.gui.windows.live_viewer.presenter.LiveViewerWindowPresenter.load_image')
@mock.patch('mantidimaging.gui.windows.live_viewer.model.ImageWatcher')
def test_live_view_opens_with_bad_data(self, _mock_image_watcher, mock_load_image):
@mock.patch("time.time", return_value=4000.0)
def test_live_view_opens_with_bad_data(self, _mock_time, _mock_image_watcher, mock_load_image):
file_list = self._make_simple_dir(self.live_directory)
image_list = [Image_Data(path) for path in file_list]
mock_load_image.side_effect = ValueError
Expand All @@ -69,7 +80,8 @@ def test_live_view_opens_with_bad_data(self, _mock_image_watcher, mock_load_imag

@mock.patch('mantidimaging.gui.windows.live_viewer.presenter.LiveViewerWindowPresenter.load_image')
@mock.patch('mantidimaging.gui.windows.live_viewer.model.ImageWatcher')
def test_rotate_operation_rotates_image(self, _mock_image_watcher, mock_load_image):
@mock.patch("time.time", return_value=4000.0)
def test_rotate_operation_rotates_image(self, _mock_time, _mock_image_watcher, mock_load_image):
file_list = self._make_simple_dir(self.live_directory)
image_list = [Image_Data(path) for path in file_list]
mock_load_image.return_value = self._generate_image()
Expand Down
1 change: 0 additions & 1 deletion mantidimaging/gui/test/gui_system_operations_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
ALLOWED_ERRORS = [
'Negative values found in result preview for slice 0.',
'Flat-fielding completed. Slices containing negative values in IMAT_Flower_Tomo_000000: all slices.',
'Error applying filter for preview: could not broadcast input array from shape (1,80,80) into shape (1,90,90)'
]


Expand Down
6 changes: 5 additions & 1 deletion mantidimaging/gui/widgets/mi_image_view/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from typing import TYPE_CHECKING
from collections.abc import Callable

from PyQt5.QtCore import Qt
from PyQt5.QtCore import Qt, QRectF
from PyQt5.QtWidgets import QApplication, QHBoxLayout, QLabel, QPushButton, QSizePolicy
from pyqtgraph import ROI, ImageItem, ImageView, ViewBox
from pyqtgraph.GraphicsScene.mouseEvents import HoverEvent
Expand Down Expand Up @@ -148,6 +148,9 @@ def angles(self, angles: ProjectionAngles | None) -> None:
self._angles = angles
self._update_message(self._last_mouse_hover_location)

def _set_roi_max_bounds(self):
self.roi.maxBounds = QRectF(0, 0, self.image_data.shape[2], self.image_data.shape[1])

def setImage(self, image: np.ndarray, *args, **kwargs):
dimensions_changed = self.image_data is None or self.image_data.shape != image.shape
if image.ndim == 3:
Expand Down Expand Up @@ -195,6 +198,7 @@ def roiChanged(self) -> None:
roi = self._update_roi_region_avg()
if self.roi_changed_callback and roi is not None:
self.roi_changed_callback(roi)
self._set_roi_max_bounds()
self._refresh_message()

def _update_roi_region_avg(self) -> SensibleROI | None:
Expand Down
4 changes: 4 additions & 0 deletions mantidimaging/gui/widgets/roi_selector/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,7 @@ def toggle_average_images(self) -> None:
self.roi_view_averaged = not self.roi_view_averaged
self.roi_view.roi.show()
self.roi_view.ui.roiPlot.hide()

def closeEvent(self, event) -> None:
self.roi_view.close()
event.accept()
7 changes: 7 additions & 0 deletions mantidimaging/gui/windows/live_viewer/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,19 @@ def image_modified_time(self) -> float:
"""Return the image modified time"""
return self._stat.st_mtime


def set_delayed_array(self) -> None:
if self.image_path.suffix.lower() in [".tif", ".tiff"]:
self.delayed_array = dask_image.imread.imread(self.image_path)[0]
elif self.image_path.suffix.lower() == ".fits":
self.delayed_array = dask.delayed(fits.open)(self.image_path)[0].data

@property
def image_modified_time_stamp(self) -> str:
"""Return the image modified time as a string"""
return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.image_modified_time))



class SubDirectory:

Expand Down
3 changes: 2 additions & 1 deletion mantidimaging/gui/windows/live_viewer/presenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ def select_image(self, index: int) -> None:
if not self.model.images:
return
self.selected_image = self.model.images[index]
self.view.label_active_filename.setText(self.selected_image.image_name)
image_timestamp = self.selected_image.image_modified_time_stamp
self.view.label_active_filename.setText(f"{self.selected_image.image_name} - {image_timestamp}")

self.display_image(self.selected_image)

Expand Down
15 changes: 4 additions & 11 deletions mantidimaging/gui/windows/operations/presenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,14 +284,9 @@ def _post_filter(self, updated_stacks: list[ImageStack], task):
if np.any(stack.data < 0):
negative_stacks.append(stack)

if self.view.roi_view is not None:
self.view.roi_view.close()
self.view.roi_view = None

self.applying_to_all = False
self.do_update_previews()

if task.error is not None:
if task.error:
# task failed, show why
self.view.show_error_dialog(f"Operation failed: {task.error}")
elif use_new_data:
Expand All @@ -312,6 +307,7 @@ def _post_filter(self, updated_stacks: list[ImageStack], task):
self.view.filter_applied.emit()
self._set_apply_buttons_enabled(self.prev_apply_single_state, self.prev_apply_all_state)
self.filter_is_running = False
self.do_update_previews()

def _do_apply_filter(self, apply_to: list[ImageStack]):
self.filter_is_running = True
Expand Down Expand Up @@ -454,11 +450,8 @@ def init_roi_field(self, roi_field: QLineEdit):
if self.stack is None:
return

larger = np.greater(self.stack.data[0].shape, (200, 200))
if all(larger):
return
x = min(self.stack.data[0].shape[0], 200)
y = min(self.stack.data[0].shape[1], 200)
x = self.stack.data.shape[1] // 2
y = self.stack.data.shape[2] // 2
crop_string = ", ".join(["0", "0", str(y), str(x)])
roi_field.setText(crop_string)

Expand Down
6 changes: 3 additions & 3 deletions mantidimaging/gui/windows/operations/test/presenter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,14 +448,14 @@ def test_init_roi_field_does_nothing_when_stack_is_none(self):
self.presenter.init_roi_field(mock_roi_field)
mock_roi_field.setText.assert_not_called()

def test_init_roi_field_does_nothing_when_image_is_greater_than_200_by_200(self):
def test_init_roi_field_called_with_smaller_values_if_image_is_greater_than_200_by_200(self):
mock_roi_field = mock.Mock()
self.presenter.stack = mock.Mock()
self.presenter.stack.data = np.ones((2, 201, 201))
self.presenter.init_roi_field(mock_roi_field)
mock_roi_field.setText.assert_not_called()
mock_roi_field.setText.assert_called_once_with("0, 0, 100, 100")

@parameterized.expand([(190, 201, "0, 0, 200, 190"), (201, 80, "0, 0, 80, 200"), (200, 200, "0, 0, 200, 200")])
@parameterized.expand([(190, 201, "0, 0, 100, 95"), (201, 80, "0, 0, 40, 100"), (200, 200, "0, 0, 100, 100")])
def test_set_text_called_when_image_not_greater_than_200_by_200(self, shape_x, shape_y, expected):
mock_roi_field = mock.Mock()
self.presenter.stack = mock.Mock()
Expand Down
13 changes: 8 additions & 5 deletions mantidimaging/gui/windows/operations/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def __init__(self, main_window: MainWindowView):
self.main_window = main_window
self.presenter = FiltersWindowPresenter(self, main_window)
self.roi_view = None
self.roi_selector_dialog: ROISelectorView | None = None
self.roi_view_averaged = False
self.splitter.setSizes([200, 9999])
self.splitter.setStretchFactor(0, 1)
Expand Down Expand Up @@ -122,8 +123,9 @@ def closeEvent(self, e):
def cleanup(self):
self.stackSelector.unsubscribe_from_main_window()
if self.roi_view is not None:
self.roi_view.close()
self.roi_view = None
self.roi_selector_dialog.close()

self.presenter.set_stack(None)
self.auto_update_triggered.disconnect()
self.main_window.filters = None
Expand Down Expand Up @@ -255,16 +257,17 @@ def roi_changed_callback(callback):
except ValueError:
roi_values = None

window = ROISelectorView(self, self.presenter.stack, self.presenter.model.preview_image_idx, roi_values,
roi_changed_callback)
self.roi_selector_dialog = ROISelectorView(self, self.presenter.stack, self.presenter.model.preview_image_idx,
roi_values, roi_changed_callback)

def close_event(event):
roi_field.setEnabled(True)
roi_button.setEnabled(True)
event.accept()

window.closeEvent = functools.partial(close_event)
window.show()
self.roi_view = self.roi_selector_dialog.roi_view
self.roi_selector_dialog.closeEvent = functools.partial(close_event)
self.roi_selector_dialog.show()

def toggle_filters_section(self):
if self.collapseToggleButton.text() == "<<":
Expand Down

0 comments on commit 519228b

Please sign in to comment.