From 319c396e684641073f07bb4c97c4f8c4349148ef Mon Sep 17 00:00:00 2001 From: ashmeigh Date: Mon, 4 Mar 2024 04:10:12 +0000 Subject: [PATCH 1/3] testing compute operations --- .../operations/nan_removal/nan_removal.py | 65 ++++++++----------- 1 file changed, 26 insertions(+), 39 deletions(-) diff --git a/mantidimaging/core/operations/nan_removal/nan_removal.py b/mantidimaging/core/operations/nan_removal/nan_removal.py index 9556b73d31f..97d3b857b03 100644 --- a/mantidimaging/core/operations/nan_removal/nan_removal.py +++ b/mantidimaging/core/operations/nan_removal/nan_removal.py @@ -3,15 +3,13 @@ from __future__ import annotations from functools import partial -from logging import getLogger from typing import Dict, TYPE_CHECKING import numpy as np -import scipy.ndimage as scipy_ndimage +from scipy.ndimage import median_filter from mantidimaging.core.operations.base_filter import BaseFilter from mantidimaging.core.parallel import shared as ps -from mantidimaging.core.utility.progress_reporting import Progress from mantidimaging.gui.utility.qt_helpers import Type if TYPE_CHECKING: @@ -51,16 +49,22 @@ def filter_func(data, replace_value=None, mode_value="Constant", progress=None) :return: The ImageStack object with the NaNs replaced. """ + params = {'replace_value': replace_value, 'mode_value': mode_value} + ps.run_compute_func(NaNRemovalFilter.compute_function, data.data.shape[0], data.shared_array, params, progress) + + return data + + @staticmethod + def compute_function(i: int, array: np.ndarray, params: dict): + mode_value = params['mode_value'] + replace_value = params['replace_value'] if mode_value == "Constant": - sample = data.data - nan_idxs = np.isnan(sample) - sample[nan_idxs] = replace_value + nan_idxs = np.isnan(array[i]) + array[i][nan_idxs] = replace_value elif mode_value == "Median": - _execute(data, 3, "reflect", progress) + array[i] = NaNRemovalFilter._nan_to_median(array[i], size=3, edgemode='reflect') else: - raise ValueError(f"Unknown mode: '{mode_value}'\nShould be one of {NaNRemovalFilter.MODES}") - - return data + raise ValueError(f"Unknown mode: '{mode_value}'. Should be one of {NaNRemovalFilter.MODES}") @staticmethod def register_gui(form: 'QFormLayout', on_change: Callable, view: 'BaseMainWindowView') -> Dict[str, 'QWidget']: @@ -87,37 +91,20 @@ def register_gui(form: 'QFormLayout', on_change: Callable, view: 'BaseMainWindow return {"mode_field": mode_field, "replace_value_field": replace_value_field} + @staticmethod + def _nan_to_median(data: np.ndarray, size: int, edgemode: str): + nans = np.isnan(data) + if np.any(nans): + median_data = np.where(nans, -np.inf, data) + median_data = median_filter(median_data, size=size, mode=edgemode) + data = np.where(nans, median_data, data) + if np.any(data == -np.inf): + data = np.where(np.logical_and(nans, data == -np.inf), np.nan, data) + + return data + @staticmethod def execute_wrapper(mode_field=None, replace_value_field=None): mode_value = mode_field.currentText() replace_value = replace_value_field.value() return partial(NaNRemovalFilter.filter_func, replace_value=replace_value, mode_value=mode_value) - - -def _nan_to_median(data: np.ndarray, size: int, edgemode: str): - nans = np.isnan(data) - if np.any(nans): - median_data = np.where(nans, -np.inf, data) - median_data = scipy_ndimage.median_filter(median_data, size=size, mode=edgemode) - data = np.where(nans, median_data, data) - - if np.any(data == -np.inf): - # Convert any left over -infs back to NaNs - data = np.where(np.logical_and(nans, data == -np.inf), np.nan, data) - - return data - - -def _execute(images: ImageStack, size, edgemode, progress=None): - log = getLogger(__name__) - progress = Progress.ensure_instance(progress, task_name='NaN Removal') - - # create the partial function to forward the parameters - f = ps.create_partial(_nan_to_median, ps.return_to_self, size=size, edgemode=edgemode) - - with progress: - log.info("PARALLEL NaN Removal filter, with pixel data type: {0}".format(images.dtype)) - - ps.execute(f, [images.shared_array], images.data.shape[0], progress, msg="NaN Removal") - - return images From 2885b3900a91490f9a61d58cc3b734bcea112396 Mon Sep 17 00:00:00 2001 From: ashmeigh Date: Wed, 6 Mar 2024 17:08:32 +0000 Subject: [PATCH 2/3] separate median and constant functions --- .../operations/nan_removal/nan_removal.py | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/mantidimaging/core/operations/nan_removal/nan_removal.py b/mantidimaging/core/operations/nan_removal/nan_removal.py index 97d3b857b03..fd577cb38ab 100644 --- a/mantidimaging/core/operations/nan_removal/nan_removal.py +++ b/mantidimaging/core/operations/nan_removal/nan_removal.py @@ -49,22 +49,27 @@ def filter_func(data, replace_value=None, mode_value="Constant", progress=None) :return: The ImageStack object with the NaNs replaced. """ - params = {'replace_value': replace_value, 'mode_value': mode_value} - ps.run_compute_func(NaNRemovalFilter.compute_function, data.data.shape[0], data.shared_array, params, progress) + if mode_value == "Constant": + params = {'replace_value': replace_value} + ps.run_compute_func(NaNRemovalFilter.compute_constant_function, data.data.shape[0], data.shared_array, + params, progress) + elif mode_value == "Median": + ps.run_compute_func(NaNRemovalFilter.compute_median_function, data.data.shape[0], data.shared_array, {}, + progress) + else: + raise ValueError(f"Unknown mode: '{mode_value}'. Should be one of {NaNRemovalFilter.MODES}") return data @staticmethod - def compute_function(i: int, array: np.ndarray, params: dict): - mode_value = params['mode_value'] + def compute_constant_function(i: int, array: np.ndarray, params: dict): replace_value = params['replace_value'] - if mode_value == "Constant": - nan_idxs = np.isnan(array[i]) - array[i][nan_idxs] = replace_value - elif mode_value == "Median": - array[i] = NaNRemovalFilter._nan_to_median(array[i], size=3, edgemode='reflect') - else: - raise ValueError(f"Unknown mode: '{mode_value}'. Should be one of {NaNRemovalFilter.MODES}") + nan_idxs = np.isnan(array[i]) + array[i][nan_idxs] = replace_value + + @staticmethod + def compute_median_function(i: int, array: np.ndarray, params: dict): + array[i] = NaNRemovalFilter._nan_to_median(array[i], size=3, edgemode='reflect') @staticmethod def register_gui(form: 'QFormLayout', on_change: Callable, view: 'BaseMainWindowView') -> Dict[str, 'QWidget']: From e6f0eefd4ad2c0b098faa5937d5539acf5f0cdd2 Mon Sep 17 00:00:00 2001 From: ashmeigh Date: Fri, 8 Mar 2024 16:56:36 +0000 Subject: [PATCH 3/3] Added message --- mantidimaging/core/operations/nan_removal/nan_removal.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mantidimaging/core/operations/nan_removal/nan_removal.py b/mantidimaging/core/operations/nan_removal/nan_removal.py index fd577cb38ab..04443126588 100644 --- a/mantidimaging/core/operations/nan_removal/nan_removal.py +++ b/mantidimaging/core/operations/nan_removal/nan_removal.py @@ -98,6 +98,11 @@ def register_gui(form: 'QFormLayout', on_change: Callable, view: 'BaseMainWindow @staticmethod def _nan_to_median(data: np.ndarray, size: int, edgemode: str): + """ + Replaces NaN values in data with median, based on a kernel 'size' and 'edgemode'. + Initially converts NaNs to -inf to avoid calculation issues, applies a median filter. + After -inf changes back to NaNs to indicate unprocessed blocks. + """ nans = np.isnan(data) if np.any(nans): median_data = np.where(nans, -np.inf, data)