Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add spectrum plotting of data in the Live Viewer via Dask #2336

Open
wants to merge 46 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
a410321
Create DaskImageDataStack to hold delayed array of all images in LV path
MikeSullivan7 Aug 7, 2024
37088ad
add dask and dask-image to dependancies
MikeSullivan7 Aug 7, 2024
fd92b8d
live viewer model unit test fixes
MikeSullivan7 Aug 8, 2024
0890fee
yapf ruff fixes
MikeSullivan7 Aug 8, 2024
85a4695
cleanup
MikeSullivan7 Aug 8, 2024
85f699a
impliment toggle to create delayed dask array
MikeSullivan7 Aug 8, 2024
a09127b
added ability to read fits files via delayed dask functions
MikeSullivan7 Aug 8, 2024
71bb53f
remove astropy import and yapf fix
MikeSullivan7 Aug 8, 2024
09927e5
live viewer eyes tests fixes
MikeSullivan7 Aug 9, 2024
c7079b1
move all delayed array creation into DaskImageDataStack
MikeSullivan7 Aug 12, 2024
9be8efe
eyes test fixes
MikeSullivan7 Aug 13, 2024
93a146d
DaskImageDataStackTest created
MikeSullivan7 Aug 13, 2024
ed192d1
test_WHEN_create_delayed_array_THEN_delayed_array_created
MikeSullivan7 Aug 13, 2024
22a3534
create _get_fake_data() to test tif and fits files
MikeSullivan7 Aug 14, 2024
96ed759
test tif files handled correctly
MikeSullivan7 Aug 14, 2024
ff47f2d
testing unsupported files
MikeSullivan7 Aug 14, 2024
aeafeb9
testing supported files
MikeSullivan7 Aug 14, 2024
ccbe595
minor refactoring and using NotImplimentedError to check supported files
MikeSullivan7 Aug 14, 2024
ab1aa55
yapf and ruff fixes
MikeSullivan7 Aug 14, 2024
47acbf3
release note
MikeSullivan7 Aug 15, 2024
161dd1a
setting create_delayed_array to false falls back to loading into memory
MikeSullivan7 Aug 16, 2024
5099710
ruff fix
MikeSullivan7 Aug 16, 2024
b80b905
mypy fixes
MikeSullivan7 Aug 16, 2024
b0876c9
eyes_tests_fix
MikeSullivan7 Aug 16, 2024
b013797
Dask array now handles current image being deleted
MikeSullivan7 Aug 19, 2024
4c01e01
yapf fix
MikeSullivan7 Aug 19, 2024
7593ea5
Dask Stack now copes with file deletion
MikeSullivan7 Aug 20, 2024
64f3883
DaskImageDataStack allows images to be added and removed dynamically
MikeSullivan7 Aug 21, 2024
3c18f43
fixes to LV Model test
MikeSullivan7 Aug 22, 2024
23ce67c
fix get_computed_image
MikeSullivan7 Aug 22, 2024
3d4b575
mean is calculated as each image is added to DaskImageStack
MikeSullivan7 Aug 23, 2024
1785d74
mypy fix
MikeSullivan7 Aug 23, 2024
7eaf611
added split spectrum plot to LV window
MikeSullivan7 Sep 9, 2024
c08fb5a
live viewer spectrum plot toggles via right click menu
MikeSullivan7 Sep 10, 2024
54c185b
Spectrum calculation now toggled on and off by right click menu
MikeSullivan7 Sep 12, 2024
427ff4b
Live Viewer behaves correctly when spectrum is turned on with no data
MikeSullivan7 Sep 13, 2024
26737ed
dask slicing is optimised
MikeSullivan7 Sep 17, 2024
056f304
Using z slider is more fluid with Dask
MikeSullivan7 Sep 17, 2024
beacb41
dask mean calculation optimised
MikeSullivan7 Sep 18, 2024
7ec86f0
refactor and optimise how DaskImageDataStack updates its image lis
MikeSullivan7 Sep 19, 2024
7b9c47d
change how image_list updates and some benchmarking
MikeSullivan7 Sep 23, 2024
708344b
small optimization of mean calc and mypy fix
MikeSullivan7 Sep 27, 2024
94888a2
release note
MikeSullivan7 Sep 27, 2024
a14efd5
100 most recent images are stored in LRU cache
MikeSullivan7 Nov 22, 2024
c85406a
Images stored in LRUCache and new means append correctly (with debug …
MikeSullivan7 Nov 26, 2024
67a58c0
Mean buffer loading and prints removed
MikeSullivan7 Nov 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions conda/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ requirements:
- qt-material=2.14
- darkdetect=0.8.0
- qt-gtk-platformtheme # [linux]
- dask
- dask-image


build:
Expand Down
1 change: 1 addition & 0 deletions docs/release_notes/next/dev-2311-dask-live-viewer
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#2311: The Live Viewer now uses Dask to load in images and create a delayed datastack for operations
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#2327: The mean spectrum of live data in the Live Spectrum can be plotted via a right-click menu, using Dask. This feature is experimental.
17 changes: 10 additions & 7 deletions mantidimaging/eyes_tests/live_viewer_window_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
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.gui.windows.live_viewer.model import Image_Data, DaskImageDataStack
from mantidimaging.test_helpers.unit_test_helper import FakeFSTestCase
from pathlib import Path
from mantidimaging.eyes_tests.base_eyes import BaseEyesTest
Expand Down Expand Up @@ -56,36 +56,39 @@ 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.presenter.LiveViewerWindowPresenter.load_image_from_path')
@mock.patch('mantidimaging.gui.windows.live_viewer.model.ImageWatcher')
@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]
dask_image_stack = DaskImageDataStack(image_list, create_delayed_array=False)
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.presenter.model._handle_image_changed_in_list(image_list, dask_image_stack)
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.presenter.LiveViewerWindowPresenter.load_image_from_path')
@mock.patch('mantidimaging.gui.windows.live_viewer.model.ImageWatcher')
@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]
dask_image_stack = DaskImageDataStack(image_list, create_delayed_array=False)
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.imaging.live_viewer.presenter.model._handle_image_changed_in_list(image_list, dask_image_stack)
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.presenter.LiveViewerWindowPresenter.load_image_from_path')
@mock.patch('mantidimaging.gui.windows.live_viewer.model.ImageWatcher')
@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]
dask_image_stack = DaskImageDataStack(image_list, create_delayed_array=False)
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.presenter.model._handle_image_changed_in_list(image_list, dask_image_stack)
self.imaging.live_viewer.rotate_angles_group.actions()[1].trigger()
self.check_target(widget=self.imaging.live_viewer)
49 changes: 48 additions & 1 deletion mantidimaging/gui/windows/live_viewer/live_view_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@
# SPDX - License - Identifier: GPL-3.0-or-later
from __future__ import annotations
from typing import TYPE_CHECKING
from pyqtgraph import GraphicsLayoutWidget

from PyQt5.QtCore import pyqtSignal
from pyqtgraph import GraphicsLayoutWidget, mkPen

from mantidimaging.core.utility.close_enough_point import CloseEnoughPoint
from mantidimaging.core.utility.sensible_roi import SensibleROI
from mantidimaging.gui.widgets.mi_mini_image_view.view import MIMiniImageView
from mantidimaging.gui.widgets.zslider.zslider import ZSlider
from mantidimaging.gui.windows.spectrum_viewer.spectrum_widget import SpectrumROI

if TYPE_CHECKING:
import numpy as np
Expand All @@ -18,6 +23,10 @@ class LiveViewWidget(GraphicsLayoutWidget):
@param parent: The parent widget
"""
image: MIMiniImageView
image_shape: tuple = (-1, -1)
roi_changed = pyqtSignal()
roi_object: SpectrumROI | None = None
sensible_roi: SensibleROI

def __init__(self) -> None:
super().__init__()
Expand Down Expand Up @@ -48,3 +57,41 @@ def handle_deleted(self) -> None:

def show_error(self, message: str | None):
self.image.show_message(message)

def add_roi(self):
if self.image_shape == (-1, -1):
return
height, width = self.image_shape
roi = SensibleROI.from_list([0, 0, width, height])
self.roi_object = SpectrumROI('roi', roi, rotatable=False, scaleSnap=True, translateSnap=True)
self.roi_object.colour = (255, 194, 10, 255)
self.roi_object.hoverPen = mkPen(self.roi_object.colour, width=3)
self.roi_object.roi.sigRegionChangeFinished.connect(self.roi_changed.emit)
self.image.vb.addItem(self.roi_object.roi)

def set_image_shape(self, shape: tuple) -> None:
self.image_shape = shape

def get_roi(self) -> SensibleROI:
if not self.roi_object:
return SensibleROI()
roi = self.roi_object.roi
pos = CloseEnoughPoint(roi.pos())
size = CloseEnoughPoint(roi.size())
return SensibleROI.from_points(pos, size)

def set_roi_alpha(self, alpha: int) -> None:
if not self.roi_object:
return
self.roi_object.colour = self.roi_object.colour[:3] + (alpha, )
self.roi_object.setPen(self.roi_object.colour)
self.roi_object.hoverPen = mkPen(self.roi_object.colour, width=3)
self.set_roi_visibility_flags(bool(alpha))

def set_roi_visibility_flags(self, visible: bool) -> None:
if not self.roi_object:
return
handles = self.roi_object.getHandles()
for handle in handles:
handle.setVisible(visible)
self.roi_object.setVisible(visible)
Loading