Skip to content

Commit

Permalink
Merge branch 'master' into kl-multiple-exits
Browse files Browse the repository at this point in the history
Signed-off-by: Edoardo Pasca <[email protected]>
  • Loading branch information
paskino authored Oct 1, 2024
2 parents 5fe4179 + b10d15d commit e71f22c
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 21 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ jobs:
- uses: conda-incubator/setup-miniconda@v3
with:
python-version: ${{ matrix.python-version }}
mamba-version: "*"
mamba-version: "1.5"
channels: conda-forge
- name: conda build & test
working-directory: recipe
Expand Down Expand Up @@ -243,7 +243,7 @@ jobs:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
- uses: docker/build-push-action@v6
with:
cache-from: type=gha
cache-to: type=gha,mode=max
Expand All @@ -254,7 +254,7 @@ jobs:
run: >
docker run --rm -v .:/CIL tomographicimaging/cil:test /bin/bash -c
'python -m unittest discover -v /CIL/Wrappers/Python/test'
- uses: docker/build-push-action@v5
- uses: docker/build-push-action@v6
with:
cache-from: type=gha
cache-to: type=gha,mode=max
Expand Down
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@
- Use ravel instead of flat in KullbackLeibler numba backend (#1874)
- Upgrade Python wrapper (#1873, #1875)
- Updated the documentation for the algorithm base class (#1809)
- Add checks on out argument passed to processors to ensure corrrect dtype and size (#1805)
- Add checks on out argument passed to processors to ensure correct dtype and size (#1805)
- Internal refactor: Replaced string-based label checks with enum-based checks for improved type safety and consistency (#1692)
- Internal refactor: Separate framework into multiple files (#1692)
- Allow the SIRT algorithm to take `initial=None` (#1906)
- Add checks on equality method of `AcquisitionData` and `ImageData` for equality of data type and geometry (#1919)
- Add check on equality method of `AcquisitionGeometry` for equality of dimension labels (#1919)
- Removed multiple exits from numba implementation of KullbackLeibler divergence (#1901)
- Testing:
- New unit tests for operators and functions to check for in place errors and the behaviour of `out` (#1805)
- Updates in SPDHG vs PDHG unit test to reduce test time and adjustments to parameters (#1898)
- Drop Jenkins in favour of GHA for conda builds (#1914)
- New unit tests for `DataContainer`, `AcquisitionData` and `ImageData` to check equality method (`__eq__`) behaves as expected (#1919)
- 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)
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,16 @@ conda create --name cil -c conda-forge -c https://software.repos.intel.com/pytho
To install CIL and the additional packages and plugins needed to run the [CIL demos](https://github.com/TomographicImaging/CIL-Demos) install the environment with:

```sh
conda create --name cil -c conda-forge -c https://software.repos.intel.com/python/conda -c ccpi cil=24.1.0 astra-toolbox=*=cuda* tigre ccpi-regulariser tomophantom ipywidgets
conda create --name cil -c conda-forge -c https://software.repos.intel.com/python/conda -c ccpi cil=24.1.0 astra-toolbox=*=cuda* tigre ccpi-regulariser tomophantom ipykernel ipywidgets
```

where:
- `astra-toolbox` enables CIL support for [ASTRA toolbox](http://www.astra-toolbox.com) CPU projector (2D Parallel beam only) (GPLv3 license)
- `astra-toolbox=*=py*` enables CIL support for [ASTRA toolbox](http://www.astra-toolbox.com) CPU projector (2D Parallel beam only) (GPLv3 license)
- `astra-toolbox=*=cuda*` (requires an NVIDIA GPU) enables CIL support for [ASTRA toolbox](http://www.astra-toolbox.com) GPU projectors (GPLv3 license)
- `tigre` (requires an NVIDIA GPU) enables support for [TIGRE](https://github.com/CERN/TIGRE) toolbox projectors (BSD license)
- `ccpi-regulariser` is the [CCPi Regularisation Toolkit](https://github.com/TomographicImaging/CCPi-Regularisation-Toolkit)
- `tomophantom` can generate phantoms to use as test data [Tomophantom](https://github.com/dkazanc/TomoPhantom)
- `ipykernel` provides the IPython kernel for Jupyter (allowing jupyter notebooks to be run)
- `ipywidgets` enables visulisation tools within jupyter noteboooks

### Dependencies
Expand Down
25 changes: 25 additions & 0 deletions Wrappers/Python/cil/framework/acquisition_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,31 @@ def __init__(self,

super(AcquisitionData, self).__init__(array, deep_copy, geometry=geometry,**kwargs)

def __eq__(self, other):
'''
Check if two AcquisitionData objects are equal. This is done by checking if the geometry, data and dtype are equal.
Also, if the other object is a numpy.ndarray, it will check if the data and dtype are equal.
Parameters
----------
other: AcquisitionData or numpy.ndarray
The object to compare with.
Returns
-------
bool
True if the two objects are equal, False otherwise.
'''

if isinstance(other, AcquisitionData):
if numpy.array_equal(self.as_array(), other.as_array()) \
and self.geometry == other.geometry \
and self.dtype == other.dtype:
return True
elif numpy.array_equal(self.as_array(), other) and self.dtype==other.dtype:
return True
else:
return False

def get_slice(self,channel=None, angle=None, vertical=None, horizontal=None, force=False):
'''Returns a new dataset of a single slice in the requested direction.'''
Expand Down
3 changes: 2 additions & 1 deletion Wrappers/Python/cil/framework/acquisition_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -2095,7 +2095,8 @@ def __eq__(self, other):

if isinstance(other, self.__class__) \
and self.config == other.config \
and self.dtype == other.dtype:
and self.dtype == other.dtype \
and self.dimension_labels == other.dimension_labels:
return True
return False

Expand Down
25 changes: 25 additions & 0 deletions Wrappers/Python/cil/framework/image_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,32 @@ def __init__(self,

super(ImageData, self).__init__(array, deep_copy, geometry=geometry, **kwargs)

def __eq__(self, other):
'''
Check if two ImageData objects are equal. This is done by checking if the geometry, data and dtype are equal.
Also, if the other object is a numpy.ndarray, it will check if the data and dtype are equal.
Parameters
----------
other: ImageData or numpy.ndarray
The object to compare with.
Returns
-------
bool
True if the two objects are equal, False otherwise.
'''

if isinstance(other, ImageData):
if numpy.array_equal(self.as_array(), other.as_array()) \
and self.geometry == other.geometry \
and self.dtype == other.dtype:
return True
elif numpy.array_equal(self.as_array(), other) and self.dtype==other.dtype:
return True
else:
return False

def get_slice(self,channel=None, vertical=None, horizontal_x=None, horizontal_y=None, force=False):
'''
Returns a new ImageData of a single slice of in the requested direction.
Expand Down
2 changes: 2 additions & 0 deletions Wrappers/Python/test/test_AcquisitionGeometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,10 +495,12 @@ def test_copy(self):
def test_get_centre_slice(self):
AG = AcquisitionGeometry.create_Parallel3D(detector_direction_y=[0,1,1])
AG.set_panel([1000,2000],[1,1])
AG.set_angles([0,1,2,3,5])
AG_cs = AG.get_centre_slice()

AG2 = AcquisitionGeometry.create_Parallel2D()
AG2.set_panel([1000,1],[1,math.sqrt(0.5)])
AG2.set_angles([0,1,2,3,5])

self.assertEqual(AG2, AG_cs)

Expand Down
96 changes: 82 additions & 14 deletions Wrappers/Python/test/test_DataContainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,6 @@ def aid(x):


class TestDataContainer(CCPiTestClass):
def create_simple_ImageData(self):
N = 64
ig = ImageGeometry(voxel_num_x=N, voxel_num_y=N)
Phantom = ImageData(geometry=ig)

x = Phantom.as_array()

x[int(round(N/4)):int(round(3*N/4)),
int(round(N/4)):int(round(3*N/4))] = 0.5
x[int(round(N/8)):int(round(7*N/8)),
int(round(3*N/8)):int(round(5*N/8))] = 1

return (ig, Phantom)

def create_DataContainer(self, X,Y,Z, value=1):
a = value * np.ones((X, Y, Z), dtype='float32')
#print("a refcount " , sys.getrefcount(a))
Expand Down Expand Up @@ -88,6 +74,88 @@ def test_ndim(self):
self.assertEqual(x_np.ndim, x_cil.ndim)
self.assertEqual(3, x_cil.ndim)

def test_DataContainer_equal(self):
array = np.linspace(-1, 1, 32, dtype=np.float32).reshape(4, 8)
data = DataContainer(array)
data1 = data.copy()

# Check two identical DataContainers are equal
self.assertTrue((data == data1).all())
# Check it works for comparing DataContainer with numpy array
self.assertTrue((data == data1.as_array()).all())

# # Check two different DataContainers are not equal
data1 += 1
self.assertFalse((data == data1).all())

def test_AcquisitionData_equal(self):
array = np.linspace(-1, 1, 32, dtype=np.float32).reshape(4, 8)
geom = AcquisitionGeometry.create_Parallel3D().set_panel((8, 4)).set_channels(1).set_angles([1])
data = AcquisitionData(array, geometry=geom)

data1 = data.copy()

# Check two identical AcquisitionData are equal
self.assertTrue(data == data1)
# Check it works for comparing AcquisitionData with numpy array
self.assertTrue(data == data1.as_array())

# # Check two different AcquisitionData are not equal
data1 += 1
self.assertFalse(data == data1)

# Check the equality of two AcquisitionData with different shapes
data_different_shape = data.copy()
data_different_shape.array = data_different_shape.array.reshape(8, 4)

self.assertFalse(data == data_different_shape)

# Check the equality of two AcquisitionData with different dtypes
data_different_dtype = data.copy()
data_different_dtype.array = data_different_dtype.array.astype(np.float64)
self.assertFalse(data == data_different_dtype)


# Check the equality of two AcquisitionData with different labels
data_different_labels = data.copy()
print(data_different_labels.geometry.dimension_labels)
data_different_labels.geometry.set_labels([AcquisitionDimension("ANGLE"), AcquisitionDimension("CHANNEL") ])
self.assertFalse(data == data_different_labels)

def test_ImageData_equal(self):
array = np.linspace(-1, 1, 32, dtype=np.float32).reshape(4, 8)
geom = ImageGeometry(voxel_num_x=8, voxel_num_y=4)
data = ImageData(array, geometry=geom)

data1 = data.copy()

# Check two identical ImageData are equal
self.assertTrue(data == data1)
# Check it works for comparing ImageData with numpy array
self.assertTrue(data == data1.as_array())

# # Check two different ImageData are not equal
data1 += 1
self.assertFalse(data == data1)

# Check the equality of two ImageData with different shapes
data_different_shape = data.copy()
data_different_shape.array = data_different_shape.array.reshape(8, 4)

self.assertFalse(data == data_different_shape)

# Check the equality of two ImageData with different dtypes
data_different_dtype = data.copy()
data_different_dtype.array = data_different_dtype.array.astype(np.float64)
self.assertFalse(data == data_different_dtype)


# Check the equality of two ImageData with different labels
data_different_labels = data.copy()
data_different_labels.geometry.set_labels([ImageDimension("VERTICAL"), ImageDimension("HORIZONTAL_X")])
self.assertFalse(data == data_different_labels)


def testInlineAlgebra(self):
X, Y, Z = 8, 16, 32
a = np.ones((X, Y, Z), dtype='float32')
Expand Down

0 comments on commit e71f22c

Please sign in to comment.