Skip to content

Commit

Permalink
Merge branch 'master' into algorithm_docs
Browse files Browse the repository at this point in the history
Signed-off-by: Margaret Duff <[email protected]>
  • Loading branch information
MargaretDuff authored Aug 23, 2024
2 parents 6796f07 + f708a1f commit 0589b35
Show file tree
Hide file tree
Showing 83 changed files with 5,021 additions and 3,443 deletions.
15 changes: 12 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,25 @@
- Added SVRG and LSVRG stochastic functions (#1625)
- Added SAG and SAGA stochastic functions (#1624)
- Allow `SumFunction` with 1 item (#1857)
- Added PD3O algorithm (#1834)
- Added Barzilai-Borwein step size rule to work with GD, ISTA, FISTA (#1859)
- Added callback `optimisation.utilities.callbacks.EarlyStoppingObjectiveValue` which stops iterations if an algorithm objective changes less than a provided threshold (#1892)
- Added callback `optimisation.utilities.callbacks.CGLSEarlyStopping` which replicates the automatic behaviour of CGLS in CIL versions <=24. (#1892)
- Added `labels` module with `ImageDimension`, `AcquisitionDimension`, `AcquisitionType`, `AngleUnit`, `FillType` (#1692)
- Enhancements:
- 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)
- 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)
- Testing:
- New unit tests for operators and functions to check for in place errors and the behaviour of `out` (#1805)
- 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)
- Changes that break backwards compatibility:
- CGLS will no longer automatically stop iterations once a default tolerance is reached. The option to pass `tolerance` will be deprecated to be replaced by `optimisation.utilities.callbacks` (#1892)

* 24.1.0
- New Features:
Expand Down Expand Up @@ -38,9 +50,6 @@
- Merged the files `BlockGeometry.py` and `BlockDataContainer.py` in `framework` to one file `block.py`. Please use `from cil.framework import BlockGeometry, BlockDataContainer` as before (#1799)
- Bug fix in `FGP_TV` function to set the default behaviour not to enforce non-negativity (#1826).




* 24.0.0
- Update to new CCPi-Regularisation toolkit v24.0.0. This is a backward incompatible release of the toolkit.
- CIL plugin support for TIGRE version v2.6
Expand Down
2 changes: 2 additions & 0 deletions NOTICE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ Ashley Gillman (2024) -12
Zeljko Kereta (2024) - 5
Evgueni Ovtchinnikov (2024) -1
Georg Schramm (2024) - 13
Joshua Hellier (2024) - 3
Nicholas Whyatt (2024) - 1

CIL Advisory Board:
Llion Evans - 9
Expand Down
28 changes: 11 additions & 17 deletions Wrappers/Python/cil/framework/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,14 @@
# Authors:
# CIL Developers, listed at: https://github.com/TomographicImaging/CIL/blob/master/NOTICE.txt

import numpy
import sys
from datetime import timedelta, datetime
import warnings
from functools import reduce
from .framework import cilacc
from .framework import DataContainer
from .framework import ImageData, AcquisitionData
from .framework import ImageGeometry, AcquisitionGeometry
from .framework import VectorData, VectorGeometry
from .framework import find_key, message
from .framework import DataProcessor, Processor
from .framework import AX, PixelByPixelDataProcessor, CastDataContainer
from .block import BlockDataContainer
from .block import BlockGeometry
from .framework import DataOrder
from .framework import Partitioner
from .cilacc import cilacc
from .acquisition_data import AcquisitionData
from .acquisition_geometry import AcquisitionGeometry, SystemConfiguration
from .data_container import DataContainer
from .image_data import ImageData
from .image_geometry import ImageGeometry
from .vector_data import VectorData
from .vector_geometry import VectorGeometry
from .processors import DataProcessor, Processor, AX, PixelByPixelDataProcessor, CastDataContainer
from .block import BlockDataContainer, BlockGeometry
from .partitioner import Partitioner
120 changes: 120 additions & 0 deletions Wrappers/Python/cil/framework/acquisition_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Copyright 2018 United Kingdom Research and Innovation
# Copyright 2018 The University of Manchester
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Authors:
# CIL Developers, listed at: https://github.com/TomographicImaging/CIL/blob/master/NOTICE.txt
import numpy

from .labels import AcquisitionDimension, Backend
from .data_container import DataContainer
from .partitioner import Partitioner


class AcquisitionData(DataContainer, Partitioner):
'''DataContainer for holding 2D or 3D sinogram'''
__container_priority__ = 1

@property
def geometry(self):
return self._geometry

@geometry.setter
def geometry(self, val):
self._geometry = val

@property
def dimension_labels(self):
return self.geometry.dimension_labels

@dimension_labels.setter
def dimension_labels(self, val):
if val is not None:
raise ValueError("Unable to set the dimension_labels directly. Use geometry.set_labels() instead")

def __init__(self,
array = None,
deep_copy=True,
geometry = None,
**kwargs):

dtype = kwargs.get('dtype', numpy.float32)

if geometry is None:
raise AttributeError("AcquisitionData requires a geometry")

labels = kwargs.get('dimension_labels', None)
if labels is not None and labels != geometry.dimension_labels:
raise ValueError("Deprecated: 'dimension_labels' cannot be set with 'allocate()'. Use 'geometry.set_labels()' to modify the geometry before using allocate.")

if array is None:
array = numpy.empty(geometry.shape, dtype=dtype)
elif issubclass(type(array) , DataContainer):
array = array.as_array()
elif issubclass(type(array) , numpy.ndarray):
# remove singleton dimensions
array = numpy.squeeze(array)
else:
raise TypeError('array must be a CIL type DataContainer or numpy.ndarray got {}'.format(type(array)))

if array.shape != geometry.shape:
raise ValueError('Shape mismatch got {} expected {}'.format(array.shape, geometry.shape))

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


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.'''
try:
geometry_new = self.geometry.get_slice(channel=channel, angle=angle, vertical=vertical, horizontal=horizontal)
except ValueError:
if force:
geometry_new = None
else:
raise ValueError ("Unable to return slice of requested AcquisitionData. Use 'force=True' to return DataContainer instead.")

#get new data
#if vertical = 'centre' slice convert to index and subset, this will interpolate 2 rows to get the center slice value
if vertical == 'centre':
dim = self.geometry.dimension_labels.index('vertical')

centre_slice_pos = (self.geometry.shape[dim]-1) / 2.
ind0 = int(numpy.floor(centre_slice_pos))
w2 = centre_slice_pos - ind0
out = DataContainer.get_slice(self, channel=channel, angle=angle, vertical=ind0, horizontal=horizontal)

if w2 > 0:
out2 = DataContainer.get_slice(self, channel=channel, angle=angle, vertical=ind0 + 1, horizontal=horizontal)
out = out * (1 - w2) + out2 * w2
else:
out = DataContainer.get_slice(self, channel=channel, angle=angle, vertical=vertical, horizontal=horizontal)

if len(out.shape) == 1 or geometry_new is None:
return out
else:
return AcquisitionData(out.array, deep_copy=False, geometry=geometry_new, suppress_warning=True)

def reorder(self, order):
'''
Reorders the data in memory as requested. This is an in-place operation.
Parameters
----------
order: list or str
Ordered list of labels from self.dimension_labels, or string 'astra' or 'tigre'.
'''
if order in Backend:
order = AcquisitionDimension.get_order_for_engine(order, self.geometry)

super().reorder(order)
Loading

0 comments on commit 0589b35

Please sign in to comment.