Commit 2234549b authored by David Frank's avatar David Frank Committed by Tobias Lasser

#63 Move functionality of DataDescriptor to VolumeDescriptor, DataDescriptor is now abstract

- add pure virtual destructor to DataDescriptor
- create VolumeDescriptor which takes over most functionality from DataDescriptor
- move static function bestCommon(...) to a free function in DescriptorUtils.h/cpp
- fix other classes and tests to return and handle VolumeDescriptors instead of DataDescriptors
parent 96386b1b
Pipeline #269129 passed with stages
in 57 minutes and 45 seconds
......@@ -12,6 +12,7 @@
#include "Logger.h"
#include "elsaDefines.h"
#include "VolumeDescriptor.h"
#include "PhantomGenerator.h"
#include "CircleTrajectoryGenerator.h"
......@@ -29,7 +30,7 @@ void runProjector2D(index_t coeffsPerDim)
// generate 2d phantom
IndexVector_t size(2);
size << coeffsPerDim, coeffsPerDim;
auto phantom = DataContainer_t(DataDescriptor(size));
auto phantom = DataContainer_t(VolumeDescriptor(size));
phantom = 0;
// generate circular trajectory
......@@ -59,7 +60,7 @@ void runProjector3D(index_t coeffsPerDim)
// generate 2d phantom
IndexVector_t size(3);
size << coeffsPerDim, coeffsPerDim, coeffsPerDim;
auto phantom = DataContainer_t(DataDescriptor(size));
auto phantom = DataContainer_t(VolumeDescriptor(size));
phantom = 0;
// generate circular trajectory
......
......@@ -9,6 +9,7 @@
#define CATCH_CONFIG_ENABLE_BENCHMARKING
#include <catch2/catch.hpp>
#include "VolumeDescriptor.h"
#include "Geometry.h"
#include <string>
#include <cstdlib>
......@@ -47,11 +48,11 @@ TEST_CASE("Ray generation for 2D")
{
IndexVector_t volCoeff(2);
volCoeff << 5, 5;
DataDescriptor ddVol(volCoeff);
VolumeDescriptor ddVol(volCoeff);
IndexVector_t detCoeff(1);
detCoeff << 5;
DataDescriptor ddDet(detCoeff);
VolumeDescriptor ddDet(detCoeff);
real_t s2c = 10;
real_t c2d = 4;
......@@ -98,11 +99,11 @@ TEST_CASE("Ray generation for 3D")
{
IndexVector_t volCoeff(3);
volCoeff << 5, 5, 5;
DataDescriptor ddVol(volCoeff);
VolumeDescriptor ddVol(volCoeff);
IndexVector_t detCoeff(2);
detCoeff << 5, 5;
DataDescriptor ddDet(detCoeff);
VolumeDescriptor ddDet(detCoeff);
real_t s2c = 10;
real_t c2d = 4;
......
......@@ -10,41 +10,45 @@ set(ELSA_MODULE_EXPORT_TARGET elsa_${ELSA_MODULE_NAME}Targets)
set(MODULE_HEADERS
elsaDefines.h
Cloneable.h
DataDescriptor.h
BlockDescriptor.h
IdenticalBlocksDescriptor.h
PartitionDescriptor.h
RandomBlocksDescriptor.h
Descriptors/DataDescriptor.h
Descriptors/DescriptorUtils.h
Descriptors/VolumeDescriptor.h
Descriptors/BlockDescriptor.h
Descriptors/IdenticalBlocksDescriptor.h
Descriptors/PartitionDescriptor.h
Descriptors/RandomBlocksDescriptor.h
DataContainer.h
DataContainerIterator.h
DataHandler.h
DataHandlerCPU.h
DataHandlerMapCPU.h
Handlers/DataHandler.h
Handlers/DataHandlerCPU.h
Handlers/DataHandlerMapCPU.h
LinearOperator.h
Expression.h
ExpressionPredicates.h)
# list all the code files of the module
set(MODULE_SOURCES
DataDescriptor.cpp
RandomBlocksDescriptor.cpp
IdenticalBlocksDescriptor.cpp
PartitionDescriptor.cpp
Descriptors/DataDescriptor.cpp
Descriptors/VolumeDescriptor.cpp
Descriptors/RandomBlocksDescriptor.cpp
Descriptors/DescriptorUtils.cpp
Descriptors/IdenticalBlocksDescriptor.cpp
Descriptors/PartitionDescriptor.cpp
DataContainer.cpp
DataHandlerCPU.cpp
DataHandlerMapCPU.cpp
Handlers/DataHandlerCPU.cpp
Handlers/DataHandlerMapCPU.cpp
LinearOperator.cpp)
if(${ELSA_CUDA_VECTOR})
set(MODULE_HEADERS
${MODULE_HEADERS}
DataHandlerGPU.h
DataHandlerMapGPU.h)
Handlers/DataHandlerGPU.h
Handlers/DataHandlerMapGPU.h)
set(MODULE_SOURCES
${MODULE_SOURCES}
DataHandlerGPU.cpp
DataHandlerMapGPU.cpp)
Handlers/DataHandlerGPU.cpp
Handlers/DataHandlerMapGPU.cpp)
endif()
# build the module library
......@@ -62,12 +66,17 @@ endif()
# link Quickvec for DataHandlerGPU
if(ELSA_CUDA_VECTOR AND Quickvec_FOUND)
target_link_libraries(${ELSA_MODULE_TARGET_NAME} PUBLIC Quickvec::quickvec)
target_compile_definitions(${ELSA_MODULE_TARGET_NAME} PUBLIC ELSA_ENABLE_CUDA_VECTOR)
endif()
target_include_directories(${ELSA_MODULE_TARGET_NAME}
PUBLIC
$<INSTALL_INTERFACE:include/elsa/${ELSA_MODULE_NAME}>
$<INSTALL_INTERFACE:include/elsa/${ELSA_MODULE_NAME}/Descriptors>
$<INSTALL_INTERFACE:include/elsa/${ELSA_MODULE_NAME}/Handlers>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Descriptors>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Handlers>
)
......
......@@ -53,6 +53,8 @@ namespace elsa
_numberOfCoefficientsPerDimension.head(i).prod();
}
DataDescriptor::~DataDescriptor() {}
index_t DataDescriptor::getNumberOfDimensions() const { return _numberOfDimensions; }
index_t DataDescriptor::getNumberOfCoefficients() const
......@@ -97,55 +99,6 @@ namespace elsa
return coordinate;
}
std::unique_ptr<DataDescriptor>
DataDescriptor::bestCommon(const std::vector<const DataDescriptor*>& descList)
{
if (descList.empty())
throw std::invalid_argument("DataDescriptor::bestCommon: descriptor list empty");
const auto& firstDesc = *descList[0];
auto coeffs = firstDesc.getNumberOfCoefficientsPerDimension();
auto size = firstDesc.getNumberOfCoefficients();
auto spacing = firstDesc.getSpacingPerDimension();
bool allSame =
std::all_of(descList.begin(), descList.end(),
[&firstDesc](const DataDescriptor* d) { return *d == firstDesc; });
if (allSame)
return firstDesc.clone();
bool allSameCoeffs =
std::all_of(descList.begin(), descList.end(), [&coeffs](const DataDescriptor* d) {
return d->getNumberOfCoefficientsPerDimension().size() == coeffs.size()
&& d->getNumberOfCoefficientsPerDimension() == coeffs;
});
if (allSameCoeffs) {
bool allSameSpacing =
std::all_of(descList.begin(), descList.end(), [&spacing](const DataDescriptor* d) {
return d->getSpacingPerDimension() == spacing;
});
if (allSameSpacing) {
return std::make_unique<DataDescriptor>(coeffs, spacing);
} else {
return std::make_unique<DataDescriptor>(coeffs);
}
}
bool allSameSize =
std::all_of(descList.begin(), descList.end(), [size](const DataDescriptor* d) {
return d->getNumberOfCoefficients() == size;
});
if (!allSameSize)
throw std::invalid_argument(
"DataDescriptor::bestCommon: descriptor sizes do not match");
return std::make_unique<DataDescriptor>(IndexVector_t::Constant(1, size));
}
DataDescriptor* DataDescriptor::cloneImpl() const { return new DataDescriptor(*this); }
bool DataDescriptor::isEqual(const DataDescriptor& other) const
{
if (typeid(other) != typeid(*this))
......
......@@ -9,15 +9,18 @@ namespace elsa
{
/**
* \brief Class representing metadata for linearized n-dimensional signal stored in memory
* \brief Base class for representing metadata for linearized n-dimensional signal stored in
* memory
*
* \author Matthias Wieczorek - initial code
* \author Tobias Lasser - modularization, modernization
* \author Maximilian Hornung - various enhancements
* \author David Frank - inheritance restructuring
*
* This class provides an interface for metadata about a signal that is stored in memory. This
* base class provides other descriptor subclasses with a fundamental interface to access the
* important parameters (i.e. dimensionality, size and spacing) of the metadata.
*
* This class provides metadata about a signal that is stored in memory (typically a
* DataContainer). This signal can be n-dimensional, and will be stored in memory in a
* linearized fashion.
*/
class DataDescriptor : public Cloneable<DataDescriptor>
{
......@@ -25,8 +28,8 @@ namespace elsa
/// delete default constructor (having no metadata is invalid)
DataDescriptor() = delete;
/// default destructor
~DataDescriptor() override = default;
/// Pure virtual destructor
virtual ~DataDescriptor() = 0;
/**
* \brief Constructor for DataDescriptor, accepts dimension and size
......@@ -90,35 +93,6 @@ namespace elsa
*/
IndexVector_t getCoordinateFromIndex(index_t index) const;
/**
* \brief Finds the descriptor with the same number of coefficients as the descriptors in
* the list that retains as much information as possible
*
* \param[in] descriptorList a vector of plain pointers to DataDescriptor
*
* \return std::unique_ptr<DataDescriptor> the best common descriptor
*
* \throw std::invalid_argument if the vector is empty or the descriptors in the vector
* don't all have the same size
*
* If all descriptors are equal, a clone of the first descriptor in the list is returned.
* If all descriptors have a common base descriptor, that data descriptor is returned.
* If the base descriptors only differ in spacing, the base descriptor with a uniform
* spacing of 1 is returned.
* Otherwise, the linearized descriptor with a spacing of 1 is returned.
*/
static std::unique_ptr<DataDescriptor>
bestCommon(const std::vector<const DataDescriptor*>& descriptorList);
/// convenience overload for invoking bestCommon() with a number of const DataDescriptor&
template <
typename... DescriptorType,
typename = std::enable_if_t<(std::is_base_of_v<DataDescriptor, DescriptorType> && ...)>>
static std::unique_ptr<DataDescriptor> bestCommon(const DescriptorType&... descriptors)
{
return bestCommon(std::vector{static_cast<const DataDescriptor*>(&descriptors)...});
}
protected:
/// Number of dimensions
index_t _numberOfDimensions;
......@@ -142,9 +116,6 @@ namespace elsa
/// default move constructor, hidden from non-derived classes to prevent potential slicing
DataDescriptor(DataDescriptor&&) = default;
/// implement the polymorphic clone operation
DataDescriptor* cloneImpl() const override;
/// implement the polymorphic comparison operation
bool isEqual(const DataDescriptor& other) const override;
};
......
#include "DescriptorUtils.h"
#include "DataDescriptor.h"
#include "VolumeDescriptor.h"
namespace elsa
{
std::unique_ptr<DataDescriptor> bestCommon(const std::vector<const DataDescriptor*>& descList)
{
if (descList.empty())
throw std::invalid_argument("DataDescriptor::bestCommon: descriptor list empty");
const auto& firstDesc = *descList[0];
auto coeffs = firstDesc.getNumberOfCoefficientsPerDimension();
auto size = firstDesc.getNumberOfCoefficients();
auto spacing = firstDesc.getSpacingPerDimension();
bool allSame =
std::all_of(descList.begin(), descList.end(),
[&firstDesc](const DataDescriptor* d) { return *d == firstDesc; });
if (allSame)
return firstDesc.clone();
bool allSameCoeffs =
std::all_of(descList.begin(), descList.end(), [&coeffs](const DataDescriptor* d) {
return d->getNumberOfCoefficientsPerDimension().size() == coeffs.size()
&& d->getNumberOfCoefficientsPerDimension() == coeffs;
});
if (allSameCoeffs) {
bool allSameSpacing =
std::all_of(descList.begin(), descList.end(), [&spacing](const DataDescriptor* d) {
return d->getSpacingPerDimension() == spacing;
});
if (allSameSpacing) {
return std::make_unique<VolumeDescriptor>(coeffs, spacing);
} else {
return std::make_unique<VolumeDescriptor>(coeffs);
}
}
bool allSameSize =
std::all_of(descList.begin(), descList.end(), [size](const DataDescriptor* d) {
return d->getNumberOfCoefficients() == size;
});
if (!allSameSize)
throw std::invalid_argument(
"DataDescriptor::bestCommon: descriptor sizes do not match");
return std::make_unique<VolumeDescriptor>(IndexVector_t::Constant(1, size));
}
} // namespace elsa
\ No newline at end of file
#pragma once
#include "DataDescriptor.h"
namespace elsa
{
/**
* \brief Finds the descriptor with the same number of coefficients as the descriptors in
* the list that retains as much information as possible
*
* \param[in] descriptorList a vector of plain pointers to DataDescriptor
*
* \return std::unique_ptr<DataDescriptor> the best common descriptor
*
* \throw std::invalid_argument if the vector is empty or the descriptors in the vector
* don't all have the same size
*
* If all descriptors are equal, a clone of the first descriptor in the list is returned.
* If all descriptors have a common base descriptor, that data descriptor is returned.
* If the base descriptors only differ in spacing, the base descriptor with a uniform
* spacing of 1 is returned.
* Otherwise, the linearized descriptor with a spacing of 1 is returned.
*/
std::unique_ptr<DataDescriptor>
bestCommon(const std::vector<const DataDescriptor*>& descriptorList);
/// convenience overload for invoking bestCommon() with a number of const DataDescriptor&
template <
typename... DescriptorType,
typename = std::enable_if_t<(std::is_base_of_v<DataDescriptor, DescriptorType> && ...)>>
std::unique_ptr<DataDescriptor> bestCommon(const DescriptorType&... descriptors)
{
return bestCommon(std::vector{static_cast<const DataDescriptor*>(&descriptors)...});
}
} // namespace elsa
\ No newline at end of file
#include "IdenticalBlocksDescriptor.h"
#include "VolumeDescriptor.h"
namespace elsa
{
......@@ -48,8 +49,8 @@ namespace elsa
return true;
}
DataDescriptor IdenticalBlocksDescriptor::initBase(index_t numberOfBlocks,
const DataDescriptor& dataDescriptor)
VolumeDescriptor IdenticalBlocksDescriptor::initBase(index_t numberOfBlocks,
const DataDescriptor& dataDescriptor)
{
if (numberOfBlocks < 1)
throw std::invalid_argument(
......@@ -63,6 +64,6 @@ namespace elsa
numberOfCoeffs[numDim - 1] = numberOfBlocks;
spacingOfCoeffs[numDim - 1] = 1;
return DataDescriptor(numberOfCoeffs, spacingOfCoeffs);
return VolumeDescriptor(numberOfCoeffs, spacingOfCoeffs);
}
} // namespace elsa
\ No newline at end of file
} // namespace elsa
#include "BlockDescriptor.h"
#include "VolumeDescriptor.h"
namespace elsa
{
......@@ -13,7 +14,7 @@ namespace elsa
* for the indexing of the different blocks, and will always have a spacing of one and a number
* of coefficients corresponding to the number of blocks.
*
* This descriptor should be the prefferred choice when dealing with vector fields.
* This descriptor should be the preferred choice when dealing with vector fields.
*/
class IdenticalBlocksDescriptor : public BlockDescriptor
{
......@@ -24,7 +25,7 @@ namespace elsa
*
* \param[in] numberOfBlocks is the desired number of blocks
* \param[in] dataDescriptor is the descriptor that will be replicated numberOfBlocks
times
* times
* along a new dimension
*
* \throw std::invalid_argument if numberOfBlocks is non-positive
......@@ -61,6 +62,6 @@ namespace elsa
private:
/// generates the
DataDescriptor initBase(index_t numberOfBlocks, const DataDescriptor& dataDescriptor);
VolumeDescriptor initBase(index_t numberOfBlocks, const DataDescriptor& dataDescriptor);
};
} // namespace elsa
\ No newline at end of file
} // namespace elsa
......@@ -123,11 +123,11 @@ namespace elsa
return _blockOffsets == otherBlock->_blockOffsets;
}
std::unique_ptr<DataDescriptor>
std::unique_ptr<VolumeDescriptor>
PartitionDescriptor::generateDescriptorOfPartition(index_t numberOfSlices) const
{
auto coeffsPerDim = getNumberOfCoefficientsPerDimension();
coeffsPerDim[_numberOfDimensions - 1] = numberOfSlices;
return std::make_unique<DataDescriptor>(coeffsPerDim, getSpacingPerDimension());
return std::make_unique<VolumeDescriptor>(coeffsPerDim, getSpacingPerDimension());
}
} // namespace elsa
#include "BlockDescriptor.h"
#include "VolumeDescriptor.h"
namespace elsa
{
/**
......@@ -11,8 +13,8 @@ namespace elsa
* slices of the original descriptor, taken along its last dimension. This also means that the
* number of coefficients per block can only vary in the last dimension.
*
* The PartitionDescriptor has the same number of coefficients and spacing per dimension as the
* original.
* The PartitionDescriptor has the same dimension, the same number of coefficients and spacing
* per dimension as the original.
*/
class PartitionDescriptor : public BlockDescriptor
{
......@@ -85,6 +87,7 @@ namespace elsa
private:
/// generates the descriptor of a partition containing numberOfSlices slices
std::unique_ptr<DataDescriptor> generateDescriptorOfPartition(index_t numberOfSlices) const;
std::unique_ptr<VolumeDescriptor>
generateDescriptorOfPartition(index_t numberOfSlices) const;
};
} // namespace elsa
#include "RandomBlocksDescriptor.h"
#include "VolumeDescriptor.h"
namespace elsa
{
RandomBlocksDescriptor::RandomBlocksDescriptor(
const std::vector<std::unique_ptr<DataDescriptor>>& blockDescriptors)
: BlockDescriptor{DataDescriptor{determineSize(blockDescriptors)}},
: BlockDescriptor{VolumeDescriptor{determineSize(blockDescriptors)}},
_blockDescriptors(0),
_blockOffsets{blockDescriptors.size()}
{
......@@ -19,7 +20,7 @@ namespace elsa
RandomBlocksDescriptor::RandomBlocksDescriptor(
std::vector<std::unique_ptr<DataDescriptor>>&& blockDescriptors)
: BlockDescriptor{DataDescriptor{determineSize(blockDescriptors)}},
: BlockDescriptor{VolumeDescriptor{determineSize(blockDescriptors)}},
_blockDescriptors{std::move(blockDescriptors)},
_blockOffsets{_blockDescriptors.size()}
{
......@@ -90,4 +91,4 @@ namespace elsa
return IndexVector_t::Constant(1, size);
}
} // namespace elsa
\ No newline at end of file
} // namespace elsa
......@@ -16,7 +16,9 @@ namespace elsa
* There are no restrictions whatsoever imposed on the descriptors of different blocks.
* Different blocks may even have different number of dimensions.
*
* The full descriptor will always be one-dimensional, and with a spacing of one.
* The full descriptor will always be one-dimensional, and with a spacing of one. The size of it
* will be the sum of the sizes of all the descriptors, i.e. the sizes returned by
* DataDescriptor::getNumberOfCoefficients() for each descriptor in the list.
*/
class RandomBlocksDescriptor : public BlockDescriptor
{
......
#include "VolumeDescriptor.h"
#include <stdexcept>
#include <algorithm>
namespace elsa
{
VolumeDescriptor::VolumeDescriptor(IndexVector_t numberOfCoefficientsPerDimension)
: DataDescriptor(numberOfCoefficientsPerDimension)
{
}
VolumeDescriptor::VolumeDescriptor(IndexVector_t numberOfCoefficientsPerDimension,
RealVector_t spacingPerDimension)
: DataDescriptor(numberOfCoefficientsPerDimension, spacingPerDimension)
{
}
VolumeDescriptor* VolumeDescriptor::cloneImpl() const
{
return new VolumeDescriptor(_numberOfCoefficientsPerDimension, _spacingPerDimension);
}
bool VolumeDescriptor::isEqual(const DataDescriptor& other) const
{
return DataDescriptor::isEqual(other);
}
} // namespace elsa
#pragma once
#include "DataDescriptor.h"
namespace elsa
{
/**
* \brief Class representing metadata for linearized n-dimensional signal stored in memory
*
* \author Matthias Wieczorek - initial code
* \author Tobias Lasser - modularization, modernization
* \author Maximilian Hornung - various enhancements
* \author David Frank - inheritance restructuring
*
* This class provides metadata about a signal that is stored in memory (typically a
* DataContainer). This signal can be n-dimensional, and will be stored in memory in a
* linearized fashion.
*/
class VolumeDescriptor : public DataDescriptor
{
public:
/// delete default constructor (having no metadata is invalid)
VolumeDescriptor() = delete;
/// default destructor
~VolumeDescriptor() override = default;
/**
* \brief Constructor for DataDescriptor, accepts dimension and size
*
* \param[in] numberOfCoefficientsPerDimension vector containing the number of coefficients
* per dimension, (dimension is set implicitly from the size of the vector)
*
* \throw std::invalid_argument if any number of coefficients is non-positive
*/
explicit VolumeDescriptor(IndexVector_t numberOfCoefficientsPerDimension);
/**
* \brief Constructor for DataDescriptor, accepts dimension, size and spacing
*
* \param[in] numberOfCoefficientsPerDimension vector containing the number of coefficients
* per dimension, (dimension is set implicitly from the size of the vector)
* \param[in] spacingPerDimension vector containing the spacing per dimension
*
* \throw std::invalid_argument if any number of coefficients is non-positive,
* or sizes of numberOfCoefficientsPerDimension and spacingPerDimension do not match
*/
explicit VolumeDescriptor(IndexVector_t numberOfCoefficientsPerDimension,
RealVector_t spacingPerDimension);
private:
/// implement the polymorphic clone operation
VolumeDescriptor* cloneImpl() const override;
/// implement the polymorphic comparison operation
bool isEqual(const DataDescriptor& other) const override;
};
} // namespace elsa