Commit 994dc043 authored by andibraimllari's avatar andibraimllari
Browse files

Merge branch 'master' into metrics

# Conflicts:
#	elsa/core/CMakeLists.txt
#	elsa/core/tests/CMakeLists.txt
parents 3f864517 0fa4cc78
Pipeline #756364 failed with stages
in 19 minutes and 20 seconds
......@@ -3,6 +3,25 @@ Change Log
All notable changes are documented in this file.
v0.7 (October 27, 2021)
-----------------------
- add deep learning module, with backends for cuDNN and OneDNN
- add initial support for dictionary learning (representation problem using OMP)
- add ADMM solver
- add preconditioning support to FGM, OGM, SQS
- add ordered subset support for SQS
- upgrades to DataContainers (e.g. reductions)
- replace Catch2 with doctest
- many improvements to the CMake setup
- add support for CMake presets
- add linting for CMake files
- use docker registry for CI images
- updates to documentation, add some guides
- improvements on unit tests
- various code clean-ups
- various bugfixes
v0.6 (February 2, 2021)
-----------------------
- switch to CPM for third party dependencies (away from git submodules)
......
......@@ -138,7 +138,7 @@ if(ELSA_MASTER_PROJECT)
CPMAddPackage(
NAME spdlog
GITHUB_REPOSITORY gabime/spdlog
VERSION 1.9.0
VERSION 1.8.5
OPTIONS "SPDLOG_INSTALL ON"
)
message(STATUS "Using bundled spdlog version in ${spdlog_SOURCE_DIR}")
......
# Credits to Matt Godbold for the idea of this!
THIS_DIR = $(shell pwd)
PHONY: help
help: # with thanks to Ben Rady
@grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
CMAKE ?= $(shell which cmake || echo .cmake-not-found)
FZF ?= $(shell which fzf)
AG ?= $(shell which ag)
ENTR ?= $(shell which entr)
CT ?= $(shell which ct)
# Choose clang over gcc if present
ifeq ($(origin CC),default)
CC = $(shell (type clang > /dev/null 2>&1 && echo "clang") || \
(type gcc > /dev/null 2>&1 && echo "gcc") || \
echo .c-compiler-not-found)
endif
ifeq ($(origin CXX),default)
CXX = $(shell (type clang++ > /dev/null 2>&1 && echo "clang++") || \
(type g++ > /dev/null 2>&1 && echo "g++") || \
echo .cxx-compiler-not-found)
endif
BUILD_TYPE?=RelWithDebInfo
BUILD_OPTIONS=
# Choose build directory depending on compiler and build type
ifeq ($(CXX),clang++)
BUILD_ROOT:=$(CURDIR)/build/$(BUILD_TYPE)/clang
endif
ifeq ($(CXX),g++)
BUILD_ROOT:=$(CURDIR)/build/$(BUILD_TYPE)/gcc
endif
# Use ninja if present
ifeq ($(shell which ninja),)
CMAKE_GENERATOR_FLAGS?=
else
CMAKE_GENERATOR_FLAGS?=-GNinja
ifeq ($(CXX),clang++)
CXXFLAGS += -fcolor-diagnostics
endif
ifeq ($(CXX),g++)
CXXFLAGS += -fdiagnostics-color=always
endif
endif
USE_CUDA?=y
ifeq ($(USE_CUDA),y)
# Check for NVCC
ifneq ($(shell which nvcc),)
CUDA_OPTIONS?=-DELSA_BUILD_CUDA_PROJECTORS=ON
else
CUDA_OPTIONS?=
endif
else
BUILD_OPTIONS+=-DELSA_BUILD_CUDA_PROJECTORS=OFF
endif
USE_DNNL?=y
ifeq ($(USE_DNNL),y)
BUILD_OPTIONS+=-DELSA_BUILD_ML_DNNL=ON
else
BUILD_OPTIONS+=-DELSA_BUILD_ML_DNNL=OFF
endif
USE_CUDNN?=n
ifeq ($(USE_CUDNN),y)
BUILD_OPTIONS+=-DELSA_BUILD_ML_CUDNN=ON
else
BUILD_OPTIONS+=-DELSA_BUILD_ML_CUDNN=OFF
endif
GENERATE_PYBINDS?=n
ifeq ($(GENERATE_PYBINDS),y)
BUILD_OPTIONS+=-DELSA_BUILD_PYTHON_BINDINGS=ON
else
BUILD_OPTIONS+=-DELSA_BUILD_PYTHON_BINDINGS=OFF
endif
ENABLE_BENCHMARKS?=y
ifeq ($(ENABLE_BENCHMARKS),y)
HYPERFINE?=$(shell which hyperfine || echo .hyperfine-not-found)
PERF?=$(shell which perf || echo .perf-not-found)
BUILD_OPTIONS+=-DELSA_BENCHMARKS=ON
else
BUILD_OPTIONS+=-DELSA_BENCHMARKS=OFF
endif
# all targets defined by cmake, but each on of the line starts with xxx, so that we can split it later as newline gets lost
CMAKE_TARGETS = $(shell cmake --build $(BUILD_ROOT) --target help | sed s/$$/xxx/)
# Turn all arguments after build, into targets to build (default) all
ifeq (build,$(firstword $(MAKECMDGOALS)))
# use the rest as arguments for "run"
BUILD_TARGETS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
# ...and turn them into do-nothing targets
$(eval $(BUILD_TARGETS):;@:)
endif
ifeq ($(BUILD_TARGETS),)
BUILD_TARGETS=all
endif
selected_test ?=
ifeq (test,$(firstword $(MAKECMDGOALS)))
selected_test = y
else ifeq (watch, $(firstword $(MAKECMDGOALS)))
selected_test = y
endif
# Turn all arguments after test, into targets to build (default) all
ifeq ($(selected_test),y)
# use the rest as arguments for "run"
SELECTED_TEST_TARGET := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
# ...and turn them into do-nothing targets
$(eval $(SELECTED_TEST_TARGET):;@:)
RUN_TEST_TARGET ?=
# replace the xxx, remove empty lines, only select targets starting with test_, remove targets with '/' then cut everything away after colon, see if grep can find the target and remove whitespaces with xargs
FILTERED_TEST_TARGETS = $(shell echo -n $(CMAKE_TARGETS) | sed 's/xxx/\n/g' | grep "\S" | grep test\_ | grep -v '/' | cut -d: -f1 | xargs)
ifeq ($(FZF),)
# If no argument is passed, we can't handle it without fzf
ifeq ($(SELECTED_TEST_TARGET),)
$(error Please specify a test target (or install fzf to interactivly select one))
endif
# Check if grep can find it
RUN_TEST_TARGET = $(shell echo -n $(FILTERED_TEST_TARGETS) | tr ' ' '\n' | grep -i $(SELECTED_TEST_TARGET))
$(info $(SELECTED_TEST_TARGET))
ifeq ($(RUN_TEST_TARGET),)
$(error Can not find unique a unique test target with argument $(SELECTED_TEST_TARGET))
endif
else
# use fzf to (if necessary interactivly) find a target
ifneq ($(SELECTED_TEST_TARGET),)
RUN_TEST_TARGET := $(shell echo -n $(FILTERED_TEST_TARGETS) | tr ' ' '\n' | $(FZF) -q $(SELECTED_TEST_TARGET) -1 -0)
else
RUN_TEST_TARGET := $(shell echo -n $(FILTERED_TEST_TARGETS) | tr ' ' '\n' | $(FZF) -1 -0)
endif
endif
TEST_EXECUTABLE = $(shell find $(BUILD_ROOT)/bin/tests -iname $(RUN_TEST_TARGET))
endif
.%-not-found:
@echo "-----------------------"
@echo "elsa needs $(@:.%-not-found=%) to build. Please install it "
@echo "-----------------------"
@exit 1
.PHONY: configure
configure: $(BUILD_ROOT)/CMakeCache.txt ## Configure elsa
.PHONY: build tests test watch
build: $(BUILD_ROOT)/CMakeCache.txt ## Build provided targets (default: all)
$(CMAKE) --build $(BUILD_ROOT) --target $(BUILD_TARGETS)
$(shell notify-send "elsa "Building target \"$(BUILD_TARGETS)\" has finished")
tests: build ## build and run all tests
$(CMAKE) --build $(BUILD_ROOT) -- tests
test: $(BUILD_ROOT)/CMakeCache.txt ## build and run individual test (e.g. make test mytest)
CXXFLAGS=$(CXXFLAGS) $(CMAKE) --build $(BUILD_ROOT) --target $(RUN_TEST_TARGET)
$(shell find $(BUILD_ROOT)/bin/tests -iname $(RUN_TEST_TARGET)) && notify-send "elsa" "Test $(RUN_TEST_TARGET) passed all tests" || notify-send --urgency=critical "elsa" "Test $(RUN_TEST_TARGET) failed"
watch: $(BUILD_ROOT)/CMakeCache.txt ## build and run individual test continuously on code changes (e.g. make watch mytest)
ifneq ($(CT),)
$(AG) -l "cpp|h|hpp|cu|cuh" | $(ENTR) -s "make CC=$(CC) CXX=$(CXX) GENERATE_PYBINDS=$(GENERATE_PYBINDS) USE_DNNL=$(USE_DNNL) USE_CUDNN=$(USE_CUDNN) USE_CUDA=$(USE_CUDA) BUILD_TYPE=$(BUILD_TYPE) test $(RUN_TEST_TARGET) | $(CT) && notify-send \"elsa\" \"finished building\" || notify-send \"elsa\" \"build failed\""
else
$(AG) -l "cpp|h|hpp|cu|cuh" | $(ENTR) -s "make CC=$(CC) CXX=$(CXX) GENERATE_PYBINDS=$(GENERATE_PYBINDS) USE_DNNL=$(USE_DNNL) USE_CUDNN=$(USE_CUDNN) USE_CUDA=$(USE_CUDA) BUILD_TYPE=$(BUILD_TYPE) test $(RUN_TEST_TARGET) && notify-send \"elsa\" \"finished building\" || notify-send \"elsa\" \"build failed\""
endif
ifneq ($(FZF),)
select-targets: configure ## select one of the available targets to build
$(eval MYTEST = $(shell echo -n $(CMAKE_TARGETS) ' ' | sed 's/xxx/\n/g' | grep -v '/' | $(FZF) | cut -d: -f1 | xargs))
CXXFLAGS=$(CXXFLAGS) $(CMAKE) --build $(BUILD_ROOT) --target $(MYTEST)
select-test: configure ## select one of the available tests to build and run
$(eval MYTEST = $(shell echo -n $(CMAKE_TARGETS) ' ' | sed 's/xxx/\n/g' | grep test\_ | grep -v '/' | $(FZF) | cut -d: -f1 | xargs))
CXXFLAGS=$(CXXFLAGS) $(CMAKE) --build $(BUILD_ROOT) --target $(MYTEST)
$(shell find $(BUILD_ROOT)/bin/tests -iname $(MYTEST)) && notify-send "elsa" "Running $(MYTEST) passed" || notify-send --urgency=critical "elsa" "Running $(MYTEST) failed"
endif
ifeq ($(ENABLE_BENCHMARKS),y)
.PHONY: benchmark
benchmark: configure ## run benchmarks using hyperfine
$(CMAKE) --build $(BUILD_ROOT) -- bench_driver
$(PERF) record --call-graph dwarf $(HYPERFINE) --show-output "$(BUILD_ROOT)/bin/bench_driver 1"
endif
$(BUILD_ROOT)/CMakeCache.txt:
@mkdir -p $(BUILD_ROOT)
CC=$(CC) CXX=$(CXX) $(CMAKE) -S . -B $(BUILD_ROOT) $(CMAKE_GENERATOR_FLAGS) \
-DCMAKE_BUILD_TYPE=$(BUILD_TYPE) $(CUDA_OPTIONS) $(BUILD_OPTIONS)
.PHONY: clean
clean: ## Remove build artifacts
$(CMAKE) --build $(BUILD_ROOT) --target clean
.PHONY: distclean
distclean: ## Clean up everything
rm -rf build/*
......@@ -28,7 +28,7 @@ elsa requires a **C++17 compliant compiler**, such as GCC or Clang in recent ver
Current testing includes Linux-based gcc 9, gcc 10, clang 9, and clang 10.
The build process is controlled using CMake, version 3.14 or higher.
The main third party dependencies (Eigen3, spdlog, Catch2) are integrated via [CPM](https://github.com/TheLartians/CPM.cmake).
The main third party dependencies (Eigen3, spdlog, doctest) are integrated via [CPM](https://github.com/TheLartians/CPM.cmake).
For CUDA support, you need a CUDA capable graphics card as well as an installation of the CUDA toolkit.
Current testing includes CUDA 10.2 combined with gcc 8 or clang 8.
......@@ -36,24 +36,50 @@ Current testing includes CUDA 10.2 combined with gcc 8 or clang 8.
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:
Once you have cloned the git repository, compilation can be done by simply by running
```
cmake ..
make
make install
make build
```
You can provide `-DCMAKE_INSTALL_PREFIX=folder` during the cmake step to select an installation destination other than the default (`/usr/local` on Unix-like systems).
The build CMake-based but a Makefile is provided as a convenience.
Calling make will configure the project with certain default configurations and create a sub-folder structured of the
form `build/$BUILD_TYPE/$compiler`.
Currently, if you want to change the install prefix, you have to directly call CMake. Provide `-DCMAKE_INSTALL_PREFIX=folder` during the CMake
step to select an installation destination other than the default (`/usr/local` on Unix-like systems).
To run all tests just run (from the root directory):
You can build and run the elsa unit tests by running (in the build folder):
```
make tests
```
Once configuration was run once, other interesting targets for developers could be:
* test <test-name>
* watch <test-name>
You might need to install [fzf](https://github.com/junegunn/fzf), [chromaterm](https://github.com/hSaria/ChromaTerm),
[ag](https://github.com/ggreer/the_silver_searcher) and/or [entr](http://eradman.com/entrproject/) for the best
experience. ag and entr are necessary for the watch command. If you have fzf installed, you can also use partial test names and
you can select one interactively.
Other build options you can pass: `USE_CUDA`, `USE_DNNL`, `GENERATE_PYBINDS` and `ENABLE_BENCHMARKS`. You can pass either `y` or `n` to any of these.
Compilation can also be done using plain CMake, without the Makefile. For create a build folder (e.g. `mkdir build; cd build`)
and run the following commands:
```bash
cmake ..
make
make install
```
You can provide the usual CMake options with a prefix of `-D` (e.g. `-DCMAKE_INSTALL_PREFIX=path/to/install/dir`)
or use [ninja](https://ninja-build.org/) to build instead of make by appending `-G Ninja` to the CMake call.
We also provide a `CMakePresets.json` to support [CMake's presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html).
You can use them the following way from the root of the repository:
......@@ -105,6 +131,7 @@ This open-source version is a modernized and cleaned up version of our internal
**Releases:** ([changelog](CHANGELOG.md))
- v0.7: major feature release, e.g. deep learning support (October 27, 2021)
- v0.6: major feature release, e.g. seamless GPU-computing, Python bindings (February 2, 2021)
- v0.5: the "projector" release (September 18, 2019)
- v0.4: first public release (July 19, 2019)
......@@ -131,3 +158,13 @@ URL = {https://doi.org/10.1117/12.2534833}
}
```
If you are using the deep learning module of elsa, we would like to ask you to cite us as well:
```txt
@inproceedings{TellenbachElsa2020,
author = {David Tellenbach and Tobias Lasser},
title = {{elsa - an elegant framework for precision learning in tomographic reconstruction}},
booktitle = {6th International Conference on Image Formation in X-ray Computed Tomography},
venue = {Regensburg, Germany},
month = {August},
year = {2020},
```
\ No newline at end of file
......@@ -4,6 +4,8 @@ set(MODULE_HEADERS
Backtrace.h
Cloneable.h
Utilities/Badge.hpp
Utilities/DataContainerFormatter.hpp
Utilities/FormatConfig.h
Utilities/Metrics.hpp
Utilities/Statistics.hpp
Utilities/TypeCasts.hpp
......@@ -69,6 +71,27 @@ if(${ELSA_BUILD_WITH_QUICKVEC})
list(APPEND MODULE_PUBLIC_DEPS "Quickvec::quickvec")
endif()
# use FFTW if available
option(WITH_FFTW "Build elsa using fftw for faster fourier transforms" ON)
# workaround for
# https://github.com/FFTW/fftw3/issues/130
# better would be find_package(fftw3)
# fftw3f: float, fftw3: double, fftw3l: 128bit, _omp: OpenMP
if(WITH_FFTW)
find_package(PkgConfig)
if(PKG_CONFIG_FOUND)
pkg_check_modules(FFTW3d IMPORTED_TARGET "fftw3")
pkg_check_modules(FFTW3f IMPORTED_TARGET "fftw3f")
if(FFTW3d_FOUND AND FFTW3f_FOUND)
set(FFTW3_FOUND TRUE)
list(APPEND MODULE_PUBLIC_DEPS "PkgConfig::FFTW3d" "PkgConfig::FFTW3f")
# TODO: also add fftw3_omp if supported
endif()
endif()
endif()
ADD_ELSA_MODULE(
core "${MODULE_HEADERS}" "${MODULE_SOURCES}" INSTALL_DIR PUBLIC_DEPS ${MODULE_PUBLIC_DEPS}
PRIVATE_DEPS ${MODULE_PRIVATE_DEPS}
......@@ -93,6 +116,11 @@ if(${ELSA_BUILD_WITH_QUICKVEC})
target_compile_definitions(${ELSA_MODULE_TARGET_NAME} PUBLIC ELSA_ENABLE_CUDA_VECTOR)
endif()
if(WITH_FFTW AND FFTW3_FOUND)
target_compile_definitions("${ELSA_MODULE_TARGET_NAME}" PRIVATE WITH_FFTW)
endif()
if(ELSA_BUILD_PYTHON_BINDINGS)
set(BINDINGS_SOURCES
${MODULE_SOURCES}
......
#include "DataContainer.h"
#include "DataContainerFormatter.hpp"
#include "FormatConfig.h"
#include "DataHandlerCPU.h"
#include "DataHandlerMapCPU.h"
#include "BlockDescriptor.h"
......@@ -182,6 +184,18 @@ namespace elsa
return _dataHandler->maxElement();
}
template <typename data_t>
void DataContainer<data_t>::fft(FFTNorm norm) const
{
this->_dataHandler->fft(*this->_dataDescriptor, norm);
}
template <typename data_t>
void DataContainer<data_t>::ifft(FFTNorm norm) const
{
this->_dataHandler->ifft(*this->_dataDescriptor, norm);
}
template <typename data_t>
DataContainer<data_t>& DataContainer<data_t>::operator+=(const DataContainer<data_t>& dc)
{
......@@ -495,6 +509,13 @@ namespace elsa
return _dataHandlerType;
}
template <typename data_t>
void DataContainer<data_t>::format(std::ostream& os, format_config cfg) const
{
DataContainerFormatter<data_t> fmt{cfg};
fmt.format(os, *this);
}
template <typename data_t>
DataContainer<data_t> DataContainer<data_t>::loadToCPU()
{
......@@ -584,6 +605,58 @@ namespace elsa
return concatenated;
}
template <typename data_t>
DataContainer<data_t> fftShift2D(DataContainer<data_t> dc)
{
assert(dc.getDataDescriptor().getNumberOfDimensions() == 2
&& "DataContainer::fftShift2D: currently only supporting 2D signals");
const DataDescriptor& dataDescriptor = dc.getDataDescriptor();
IndexVector_t numOfCoeffsPerDim = dataDescriptor.getNumberOfCoefficientsPerDimension();
index_t m = numOfCoeffsPerDim[0];
index_t n = numOfCoeffsPerDim[1];
index_t firstShift = m / 2;
index_t secondShift = n / 2;
DataContainer<data_t> copyDC(dataDescriptor);
for (index_t i = 0; i < m; ++i) {
for (index_t j = 0; j < n; ++j) {
copyDC((i + firstShift) % m, (j + secondShift) % n) = dc(i, j);
}
}
return copyDC;
}
template <typename data_t>
DataContainer<data_t> ifftShift2D(DataContainer<data_t> dc)
{
assert(dc.getDataDescriptor().getNumberOfDimensions() == 2
&& "DataContainer::ifftShift2D: currently only supporting 2D signals");
const DataDescriptor& dataDescriptor = dc.getDataDescriptor();
IndexVector_t numOfCoeffsPerDim = dataDescriptor.getNumberOfCoefficientsPerDimension();
index_t m = numOfCoeffsPerDim[0];
index_t n = numOfCoeffsPerDim[1];
index_t firstShift = -m / 2;
index_t secondShift = -n / 2;
DataContainer<data_t> copyDC(dataDescriptor);
for (index_t i = 0; i < m; ++i) {
for (index_t j = 0; j < n; ++j) {
index_t leftIndex = (((i + firstShift) % m) + m) % m;
index_t rightIndex = (((j + secondShift) % n) + n) % n;
copyDC(leftIndex, rightIndex) = dc(i, j);
}
}
return copyDC;
}
// ------------------------------------------
// explicit template instantiation
template class DataContainer<float>;
......@@ -603,4 +676,18 @@ namespace elsa
concatenate<std::complex<double>>(const DataContainer<std::complex<double>>&,
const DataContainer<std::complex<double>>&);
template DataContainer<float> fftShift2D<float>(DataContainer<float>);
template DataContainer<std::complex<float>>
fftShift2D<std::complex<float>>(DataContainer<std::complex<float>>);
template DataContainer<double> fftShift2D<double>(DataContainer<double>);
template DataContainer<std::complex<double>>
fftShift2D<std::complex<double>>(DataContainer<std::complex<double>>);
template DataContainer<float> ifftShift2D<float>(DataContainer<float>);
template DataContainer<std::complex<float>>
ifftShift2D<std::complex<float>>(DataContainer<std::complex<float>>);
template DataContainer<double> ifftShift2D<double>(DataContainer<double>);
template DataContainer<std::complex<double>>
ifftShift2D<std::complex<double>>(DataContainer<std::complex<double>>);
} // namespace elsa
......@@ -8,6 +8,7 @@
#include "DataContainerIterator.h"
#include "Error.h"
#include "Expression.h"
#include "FormatConfig.h"
#include "TypeCasts.hpp"
#ifdef ELSA_CUDA_VECTOR
......@@ -20,7 +21,6 @@
namespace elsa
{
/**
* @brief class representing and storing a linearized n-dimensional signal
*
......@@ -29,6 +29,7 @@ namespace elsa
* @author David Frank - added DataHandler concept, iterators
* @author Nikola Dinev - add block support
* @author Jens Petit - expression templates
* @author Jonas Jelten - various enhancements, fft, complex handling, pretty formatting
*
* @tparam data_t - data type that is stored in the DataContainer, defaulting to real_t.
*
......@@ -47,7 +48,8 @@ namespace elsa
DataContainer() = delete;
/**
* @brief Constructor for empty DataContainer, no initialisation is performed
* @brief Constructor for empty DataContainer, no initialisation is performed,
* but the underlying space is allocated.
*
* @param[in] dataDescriptor containing the associated metadata
* @param[in] handlerType the data handler (default: CPU)
......@@ -246,6 +248,38 @@ namespace elsa
/// return the max of all elements of this signal
data_t maxElement() const;
/// convert to the fourier transformed signal
void fft(FFTNorm norm) const;
/// convert to the inverse fourier transformed signal
void ifft(FFTNorm norm) const;
/// if the datacontainer is already complex, return itself.
template <typename _data_t = data_t>
typename std::enable_if_t<isComplex<_data_t>, DataContainer<_data_t>> asComplex() const
{
return *this;
}
/// if the datacontainer is not complex,
/// return a copy and fill in 0 as imaginary values
template <typename _data_t = data_t>
typename std::enable_if_t<not isComplex<_data_t>, DataContainer<std::complex<_data_t>>>
asComplex() const
{
DataContainer<std::complex<data_t>> ret{
*this->_dataDescriptor,
this->_dataHandlerType,
};
// extend with complex zero value
for (index_t idx = 0; idx < this->getSize(); ++idx) {
ret[idx] = std::complex<data_t>{(*this)[idx], 0};
}
return ret;
}
/// compute in-place element-wise addition of another container
DataContainer<data_t>& operator+=(const DataContainer<data_t>& dc);
......@@ -409,13 +443,16 @@ namespace elsa
template <bool GPU, class Operand, std::enable_if_t<isDataContainer<Operand>, int>>
friend constexpr auto evaluateOrReturn(Operand const& operand);
/// write a pretty-formatted string representation to stream
void format(std::ostream& os, format_config cfg = {}) const;
/**
* @brief Factory function which returns GPU based DataContainers
*
* @return the GPU based DataContainer
*
* Note that if this function is called on a container which is already GPU based, it will
* throw an exception.
* Note that if this function is called on a container which is already GPU based, it
* will throw an exception.
*/
DataContainer loadToGPU();
......@@ -465,11 +502,32 @@ namespace elsa
bool canAssign(DataHandlerType handlerType);
};
/// pretty output formatting.
/// for configurable output, use `DataContainerFormatter` directly.
template <typename T>
std::ostream& operator<<(std::ostream& os, const elsa::DataContainer<T>& dc)
{
dc.format(os);
return os;
}
/// Concatenate two DataContainers to one (requires copying of both)
template <typename data_t>
DataContainer<data_t> concatenate(const DataContainer<data_t>& dc1,
const DataContainer<data_t>& dc2);
/// Perform the FFT shift operation to the provided signal. Refer to
/// https://numpy.org/doc/stable/reference/generated/numpy.fft.fftshift.html for further
/// details.
template <typename data_t>
DataContainer<data_t> fftShift2D(DataContainer<data_t> dc);
/// Perform the IFFT shift operation to the provided signal. Refer to
/// https://numpy.org/doc/stable/reference/generated/numpy.fft.ifftshift.html for further
/// details.
template <typename data_t>
DataContainer<</