Commit 0a9e43a9 authored by Tobias Lasser's avatar Tobias Lasser

First pre-release commit

parent ea1322b7
.idea
.vscode
cmake-build-debug
cmake-build-release
build/
[submodule "thirdparty/Catch2"]
path = thirdparty/Catch2
url = https://github.com/catchorg/Catch2.git
[submodule "thirdparty/eigen3"]
path = thirdparty/eigen3
url = https://github.com/eigenteam/eigen-git-mirror
[submodule "thirdparty/spdlog"]
path = thirdparty/spdlog
url = https://github.com/gabime/spdlog.git
cmake_minimum_required(VERSION 3.9)
project(elsa VERSION 0.9
DESCRIPTION "elsa recon toolbox"
LANGUAGES CXX)
# ------------ elsa options ------------
# ------------
option(ELSA_TESTING "Enable the unit tests of elsa" ON)
option(GIT_SUBMODULE "Check submodules during build" ON)
# ------------ general setup -----------
# ------------
# add our cmake modules under cmake/
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
# set default built type to "Debug" (if no other specified)
set(DEFAULT_BUILD_TYPE "Release")
include(SetDefaultBuildType)
# set where to install the exports/targets
set(INSTALL_CONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/cmake)
# ------------ dependencies ------------
# ------------
# include the git submodule update
include(UpdateGitSubmodules)
# include the InstallElsaModule function
include(InstallElsaModule)
# setup the Eigen3 library
find_package(Eigen3 3.3 NO_MODULE QUIET) # first try if there is an installed version
if(NOT EIGEN3_INCLUDE_DIR) # if not found, try our submodule
set(EIGEN3_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/thirdparty/eigen3")
message(STATUS "Using eigen3 submodule at ${EIGEN3_INCLUDE_DIR}")
find_package(Eigen3 3.3 REQUIRED)
endif()
# ------------ setup testing -----------
# ------------
if(ELSA_TESTING)
message(STATUS "elsa testing is enabled")
enable_testing()
add_subdirectory(thirdparty/Catch2)
# add the CMake modules for automatic test discovery
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/thirdparty/Catch2/contrib" ${CMAKE_MODULE_PATH})
else(ELSA_TESTING)
message(STATUS "elsa testing is disabled")
endif(ELSA_TESTING)
# ------------ add code/docs -----------
# ------------
# the elsa library
add_subdirectory(elsa)
# the documentation
add_subdirectory(docs EXCLUDE_FROM_ALL)
# the examples
add_subdirectory(examples EXCLUDE_FROM_ALL)
# ------------ setup installation ------
# ------------
# set up the target/library for make install
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
# setup the ElsaConfig*.cmake files
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/elsa/elsaConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion
)
configure_package_config_file(
${CMAKE_CURRENT_LIST_DIR}/cmake/elsaConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/elsa/elsaConfig.cmake
INSTALL_DESTINATION ${INSTALL_CONFIG_DIR}
)
# install the config files
install(
FILES
${CMAKE_CURRENT_BINARY_DIR}/elsa/elsaConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/elsa/elsaConfigVersion.cmake
DESTINATION ${INSTALL_CONFIG_DIR}
)
# this puts the local build tree into the user package repository, but not the installed version...
# ...but we rely on the paths in the installed version. sigh. commented out for now.
#export(PACKAGE elsa)
......@@ -2,4 +2,41 @@
elsa - an elegant framework for tomographic reconstruction
The open source release will take place on Mar. 29, 2019.
\ No newline at end of file
elsa is a modern, flexible C++ library intended for use in tomographic reconstruction.
Currently, operators are implemented for X-ray Computed Tomography.
Other imaging modalities can be supported by implemented appropriate operators.
## Pre-release status
elsa is currently in pre-release status and not yet fully functional.
# Requirements
elsa requires a C++17 compliant compiler, such as GCC, Clang or Microsoft Visual Studio in recent versions.
The main third party dependencies (Eigen3, spdlog, Catch2) are integrated via git submodules.
# Compiling
Once you have cloned the git repository, compilation can be done by following these steps:
* go to the elsa folder and create a build folder (e.g. mkdir build; cd build)
* run the following commands in the build folder:
* cmake ..
* make
* make install
You can provide -DCMAKE_INSTALL_PREFIX=folder during the cmake step to select an installation destination other than the default (/usr/local on Unix).
You can run the elsa unit tests by running ctest.
# Building against the elsa library
When using the elsa library in your project, we advise using CMake as the build system. You can then include elsa via the find_package(elsa) statement and linking your target against the corresponding elsa modules, e.g. target_link_libraries(myTarget elsa::core).
As elsa depends on Eigen3 and spdlog, you will need to have these packages installed on your system, and you have to point CMake to those installations.
# - Try to find Eigen3 lib
#
# This module supports requiring a minimum version, e.g. you can do
# find_package(Eigen3 3.1.2)
# to require version 3.1.2 or newer of Eigen3.
#
# Once done this will define
#
# EIGEN3_FOUND - system has eigen lib with correct version
# EIGEN3_INCLUDE_DIR - the eigen include directory
# EIGEN3_VERSION - eigen version
#
# and the following imported target:
#
# Eigen3::Eigen - The header-only Eigen library
#
# This module reads hints about search locations from
# the following environment variables:
#
# EIGEN3_ROOT
# EIGEN3_ROOT_DIR
# Copyright (c) 2006, 2007 Montel Laurent, <montel@kde.org>
# Copyright (c) 2008, 2009 Gael Guennebaud, <g.gael@free.fr>
# Copyright (c) 2009 Benoit Jacob <jacob.benoit.1@gmail.com>
# Redistribution and use is allowed according to the terms of the 2-clause BSD license.
if(NOT Eigen3_FIND_VERSION)
if(NOT Eigen3_FIND_VERSION_MAJOR)
set(Eigen3_FIND_VERSION_MAJOR 2)
endif(NOT Eigen3_FIND_VERSION_MAJOR)
if(NOT Eigen3_FIND_VERSION_MINOR)
set(Eigen3_FIND_VERSION_MINOR 91)
endif(NOT Eigen3_FIND_VERSION_MINOR)
if(NOT Eigen3_FIND_VERSION_PATCH)
set(Eigen3_FIND_VERSION_PATCH 0)
endif(NOT Eigen3_FIND_VERSION_PATCH)
set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}")
endif(NOT Eigen3_FIND_VERSION)
macro(_eigen3_check_version)
file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header)
string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}")
set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}")
string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}")
set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}")
string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}")
set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}")
set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION})
if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})
set(EIGEN3_VERSION_OK FALSE)
else(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})
set(EIGEN3_VERSION_OK TRUE)
endif(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})
if(NOT EIGEN3_VERSION_OK)
message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, "
"but at least version ${Eigen3_FIND_VERSION} is required")
endif(NOT EIGEN3_VERSION_OK)
endmacro(_eigen3_check_version)
if (EIGEN3_INCLUDE_DIR)
# in cache already
_eigen3_check_version()
set(EIGEN3_FOUND ${EIGEN3_VERSION_OK})
set(Eigen3_FOUND ${EIGEN3_VERSION_OK})
else (EIGEN3_INCLUDE_DIR)
# search first if an Eigen3Config.cmake is available in the system,
# if successful this would set EIGEN3_INCLUDE_DIR and the rest of
# the script will work as usual
find_package(Eigen3 ${Eigen3_FIND_VERSION} NO_MODULE QUIET)
if(NOT EIGEN3_INCLUDE_DIR)
find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library
HINTS
ENV EIGEN3_ROOT
ENV EIGEN3_ROOT_DIR
PATHS
${CMAKE_INSTALL_PREFIX}/include
${KDE4_INCLUDE_DIR}
PATH_SUFFIXES eigen3 eigen
)
endif(NOT EIGEN3_INCLUDE_DIR)
if(EIGEN3_INCLUDE_DIR)
_eigen3_check_version()
endif(EIGEN3_INCLUDE_DIR)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK)
mark_as_advanced(EIGEN3_INCLUDE_DIR)
endif(EIGEN3_INCLUDE_DIR)
if(EIGEN3_FOUND AND NOT TARGET Eigen3::Eigen)
add_library(Eigen3::Eigen INTERFACE IMPORTED)
set_target_properties(Eigen3::Eigen PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${EIGEN3_INCLUDE_DIR}")
endif()
# install an elsa module
function(InstallElsaModule ELSA_MODULE_NAME ELSA_MODULE_TARGET_NAME ELSA_MODULE_EXPORT_TARGET)
#This is required so that the exported target has the name core and not elsa_core
set_target_properties(${ELSA_MODULE_TARGET_NAME} PROPERTIES EXPORT_NAME ${ELSA_MODULE_NAME})
include(GNUInstallDirs)
# install the module
install(TARGETS ${ELSA_MODULE_TARGET_NAME}
EXPORT ${ELSA_MODULE_EXPORT_TARGET}
INCLUDES DESTINATION include
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
# install the header files
install(FILES ${MODULE_HEADERS}
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${ELSA_MODULE_NAME}
)
# create the config file for the module
install(EXPORT ${ELSA_MODULE_EXPORT_TARGET}
FILE ${ELSA_MODULE_EXPORT_TARGET}.cmake
NAMESPACE elsa::
DESTINATION ${INSTALL_CONFIG_DIR}
)
endfunction()
# set default build type to DEFAULT_BUILD_TYPE, if none is specified
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message(STATUS "Setting build type to '${DEFAULT_BUILD_TYPE}' as none was specified.")
set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE
STRING "Choose the type of build." FORCE)
# set the possible values for cmake-gui
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif()
\ No newline at end of file
# update/init git submodules if necessary
# idea from https://cliutils.gitlab.io/modern-cmake/chapters/projects/submodule.html
# try to perform the git submodule update
find_package(Git QUIET)
if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
if(GIT_SUBMODULE) # do this only if the GIT_SUBMODULE option is enabled
message(STATUS "Git submodule update")
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE GIT_SUBMODULE_RESULT)
if(NOT GIT_SUBMODULE_RESULT EQUAL "0")
message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMODULE_RESULT}, please check out submodules")
endif()
endif()
endif()
# check the results
if (NOT EXISTS "${PROJECT_SOURCE_DIR}/thirdparty/Catch2/CMakeLists.txt" OR
NOT EXISTS "${PROJECT_SOURCE_DIR}/thirdparty/eigen3/CMakeLists.txt" OR
NOT EXISTS "${PROJECT_SOURCE_DIR}/thirdparty/spdlog/CMakeLists.txt")
message(FATAL_ERROR "The git submodules were not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.")
endif()
\ No newline at end of file
@PACKAGE_INIT@
include(CmakeFindDependencyMacro)
find_dependency(Eigen3 3.3 NO_MODULE REQUIRED)
set(_supported_components @ELSA_REGISTERED_COMPONENTS@)
message(STATUS "elsa components found: ${_supported_components}")
foreach(_comp ${elsa_FIND_COMPONENTS})
if (NOT _comp IN_LIST _supported_components)
set(elsa_FOUND False)
set(elsa_NOT_FOUND_MESSAGE "Unsupported component: ${_comp}")
endif()
include(${CMAKE_CURRENT_LIST_DIR}/elsa_${_comp}Targets.cmake)
endforeach()
cmake_minimum_required(VERSION 3.9)
# setup macro for component registration
# so you can use find_package(elsa COMPONENT name)
macro(registerComponent name)
set(ELSA_REGISTERED_COMPONENTS "${ELSA_REGISTERED_COMPONENTS};${name}" PARENT_SCOPE)
endmacro()
# add the elsa modules
add_subdirectory(core)
add_subdirectory(logging)
add_subdirectory(io)
add_subdirectory(operators)
# propogate the variable to the parent scope
set(ELSA_REGISTERED_COMPONENTS "${ELSA_REGISTERED_COMPONENTS};" PARENT_SCOPE)
#include "BlockDescriptor.h"
#include <stdexcept>
namespace elsa
{
BlockDescriptor::BlockDescriptor(index_t numberOfBlocks, const DataDescriptor &dataDescriptor)
: DataDescriptor(dataDescriptor),
_blockDescriptors{},
_blockOffsets(numberOfBlocks)
{
// sanity check
if (numberOfBlocks <= 0)
throw std::invalid_argument("BlockDescriptor: number of blocks has to be positive");
for (index_t i = 0; i < numberOfBlocks; ++i) {
_blockDescriptors.emplace_back(dataDescriptor.clone());
_blockOffsets(i) = dataDescriptor.getNumberOfCoefficients() * i;
}
// update the base class DataDescriptor with additional dimension/size
_numberOfDimensions++;
_numberOfCoefficientsPerDimension.conservativeResize(_numberOfDimensions);
_numberOfCoefficientsPerDimension(_numberOfDimensions-1) = numberOfBlocks;
_spacingPerDimension.conservativeResize(_numberOfDimensions);
_spacingPerDimension(_numberOfDimensions-1) = 1.0;
_productOfCoefficientsPerDimension.conservativeResize(_numberOfDimensions);
_productOfCoefficientsPerDimension(_numberOfDimensions-1) =
_numberOfCoefficientsPerDimension.head(_numberOfDimensions-1).prod();
}
index_t BlockDescriptor::getNumberOfBlocks() const {
return _blockDescriptors.size();
}
const DataDescriptor& BlockDescriptor::getIthDescriptor(index_t i) const {
return *_blockDescriptors.at(i);
}
index_t BlockDescriptor::getIthBlockOffset(elsa::index_t i) const {
if (i < 0 || i >= _blockOffsets.size())
throw std::invalid_argument("BlockDescriptor: index i is out of bounds");
return _blockOffsets.coeff(i);
}
BlockDescriptor::BlockDescriptor(const BlockDescriptor &blockDescriptor)
: DataDescriptor(blockDescriptor),
_blockDescriptors{},
_blockOffsets{blockDescriptor._blockOffsets}
{
for (const auto& descriptor : blockDescriptor._blockDescriptors)
_blockDescriptors.emplace_back(descriptor->clone());
}
BlockDescriptor* BlockDescriptor::cloneImpl() const
{
return new BlockDescriptor(*this);
}
bool BlockDescriptor::isEqual(const DataDescriptor& other) const
{
if (!DataDescriptor::isEqual(other))
return false;
auto otherBlock = dynamic_cast<const BlockDescriptor*>(&other);
if (!otherBlock)
return false;
if (_blockDescriptors.size() != otherBlock->_blockDescriptors.size())
return false;
for (index_t i = 0; i < _blockDescriptors.size(); ++i)
if (*_blockDescriptors.at(i) != *otherBlock->_blockDescriptors.at(i))
return false;
if (_blockOffsets != otherBlock->_blockOffsets)
return false;
return true;
}
} // namespace elsa
#pragma once
#include "elsa.h"
#include "Cloneable.h"
#include "DataDescriptor.h"
#include <memory>
#include <vector>
namespace elsa
{
/**
* \brief Class representing metadata for blocked, linearized n-dimensional signal stored in memory
*
* \author Matthias Wieczorek - initial code
* \author David Frank - rewrite
* \author Nikola Dinev - various enhancements
* \author Tobias Lasser - rewrite, modularization, modernization
*
* 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 in blocks.
* The blocks can be used to support various operations (like blocked operators or ordered subsets),
* however, the blocks have to lie in memory one after the other (i.e. no stride is supported).
*/
class BlockDescriptor : public DataDescriptor
{
public:
/// delete default constructor (having no metadata is invalid)
BlockDescriptor() = delete;
/// default destructor
~BlockDescriptor() override = default;
/**
* \brief Create a new descriptor, replicating the dataDescriptor numberOfBlocks times along a new dimension
*
* \param[in] numberOfBlocks is the desired number of blocks
* \param[in] dataDescriptor is the descriptor that will be replicated numberOfBlocks times along a new dimension
*
* \throw std::invalid_argument if numberOfBlocks is non-positive
*/
explicit BlockDescriptor(index_t numberOfBlocks, const DataDescriptor& dataDescriptor);
/// return the number of blocks
index_t getNumberOfBlocks() const;
/// return the DataDescriptor of the i-th block
const DataDescriptor& getIthDescriptor(index_t i) const;
/// return the offset to access the data of the i-th block
index_t getIthBlockOffset(index_t i) const;
protected:
/// vector of DataDescriptors describing the individual blocks
std::vector<std::unique_ptr<DataDescriptor>> _blockDescriptors;
/// vector of the individual block data offsets
IndexVector_t _blockOffsets;
/// protected copy constructor
BlockDescriptor(const BlockDescriptor& blockDescriptor);
/// implement the polymorphic clone operation
BlockDescriptor* cloneImpl() const override;
/// implement the polymorphic comparison operation
bool isEqual(const DataDescriptor& other) const override;
};
} // namespace elsa
cmake_minimum_required(VERSION 3.9)
# set the name of the module
set(ELSA_MODULE_NAME core)
set(ELSA_MODULE_TARGET_NAME elsa_core)
set(ELSA_MODULE_EXPORT_TARGET elsa_${ELSA_MODULE_NAME}Targets)
# list all the headers of the module
set(MODULE_HEADERS
elsa.h
Cloneable.h
DataDescriptor.h
BlockDescriptor.h
DataContainer.h
DataHandler.h
DataHandlerCPU.h
LinearOperator.h)
# list all the code files of the module
set(MODULE_SOURCES
DataDescriptor.cpp
BlockDescriptor.cpp
DataContainer.cpp
DataHandlerCPU.cpp
LinearOperator.cpp)
# build the module library
add_library(${ELSA_MODULE_TARGET_NAME} ${MODULE_HEADERS} ${MODULE_SOURCES})
add_library(elsa::${ELSA_MODULE_NAME} ALIAS ${ELSA_MODULE_TARGET_NAME})
target_include_directories(${ELSA_MODULE_TARGET_NAME}
PUBLIC
$<INSTALL_INTERFACE:include/${ELSA_MODULE_NAME}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
)
target_link_libraries(${ELSA_MODULE_TARGET_NAME} PUBLIC Eigen3::Eigen)
# require C++17
target_compile_features(${ELSA_MODULE_TARGET_NAME} PUBLIC cxx_std_17)
# build the tests (if enabled)
if(ELSA_TESTING)
add_subdirectory(tests)
endif(ELSA_TESTING)
# register the module
registerComponent(${ELSA_MODULE_NAME})
# install the module
InstallElsaModule(${ELSA_MODULE_NAME} ${ELSA_MODULE_TARGET_NAME} ${ELSA_MODULE_EXPORT_TARGET})
\ No newline at end of file
#pragma once
#include <memory>
namespace elsa
{
/**
* \brief Class implementing polymorphic clones with smart pointers and CRTP, as well as comparison operators.
*
* \author Tobias Lasser
*
* This class provides a clone method using CRTP to support covariance with smart pointers.