Skip to content

Commit

Permalink
Liveview screenshot tests (#2040)
Browse files Browse the repository at this point in the history
  • Loading branch information
JackEAllen authored Feb 16, 2024
2 parents 3d011f4 + 1e416a8 commit ece562d
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 12 deletions.
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ CHANNELS=$(shell cat environment.yml | sed -ne '/channels:/,/dependencies:/{//!p
ifeq ($(OS),Windows_NT)
XVFBRUN=
TEST_RESULT_DIR:=$(TEMP)\mantidimaging_tests
export APPLITOOLS_API_KEY=local
export APPLITOOLS_IMAGE_DIR:=${TEST_RESULT_DIR}
else
XVFBRUN=xvfb-run --auto-servernum
TEST_RESULT_DIR:=$(shell mktemp -d)
Expand Down Expand Up @@ -54,6 +56,11 @@ test-screenshots:
APPLITOOLS_API_KEY=local APPLITOOLS_IMAGE_DIR=${TEST_RESULT_DIR} ${XVFBRUN} pytest -p no:xdist -p no:randomly -p no:cov mantidimaging/eyes_tests/ -vs
@echo "Screenshots writen to" ${TEST_RESULT_DIR}

test-screenshots-win:
-mkdir ${TEST_RESULT_DIR}
${XVFBRUN} pytest -p no:xdist -p no:randomly -p no:cov mantidimaging/eyes_tests/ -vs
@echo "Screenshots writen to" ${TEST_RESULT_DIR}

mypy:
python -m mypy --ignore-missing-imports --no-site-packages ${SOURCE_DIRS}

Expand Down
79 changes: 79 additions & 0 deletions mantidimaging/eyes_tests/live_viewer_window_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Copyright (C) 2024 ISIS Rutherford Appleton Laboratory UKRI
# SPDX - License - Identifier: GPL-3.0-or-later
from __future__ import annotations

from unittest import mock

import numpy as np

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


class LiveViewerWindowTest(FakeFSTestCase, BaseEyesTest):

@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
load_filter_packages() # Needs to be called before pyfakefs hides the filesystem

def setUp(self) -> None:
super().setUp()
self.fs.add_real_directory(Path(__file__).parent.parent) # Allow ui file to be found
self.live_directory = Path("/live_dir")
self.fs.create_dir(self.live_directory)

def _generate_image(self):
image = np.zeros((10, 10))
image[5, :] = np.arange(10)
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)

for file in file_list:
self.fs.create_file(file)

return file_list

@mock.patch('mantidimaging.gui.windows.live_viewer.model.ImageWatcher')
def test_live_view_opens_without_data(self, _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):
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()
self.imaging.show_live_viewer(self.live_directory)
self.imaging.live_viewer.presenter.model._handle_image_changed_in_list(image_list)
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_bad_data(self, _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
self.imaging.show_live_viewer(self.live_directory)
self.imaging.live_viewer.presenter.model._handle_image_changed_in_list(image_list)
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_rotate_operation_rotates_image(self, _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()
self.imaging.show_live_viewer(self.live_directory)
self.imaging.live_viewer.presenter.model._handle_image_changed_in_list(image_list)
self.imaging.live_viewer.rotate_angles_group.actions()[1].trigger()
self.check_target(widget=self.imaging.live_viewer)
35 changes: 23 additions & 12 deletions mantidimaging/gui/windows/live_viewer/presenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,24 +79,21 @@ def select_image(self, index: int) -> None:
self.selected_image = self.model.images[index]
self.view.label_active_filename.setText(self.selected_image.image_name)

self.load_and_display_image(self.selected_image.image_path)
self.display_image(self.selected_image.image_path)

def load_and_display_image(self, image_path: Path):
def display_image(self, image_path: Path):
"""
Display image in the view after validating contents
"""
try:
if image_path.suffix.lower() in [".tif", ".tiff"]:
with tifffile.TiffFile(image_path) as tif:
image_data = tif.asarray()
elif image_path.suffix.lower() == ".fits":
with fits.open(image_path.__str__()) as fit:
image_data = fit[0].data

image_data = self.perform_operations(image_data)
image_data = self.load_image(image_path)
except (IOError, KeyError, ValueError, TiffFileError, DeflateError) as error:
message = f"{type(error).__name__} reading image: {image_path}: {error}"
logger.error(message)
self.view.remove_image()
self.view.live_viewer.show_error(message)
return
image_data = self.perform_operations(image_data)
if image_data.size == 0:
message = "reading image: {image_path}: Image has zero size"
logger.error("reading image: %s: Image has zero size", image_path)
Expand All @@ -107,18 +104,32 @@ def load_and_display_image(self, image_path: Path):
self.view.show_most_recent_image(image_data)
self.view.live_viewer.show_error(None)

@staticmethod
def load_image(image_path: Path) -> np.ndarray:
"""
Load a .Tif, .Tiff or .Fits file only if it exists
and returns as an ndarray
"""
if image_path.suffix.lower() in [".tif", ".tiff"]:
with tifffile.TiffFile(image_path) as tif:
image_data = tif.asarray()
elif image_path.suffix.lower() == ".fits":
with fits.open(image_path.__str__()) as fit:
image_data = fit[0].data
return image_data

def update_image_modified(self, image_path: Path):
"""
Update the displayed image when the file is modified
"""
if self.selected_image and image_path == self.selected_image.image_path:
self.load_and_display_image(image_path)
self.display_image(image_path)

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)
self.display_image(self.selected_image.image_path)

def convert_image_to_imagestack(self, image_data):
"""
Expand Down
13 changes: 13 additions & 0 deletions mantidimaging/test_helpers/unit_test_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

from mantidimaging.core.data import ImageStack
from mantidimaging.core.parallel import utility as pu
from mantidimaging.core.parallel.utility import SharedArray
from mantidimaging.core.utility.data_containers import ProjectionAngles

backup_mp_avail = None
Expand Down Expand Up @@ -165,6 +166,18 @@ class FakeFSTestCase(pyfakefs.fake_filesystem_unittest.TestCase):
def setUp(self) -> None:
super().setUp()
self.setUpPyfakefs()
if sys.platform == 'linux':
self.fs.add_real_file("/proc/meminfo", read_only=True)

#COMPAT: work around https://github.com/pytest-dev/pyfakefs/issues/949
self._mock_create_shared_array = mock.patch(
"mantidimaging.core.parallel.utility._create_shared_array",
side_effect=lambda s, d: SharedArray(array=np.zeros(s, dtype=d), shared_memory=None))
self._mock_create_shared_array.start()

def tearDown(self) -> None:
if sys.platform == 'linux':
self._mock_create_shared_array.stop()

def _files_equal(self, file1, file2) -> None:
self.assertIsNotNone(file1)
Expand Down

0 comments on commit ece562d

Please sign in to comment.