Skip to content

Commit 1287c24

Browse files
authored
Update operations validation method to have access to the ImageStack (#2824)
2 parents 1485530 + ece11d2 commit 1287c24

File tree

11 files changed

+57
-28
lines changed

11 files changed

+57
-28
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#2795: Allow operation validation method to access image stack

mantidimaging/core/operations/base_filter.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,15 @@ def sv_params() -> dict[str, Any]:
8888
return {}
8989

9090
@staticmethod
91-
def validate_execute_kwargs(kwargs: dict[str, Any]) -> bool:
92-
return True
91+
def validate_execute_kwargs(kwargs: dict[str, Any], images: ImageStack) -> str | None:
92+
"""
93+
Check operation parameters to highlight issues that need to be caught before calling filter_func()
94+
95+
The method also has access to the ImageStack so it can check dimensions and metadata if needed.
96+
97+
:return: None if no issues, a string message if the is a problem
98+
"""
99+
return None
93100

94101
@staticmethod
95102
def group_name() -> FilterGroup:

mantidimaging/core/operations/flat_fielding/flat_fielding.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,19 +264,19 @@ def execute_wrapper( # type: ignore
264264
use_dark=use_dark)
265265

266266
@staticmethod
267-
def validate_execute_kwargs(kwargs):
267+
def validate_execute_kwargs(kwargs: dict[str, Any], images: ImageStack) -> str | None:
268268
# Validate something is in both path text inputs
269269
if 'selected_flat_fielding_widget' not in kwargs:
270-
return False
270+
return "Not all required parameters specified"
271271

272272
if 'flat_before_widget' not in kwargs and 'dark_before_widget' not in kwargs or\
273273
'flat_after_widget' not in kwargs and 'dark_after_widget' not in kwargs:
274-
return False
274+
return "Not all required parameters specified"
275275
assert isinstance(kwargs["flat_before_widget"], DatasetSelectorWidgetView)
276276
assert isinstance(kwargs["flat_after_widget"], DatasetSelectorWidgetView)
277277
assert isinstance(kwargs["dark_before_widget"], DatasetSelectorWidgetView)
278278
assert isinstance(kwargs["dark_after_widget"], DatasetSelectorWidgetView)
279-
return True
279+
return None
280280

281281
@staticmethod
282282
def group_name() -> FilterGroup:

mantidimaging/core/operations/monitor_normalisation/monitor_normalisation.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def filter_func(images: ImageStack, progress=None) -> ImageStack:
3737
if images.num_projections == 1:
3838
# we can't really compute the preview as the image stack copy
3939
# passed in doesn't have the logfile in it
40-
raise RuntimeError("No logfile available for this stack.")
40+
return images
4141

4242
counts = images.counts()
4343
if counts is None:
@@ -63,5 +63,11 @@ def execute_wrapper(*args) -> partial:
6363
return partial(MonitorNormalisation.filter_func)
6464

6565
@staticmethod
66-
def validate_execute_kwargs(kwargs: dict[str, Any]) -> bool:
67-
return True
66+
def validate_execute_kwargs(kwargs: dict[str, Any], images: ImageStack) -> str | None:
67+
counts = images.counts()
68+
if counts is None:
69+
return "Image stack has no counts. Ensure these have been loaded with a log file"
70+
71+
if counts.value.size != images.num_projections:
72+
return "Number of entries in counts does not match number of images"
73+
return None

mantidimaging/core/operations/monitor_normalisation/test/monitor_normalisation_test.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,34 @@ def test_one_projection(self):
2020
images = generate_images((1, 1, 1))
2121
images._log_file = mock.Mock()
2222
images._log_file.counts = mock.Mock(return_value=Counts(np.sin(np.linspace(0, 1, images.num_projections))))
23-
self.assertRaises(RuntimeError, MonitorNormalisation.filter_func, images)
23+
result = MonitorNormalisation.filter_func(images)
24+
self.assertIs(images, result)
2425

2526
def test_no_counts(self):
2627
images = generate_images((2, 2, 2))
2728
images._log_file = mock.Mock()
2829
images._log_file.counts = mock.Mock(return_value=None)
30+
31+
self.assertIn("no counts", MonitorNormalisation.validate_execute_kwargs({}, images))
2932
self.assertRaises(RuntimeError, MonitorNormalisation.filter_func, images)
3033

34+
def test_wrong_number_of_counts(self):
35+
images = generate_images()
36+
images._log_file = mock.Mock()
37+
images._log_file.counts = mock.Mock(return_value=Counts(np.sin(np.linspace(0, 1, images.num_projections + 1))))
38+
39+
self.assertIn("counts does not match number of images", MonitorNormalisation.validate_execute_kwargs({},
40+
images))
41+
3142
def test_execute(self):
3243
images = generate_images()
3344
images._log_file = mock.Mock()
3445
images._log_file.counts = mock.Mock(return_value=Counts(np.sin(np.linspace(0, 1, images.num_projections))))
3546

3647
original = images.copy()
48+
self.assertIsNone(MonitorNormalisation.validate_execute_kwargs({}, images))
3749
MonitorNormalisation.filter_func(images)
38-
images._log_file.counts.assert_called_once()
50+
images._log_file.counts.assert_called()
3951
self.assertEqual(original.data.shape, original.data.shape)
4052
assert_not_equals(original.data, images.data)
4153

@@ -51,7 +63,7 @@ def test_execute2(self):
5163

5264
original = images.copy()
5365
MonitorNormalisation.filter_func(images)
54-
images._log_file.counts.assert_called_once()
66+
images._log_file.counts.assert_called()
5567
npt.assert_equal(original.data, images.data)
5668

5769
def test_register_gui(self):

mantidimaging/core/operations/overlap_correction/overlap_correction.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from functools import partial
77
from pathlib import Path
8-
from typing import TYPE_CHECKING, Any
8+
from typing import TYPE_CHECKING
99
from collections.abc import Callable
1010
from logging import getLogger
1111

@@ -76,10 +76,6 @@ def register_gui(form: QFormLayout, on_change: Callable, view: BaseMainWindowVie
7676
def execute_wrapper(*args) -> partial:
7777
return partial(OverlapCorrection.filter_func)
7878

79-
@staticmethod
80-
def validate_execute_kwargs(kwargs: dict[str, Any]) -> bool:
81-
return True
82-
8379
@staticmethod
8480
def get_shutters(data_dir: Path) -> list[ShutterInfo]:
8581
shutter_count_file = sorted(data_dir.glob("*ShutterCount.txt"))[0]

mantidimaging/core/operations/rescale/rescale.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,3 @@ def execute_wrapper( # type: ignore
115115
max_output = 2147483647.0
116116

117117
return partial(RescaleFilter.filter_func, min_input=min_input, max_input=max_input, max_output=max_output)
118-
119-
@staticmethod
120-
def validate_execute_kwargs(kwargs: dict[str, Any]) -> bool:
121-
return True

mantidimaging/gui/windows/operations/model.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,11 @@ def apply_to_stacks(self, stacks: list[ImageStack], progress=None):
120120
it to the function that actually processes the images.
121121
"""
122122
for stack in stacks:
123+
self.validate_kwargs(stack)
123124
self.apply_to_images(stack, progress=progress)
124125

125126
def apply_to_images(self, images: ImageStack, progress=None, is_preview=False) -> None:
126127
input_kwarg_widgets = self.filter_widget_kwargs.copy()
127-
# Validate required kwargs are supplied so pre-processing does not happen unnecessarily
128-
if not self.selected_filter.validate_execute_kwargs(input_kwarg_widgets):
129-
raise ValueError("Not all required parameters specified")
130128

131129
# Run filter
132130
exec_func = self.selected_filter.execute_wrapper(**input_kwarg_widgets)
@@ -149,6 +147,14 @@ def apply_to_images(self, images: ImageStack, progress=None, is_preview=False) -
149147
*exec_func.args,
150148
**exec_func.keywords)
151149

150+
def validate_kwargs(self, images: ImageStack) -> None:
151+
"""
152+
Validate required kwargs are supplied so pre-processing does not happen unnecessarily
153+
"""
154+
input_kwarg_widgets = self.filter_widget_kwargs.copy()
155+
if (message := self.selected_filter.validate_execute_kwargs(input_kwarg_widgets, images)) is not None:
156+
raise ValueError(f"Issue with operation inputs: {message}")
157+
152158
def do_apply_filter(self, stacks: list[ImageStack], post_filter: Callable[[Any], None]):
153159
"""
154160
Applies the selected filter to the selected stack.

mantidimaging/gui/windows/operations/presenter.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,9 @@ def _do_apply_filter_sync(self, apply_to):
354354
self.model.do_apply_filter_sync(apply_to, partial(self._post_filter, apply_to))
355355

356356
def do_update_previews(self) -> None:
357+
if not self.view.window_ready:
358+
return
359+
357360
if self.stack is None:
358361
self.view.clear_previews()
359362
return
@@ -383,8 +386,8 @@ def do_update_previews(self) -> None:
383386
# Take copies for display to prevent issues when shared memory is cleaned
384387
before_image = np.copy(subset.data.squeeze(squeeze_axis))
385388
try:
386-
if self.model.filter_widget_kwargs:
387-
self.model.apply_to_images(subset, is_preview=True)
389+
self.model.validate_kwargs(self.stack)
390+
self.model.apply_to_images(subset, is_preview=True)
388391
except Exception as e:
389392
msg = f"Error applying filter for preview: {e}"
390393
self.show_error(msg, traceback.format_exc())

mantidimaging/gui/windows/operations/test/model_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def setup_mocks(self, execute_mock):
4646
orig_exec, orig_validate = f.execute_wrapper, f.validate_execute_kwargs
4747
self.model.setup_filter("", {})
4848
f.execute_wrapper = lambda: execute_mock
49-
f.validate_execute_kwargs = lambda _: True
49+
f.validate_execute_kwargs = lambda _, __: None
5050

5151
return orig_exec, orig_validate
5252

@@ -144,6 +144,7 @@ def test_apply_filter_to_images(self):
144144
selected_filter_mock.__name__ = mock.Mock()
145145
selected_filter_mock.__name__.return_value = "Test filter"
146146
selected_filter_mock.filter_name.return_value = "Test filter"
147+
selected_filter_mock.validate_execute_kwargs.return_value = None
147148
progress_mock = mock.Mock()
148149

149150
callback_mock = mock.Mock()
@@ -152,7 +153,6 @@ def test_apply_filter_to_images(self):
152153
self.model.selected_filter = selected_filter_mock
153154
self.model.apply_to_images(images, progress=progress_mock)
154155

155-
selected_filter_mock.validate_execute_kwargs.assert_called_once()
156156
callback_mock.assert_called_once_with(images, progress=progress_mock)
157157

158158
def test_get_filter_module_name(self):

0 commit comments

Comments
 (0)