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

Allow integer or boolean mask to be passed to Masker #1532

Merged
merged 17 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
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
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- Bug fix in the adjoint of the Diagonal Operator for complex values
- Update conda build action to v2 for 2.5x quicker builds
- Add docker image & push to [`ghcr.io/tomographicimaging/cil`](https://github.com/TomographicImaging/CIL/pkgs/container/cil)
- Allow Masker to take integer arrays in addition to boolean

* 23.1.0
- Fix bug in IndicatorBox proximal_conjugate
Expand Down Expand Up @@ -132,8 +133,8 @@
- Recon.FBP allows 'astra' backend
- Fixed PowerMethod for square/non-square, complex/float matrices with stopping criterion.
- CofR image_sharpness improved for large datasets
- Geometry alignmentment fix for 2D datasets
- CGLS update for sapyb to enable complex data, bugfix in use of initial
- Geometry alignment fix for 2D datasets
- CGLS update for sapyb to enable complex data, bugfix in use of initial
- added sapyb and deprecated axpby. All algorithm updated to use sapyb.
- Allow use of square brackets in file paths to TIFF and Nikon datasets

Expand Down Expand Up @@ -175,7 +176,7 @@
- Fixed bug in Zeiss reader geometry direction of rotation

* 21.0.0
- Show2D now takes 4D datasets and slice infomation as input
- Show2D now takes 4D datasets and slice information as input
- TIGRE reconstruction package wrapped for cone-beam tomography
- Datacontainers have get_slice method which returns a dataset with a single slice of the data
- Datacontainers have reorder method which reorders the data in memory as requested, or for use with 'astra' or 'tigre'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def xcorrelation(slice_index='centre', projection_index=0, ang_tol=0.1):

>>> processor = CentreOfRotationCorrector.xcorrelation(slice_index=120)
>>> processor.set_input(data)
>>> data_centred = processor.get_ouput()
>>> data_centred = processor.get_output()


Note
Expand Down
122 changes: 79 additions & 43 deletions Wrappers/Python/cil/processors/Masker.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,52 @@
# CIL Developers, listed at: https://github.com/TomographicImaging/CIL/blob/master/NOTICE.txt

from cil.framework import DataProcessor, AcquisitionData, ImageData, ImageGeometry, DataContainer
import warnings
import numpy
from scipy import interpolate

class Masker(DataProcessor):
r'''
Processor to fill missing values provided by mask. Please use the desiried method to configure a processor for your needs.
Processor to fill missing values provided by mask.
paskino marked this conversation as resolved.
Show resolved Hide resolved
Please use the desired method to configure a processor for your needs.

Parameters
----------
mask : DataContainer, ImageData, AcquisitionData, numpy.ndarray
A boolean array with the same dimensions as input, where 'False' represents masked values.
Alternatively an integer array where 0 represents masked values, and any other value represents unmasked values.
Mask can be generated using 'MaskGenerator' processor to identify outliers.
mode : {'value', 'mean', 'median', 'interpolate'}, default='value'
The method to fill in missing values
value : float, default=0
Substitute all outliers with a specific value if method='value', otherwise discarded.
axis : str or int
Specify axis as int or from 'dimension_labels' to calculate mean, median or interpolation
(depending on mode) along that axis
method : {'linear', 'nearest', 'zeros', 'linear', 'quadratic', 'cubic', 'previous', 'next'}, default='linear'
Interpolation method to use.

Returns
-------
DataContainer or its subclass with masked outliers

lauramurgatroyd marked this conversation as resolved.
Show resolved Hide resolved
'''

@staticmethod
def value(mask=None, value=0):
r'''This sets the masked values of the input data to the requested value.
lauramurgatroyd marked this conversation as resolved.
Show resolved Hide resolved
lauramurgatroyd marked this conversation as resolved.
Show resolved Hide resolved

:param mask: A boolean array with the same dimensions as input, where 'False' represents masked values. Mask can be generated using 'MaskGenerator' processor to identify outliers.
:type mask: DataContainer, ImageData, AcquisitionData, numpy.ndarray
:param value: values to be assigned to missing elements
:type value: float, default=0
Parameters
----------
mask : DataContainer, ImageData, AcquisitionData, numpy.ndarray
A boolean array with the same dimensions as input, where 'False' represents masked values.
Alternatively an integer array where 0 represents masked values, and any other value represents unmasked values.
Mask can be generated using 'MaskGenerator' processor to identify outliers.
value : float, default=0
Values to be assigned to missing elements

Returns
-------
Masker processor
'''

processor = Masker(mode='value', mask=mask, value=value)
Expand All @@ -45,10 +74,18 @@ def value(mask=None, value=0):
def mean(mask=None, axis=None):
r'''This sets the masked values of the input data to the mean of the unmasked values across the array or axis.
lauramurgatroyd marked this conversation as resolved.
Show resolved Hide resolved

:param mask: A boolean array with the same dimensions as input, where 'False' represents masked values. Mask can be generated using 'MaskGenerator' processor to identify outliers.
:type mask: DataContainer, ImageData, AcquisitionData, numpy.ndarray
:param axis: specify axis as int or from 'dimension_labels' to calculate mean.
:type axis: str, int
Parameters
----------
mask : DataContainer, ImageData, AcquisitionData, numpy.ndarray
A boolean array with the same dimensions as input, where 'False' represents masked values.
Alternatively an integer array where 0 represents masked values, and any other value represents unmasked values.
Mask can be generated using 'MaskGenerator' processor to identify outliers.
axis : str, int
Specify axis as int or from 'dimension_labels' to calculate mean.

Returns
-------
Masker processor
'''

processor = Masker(mode='mean', mask=mask, axis=axis)
Expand All @@ -59,10 +96,18 @@ def mean(mask=None, axis=None):
def median(mask=None, axis=None):
r'''This sets the masked values of the input data to the median of the unmasked values across the array or axis.
lauramurgatroyd marked this conversation as resolved.
Show resolved Hide resolved

:param mask: A boolean array with the same dimensions as input, where 'False' represents masked values. Mask can be generated using 'MaskGenerator' processor to identify outliers.
:type mask: DataContainer, ImageData, AcquisitionData, numpy.ndarray
:param axis: specify axis as int or from 'dimension_labels' to calculate median.
:type axis: str, int
Parameters
----------
mask : DataContainer, ImageData, AcquisitionData, numpy.ndarray
A boolean array with the same dimensions as input, where 'False' represents masked values.
Alternatively an integer array where 0 represents masked values, and any other value represents unmasked values.
Mask can be generated using 'MaskGenerator' processor to identify outliers.
axis : str, int
Specify axis as int or from 'dimension_labels' to calculate median.

Returns
-------
Masker processor
'''

processor = Masker(mode='median', mask=mask, axis=axis)
Expand All @@ -71,14 +116,22 @@ def median(mask=None, axis=None):

@staticmethod
def interpolate(mask=None, axis=None, method='linear'):
r'''This operates over the specified axis and uses 1D interpolation over remaining flattened array to fill in missing vaues.
r'''This operates over the specified axis and uses 1D interpolation over remaining flattened array to fill in missing values.
lauramurgatroyd marked this conversation as resolved.
Show resolved Hide resolved

:param mask: A boolean array with the same dimensions as input, where 'False' represents masked values. Mask can be generated using 'MaskGenerator' processor to identify outliers.
:type mask: DataContainer, ImageData, AcquisitionData, numpy.ndarray
:param axis: specify axis as int or from 'dimension_labels' to loop over and perform 1D interpolation.
:type axis: str, int
:param method: One of the following interpoaltion methods: linear, nearest, zeros, linear, quadratic, cubic, previous, next
:param method: str, default='linear'
Parameters
----------
mask : DataContainer, ImageData, AcquisitionData, numpy.ndarray
A boolean array with the same dimensions as input, where 'False' represents masked values.
Alternatively an integer array where 0 represents masked values, and any other value represents unmasked values.
Mask can be generated using 'MaskGenerator' processor to identify outliers.
axis : str, int
Specify axis as int or from 'dimension_labels' to loop over and perform 1D interpolation.
method : {'linear', 'nearest', 'zeros', 'linear', 'quadratic', 'cubic', 'previous', 'next'}, default='linear'
Interpolation method to use.

Returns
-------
Masker processor
'''

processor = Masker(mode='interpolate', mask=mask, axis=axis, method=method)
Expand All @@ -91,22 +144,6 @@ def __init__(self,
value = 0,
axis = None,
method = 'linear'):

r'''Processor to fill missing values provided by mask.

:param mask: A boolean array with the same dimensions as input, where 'False' represents masked values. Mask can be generated using 'MaskGenerator' processor to identify outliers.
:type mask: DataContainer, ImageData, AcquisitionData, numpy.ndarray
:param mode: a method to fill in missing values (value, mean, median, interpolate)
:type mode: str, default=value
:param value: substitute all outliers with a specific value
:type value: float, default=0
:param axis: specify axis as int or from 'dimension_labels' to calculate mean or median in respective modes
:type axis: str or int
:param method: One of the following interpoaltion methods: linear, nearest, zeros, linear, quadratic, cubic, previous, next
:param method: str, default='linear'
:return: DataContainer or it's subclass with masked outliers
:rtype: DataContainer or it's subclass
'''

kwargs = {'mask': mask,
'mode': mode,
Expand Down Expand Up @@ -153,11 +190,10 @@ def process(self, out=None):
except:
mask_arr = self.mask

try:
mask_invert = ~mask_arr
except TypeError:
raise TypeError("Mask expected to be a boolean array got {}".format(mask_arr.dtype))

mask_arr = numpy.array(mask_arr, dtype=bool)

mask_invert = ~mask_arr
paskino marked this conversation as resolved.
Show resolved Hide resolved

try:
axis_index = data.dimension_labels.index(self.axis)
except:
Expand Down Expand Up @@ -199,7 +235,7 @@ def process(self, out=None):
elif self.mode == 'interpolate':
if self.method not in ['linear', 'nearest', 'zeros', 'linear', \
'quadratic', 'cubic', 'previous', 'next']:
raise TypeError("Wrong interpolation method, one of the follwoing is expected:\n" +
raise TypeError("Wrong interpolation method, one of the following is expected:\n" +
"linear, nearest, zeros, linear, quadratic, cubic, previous, next")

ndim = data.number_of_dimensions
Expand Down
2 changes: 1 addition & 1 deletion Wrappers/Python/cil/processors/Normaliser.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class Normaliser(Processor):
Input: AcquisitionData
Parameter: 2D projection with flat field (or stack)
2D projection with dark field (or stack)
Output: AcquisitionDataSetn
Output: AcquisitionDataSet
'''

def __init__(self, flat_field = None, dark_field = None, tolerance = 1e-5):
Expand Down
21 changes: 19 additions & 2 deletions Wrappers/Python/test/test_DataProcessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from cil.framework import ImageGeometry, VectorGeometry, AcquisitionGeometry
from cil.framework import ImageData, AcquisitionData
from cil.utilities import dataexample
from timeit import default_timer as timer

from cil.framework import AX, CastDataContainer, PixelByPixelDataProcessor
from cil.recon import FBP
Expand Down Expand Up @@ -2521,15 +2520,33 @@ def setUp(self):
self.mask_manual = DataContainer(mask_manual, dimension_labels=self.data.dimension_labels)
self.mask_generated = MaskGenerator.special_values()(self.data)

# make a copy of mask_manual with 1s and 0s instead of bools:
mask_int_manual = mask_manual.astype(numpy.int32)
self.mask_int_manual = DataContainer(mask_int_manual, dimension_labels=self.data.dimension_labels)


def test_Masker_Manual(self):
self.Masker_check(self.mask_manual, self.data, self.data_init)

def test_Masker_generated(self):
self.Masker_check(self.mask_generated, self.data, self.data_init)

def test_Masker_with_integer_mask(self):
self.Masker_check(self.mask_int_manual, self.data, self.data_init)

def test_Masker_doesnt_modify_input_mask(self):
mask = self.mask_manual.copy()
self.Masker_check(self.mask_manual, self.data, self.data_init)
numpy.testing.assert_array_equal(mask.as_array(), self.mask_manual.as_array())

def test_Masker_doesnt_modify_input_integer_mask(self):
mask = self.mask_int_manual.copy()
self.Masker_check(self.mask_int_manual, self.data, self.data_init)
numpy.testing.assert_array_equal(mask.as_array(), self.mask_int_manual.as_array())

def Masker_check(self, mask, data, data_init):

# test vaue mode
# test value mode
m = Masker.value(mask=mask, value=10)
m.set_input(data)
res = m.process()
Expand Down
Loading