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

Unit tests for out argument to processors #1805

Merged
merged 42 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
fea2b4c
Setup tests
hrobarts May 14, 2024
4188f70
Add Normaliser, MaskGenerator, Masker
hrobarts May 14, 2024
5faa20e
Add CentreOfRotation
hrobarts May 14, 2024
707c084
Add Slicer, Binner, Padder
hrobarts May 15, 2024
263fdd6
Check out doesn't suppress return
hrobarts May 15, 2024
0bf63a0
Merge branch 'master' into processors_out
hrobarts May 15, 2024
1966454
Remove old comment
hrobarts May 15, 2024
08b8acc
Merge branch 'processors_out' of github.com:TomographicImaging/CIL in…
hrobarts May 15, 2024
f005d3f
Review changes
hrobarts May 16, 2024
ff5bc46
Merge branch 'master' into processors_out
hrobarts May 16, 2024
6ad68ea
Remove comment
hrobarts May 20, 2024
f99559d
Merge branch 'processors_out' of github.com:TomographicImaging/CIL in…
hrobarts May 20, 2024
d65c54f
Add extra comments for new processors
hrobarts May 22, 2024
95d5feb
Merge branch 'master' into processors_out
hrobarts Jun 4, 2024
2947d1b
Always return from processor
hrobarts Jun 18, 2024
90bbf8b
Tidy
hrobarts Jun 18, 2024
c22179c
Merge branch 'master' into processors_out
hrobarts Jun 18, 2024
86a675f
Update changelog
hrobarts Jun 18, 2024
28e884f
Merge branch 'master' into processors_out
hrobarts Jun 28, 2024
c593c6d
Merge branch 'master' into processors_out
hrobarts Jun 28, 2024
00f0d06
Merge branch 'master' into processors_out
hrobarts Jul 24, 2024
1392dd9
Update tests and add CofR image sharpness
hrobarts Jul 24, 2024
e782e81
Add docstrings
hrobarts Jul 24, 2024
f04cbed
Check for TIGRE and GPU in image sharpness test
hrobarts Jul 24, 2024
744b850
Add tests for PaganinProccesor
hrobarts Jul 24, 2024
0ec5ea4
Remove data.copy()
hrobarts Jul 29, 2024
78805ef
Fix test order bug, make Processors check type
hrobarts Jul 31, 2024
0f34554
Merge branch 'master' into processors_out
hrobarts Aug 5, 2024
8253d5f
Fix check_output error
hrobarts Aug 5, 2024
a8e7abd
PaganinProcessor check for dtype=float32
hrobarts Aug 5, 2024
5b07539
Add test for out argument with wrong dtype
hrobarts Aug 9, 2024
fa08f5d
Check geometry and shape are correct in processors check_output
hrobarts Aug 13, 2024
ea3f3f9
Make check_output return False for projection operators
hrobarts Aug 13, 2024
54cd8c1
Add type and shape checks for plugin projectors
hrobarts Aug 13, 2024
22ebef6
Check forward projector size matches sinogram size
hrobarts Aug 13, 2024
15a9702
Review changes
hrobarts Aug 20, 2024
be6cf3d
Merge branch 'master' into processors_out
hrobarts Aug 20, 2024
3ef1cc6
Undo change to scripts
hrobarts Aug 21, 2024
8a00557
Merge branch 'processors_out' of github.com:TomographicImaging/CIL in…
hrobarts Aug 21, 2024
9a2a85a
Merge branch 'master' into processors_out
hrobarts Aug 21, 2024
9318ae9
Merge branch 'master' into processors_out
hrobarts Aug 21, 2024
ac314ca
Merge branch 'master' into processors_out
hrobarts Aug 22, 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@
- Enhancements:
- Use ravel instead of flat in KullbackLeibler numba backend (#1874)
- Upgrade Python wrapper (#1873, #1875)
- Add checks on out argument passed to processors to ensure corrrect dtype and size (#1805)
- Testing:
- New unit tests for operators and functions to check for in place errors and the behaviour of `out` (#1805)
hrobarts marked this conversation as resolved.
Show resolved Hide resolved
- Bug fixes:
- `ImageData` removes dimensions of size 1 from the input array. This fixes an issue where single slice reconstructions from 3D data would fail due to shape mismatches (#1885)
- Make Binner accept accelerated=False (#1887)


* 24.1.0
- New Features:
- Added method to plot filter in `GenericFilteredBackProjection` (#1667)
Expand Down
71 changes: 47 additions & 24 deletions Wrappers/Python/cil/framework/framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -2588,7 +2588,9 @@ def get_order_by_label(self, dimension_labels, default_dimension_labels):

def __eq__(self, other):

if isinstance(other, self.__class__) and self.config == other.config :
if isinstance(other, self.__class__) \
and self.config == other.config \
and self.dtype == other.dtype:
hrobarts marked this conversation as resolved.
Show resolved Hide resolved
return True
return False

Expand Down Expand Up @@ -3871,6 +3873,7 @@ def __init__(self, **attributes):
attributes['output'] = None
attributes['shouldRun'] = True
attributes['input'] = None
attributes['_shape_out'] = None,

for key, value in attributes.items():
self.__dict__[key] = value
Expand All @@ -3890,6 +3893,14 @@ def __setattr__(self, name, value):
self.__dict__['shouldRun'] = True
else:
raise KeyError('Attribute {0} not found'.format(name))

def _set_up(self):
"""
Configure processor attributes that require the data to setup
Must set _shape_out
"""
dataset = self.get_input()
self._shape_out = dataset.shape

def set_input(self, dataset):
"""
Expand All @@ -3900,7 +3911,6 @@ def set_input(self, dataset):
input : DataContainer
The input DataContainer
"""

if issubclass(type(dataset), DataContainer):
if self.check_input(dataset):
self.__dict__['input'] = weakref.ref(dataset)
Expand All @@ -3910,7 +3920,8 @@ def set_input(self, dataset):
else:
raise TypeError("Input type mismatch: got {0} expecting {1}"\
.format(type(dataset), DataContainer))


self._set_up()

def check_input(self, dataset):
'''Checks parameters of the input DataContainer
Expand All @@ -3927,27 +3938,40 @@ def get_output(self, out=None):
Parameters
----------
out : DataContainer, optional
Fills the referenced DataContainer with the processed data and suppresses the return
Fills the referenced DataContainer with the processed data

Returns
-------
DataContainer
The processed data. Suppressed if `out` is passed
The processed data
"""
if not self.check_output(out):
raise ValueError('Output data not compatible with processor')

if self.output is None or self.shouldRun:
if out is None:
out = self.process()
else:
self.process(out=out)

out = self.process(out=out)
if self.store_output:
self.output = out.copy()

return out

else:
return self.output.copy()

if out is not None:
out.fill(self.output)
return out
return self.output.copy()

def check_output(self, out):

if out is not None:
data = self.get_input()
if data.array.dtype != out.array.dtype:
raise TypeError("Input type mismatch: got {0} expecting {1}"\
.format(out.array.dtype, data.array.dtype))

if self._shape_out != out.shape:
raise ValueError("out size mismatch: got {0} expecting {1}"\
.format(out.shape, self._shape_out))

return True

def set_input_processor(self, processor):
if issubclass(type(processor), DataProcessor):
Expand Down Expand Up @@ -3976,14 +4000,7 @@ def process(self, out=None):
def __call__(self, x, out=None):

self.set_input(x)

if out is None:
out = self.get_output()
else:
self.get_output(out=out)

return out

return self.get_output(out=out)

class DataProcessor(Processor):
'''Basically an alias of Processor Class'''
Expand Down Expand Up @@ -4030,17 +4047,20 @@ def __init__(self):
def check_input(self, dataset):
return True

def check_output(self, dataset):
return True

def process(self, out=None):

dsi = self.get_input()
a = self.scalar
if out is None:
y = DataContainer( a * dsi.as_array() , True,
out = DataContainer( a * dsi.as_array() , True,
dimension_labels=dsi.dimension_labels )
#self.setParameter(output_dataset=y)
return y
else:
out.fill(a * dsi.as_array())
return out


###### Example of DataProcessors
Expand All @@ -4067,6 +4087,9 @@ def __init__(self, dtype=None):

def check_input(self, dataset):
return True

def check_output(self, dataset):
return True

def process(self, out=None):

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ def check_input(self, dataset):
else:
raise ValueError("Expected input dimensions is 1 or 2, got {0}"\
.format(dataset.number_of_dimensions))

def _set_up(self):
"""
Configure processor attributes that require the data to setup
Must set _shape_out
"""
self._shape_out = self.volume_geometry.shape

def set_projector(self, proj_id):
self.proj_id = proj_id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,19 @@ def __init__(self,

self.vol_geom, self.proj_geom = convert_geometry_to_astra_vec_3D(self.volume_geometry, self.sinogram_geometry)


def check_input(self, dataset):

if self.sinogram_geometry.shape != dataset.geometry.shape:
raise ValueError("Dataset not compatible with geometry used to create the projector")

return True

def _set_up(self):
"""
Configure processor attributes that require the data to setup
Must set _shape_out
"""
self._shape_out = self.volume_geometry.shape

def set_ImageGeometry(self, volume_geometry):

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ def check_input(self, dataset):
raise ValueError("Expected input dimensions is 1 or 2, got {0}"\
.format(dataset.number_of_dimensions))

def _set_up(self):
"""
Configure processor attributes that require the data to setup
Must set _shape_out
"""
self._shape_out = self.sinogram_geometry.shape

def set_projector(self, proj_id):
self.proj_id = proj_id

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ def check_input(self, dataset):
raise ValueError("Dataset not compatible with geometry used to create the projector")

return True

def _set_up(self):
"""
Configure processor attributes that require the data to setup
Must set _shape_out
"""
self._shape_out = self.sinogram_geometry.shape

def set_ImageGeometry(self, volume_geometry):

Expand Down
3 changes: 3 additions & 0 deletions Wrappers/Python/cil/plugins/astra/processors/FBP.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ def get_output(self, out=None):

def check_input(self, dataset):
return self.processor.check_input(dataset)

def check_output(self, out):
return self.processor.check_output(out=out)

def process(self, out=None):
return self.processor.process(out=out)
8 changes: 7 additions & 1 deletion Wrappers/Python/cil/plugins/astra/processors/FBP_Flexible.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,13 @@ def check_input(self, dataset):
log.warning("The ASTRA backend FBP will use simple geometry only. Any configuration offsets or rotations may be ignored.")

return True


def _set_up(self):
"""
Configure processor attributes that require the data to setup
Must set _shape_out
"""
self._shape_out = self.volume_geometry.shape

def process(self, out=None):

Expand Down
10 changes: 8 additions & 2 deletions Wrappers/Python/cil/plugins/astra/processors/FDK_Flexible.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,14 @@ def check_input(self, dataset):
.format(self.sinogram_geometry.geom_type))

return True



def _set_up(self):
"""
Configure processor attributes that require the data to setup
Must set _shape_out
"""
self._shape_out = self.volume_geometry.shape

def process(self, out=None):

# Get DATA
Expand Down
7 changes: 7 additions & 0 deletions Wrappers/Python/cil/plugins/tigre/FBP.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ def check_input(self, dataset):

DataOrder.check_order_for_engine('tigre', dataset.geometry)
return True

def _set_up(self):
"""
Configure processor attributes that require the data to setup
Must set _shape_out
"""
self._shape_out = self.image_geometry.shape

def process(self, out=None):

Expand Down
8 changes: 4 additions & 4 deletions Wrappers/Python/cil/processors/Binner.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,13 @@ def _configure(self):
"""

#as binning we only include bins that are inside boundaries
self._shape_out = [int((x.stop - x.start)//x.step) for x in self._roi_ordered]
self._shape_out_full = [int((x.stop - x.start)//x.step) for x in self._roi_ordered]
self._pixel_indices = []

# fix roi_ordered for binner based on shape out
for i in range(4):
start = self._roi_ordered[i].start
stop = self._roi_ordered[i].start + self._shape_out[i] * self._roi_ordered[i].step
stop = self._roi_ordered[i].start + self._shape_out_full[i] * self._roi_ordered[i].step

self._roi_ordered[i] = range(
start,
Expand Down Expand Up @@ -146,7 +146,7 @@ def _bin_array_numpy(self, array_in, array_binned):

for i in range(4):
# reshape the data to add each 'bin' dimensions
shape_object.append(self._shape_out[i])
shape_object.append(self._shape_out_full[i])
shape_object.append(self._roi_ordered[i].step)

shape_object = tuple(shape_object)
Expand All @@ -168,7 +168,7 @@ def _bin_array_acc(self, array_in, array_binned):
indices_start = [x.start for x in self._roi_ordered]
bins = [x.step for x in self._roi_ordered]

binner_ipp = Binner_IPP(self._shape_in, self._shape_out, indices_start, bins)
binner_ipp = Binner_IPP(self._shape_in, self._shape_out_full, indices_start, bins)

res = binner_ipp.bin(array_in, array_binned)
if res != 0:
Expand Down
1 change: 1 addition & 0 deletions Wrappers/Python/cil/processors/CofR_image_sharpness.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,3 +358,4 @@ def process(self, out=None):
return AcquisitionData(array=data_full, deep_copy=True, geometry=new_geometry, supress_warning=True)
else:
out.geometry = new_geometry
return out
1 change: 1 addition & 0 deletions Wrappers/Python/cil/processors/CofR_xcorrelation.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,4 @@ def process(self, out=None):
return AcquisitionData(array = data_full, deep_copy = True, geometry = new_geometry, supress_warning=True)
else:
out.geometry = new_geometry
return out
14 changes: 12 additions & 2 deletions Wrappers/Python/cil/processors/MaskGenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,15 @@ def check_input(self, data):
"Expected {}, got {}.".format(data.dimension_labels, self.axis))

return True

def check_output(self, out):
if out is not None:
if out.array.dtype != bool:
raise TypeError("Input type mismatch: got {0} expecting {1}"\
.format(out.array.dtype, bool))

return True


def process(self, out=None):

Expand Down Expand Up @@ -365,10 +374,11 @@ def process(self, out=None):

if out is None:
mask = numpy.asarray(mask, dtype=bool)
out = type(data)(mask, deep_copy=False, dtype=mask.dtype, geometry=data.geometry, suppress_warning=True, dimension_labels=data.dimension_labels)
return out
out = type(data)(mask, deep_copy=False, dtype=mask.dtype, geometry=data.geometry.copy(), suppress_warning=True, dimension_labels=data.dimension_labels)
else:
out.fill(mask)

return out

def _parse_threshold_value(self, arr, quantile=False):

Expand Down
6 changes: 2 additions & 4 deletions Wrappers/Python/cil/processors/Masker.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,11 @@ def process(self, out=None):

data = self.get_input()

return_arr = False
if out is None:
out = data.copy()
arr = out.as_array()
return_arr = True
else:
out.fill(data.as_array())
arr = out.as_array()

#assumes mask has 'as_array' method, i.e. is a DataContainer or is a numpy array
Expand Down Expand Up @@ -283,5 +282,4 @@ def process(self, out=None):

out.fill(arr)

if return_arr is True:
return out
return out
13 changes: 9 additions & 4 deletions Wrappers/Python/cil/processors/Normaliser.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,12 @@ def process(self, out=None):
elif projections.number_of_dimensions == 2:
a = Normaliser.Normalise_projection(projections.as_array(),
flat, dark, self.tolerance)
y = type(projections)( a , True,
dimension_labels=projections.dimension_labels,
geometry=projections.geometry)
return y

if out is None:
out = type(projections)( a , True,
dimension_labels=projections.dimension_labels,
geometry=projections.geometry)
else:
out.fill(a)

return out
Loading
Loading