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 8 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: 2 additions & 2 deletions Wrappers/Python/cil/framework/framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -3898,12 +3898,12 @@ 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 self.output is None or self.shouldRun:
if out is None:
Expand Down
1 change: 1 addition & 0 deletions Wrappers/Python/cil/processors/Masker.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ def process(self, out=None):
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
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
3 changes: 2 additions & 1 deletion Wrappers/Python/cil/processors/RingRemover.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ def process(self, out = None):
vertical = geom.pixel_num_v

# allocate datacontainer space
out = 0.*data
if out is None:
out = 0.*data

# for non multichannel data
if 'channel' not in geom.dimension_labels:
Expand Down
156 changes: 155 additions & 1 deletion Wrappers/Python/test/test_out_in_place.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import numpy as np

from cil.utilities.errors import InPlaceError
from cil.framework import AcquisitionGeometry, ImageGeometry, VectorGeometry
from cil.framework import AcquisitionGeometry, ImageGeometry, VectorGeometry, DataContainer

from cil.optimisation.operators import IdentityOperator, WaveletOperator
from cil.optimisation.functions import KullbackLeibler, ConstantFunction, TranslateFunction, soft_shrinkage, L1Sparsity, BlockFunction
Expand All @@ -38,6 +38,8 @@
IndicatorBox, TotalVariation, SumFunction, SumScalarFunction, \
WeightedL2NormSquared, MixedL11Norm, ZeroFunction

from cil.processors import AbsorptionTransmissionConverter, Binner, CentreOfRotationCorrector, MaskGenerator, Masker, Normaliser, Padder, \
RingRemover, Slicer, TransmissionAbsorptionConverter

import numpy

Expand Down Expand Up @@ -313,3 +315,155 @@ def test_proximal_out(self):
result=self.get_result(operator, 'adjoint', data)
self.out_test(result, operator, 'adjoint', data)
self.in_place_test(result,operator, 'adjoint', data)

class TestProcessorOutandInPlace(CCPiTestClass):
def setUp(self):

self.data_arrays=[np.random.normal(0,1, (10,20)).astype(np.float32),
np.array(range(0,65500, 328), dtype=np.uint16).reshape((10,20)),
np.random.uniform(0,1,(10,20)).astype(np.float32)]

ag_parallel_2D = AcquisitionGeometry.create_Parallel2D()
angles = np.linspace(0, 360, 10, dtype=np.float32)
ag_parallel_2D.set_angles(angles)
ag_parallel_2D.set_panel(20)

ag_parallel_3D = AcquisitionGeometry.create_Parallel3D()
ag_parallel_3D.set_angles(angles)
ag_parallel_3D.set_panel([20,2])

ag_cone_2D = AcquisitionGeometry.create_Cone2D(source_position=[0,-10],detector_position=[0,10])
ag_cone_2D.set_angles(angles)
ag_cone_2D.set_panel(20)

ag_cone_3D = AcquisitionGeometry.create_Cone3D(source_position=[0,-10,0],detector_position=[0,10,0])
ag_cone_3D.set_angles(angles)
ag_cone_3D.set_panel([20,2])

self.geometry_test_list = [ag_parallel_2D, ag_parallel_3D, ag_cone_2D, ag_cone_3D]
self.data_test_list= [geom.allocate(None) for geom in self.geometry_test_list]
hrobarts marked this conversation as resolved.
Show resolved Hide resolved

flat_field = self.data_test_list[0]*1
dark_field = self.data_test_list[0]*1e-5

self.processor_list = [
TransmissionAbsorptionConverter(min_intensity=0.01),
AbsorptionTransmissionConverter(),
RingRemover(),
Slicer(roi={'horizontal':(None,None,2),'angle':(None,None,2)}),
Binner(roi={'horizontal':(None,None,2),'angle':(None,None,2)}),
Padder(pad_width=1)]
self.processor_out_same_size = [
True,
True,
True,
False,
False,
False
]

def get_result(self, processor, data, *args):
input=data.copy() #To check that it isn't changed after function calls
try:
processor.set_input(data)
hrobarts marked this conversation as resolved.
Show resolved Hide resolved
out = processor.get_output()
self.assertDataArraysInContainerAllClose(input, data, rtol=1e-5, msg= "In case processor.set_input(data), processor.get_output() where processor is " + processor.__class__.__name__+ " the input data has been incorrectly affected by the calculation.")
return out
except NotImplementedError:
print("get_result test not implemented for " + processor.__class__.__name__)
hrobarts marked this conversation as resolved.
Show resolved Hide resolved
return None

def in_place_test(self, desired_result, processor, data, output_same_size=True):
if output_same_size==True:
out = data.copy()
else:
out=desired_result.copy()
try:
processor.set_input(data)
processor.get_output(out=out)
self.assertDataArraysInContainerAllClose(desired_result, out, rtol=1e-5, msg= "In place calculation failed for processor.set_input(data), processor.get_output(out=data) where processor is " + processor.__class__.__name__+ "." )

except (InPlaceError, NotImplementedError):
print("in_place_test test not implemented for " + processor.__class__.__name__)
pass
hrobarts marked this conversation as resolved.
Show resolved Hide resolved

def out_test(self, desired_result, processor, data, output_same_size=True):
input = data.copy()
if output_same_size==True:
out=0*(data.copy())
else:
out=0*(desired_result.copy())

try:
processor.set_input(input)
output = processor.get_output(out=out)

self.assertDataArraysInContainerAllClose(desired_result, out, rtol=1e-5, msg= "Calculation failed using processor.set_input(data), processor.get_output(out=out) where func is " + processor.__class__.__name__+ ".")
self.assertDataArraysInContainerAllClose(input, data, rtol=1e-5, msg= "In case processor.set_input(data), processor.get_output(out=data) where processor is " + processor.__class__.__name__+ " the input data has been incorrectly affected by the calculation. ")
self.assertDataArraysInContainerAllClose(desired_result, output, rtol=1e-5, msg= "In case processor.set_input(data), output=processor.get_output(out=data) where processor is " + processor.__class__.__name__+ " the processor supresses the output. ")
MargaretDuff marked this conversation as resolved.
Show resolved Hide resolved

except (InPlaceError, NotImplementedError):
print("out_test test not implemented for " + processor.__class__.__name__)
pass

def test_out(self):

hrobarts marked this conversation as resolved.
Show resolved Hide resolved
for geom in self.geometry_test_list:
for data_array in self.data_arrays:
data=geom.allocate(None)
try:
data.fill(data_array)
hrobarts marked this conversation as resolved.
Show resolved Hide resolved
except:
data.fill(np.repeat(data_array[:,None, :], repeats=2, axis=1))

i = 0
for processor in self.processor_list:
result=self.get_result(processor, data)
self.out_test(result, processor, data, output_same_size=self.processor_out_same_size[i])
self.in_place_test(result, processor, data, output_same_size=self.processor_out_same_size[i])
i+=1

# Test the processors that need data size as an input
processor = Normaliser(flat_field=data.get_slice(angle=0).as_array()*1, dark_field=data.get_slice(angle=0).as_array()*1e-5)
result=self.get_result(processor, data)
self.out_test(result, processor, data)
self.in_place_test(result, processor, data)

processor = MaskGenerator.median(threshold_factor=3, window=7)
mask=self.get_result(processor, data)
self.out_test(mask, processor, data)
self.in_place_test(mask, processor, data)

processor = Masker.median(mask=mask)
result=self.get_result(processor, data)
self.out_test(result, processor, data)
self.in_place_test(result, processor, data)

def test_centre_of_rotation_out(self):
for geom in self.geometry_test_list[0:2]:
for data_array in self.data_arrays:
data=geom.allocate(None)
try:
data.fill(data_array)
except:
data.fill(np.repeat(data_array[:,None, :], repeats=2, axis=1))
processor = CentreOfRotationCorrector.xcorrelation(ang_tol=180)
result=self.get_result(processor, data)
# out_test fails because the processor only updates geometry, I think this is expected behaviour
# self.out_test(result, processor, data)
# test geometry instead
hrobarts marked this conversation as resolved.
Show resolved Hide resolved
input = data.copy()
out=0*(data.copy())
try:
processor.set_input(input)
output = processor.get_output(out=out)

numpy.testing.assert_array_equal(result.geometry.config.system.rotation_axis.position, out.geometry.config.system.rotation_axis.position, err_msg= "Calculation failed using processor.set_input(data), processor.get_output(out=out) where func is " + processor.__class__.__name__+ ".")
numpy.testing.assert_array_equal(input.geometry.config.system.rotation_axis.position, data.geometry.config.system.rotation_axis.position, err_msg= "In case processor.set_input(data), processor.get_output(out=data) where processor is " + processor.__class__.__name__+ " the input data has been incorrectly affected by the calculation. ")
self.assertDataArraysInContainerAllClose(out, output, rtol=1e-5, msg= "In case processor.set_input(data), output=processor.get_output(out=data) where processor is " + processor.__class__.__name__+ " the processor incorrectly supresses the output. ")

hrobarts marked this conversation as resolved.
Show resolved Hide resolved
except (InPlaceError, NotImplementedError):
print("out_test_for_geometry test not implemented for " + processor.__class__.__name__)
pass

self.in_place_test(result, processor, data)
Loading