mirror of
https://github.com/slsdetectorgroup/aare.git
synced 2025-06-14 08:17:13 +02:00
pedestal (#67)
* add config files for multimodule receiving * read subfiles with unordered and missing frames * save work debugging * Revert "save work debugging" This reverts commite791992a05
. * Revert "read subfiles with unordered and missing frames" This reverts commit1177fd129d
. * throw when two frames have different frame numbers * write single part RawFile (working beta) * correct total number of frames in master file * add new mythen file with syncd frames * save work * save work for receive multimodule multimodule config results in too much packet loss. needs more debugging. * setup Task Distributiosn/ parallelization programming model * read frames with same frame number * clang-tidy fixes, formatting, add tests * added second receiver * Synchronize between zmq streams and merge frames * improve readability in loop * fix failing tests * add simple test for merge frames * restructure files and use top-level header * working pedestal + tests * test_pedestal statistics * QA test pedestal, fix clang-tidy errors --------- Co-authored-by: Bechir <bechir.brahem420@gmail.com> Co-authored-by: Erik Frojdh <erik.frojdh@gmail.com>
This commit is contained in:
34
src/core/CMakeLists.txt
Normal file
34
src/core/CMakeLists.txt
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
|
||||
set(SourceFiles
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/defs.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/DType.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Frame.cpp
|
||||
)
|
||||
|
||||
|
||||
add_library(core STATIC ${SourceFiles})
|
||||
target_include_directories(core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
|
||||
|
||||
target_link_libraries(core PUBLIC fmt::fmt PRIVATE aare_compiler_flags utils )
|
||||
|
||||
if (AARE_PYTHON_BINDINGS)
|
||||
set_property(TARGET core PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
if(AARE_TESTS)
|
||||
set(TestSources
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/defs.test.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/DType.test.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/Frame.test.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/ProducerConsumerQueue.test.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/NDArray.test.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/NDView.test.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/CircularFifo.test.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/wrappers.test.cpp
|
||||
|
||||
)
|
||||
target_sources(tests PRIVATE ${TestSources} )
|
||||
target_link_libraries(tests PRIVATE core utils)
|
||||
endif()
|
97
src/core/include/aare/core/CircularFifo.hpp
Normal file
97
src/core/include/aare/core/CircularFifo.hpp
Normal file
@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <fmt/color.h>
|
||||
#include <fmt/format.h>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#include "aare/core/ProducerConsumerQueue.hpp"
|
||||
|
||||
namespace aare {
|
||||
|
||||
template <class ItemType> class CircularFifo {
|
||||
uint32_t fifo_size;
|
||||
folly::ProducerConsumerQueue<ItemType> free_slots;
|
||||
folly::ProducerConsumerQueue<ItemType> filled_slots;
|
||||
|
||||
public:
|
||||
CircularFifo() : CircularFifo(100){};
|
||||
CircularFifo(uint32_t size) : fifo_size(size), free_slots(size + 1), filled_slots(size + 1) {
|
||||
|
||||
// TODO! how do we deal with alignment for writing? alignas???
|
||||
// Do we give the user a chance to provide memory locations?
|
||||
// Templated allocator?
|
||||
for (size_t i = 0; i < fifo_size; ++i) {
|
||||
free_slots.write(ItemType{});
|
||||
}
|
||||
}
|
||||
|
||||
bool next() {
|
||||
// TODO! avoid default constructing ItemType
|
||||
ItemType it;
|
||||
if (!filled_slots.read(it))
|
||||
return false;
|
||||
if (!free_slots.write(std::move(it)))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
~CircularFifo() {}
|
||||
|
||||
using value_type = ItemType;
|
||||
|
||||
auto numFilledSlots() const noexcept { return filled_slots.sizeGuess(); }
|
||||
auto numFreeSlots() const noexcept { return free_slots.sizeGuess(); }
|
||||
auto isFull() const noexcept { return filled_slots.isFull(); }
|
||||
|
||||
ItemType pop_free() {
|
||||
ItemType v;
|
||||
while (!free_slots.read(v))
|
||||
;
|
||||
return std::move(v);
|
||||
// return v;
|
||||
}
|
||||
|
||||
bool try_pop_free(ItemType &v) { return free_slots.read(v); }
|
||||
|
||||
ItemType pop_value(std::chrono::nanoseconds wait, std::atomic<bool> &stopped) {
|
||||
ItemType v;
|
||||
while (!filled_slots.read(v) && !stopped) {
|
||||
std::this_thread::sleep_for(wait);
|
||||
}
|
||||
return std::move(v);
|
||||
}
|
||||
|
||||
ItemType pop_value() {
|
||||
ItemType v;
|
||||
while (!filled_slots.read(v))
|
||||
;
|
||||
return std::move(v);
|
||||
}
|
||||
|
||||
ItemType *frontPtr() { return filled_slots.frontPtr(); }
|
||||
|
||||
// TODO! Add function to move item from filled to free to be used
|
||||
// with the frontPtr function
|
||||
|
||||
template <class... Args> void push_value(Args &&...recordArgs) {
|
||||
while (!filled_slots.write(std::forward<Args>(recordArgs)...))
|
||||
;
|
||||
}
|
||||
|
||||
template <class... Args> bool try_push_value(Args &&...recordArgs) {
|
||||
return filled_slots.write(std::forward<Args>(recordArgs)...);
|
||||
}
|
||||
|
||||
template <class... Args> void push_free(Args &&...recordArgs) {
|
||||
while (!free_slots.write(std::forward<Args>(recordArgs)...))
|
||||
;
|
||||
}
|
||||
|
||||
template <class... Args> bool try_push_free(Args &&...recordArgs) {
|
||||
return free_slots.write(std::forward<Args>(recordArgs)...);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace aare
|
55
src/core/include/aare/core/DType.hpp
Normal file
55
src/core/include/aare/core/DType.hpp
Normal file
@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <typeinfo>
|
||||
|
||||
namespace aare {
|
||||
|
||||
/**
|
||||
* @brief enum class to define the endianess of the system
|
||||
*/
|
||||
enum class endian {
|
||||
#ifdef _WIN32
|
||||
little = 0,
|
||||
big = 1,
|
||||
native = little
|
||||
#else
|
||||
little = __ORDER_LITTLE_ENDIAN__,
|
||||
big = __ORDER_BIG_ENDIAN__,
|
||||
native = __BYTE_ORDER__
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief class to define the data type of the pixels
|
||||
* @note only native endianess is supported
|
||||
*/
|
||||
class DType {
|
||||
// TODO! support for non native endianess?
|
||||
static_assert(sizeof(long) == sizeof(int64_t), "long should be 64bits"); // NOLINT
|
||||
|
||||
public:
|
||||
enum TypeIndex { INT8, UINT8, INT16, UINT16, INT32, UINT32, INT64, UINT64, FLOAT, DOUBLE, ERROR };
|
||||
|
||||
uint8_t bitdepth() const;
|
||||
|
||||
explicit DType(const std::type_info &t);
|
||||
explicit DType(std::string_view sv);
|
||||
|
||||
// not explicit to allow conversions form enum to DType
|
||||
DType(DType::TypeIndex ti); // NOLINT
|
||||
|
||||
bool operator==(const DType &other) const noexcept;
|
||||
bool operator!=(const DType &other) const noexcept;
|
||||
bool operator==(const std::type_info &t) const;
|
||||
bool operator!=(const std::type_info &t) const;
|
||||
|
||||
// bool operator==(DType::TypeIndex ti) const;
|
||||
// bool operator!=(DType::TypeIndex ti) const;
|
||||
std::string str() const;
|
||||
|
||||
private:
|
||||
TypeIndex m_type{TypeIndex::ERROR};
|
||||
};
|
||||
|
||||
} // namespace aare
|
93
src/core/include/aare/core/Frame.hpp
Normal file
93
src/core/include/aare/core/Frame.hpp
Normal file
@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
#include "aare/core/NDArray.hpp"
|
||||
#include "aare/core/defs.hpp"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace aare {
|
||||
|
||||
/**
|
||||
* @brief Frame class to represent a single frame of data
|
||||
* model class
|
||||
* should be able to work with streams coming from files or network
|
||||
*/
|
||||
class Frame {
|
||||
size_t m_rows;
|
||||
size_t m_cols;
|
||||
size_t m_bitdepth;
|
||||
std::byte *m_data;
|
||||
|
||||
public:
|
||||
Frame(size_t rows, size_t cols, size_t m_bitdepth);
|
||||
Frame(std::byte *bytes, size_t rows, size_t cols, size_t m_bitdepth);
|
||||
std::byte *get(size_t row, size_t col);
|
||||
|
||||
// TODO! can we, or even want to remove the template?
|
||||
template <typename T> void set(size_t row, size_t col, T data) {
|
||||
assert(sizeof(T) == m_bitdepth / 8);
|
||||
if (row >= m_rows or col >= m_cols) {
|
||||
throw std::out_of_range("Invalid row or column index");
|
||||
}
|
||||
std::memcpy(m_data + (row * m_cols + col) * (m_bitdepth / 8), &data, m_bitdepth / 8);
|
||||
}
|
||||
|
||||
size_t rows() const { return m_rows; }
|
||||
size_t cols() const { return m_cols; }
|
||||
size_t bitdepth() const { return m_bitdepth; }
|
||||
size_t size() const { return m_rows * m_cols * m_bitdepth / 8; }
|
||||
std::byte *data() const { return m_data; }
|
||||
|
||||
Frame &operator=(const Frame &other) {
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
m_rows = other.rows();
|
||||
m_cols = other.cols();
|
||||
m_bitdepth = other.bitdepth();
|
||||
m_data = new std::byte[m_rows * m_cols * m_bitdepth / 8];
|
||||
if (m_data == nullptr) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
std::memcpy(m_data, other.m_data, m_rows * m_cols * m_bitdepth / 8);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Frame &operator=(Frame &&other) noexcept {
|
||||
m_rows = other.rows();
|
||||
m_cols = other.cols();
|
||||
m_bitdepth = other.bitdepth();
|
||||
m_data = other.m_data;
|
||||
other.m_data = nullptr;
|
||||
other.m_rows = other.m_cols = other.m_bitdepth = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// add move constructor
|
||||
Frame(Frame &&other) noexcept
|
||||
: m_rows(other.rows()), m_cols(other.cols()), m_bitdepth(other.bitdepth()), m_data(other.m_data) {
|
||||
|
||||
other.m_data = nullptr;
|
||||
other.m_rows = other.m_cols = other.m_bitdepth = 0;
|
||||
}
|
||||
// copy constructor
|
||||
Frame(const Frame &other)
|
||||
: m_rows(other.rows()), m_cols(other.cols()), m_bitdepth(other.bitdepth()),
|
||||
m_data(new std::byte[m_rows * m_cols * m_bitdepth / 8]) {
|
||||
|
||||
std::memcpy(m_data, other.m_data, m_rows * m_cols * m_bitdepth / 8);
|
||||
}
|
||||
|
||||
template <typename T> NDView<T, 2> view() {
|
||||
std::array<ssize_t, 2> shape = {static_cast<ssize_t>(m_rows), static_cast<ssize_t>(m_cols)};
|
||||
T *data = reinterpret_cast<T *>(m_data);
|
||||
return NDView<T, 2>(data, shape);
|
||||
}
|
||||
|
||||
template <typename T> NDArray<T> image() { return NDArray<T>(this->view<T>()); }
|
||||
|
||||
~Frame() { delete[] m_data; }
|
||||
};
|
||||
|
||||
} // namespace aare
|
379
src/core/include/aare/core/NDArray.hpp
Normal file
379
src/core/include/aare/core/NDArray.hpp
Normal file
@ -0,0 +1,379 @@
|
||||
#pragma once
|
||||
/*
|
||||
Container holding image data, or a time series of image data in contigious
|
||||
memory.
|
||||
|
||||
|
||||
TODO! Add expression templates for operators
|
||||
|
||||
*/
|
||||
#include "aare/core/NDView.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <fmt/format.h>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <numeric>
|
||||
|
||||
namespace aare {
|
||||
|
||||
template <typename T, ssize_t Ndim = 2> class NDArray {
|
||||
public:
|
||||
NDArray() : shape_(), strides_(c_strides<Ndim>(shape_)), data_(nullptr){};
|
||||
|
||||
explicit NDArray(std::array<ssize_t, Ndim> shape)
|
||||
: shape_(shape), strides_(c_strides<Ndim>(shape_)),
|
||||
size_(std::accumulate(shape_.begin(), shape_.end(), 1, std::multiplies<>())), data_(new T[size_]){};
|
||||
|
||||
NDArray(std::array<ssize_t, Ndim> shape, T value) : NDArray(shape) { this->operator=(value); }
|
||||
|
||||
/* When constructing from a NDView we need to copy the data since
|
||||
NDArray expect to own its data, and span is just a view*/
|
||||
explicit NDArray(NDView<T, Ndim> span) : NDArray(span.shape()) {
|
||||
std::copy(span.begin(), span.end(), begin());
|
||||
// fmt::print("NDArray(NDView<T, Ndim> span)\n");
|
||||
}
|
||||
|
||||
// Move constructor
|
||||
NDArray(NDArray &&other) noexcept
|
||||
: shape_(other.shape_), strides_(c_strides<Ndim>(shape_)), size_(other.size_), data_(other.data_) {
|
||||
|
||||
other.reset();
|
||||
// fmt::print("NDArray(NDArray &&other)\n");
|
||||
}
|
||||
|
||||
// Copy constructor
|
||||
NDArray(const NDArray &other)
|
||||
: shape_(other.shape_), strides_(c_strides<Ndim>(shape_)), size_(other.size_), data_(new T[size_]) {
|
||||
std::copy(other.data_, other.data_ + size_, data_);
|
||||
// fmt::print("NDArray(const NDArray &other)\n");
|
||||
}
|
||||
|
||||
~NDArray() { delete[] data_; }
|
||||
|
||||
auto begin() { return data_; }
|
||||
auto end() { return data_ + size_; }
|
||||
|
||||
using value_type = T;
|
||||
|
||||
NDArray &operator=(NDArray &&other) noexcept; // Move assign
|
||||
NDArray &operator=(const NDArray &other); // Copy assign
|
||||
|
||||
NDArray operator+(const NDArray &other);
|
||||
NDArray &operator+=(const NDArray &other);
|
||||
NDArray operator-(const NDArray &other);
|
||||
NDArray &operator-=(const NDArray &other);
|
||||
NDArray operator*(const NDArray &other);
|
||||
NDArray &operator*=(const NDArray &other);
|
||||
NDArray operator/(const NDArray &other);
|
||||
// NDArray& operator/=(const NDArray& other);
|
||||
template <typename V> NDArray &operator/=(const NDArray<V, Ndim> &other) {
|
||||
// check shape
|
||||
if (shape_ == other.shape()) {
|
||||
for (int i = 0; i < size_; ++i) {
|
||||
data_[i] /= other(i);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
throw(std::runtime_error("Shape of NDArray must match"));
|
||||
}
|
||||
|
||||
NDArray<bool, Ndim> operator>(const NDArray &other);
|
||||
|
||||
bool operator==(const NDArray &other) const;
|
||||
bool operator!=(const NDArray &other) const;
|
||||
|
||||
NDArray &operator=(const T & /*value*/);
|
||||
NDArray &operator+=(const T & /*value*/);
|
||||
NDArray operator+(const T & /*value*/);
|
||||
NDArray &operator-=(const T & /*value*/);
|
||||
NDArray operator-(const T & /*value*/);
|
||||
NDArray &operator*=(const T & /*value*/);
|
||||
NDArray operator*(const T & /*value*/);
|
||||
NDArray &operator/=(const T & /*value*/);
|
||||
NDArray operator/(const T & /*value*/);
|
||||
|
||||
NDArray &operator&=(const T & /*mask*/);
|
||||
|
||||
void sqrt() {
|
||||
for (int i = 0; i < size_; ++i) {
|
||||
data_[i] = std::sqrt(data_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
NDArray &operator++(); // pre inc
|
||||
|
||||
template <typename... Ix> std::enable_if_t<sizeof...(Ix) == Ndim, T &> operator()(Ix... index) {
|
||||
return data_[element_offset(strides_, index...)];
|
||||
}
|
||||
|
||||
template <typename... Ix> std::enable_if_t<sizeof...(Ix) == Ndim, T &> operator()(Ix... index) const {
|
||||
return data_[element_offset(strides_, index...)];
|
||||
}
|
||||
|
||||
template <typename... Ix> std::enable_if_t<sizeof...(Ix) == Ndim, T> value(Ix... index) {
|
||||
return data_[element_offset(strides_, index...)];
|
||||
}
|
||||
|
||||
T &operator()(int i) { return data_[i]; }
|
||||
const T &operator()(int i) const { return data_[i]; }
|
||||
|
||||
T *data() { return data_; }
|
||||
std::byte *buffer() { return reinterpret_cast<std::byte *>(data_); }
|
||||
ssize_t size() const { return size_; }
|
||||
size_t total_bytes() const { return size_ * sizeof(T); }
|
||||
std::array<ssize_t, Ndim> shape() const noexcept { return shape_; }
|
||||
ssize_t shape(ssize_t i) const noexcept { return shape_[i]; }
|
||||
std::array<ssize_t, Ndim> strides() const noexcept { return strides_; }
|
||||
std::array<ssize_t, Ndim> byte_strides() const noexcept {
|
||||
auto byte_strides = strides_;
|
||||
for (auto &val : byte_strides)
|
||||
val *= sizeof(T);
|
||||
return byte_strides;
|
||||
// return strides_;
|
||||
}
|
||||
|
||||
NDView<T, Ndim> span() const { return NDView<T, Ndim>{data_, shape_}; }
|
||||
|
||||
void Print();
|
||||
void Print_all();
|
||||
void Print_some();
|
||||
|
||||
void reset() {
|
||||
data_ = nullptr;
|
||||
size_ = 0;
|
||||
std::fill(shape_.begin(), shape_.end(), 0);
|
||||
std::fill(strides_.begin(), strides_.end(), 0);
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<ssize_t, Ndim> shape_;
|
||||
std::array<ssize_t, Ndim> strides_;
|
||||
ssize_t size_{};
|
||||
T *data_;
|
||||
};
|
||||
|
||||
// Move assign
|
||||
template <typename T, ssize_t Ndim> NDArray<T, Ndim> &NDArray<T, Ndim>::operator=(NDArray<T, Ndim> &&other) noexcept {
|
||||
if (this != &other) {
|
||||
delete[] data_;
|
||||
data_ = other.data_;
|
||||
shape_ = other.shape_;
|
||||
size_ = other.size_;
|
||||
strides_ = other.strides_;
|
||||
other.reset();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T, ssize_t Ndim> NDArray<T, Ndim> NDArray<T, Ndim>::operator+(const NDArray &other) {
|
||||
NDArray result(*this);
|
||||
result += other;
|
||||
return result;
|
||||
}
|
||||
template <typename T, ssize_t Ndim> NDArray<T, Ndim> &NDArray<T, Ndim>::operator+=(const NDArray<T, Ndim> &other) {
|
||||
// check shape
|
||||
if (shape_ == other.shape_) {
|
||||
for (int i = 0; i < size_; ++i) {
|
||||
data_[i] += other.data_[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
throw(std::runtime_error("Shape of ImageDatas must match"));
|
||||
}
|
||||
|
||||
template <typename T, ssize_t Ndim> NDArray<T, Ndim> NDArray<T, Ndim>::operator-(const NDArray &other) {
|
||||
NDArray result{*this};
|
||||
result -= other;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T, ssize_t Ndim> NDArray<T, Ndim> &NDArray<T, Ndim>::operator-=(const NDArray<T, Ndim> &other) {
|
||||
// check shape
|
||||
if (shape_ == other.shape_) {
|
||||
for (int i = 0; i < size_; ++i) {
|
||||
data_[i] -= other.data_[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
throw(std::runtime_error("Shape of ImageDatas must match"));
|
||||
}
|
||||
template <typename T, ssize_t Ndim> NDArray<T, Ndim> NDArray<T, Ndim>::operator*(const NDArray &other) {
|
||||
NDArray result = *this;
|
||||
result *= other;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T, ssize_t Ndim> NDArray<T, Ndim> &NDArray<T, Ndim>::operator*=(const NDArray<T, Ndim> &other) {
|
||||
// check shape
|
||||
if (shape_ == other.shape_) {
|
||||
for (int i = 0; i < size_; ++i) {
|
||||
data_[i] *= other.data_[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
throw(std::runtime_error("Shape of ImageDatas must match"));
|
||||
}
|
||||
|
||||
template <typename T, ssize_t Ndim> NDArray<T, Ndim> NDArray<T, Ndim>::operator/(const NDArray &other) {
|
||||
NDArray result = *this;
|
||||
result /= other;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T, ssize_t Ndim> NDArray<T, Ndim> &NDArray<T, Ndim>::operator&=(const T &mask) {
|
||||
for (auto it = begin(); it != end(); ++it)
|
||||
*it &= mask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// template <typename T, ssize_t Ndim>
|
||||
// NDArray<T, Ndim>& NDArray<T, Ndim>::operator/=(const NDArray<T, Ndim>&
|
||||
// other)
|
||||
// {
|
||||
// //check shape
|
||||
// if (shape_ == other.shape_) {
|
||||
// for (int i = 0; i < size_; ++i) {
|
||||
// data_[i] /= other.data_[i];
|
||||
// }
|
||||
// return *this;
|
||||
// } else {
|
||||
// throw(std::runtime_error("Shape of ImageDatas must match"));
|
||||
// }
|
||||
// }
|
||||
|
||||
template <typename T, ssize_t Ndim> NDArray<bool, Ndim> NDArray<T, Ndim>::operator>(const NDArray &other) {
|
||||
if (shape_ == other.shape_) {
|
||||
NDArray<bool> result{shape_};
|
||||
for (int i = 0; i < size_; ++i) {
|
||||
result(i) = (data_[i] > other.data_[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
throw(std::runtime_error("Shape of ImageDatas must match"));
|
||||
}
|
||||
|
||||
template <typename T, ssize_t Ndim> NDArray<T, Ndim> &NDArray<T, Ndim>::operator=(const NDArray<T, Ndim> &other) {
|
||||
if (this != &other) {
|
||||
delete[] data_;
|
||||
shape_ = other.shape_;
|
||||
strides_ = other.strides_;
|
||||
size_ = other.size_;
|
||||
data_ = new T[size_];
|
||||
std::copy(other.data_, other.data_ + size_, data_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T, ssize_t Ndim> bool NDArray<T, Ndim>::operator==(const NDArray<T, Ndim> &other) const {
|
||||
if (shape_ != other.shape_)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i != size_; ++i)
|
||||
if (data_[i] != other.data_[i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T, ssize_t Ndim> bool NDArray<T, Ndim>::operator!=(const NDArray<T, Ndim> &other) const {
|
||||
return !((*this) == other);
|
||||
}
|
||||
template <typename T, ssize_t Ndim> NDArray<T, Ndim> &NDArray<T, Ndim>::operator++() {
|
||||
for (int i = 0; i < size_; ++i)
|
||||
data_[i] += 1;
|
||||
return *this;
|
||||
}
|
||||
template <typename T, ssize_t Ndim> NDArray<T, Ndim> &NDArray<T, Ndim>::operator=(const T &value) {
|
||||
std::fill_n(data_, size_, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T, ssize_t Ndim> NDArray<T, Ndim> &NDArray<T, Ndim>::operator+=(const T &value) {
|
||||
for (int i = 0; i < size_; ++i)
|
||||
data_[i] += value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T, ssize_t Ndim> NDArray<T, Ndim> NDArray<T, Ndim>::operator+(const T &value) {
|
||||
NDArray result = *this;
|
||||
result += value;
|
||||
return result;
|
||||
}
|
||||
template <typename T, ssize_t Ndim> NDArray<T, Ndim> &NDArray<T, Ndim>::operator-=(const T &value) {
|
||||
for (int i = 0; i < size_; ++i)
|
||||
data_[i] -= value;
|
||||
return *this;
|
||||
}
|
||||
template <typename T, ssize_t Ndim> NDArray<T, Ndim> NDArray<T, Ndim>::operator-(const T &value) {
|
||||
NDArray result = *this;
|
||||
result -= value;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T, ssize_t Ndim> NDArray<T, Ndim> &NDArray<T, Ndim>::operator/=(const T &value) {
|
||||
for (int i = 0; i < size_; ++i)
|
||||
data_[i] /= value;
|
||||
return *this;
|
||||
}
|
||||
template <typename T, ssize_t Ndim> NDArray<T, Ndim> NDArray<T, Ndim>::operator/(const T &value) {
|
||||
NDArray result = *this;
|
||||
result /= value;
|
||||
return result;
|
||||
}
|
||||
template <typename T, ssize_t Ndim> NDArray<T, Ndim> &NDArray<T, Ndim>::operator*=(const T &value) {
|
||||
for (int i = 0; i < size_; ++i)
|
||||
data_[i] *= value;
|
||||
return *this;
|
||||
}
|
||||
template <typename T, ssize_t Ndim> NDArray<T, Ndim> NDArray<T, Ndim>::operator*(const T &value) {
|
||||
NDArray result = *this;
|
||||
result *= value;
|
||||
return result;
|
||||
}
|
||||
template <typename T, ssize_t Ndim> void NDArray<T, Ndim>::Print() {
|
||||
if (shape_[0] < 20 && shape_[1] < 20)
|
||||
Print_all();
|
||||
else
|
||||
Print_some();
|
||||
}
|
||||
template <typename T, ssize_t Ndim> void NDArray<T, Ndim>::Print_all() {
|
||||
for (auto row = 0; row < shape_[0]; ++row) {
|
||||
for (auto col = 0; col < shape_[1]; ++col) {
|
||||
std::cout << std::setw(3);
|
||||
std::cout << (*this)(row, col) << " ";
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
}
|
||||
template <typename T, ssize_t Ndim> void NDArray<T, Ndim>::Print_some() {
|
||||
for (auto row = 0; row < 5; ++row) {
|
||||
for (auto col = 0; col < 5; ++col) {
|
||||
std::cout << std::setw(7);
|
||||
std::cout << (*this)(row, col) << " ";
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, ssize_t Ndim> void save(NDArray<T, Ndim> &img, std::string &pathname) {
|
||||
std::ofstream f;
|
||||
f.open(pathname, std::ios::binary);
|
||||
f.write(img.buffer(), img.size() * sizeof(T));
|
||||
f.close();
|
||||
}
|
||||
|
||||
template <typename T, ssize_t Ndim>
|
||||
NDArray<T, Ndim> load(const std::string &pathname, std::array<ssize_t, Ndim> shape) {
|
||||
NDArray<T, Ndim> img{shape};
|
||||
std::ifstream f;
|
||||
f.open(pathname, std::ios::binary);
|
||||
f.read(img.buffer(), img.size() * sizeof(T));
|
||||
f.close();
|
||||
return img;
|
||||
}
|
||||
|
||||
} // namespace aare
|
145
src/core/include/aare/core/NDView.hpp
Normal file
145
src/core/include/aare/core/NDView.hpp
Normal file
@ -0,0 +1,145 @@
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <numeric>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace aare {
|
||||
|
||||
template <ssize_t Ndim> using Shape = std::array<ssize_t, Ndim>;
|
||||
|
||||
// TODO! fix mismatch between signed and unsigned
|
||||
template <ssize_t Ndim> Shape<Ndim> make_shape(const std::vector<size_t> &shape) {
|
||||
if (shape.size() != Ndim)
|
||||
throw std::runtime_error("Shape size mismatch");
|
||||
Shape<Ndim> arr;
|
||||
std::copy_n(shape.begin(), Ndim, arr.begin());
|
||||
return arr;
|
||||
}
|
||||
|
||||
template <ssize_t Dim = 0, typename Strides> ssize_t element_offset(const Strides & /*unused*/) { return 0; }
|
||||
|
||||
template <ssize_t Dim = 0, typename Strides, typename... Ix>
|
||||
ssize_t element_offset(const Strides &strides, ssize_t i, Ix... index) {
|
||||
return i * strides[Dim] + element_offset<Dim + 1>(strides, index...);
|
||||
}
|
||||
|
||||
template <ssize_t Ndim> std::array<ssize_t, Ndim> c_strides(const std::array<ssize_t, Ndim> &shape) {
|
||||
std::array<ssize_t, Ndim> strides{};
|
||||
std::fill(strides.begin(), strides.end(), 1);
|
||||
for (ssize_t i = Ndim - 1; i > 0; --i) {
|
||||
strides[i - 1] = strides[i] * shape[i];
|
||||
}
|
||||
return strides;
|
||||
}
|
||||
|
||||
template <ssize_t Ndim> std::array<ssize_t, Ndim> make_array(const std::vector<ssize_t> &vec) {
|
||||
assert(vec.size() == Ndim);
|
||||
std::array<ssize_t, Ndim> arr{};
|
||||
std::copy_n(vec.begin(), Ndim, arr.begin());
|
||||
return arr;
|
||||
}
|
||||
|
||||
template <typename T, ssize_t Ndim = 2> class NDView {
|
||||
public:
|
||||
NDView() = default;
|
||||
~NDView() = default;
|
||||
NDView(const NDView &) = default;
|
||||
NDView(NDView &&) = default;
|
||||
|
||||
NDView(T *buffer, std::array<ssize_t, Ndim> shape)
|
||||
: buffer_(buffer), strides_(c_strides<Ndim>(shape)), shape_(shape),
|
||||
size_(std::accumulate(std::begin(shape), std::end(shape), 1, std::multiplies<>())) {}
|
||||
|
||||
// NDView(T *buffer, const std::vector<ssize_t> &shape)
|
||||
// : buffer_(buffer), strides_(c_strides<Ndim>(make_array<Ndim>(shape))), shape_(make_array<Ndim>(shape)),
|
||||
// size_(std::accumulate(std::begin(shape), std::end(shape), 1, std::multiplies<>())) {}
|
||||
|
||||
template <typename... Ix> std::enable_if_t<sizeof...(Ix) == Ndim, T &> operator()(Ix... index) {
|
||||
return buffer_[element_offset(strides_, index...)];
|
||||
}
|
||||
|
||||
template <typename... Ix> std::enable_if_t<sizeof...(Ix) == Ndim, T &> operator()(Ix... index) const {
|
||||
return buffer_[element_offset(strides_, index...)];
|
||||
}
|
||||
|
||||
ssize_t size() const { return size_; }
|
||||
|
||||
T *begin() { return buffer_; }
|
||||
T *end() { return buffer_ + size_; }
|
||||
T &operator()(ssize_t i) const { return buffer_[i]; }
|
||||
T &operator[](ssize_t i) const { return buffer_[i]; }
|
||||
|
||||
bool operator==(const NDView &other) const {
|
||||
if (size_ != other.size_)
|
||||
return false;
|
||||
for (ssize_t i = 0; i != size_; ++i) {
|
||||
if (buffer_[i] != other.buffer_[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
NDView &operator+=(const T val) { return elemenwise(val, std::plus<T>()); }
|
||||
NDView &operator-=(const T val) { return elemenwise(val, std::minus<T>()); }
|
||||
NDView &operator*=(const T val) { return elemenwise(val, std::multiplies<T>()); }
|
||||
NDView &operator/=(const T val) { return elemenwise(val, std::divides<T>()); }
|
||||
|
||||
NDView &operator/=(const NDView &other) { return elemenwise(other, std::divides<T>()); }
|
||||
|
||||
NDView &operator=(const T val) {
|
||||
for (auto it = begin(); it != end(); ++it)
|
||||
*it = val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
NDView &operator=(const NDView &other) {
|
||||
if (this == &other)
|
||||
return *this;
|
||||
shape_ = other.shape_;
|
||||
strides_ = other.strides_;
|
||||
size_ = other.size_;
|
||||
buffer_ = other.buffer_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
NDView &operator=(NDView &&other) noexcept {
|
||||
if (this == &other)
|
||||
return *this;
|
||||
shape_ = std::move(other.shape_);
|
||||
strides_ = std::move(other.strides_);
|
||||
size_ = other.size_;
|
||||
buffer_ = other.buffer_;
|
||||
other.buffer_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto &shape() { return shape_; }
|
||||
auto shape(ssize_t i) const { return shape_[i]; }
|
||||
|
||||
T *data() { return buffer_; }
|
||||
|
||||
private:
|
||||
T *buffer_{nullptr};
|
||||
std::array<ssize_t, Ndim> strides_{};
|
||||
std::array<ssize_t, Ndim> shape_{};
|
||||
ssize_t size_{};
|
||||
|
||||
template <class BinaryOperation> NDView &elemenwise(T val, BinaryOperation op) {
|
||||
for (ssize_t i = 0; i != size_; ++i) {
|
||||
buffer_[i] = op(buffer_[i], val);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
template <class BinaryOperation> NDView &elemenwise(const NDView &other, BinaryOperation op) {
|
||||
for (ssize_t i = 0; i != size_; ++i) {
|
||||
buffer_[i] = op(buffer_[i], other.buffer_[i]);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace aare
|
181
src/core/include/aare/core/ProducerConsumerQueue.hpp
Normal file
181
src/core/include/aare/core/ProducerConsumerQueue.hpp
Normal file
@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @author Bo Hu (bhu@fb.com)
|
||||
// @author Jordan DeLong (delong.j@fb.com)
|
||||
|
||||
// Changes made by PSD Detector Group:
|
||||
// Copied: Line 34 constexpr std::size_t hardware_destructive_interference_size = 128; from folly/lang/Align.h
|
||||
// Changed extension to .hpp
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
constexpr std::size_t hardware_destructive_interference_size = 128;
|
||||
namespace folly {
|
||||
|
||||
/*
|
||||
* ProducerConsumerQueue is a one producer and one consumer queue
|
||||
* without locks.
|
||||
*/
|
||||
template <class T> struct ProducerConsumerQueue {
|
||||
typedef T value_type;
|
||||
|
||||
ProducerConsumerQueue(const ProducerConsumerQueue &) = delete;
|
||||
ProducerConsumerQueue &operator=(const ProducerConsumerQueue &) = delete;
|
||||
|
||||
// size must be >= 2.
|
||||
//
|
||||
// Also, note that the number of usable slots in the queue at any
|
||||
// given time is actually (size-1), so if you start with an empty queue,
|
||||
// isFull() will return true after size-1 insertions.
|
||||
explicit ProducerConsumerQueue(uint32_t size)
|
||||
: size_(size), records_(static_cast<T *>(std::malloc(sizeof(T) * size))), readIndex_(0), writeIndex_(0) {
|
||||
assert(size >= 2);
|
||||
if (!records_) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
}
|
||||
|
||||
~ProducerConsumerQueue() {
|
||||
// We need to destruct anything that may still exist in our queue.
|
||||
// (No real synchronization needed at destructor time: only one
|
||||
// thread can be doing this.)
|
||||
if (!std::is_trivially_destructible<T>::value) {
|
||||
size_t readIndex = readIndex_;
|
||||
size_t endIndex = writeIndex_;
|
||||
while (readIndex != endIndex) {
|
||||
records_[readIndex].~T();
|
||||
if (++readIndex == size_) {
|
||||
readIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::free(records_);
|
||||
}
|
||||
|
||||
template <class... Args> bool write(Args &&...recordArgs) {
|
||||
auto const currentWrite = writeIndex_.load(std::memory_order_relaxed);
|
||||
auto nextRecord = currentWrite + 1;
|
||||
if (nextRecord == size_) {
|
||||
nextRecord = 0;
|
||||
}
|
||||
if (nextRecord != readIndex_.load(std::memory_order_acquire)) {
|
||||
new (&records_[currentWrite]) T(std::forward<Args>(recordArgs)...);
|
||||
writeIndex_.store(nextRecord, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
// queue is full
|
||||
return false;
|
||||
}
|
||||
|
||||
// move (or copy) the value at the front of the queue to given variable
|
||||
bool read(T &record) {
|
||||
auto const currentRead = readIndex_.load(std::memory_order_relaxed);
|
||||
if (currentRead == writeIndex_.load(std::memory_order_acquire)) {
|
||||
// queue is empty
|
||||
return false;
|
||||
}
|
||||
|
||||
auto nextRecord = currentRead + 1;
|
||||
if (nextRecord == size_) {
|
||||
nextRecord = 0;
|
||||
}
|
||||
record = std::move(records_[currentRead]);
|
||||
records_[currentRead].~T();
|
||||
readIndex_.store(nextRecord, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
// pointer to the value at the front of the queue (for use in-place) or
|
||||
// nullptr if empty.
|
||||
T *frontPtr() {
|
||||
auto const currentRead = readIndex_.load(std::memory_order_relaxed);
|
||||
if (currentRead == writeIndex_.load(std::memory_order_acquire)) {
|
||||
// queue is empty
|
||||
return nullptr;
|
||||
}
|
||||
return &records_[currentRead];
|
||||
}
|
||||
|
||||
// queue must not be empty
|
||||
void popFront() {
|
||||
auto const currentRead = readIndex_.load(std::memory_order_relaxed);
|
||||
assert(currentRead != writeIndex_.load(std::memory_order_acquire));
|
||||
|
||||
auto nextRecord = currentRead + 1;
|
||||
if (nextRecord == size_) {
|
||||
nextRecord = 0;
|
||||
}
|
||||
records_[currentRead].~T();
|
||||
readIndex_.store(nextRecord, std::memory_order_release);
|
||||
}
|
||||
|
||||
bool isEmpty() const {
|
||||
return readIndex_.load(std::memory_order_acquire) == writeIndex_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
bool isFull() const {
|
||||
auto nextRecord = writeIndex_.load(std::memory_order_acquire) + 1;
|
||||
if (nextRecord == size_) {
|
||||
nextRecord = 0;
|
||||
}
|
||||
if (nextRecord != readIndex_.load(std::memory_order_acquire)) {
|
||||
return false;
|
||||
}
|
||||
// queue is full
|
||||
return true;
|
||||
}
|
||||
|
||||
// * If called by consumer, then true size may be more (because producer may
|
||||
// be adding items concurrently).
|
||||
// * If called by producer, then true size may be less (because consumer may
|
||||
// be removing items concurrently).
|
||||
// * It is undefined to call this from any other thread.
|
||||
size_t sizeGuess() const {
|
||||
int ret = writeIndex_.load(std::memory_order_acquire) - readIndex_.load(std::memory_order_acquire);
|
||||
if (ret < 0) {
|
||||
ret += size_;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// maximum number of items in the queue.
|
||||
size_t capacity() const { return size_ - 1; }
|
||||
|
||||
private:
|
||||
using AtomicIndex = std::atomic<unsigned int>;
|
||||
|
||||
char pad0_[hardware_destructive_interference_size];
|
||||
const uint32_t size_;
|
||||
T *const records_;
|
||||
|
||||
alignas(hardware_destructive_interference_size) AtomicIndex readIndex_;
|
||||
alignas(hardware_destructive_interference_size) AtomicIndex writeIndex_;
|
||||
|
||||
char pad1_[hardware_destructive_interference_size - sizeof(AtomicIndex)];
|
||||
};
|
||||
|
||||
} // namespace folly
|
307
src/core/include/aare/core/VariableSizeClusterFinder.hpp
Normal file
307
src/core/include/aare/core/VariableSizeClusterFinder.hpp
Normal file
@ -0,0 +1,307 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "aare/core/NDArray.hpp"
|
||||
|
||||
const int MAX_CLUSTER_SIZE = 200;
|
||||
namespace aare {
|
||||
|
||||
template <typename T> class ClusterFinder {
|
||||
public:
|
||||
struct Hit {
|
||||
int16_t size{};
|
||||
int16_t row{};
|
||||
int16_t col{};
|
||||
uint16_t reserved{}; // for alignment
|
||||
T energy{};
|
||||
T max{};
|
||||
|
||||
// std::vector<int16_t> rows{};
|
||||
// std::vector<int16_t> cols{};
|
||||
int16_t rows[MAX_CLUSTER_SIZE] = {0};
|
||||
int16_t cols[MAX_CLUSTER_SIZE] = {0};
|
||||
double enes[MAX_CLUSTER_SIZE] = {0};
|
||||
};
|
||||
|
||||
private:
|
||||
const std::array<ssize_t, 2> shape_;
|
||||
NDView<T, 2> original_;
|
||||
NDArray<int, 2> labeled_;
|
||||
NDArray<int, 2> peripheral_labeled_;
|
||||
NDArray<bool, 2> binary_; // over threshold flag
|
||||
T threshold_;
|
||||
NDView<T, 2> noiseMap;
|
||||
bool use_noise_map = false;
|
||||
int peripheralThresholdFactor_ = 5;
|
||||
int current_label;
|
||||
const std::array<int, 4> di{{0, -1, -1, -1}}; // row ### 8-neighbour by scaning from left to right
|
||||
const std::array<int, 4> dj{{-1, -1, 0, 1}}; // col ### 8-neighbour by scaning from top to bottom
|
||||
const std::array<int, 8> di_{{0, 0, -1, 1, -1, 1, -1, 1}}; // row
|
||||
const std::array<int, 8> dj_{{-1, 1, 0, 0, 1, -1, -1, 1}}; // col
|
||||
std::map<int, int> child; // heirachy: key: child; val: parent
|
||||
std::unordered_map<int, Hit> h_size;
|
||||
std::vector<Hit> hits;
|
||||
// std::vector<std::vector<int16_t>> row
|
||||
int check_neighbours(int i, int j);
|
||||
|
||||
public:
|
||||
ClusterFinder(image_shape shape, T threshold)
|
||||
: shape_(shape), labeled_(shape, 0), peripheral_labeled_(shape, 0), binary_(shape), threshold_(threshold) {
|
||||
hits.reserve(2000);
|
||||
}
|
||||
|
||||
NDArray<int, 2> labeled() { return labeled_; }
|
||||
|
||||
void set_noiseMap(NDView<T, 2> noise_map) {
|
||||
noiseMap = noise_map;
|
||||
use_noise_map = true;
|
||||
}
|
||||
void set_peripheralThresholdFactor(int factor) { peripheralThresholdFactor_ = factor; }
|
||||
void find_clusters(NDView<T, 2> img);
|
||||
void find_clusters_X(NDView<T, 2> img);
|
||||
void rec_FillHit(int clusterIndex, int i, int j);
|
||||
void single_pass(NDView<T, 2> img);
|
||||
void first_pass();
|
||||
void second_pass();
|
||||
void store_clusters();
|
||||
|
||||
std::vector<Hit> steal_hits() {
|
||||
std::vector<Hit> tmp;
|
||||
std::swap(tmp, hits);
|
||||
return tmp;
|
||||
};
|
||||
void clear_hits() { hits.clear(); };
|
||||
|
||||
void print_connections() {
|
||||
fmt::print("Connections:\n");
|
||||
for (auto it = child.begin(); it != child.end(); ++it) {
|
||||
fmt::print("{} -> {}\n", it->first, it->second);
|
||||
}
|
||||
}
|
||||
size_t total_clusters() const {
|
||||
// TODO! fix for stealing
|
||||
return hits.size();
|
||||
}
|
||||
|
||||
private:
|
||||
void add_link(int from, int to) {
|
||||
// we want to add key from -> value to
|
||||
// fmt::print("add_link({},{})\n", from, to);
|
||||
auto it = child.find(from);
|
||||
if (it == child.end()) {
|
||||
child[from] = to;
|
||||
} else {
|
||||
// found need to disambiguate
|
||||
if (it->second == to)
|
||||
return;
|
||||
else {
|
||||
if (it->second > to) {
|
||||
// child[from] = to;
|
||||
auto old = it->second;
|
||||
it->second = to;
|
||||
add_link(old, to);
|
||||
} else {
|
||||
// found value is smaller than what we want to link
|
||||
add_link(to, it->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
template <typename T> int ClusterFinder<T>::check_neighbours(int i, int j) {
|
||||
std::vector<int> neighbour_labels;
|
||||
|
||||
for (int k = 0; k < 4; ++k) {
|
||||
const auto row = i + di[k];
|
||||
const auto col = j + dj[k];
|
||||
if (row >= 0 && col >= 0 && row < shape_[0] && col < shape_[1]) {
|
||||
auto tmp = labeled_.value(i + di[k], j + dj[k]);
|
||||
if (tmp != 0)
|
||||
neighbour_labels.push_back(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
if (neighbour_labels.size() == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
|
||||
// need to sort and add to union field
|
||||
std::sort(neighbour_labels.rbegin(), neighbour_labels.rend());
|
||||
auto first = neighbour_labels.begin();
|
||||
auto last = std::unique(first, neighbour_labels.end());
|
||||
if (last - first == 1)
|
||||
return *neighbour_labels.begin();
|
||||
|
||||
for (auto current = first; current != last - 1; ++current) {
|
||||
auto next = current + 1;
|
||||
add_link(*current, *next);
|
||||
}
|
||||
return neighbour_labels.back(); // already sorted
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void ClusterFinder<T>::find_clusters(NDView<T, 2> img) {
|
||||
original_ = img;
|
||||
labeled_ = 0;
|
||||
peripheral_labeled_ = 0;
|
||||
current_label = 0;
|
||||
child.clear();
|
||||
first_pass();
|
||||
// print_connections();
|
||||
second_pass();
|
||||
store_clusters();
|
||||
}
|
||||
|
||||
template <typename T> void ClusterFinder<T>::find_clusters_X(NDView<T, 2> img) {
|
||||
original_ = img;
|
||||
int clusterIndex = 0;
|
||||
for (int i = 0; i < shape_[0]; ++i) {
|
||||
for (int j = 0; j < shape_[1]; ++j) {
|
||||
if (use_noise_map)
|
||||
threshold_ = 5 * noiseMap(i, j);
|
||||
if (original_(i, j) > threshold_) {
|
||||
// printf("========== Cluster index: %d\n", clusterIndex);
|
||||
rec_FillHit(clusterIndex, i, j);
|
||||
clusterIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto &h : h_size)
|
||||
hits.push_back(h.second);
|
||||
h_size.clear();
|
||||
}
|
||||
|
||||
template <typename T> void ClusterFinder<T>::rec_FillHit(int clusterIndex, int i, int j) {
|
||||
// printf("original_(%d, %d)=%f\n", i, j, original_(i,j));
|
||||
// printf("h_size[%d].size=%d\n", clusterIndex, h_size[clusterIndex].size);
|
||||
if (h_size[clusterIndex].size < MAX_CLUSTER_SIZE) {
|
||||
h_size[clusterIndex].rows[h_size[clusterIndex].size] = i;
|
||||
h_size[clusterIndex].cols[h_size[clusterIndex].size] = j;
|
||||
h_size[clusterIndex].enes[h_size[clusterIndex].size] = original_(i, j);
|
||||
}
|
||||
h_size[clusterIndex].size += 1;
|
||||
h_size[clusterIndex].energy += original_(i, j);
|
||||
if (h_size[clusterIndex].max < original_(i, j)) {
|
||||
h_size[clusterIndex].row = i;
|
||||
h_size[clusterIndex].col = j;
|
||||
h_size[clusterIndex].max = original_(i, j);
|
||||
}
|
||||
original_(i, j) = 0;
|
||||
|
||||
for (int k = 0; k < 8; ++k) { // 8 for 8-neighbour
|
||||
const auto row = i + di_[k];
|
||||
const auto col = j + dj_[k];
|
||||
if (row >= 0 && col >= 0 && row < shape_[0] && col < shape_[1]) {
|
||||
if (use_noise_map)
|
||||
threshold_ = peripheralThresholdFactor_ * noiseMap(row, col);
|
||||
if (original_(row, col) > threshold_) {
|
||||
rec_FillHit(clusterIndex, row, col);
|
||||
} else {
|
||||
// if (h_size[clusterIndex].size < MAX_CLUSTER_SIZE){
|
||||
// h_size[clusterIndex].size += 1;
|
||||
// h_size[clusterIndex].rows[h_size[clusterIndex].size] = row;
|
||||
// h_size[clusterIndex].cols[h_size[clusterIndex].size] = col;
|
||||
// h_size[clusterIndex].enes[h_size[clusterIndex].size] = original_(row, col);
|
||||
// }// ? weather to include peripheral pixels
|
||||
original_(row, col) = 0; // remove peripheral pixels, to avoid potential influence for pedestal updating
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void ClusterFinder<T>::single_pass(NDView<T, 2> img) {
|
||||
original_ = img;
|
||||
labeled_ = 0;
|
||||
current_label = 0;
|
||||
child.clear();
|
||||
first_pass();
|
||||
// print_connections();
|
||||
// second_pass();
|
||||
// store_clusters();
|
||||
}
|
||||
|
||||
template <typename T> void ClusterFinder<T>::first_pass() {
|
||||
|
||||
for (int i = 0; i < original_.size(); ++i) {
|
||||
if (use_noise_map)
|
||||
threshold_ = 5 * noiseMap(i);
|
||||
binary_(i) = (original_(i) > threshold_);
|
||||
}
|
||||
|
||||
for (int i = 0; i < shape_[0]; ++i) {
|
||||
for (int j = 0; j < shape_[1]; ++j) {
|
||||
|
||||
// do we have someting to process?
|
||||
if (binary_(i, j)) {
|
||||
auto tmp = check_neighbours(i, j);
|
||||
if (tmp != 0) {
|
||||
labeled_(i, j) = tmp;
|
||||
} else {
|
||||
labeled_(i, j) = ++current_label;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void ClusterFinder<T>::second_pass() {
|
||||
|
||||
for (ssize_t i = 0; i != labeled_.size(); ++i) {
|
||||
auto current_label = labeled_(i);
|
||||
if (current_label != 0) {
|
||||
auto it = child.find(current_label);
|
||||
while (it != child.end()) {
|
||||
current_label = it->second;
|
||||
it = child.find(current_label);
|
||||
// do this once before doing the second pass?
|
||||
// all values point to the final one...
|
||||
}
|
||||
labeled_(i) = current_label;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void ClusterFinder<T>::store_clusters() {
|
||||
|
||||
// Accumulate hit information in a map
|
||||
// Do we always have monotonic increasing
|
||||
// labels? Then vector?
|
||||
// here the translation is label -> Hit
|
||||
std::unordered_map<int, Hit> h_size;
|
||||
for (int i = 0; i < shape_[0]; ++i) {
|
||||
for (int j = 0; j < shape_[1]; ++j) {
|
||||
if (labeled_(i, j) != 0 or false
|
||||
// (i-1 >= 0 and labeled_(i-1, j) != 0) or // another circle of peripheral pixels
|
||||
// (j-1 >= 0 and labeled_(i, j-1) != 0) or
|
||||
// (i+1 < shape_[0] and labeled_(i+1, j) != 0) or
|
||||
// (j+1 < shape_[1] and labeled_(i, j+1) != 0)
|
||||
) {
|
||||
Hit &record = h_size[labeled_(i, j)];
|
||||
if (record.size < MAX_CLUSTER_SIZE) {
|
||||
record.rows[record.size] = i;
|
||||
record.cols[record.size] = j;
|
||||
record.enes[record.size] = original_(i, j);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
record.size += 1;
|
||||
record.energy += original_(i, j);
|
||||
|
||||
if (record.max < original_(i, j)) {
|
||||
record.row = i;
|
||||
record.col = j;
|
||||
record.max = original_(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &h : h_size)
|
||||
hits.push_back(h.second);
|
||||
}
|
||||
|
||||
} // namespace aare
|
74
src/core/include/aare/core/defs.hpp
Normal file
74
src/core/include/aare/core/defs.hpp
Normal file
@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace aare {
|
||||
|
||||
struct Cluster {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
std::array<int32_t, 9> data;
|
||||
std::string to_string() const {
|
||||
std::string s = "x: " + std::to_string(x) + " y: " + std::to_string(y) + "\ndata: [";
|
||||
for (auto d : data) {
|
||||
s += std::to_string(d) + " ";
|
||||
}
|
||||
s += "]";
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief header contained in parts of frames
|
||||
*/
|
||||
struct sls_detector_header {
|
||||
uint64_t frameNumber;
|
||||
uint32_t expLength;
|
||||
uint32_t packetNumber;
|
||||
uint64_t bunchId;
|
||||
uint64_t timestamp;
|
||||
uint16_t modId;
|
||||
uint16_t row;
|
||||
uint16_t column;
|
||||
uint16_t reserved;
|
||||
uint32_t debug;
|
||||
uint16_t roundRNumber;
|
||||
uint8_t detType;
|
||||
uint8_t version;
|
||||
std::array<uint8_t, 64> packetMask;
|
||||
};
|
||||
|
||||
template <typename T> struct t_xy {
|
||||
T row;
|
||||
T col;
|
||||
bool operator==(const t_xy &other) const { return row == other.row && col == other.col; }
|
||||
bool operator!=(const t_xy &other) const { return !(*this == other); }
|
||||
std::string to_string() const { return "{ x: " + std::to_string(row) + " y: " + std::to_string(col) + " }"; }
|
||||
};
|
||||
using xy = t_xy<uint32_t>;
|
||||
|
||||
using dynamic_shape = std::vector<ssize_t>;
|
||||
|
||||
enum class DetectorType { Jungfrau, Eiger, Mythen3, Moench, ChipTestBoard, Unknown };
|
||||
|
||||
enum class TimingMode { Auto, Trigger };
|
||||
|
||||
template <class T> T StringTo(const std::string &arg) { return T(arg); }
|
||||
|
||||
template <class T> std::string toString(T arg) { return T(arg); }
|
||||
|
||||
template <> DetectorType StringTo(const std::string & /*name*/);
|
||||
template <> std::string toString(DetectorType arg);
|
||||
|
||||
template <> TimingMode StringTo(const std::string & /*mode*/);
|
||||
|
||||
using DataTypeVariants = std::variant<uint16_t, uint32_t>;
|
||||
|
||||
} // namespace aare
|
169
src/core/src/DType.cpp
Normal file
169
src/core/src/DType.cpp
Normal file
@ -0,0 +1,169 @@
|
||||
|
||||
#include "aare/core/DType.hpp"
|
||||
#include "aare/utils/logger.hpp"
|
||||
#include <fmt/core.h>
|
||||
|
||||
namespace aare {
|
||||
|
||||
/**
|
||||
* @brief Construct a DType object from a type_info object
|
||||
* @param t type_info object
|
||||
* @throw runtime_error if the type is not supported
|
||||
* @note supported types are: int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t, float, double
|
||||
* @note the type_info object is obtained using typeid (e.g. typeid(int))
|
||||
*/
|
||||
DType::DType(const std::type_info &t) {
|
||||
if (t == typeid(int8_t))
|
||||
m_type = TypeIndex::INT8;
|
||||
else if (t == typeid(uint8_t))
|
||||
m_type = TypeIndex::UINT8;
|
||||
else if (t == typeid(int16_t))
|
||||
m_type = TypeIndex::INT16;
|
||||
else if (t == typeid(uint16_t))
|
||||
m_type = TypeIndex::UINT16;
|
||||
else if (t == typeid(int32_t))
|
||||
m_type = TypeIndex::INT32;
|
||||
else if (t == typeid(uint32_t))
|
||||
m_type = TypeIndex::UINT32;
|
||||
else if (t == typeid(int64_t) || t == typeid(long)) // NOLINT
|
||||
m_type = TypeIndex::INT64;
|
||||
else if (t == typeid(uint64_t))
|
||||
m_type = TypeIndex::UINT64;
|
||||
else if (t == typeid(float))
|
||||
m_type = TypeIndex::FLOAT;
|
||||
else if (t == typeid(double))
|
||||
m_type = TypeIndex::DOUBLE;
|
||||
else
|
||||
throw std::runtime_error("Could not construct data type. Type not supported.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the bitdepth of the data type
|
||||
* @return bitdepth
|
||||
*/
|
||||
uint8_t DType::bitdepth() const {
|
||||
switch (m_type) {
|
||||
case TypeIndex::INT8:
|
||||
case TypeIndex::UINT8:
|
||||
return 8;
|
||||
case TypeIndex::INT16:
|
||||
case TypeIndex::UINT16:
|
||||
return 16;
|
||||
case TypeIndex::INT32:
|
||||
case TypeIndex::UINT32:
|
||||
return 32;
|
||||
case TypeIndex::INT64:
|
||||
case TypeIndex::UINT64:
|
||||
return 64;
|
||||
case TypeIndex::FLOAT:
|
||||
return 32;
|
||||
case TypeIndex::DOUBLE:
|
||||
return 64;
|
||||
case TypeIndex::ERROR:
|
||||
return 0;
|
||||
default:
|
||||
throw std::runtime_error(LOCATION + "Could not get bitdepth. Type not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a DType object from a TypeIndex
|
||||
* @param ti TypeIndex
|
||||
*
|
||||
*/
|
||||
DType::DType(DType::TypeIndex ti) : m_type(ti) {}
|
||||
|
||||
/**
|
||||
* @brief Construct a DType object from a string
|
||||
* @param sv string_view
|
||||
* @throw runtime_error if the type is not supported
|
||||
* @note example strings: "<i4", "u8", "f4"
|
||||
* @note the endianess is checked and only native endianess is supported
|
||||
*/
|
||||
DType::DType(std::string_view sv) {
|
||||
|
||||
// Check if the file is using our native endianess
|
||||
if (auto pos = sv.find_first_of("<>"); pos != std::string_view::npos) {
|
||||
const auto endianess = [](const char c) {
|
||||
if (c == '<')
|
||||
return endian::little;
|
||||
return endian::big;
|
||||
}(sv[pos]);
|
||||
if (endianess != endian::native) {
|
||||
throw std::runtime_error("Non native endianess not supported");
|
||||
}
|
||||
}
|
||||
|
||||
// we are done with the endianess so we can remove the prefix
|
||||
sv.remove_prefix(std::min(sv.find_first_not_of("<>"), sv.size()));
|
||||
|
||||
if (sv == "i1")
|
||||
m_type = TypeIndex::INT8;
|
||||
else if (sv == "u1")
|
||||
m_type = TypeIndex::UINT8;
|
||||
else if (sv == "i2")
|
||||
m_type = TypeIndex::INT16;
|
||||
else if (sv == "u2")
|
||||
m_type = TypeIndex::UINT16;
|
||||
else if (sv == "i4")
|
||||
m_type = TypeIndex::INT32;
|
||||
else if (sv == "u4")
|
||||
m_type = TypeIndex::UINT32;
|
||||
else if (sv == "i8")
|
||||
m_type = TypeIndex::INT64;
|
||||
else if (sv == "u8")
|
||||
m_type = TypeIndex::UINT64;
|
||||
else if (sv == "f4")
|
||||
m_type = TypeIndex::FLOAT;
|
||||
else if (sv == "f8")
|
||||
m_type = TypeIndex::DOUBLE;
|
||||
else
|
||||
throw std::runtime_error("Could not construct data type. Type no supported.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the string representation of the data type
|
||||
* @return string representation
|
||||
*/
|
||||
std::string DType::str() const {
|
||||
|
||||
char ec{};
|
||||
if (endian::native == endian::little)
|
||||
ec = '<';
|
||||
else
|
||||
ec = '>';
|
||||
|
||||
switch (m_type) {
|
||||
case TypeIndex::INT8:
|
||||
return fmt::format("{}i1", ec);
|
||||
case TypeIndex::UINT8:
|
||||
return fmt::format("{}u1", ec);
|
||||
case TypeIndex::INT16:
|
||||
return fmt::format("{}i2", ec);
|
||||
case TypeIndex::UINT16:
|
||||
return fmt::format("{}u2", ec);
|
||||
case TypeIndex::INT32:
|
||||
return fmt::format("{}i4", ec);
|
||||
case TypeIndex::UINT32:
|
||||
return fmt::format("{}u4", ec);
|
||||
case TypeIndex::INT64:
|
||||
return fmt::format("{}i8", ec);
|
||||
case TypeIndex::UINT64:
|
||||
return fmt::format("{}u8", ec);
|
||||
case TypeIndex::FLOAT:
|
||||
return "f4";
|
||||
case TypeIndex::DOUBLE:
|
||||
return "f8";
|
||||
case TypeIndex::ERROR:
|
||||
return "ERROR";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool DType::operator==(const DType &other) const noexcept { return m_type == other.m_type; }
|
||||
bool DType::operator!=(const DType &other) const noexcept { return !(*this == other); }
|
||||
|
||||
bool DType::operator==(const std::type_info &t) const { return DType(t) == *this; }
|
||||
bool DType::operator!=(const std::type_info &t) const { return DType(t) != *this; }
|
||||
|
||||
} // namespace aare
|
50
src/core/src/Frame.cpp
Normal file
50
src/core/src/Frame.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
#include "aare/core/Frame.hpp"
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace aare {
|
||||
|
||||
/**
|
||||
* @brief Construct a new Frame
|
||||
* @param bytes pointer to the data to be copied into the frame
|
||||
* @param rows number of rows
|
||||
* @param cols number of columns
|
||||
* @param bitdepth bitdepth of the pixels
|
||||
*/
|
||||
Frame::Frame(std::byte *bytes, size_t rows, size_t cols, size_t bitdepth)
|
||||
: m_rows(rows), m_cols(cols), m_bitdepth(bitdepth), m_data(new std::byte[rows * cols * bitdepth / 8]) {
|
||||
|
||||
std::memcpy(m_data, bytes, rows * cols * bitdepth / 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a new Frame
|
||||
* @param rows number of rows
|
||||
* @param cols number of columns
|
||||
* @param bitdepth bitdepth of the pixels
|
||||
* @note the data is initialized to zero
|
||||
*/
|
||||
Frame::Frame(size_t rows, size_t cols, size_t bitdepth)
|
||||
: m_rows(rows), m_cols(cols), m_bitdepth(bitdepth), m_data(new std::byte[rows * cols * bitdepth / 8]) {
|
||||
|
||||
std::memset(m_data, 0, rows * cols * bitdepth / 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the pointer to the pixel at the given row and column
|
||||
* @param row row index
|
||||
* @param col column index
|
||||
* @return pointer to the pixel
|
||||
* @note the user should cast the pointer to the appropriate type
|
||||
*/
|
||||
std::byte *Frame::get(size_t row, size_t col) {
|
||||
if (row >= m_rows or col >= m_cols) {
|
||||
std::cerr << "Invalid row or column index" << '\n';
|
||||
return nullptr;
|
||||
}
|
||||
return m_data + (row * m_cols + col) * (m_bitdepth / 8);
|
||||
}
|
||||
|
||||
} // namespace aare
|
65
src/core/src/defs.cpp
Normal file
65
src/core/src/defs.cpp
Normal file
@ -0,0 +1,65 @@
|
||||
#include "aare/core/defs.hpp"
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace aare {
|
||||
|
||||
/**
|
||||
* @brief Convert a DetectorType to a string
|
||||
* @param type DetectorType
|
||||
* @return string representation of the DetectorType
|
||||
*/
|
||||
template <> std::string toString(DetectorType arg) {
|
||||
switch (arg) {
|
||||
case DetectorType::Jungfrau:
|
||||
return "Jungfrau";
|
||||
case DetectorType::Eiger:
|
||||
return "Eiger";
|
||||
case DetectorType::Mythen3:
|
||||
return "Mythen3";
|
||||
case DetectorType::Moench:
|
||||
return "Moench";
|
||||
case DetectorType::ChipTestBoard:
|
||||
return "ChipTestBoard";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a string to a DetectorType
|
||||
* @param name string representation of the DetectorType
|
||||
* @return DetectorType
|
||||
* @throw runtime_error if the string does not match any DetectorType
|
||||
*/
|
||||
template <> DetectorType StringTo(const std::string &arg) {
|
||||
if (arg == "Jungfrau")
|
||||
return DetectorType::Jungfrau;
|
||||
if (arg == "Eiger")
|
||||
return DetectorType::Eiger;
|
||||
if (arg == "Mythen3")
|
||||
return DetectorType::Mythen3;
|
||||
if (arg == "Moench")
|
||||
return DetectorType::Moench;
|
||||
if (arg == "ChipTestBoard")
|
||||
return DetectorType::ChipTestBoard;
|
||||
throw std::runtime_error("Could not decode dector from: \"" + arg + "\"");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a string to a TimingMode
|
||||
* @param mode string representation of the TimingMode
|
||||
* @return TimingMode
|
||||
* @throw runtime_error if the string does not match any TimingMode
|
||||
*/
|
||||
template <> TimingMode StringTo(const std::string &arg) {
|
||||
if (arg == "auto")
|
||||
return TimingMode::Auto;
|
||||
if (arg == "trigger")
|
||||
return TimingMode::Trigger;
|
||||
throw std::runtime_error("Could not decode timing mode from: \"" + arg + "\"");
|
||||
}
|
||||
|
||||
// template <> TimingMode StringTo<TimingMode>(std::string mode);
|
||||
|
||||
} // namespace aare
|
116
src/core/test/CircularFifo.test.cpp
Normal file
116
src/core/test/CircularFifo.test.cpp
Normal file
@ -0,0 +1,116 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include "aare/core/CircularFifo.hpp"
|
||||
|
||||
using aare::CircularFifo;
|
||||
|
||||
// Only for testing. To make sure we can avoid copy constructor
|
||||
// and copy assignment
|
||||
|
||||
struct MoveOnlyInt {
|
||||
int value{};
|
||||
|
||||
MoveOnlyInt() = default;
|
||||
MoveOnlyInt(int i) : value(i){};
|
||||
MoveOnlyInt(const MoveOnlyInt &) = delete;
|
||||
MoveOnlyInt &operator=(const MoveOnlyInt &) = delete;
|
||||
MoveOnlyInt(MoveOnlyInt &&other) { std::swap(value, other.value); }
|
||||
MoveOnlyInt &operator=(MoveOnlyInt &&other) {
|
||||
std::swap(value, other.value);
|
||||
return *this;
|
||||
}
|
||||
bool operator==(int other) const { return value == other; }
|
||||
};
|
||||
|
||||
TEST_CASE("CircularFifo can be default constructed") { CircularFifo<MoveOnlyInt> f; }
|
||||
|
||||
TEST_CASE("Newly constructed fifo has the right size") {
|
||||
size_t size = 17;
|
||||
CircularFifo<MoveOnlyInt> f(size);
|
||||
CHECK(f.numFreeSlots() == size);
|
||||
CHECK(f.numFilledSlots() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Can fit size number of objects") {
|
||||
size_t size = 8;
|
||||
size_t numPushedItems = 0;
|
||||
CircularFifo<MoveOnlyInt> f(size);
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
MoveOnlyInt a;
|
||||
bool popped = f.try_pop_free(a);
|
||||
CHECK(popped);
|
||||
if (popped) {
|
||||
a.value = i;
|
||||
bool pushed = f.try_push_value(std::move(a));
|
||||
CHECK(pushed);
|
||||
if (pushed)
|
||||
numPushedItems++;
|
||||
}
|
||||
}
|
||||
CHECK(f.numFreeSlots() == 0);
|
||||
CHECK(f.numFilledSlots() == size);
|
||||
CHECK(numPushedItems == size);
|
||||
}
|
||||
|
||||
TEST_CASE("Push move only type") {
|
||||
CircularFifo<MoveOnlyInt> f;
|
||||
f.push_value(5);
|
||||
}
|
||||
|
||||
TEST_CASE("Push pop") {
|
||||
CircularFifo<MoveOnlyInt> f;
|
||||
f.push_value(MoveOnlyInt(1));
|
||||
|
||||
auto a = f.pop_value();
|
||||
CHECK(a == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Pop free and then push") {
|
||||
CircularFifo<MoveOnlyInt> f;
|
||||
|
||||
auto a = f.pop_free();
|
||||
a.value = 5;
|
||||
f.push_value(std::move(a)); // Explicit move since we can't copy
|
||||
auto b = f.pop_value();
|
||||
|
||||
CHECK(a == 0); // Moved from value
|
||||
CHECK(b == 5); // Original value
|
||||
}
|
||||
|
||||
TEST_CASE("Skip the first value") {
|
||||
CircularFifo<MoveOnlyInt> f;
|
||||
|
||||
for (int i = 0; i != 10; ++i) {
|
||||
auto a = f.pop_free();
|
||||
a.value = i + 1;
|
||||
f.push_value(std::move(a)); // Explicit move since we can't copy
|
||||
}
|
||||
|
||||
auto b = f.pop_value();
|
||||
CHECK(b == 1);
|
||||
f.next();
|
||||
auto c = f.pop_value();
|
||||
CHECK(c == 3);
|
||||
}
|
||||
|
||||
TEST_CASE("Use in place and move to free") {
|
||||
size_t size = 18;
|
||||
CircularFifo<MoveOnlyInt> f(size);
|
||||
|
||||
// Push 10 values to the fifo
|
||||
for (int i = 0; i != 10; ++i) {
|
||||
auto a = f.pop_free();
|
||||
a.value = i + 1;
|
||||
f.push_value(std::move(a)); // Explicit move since we can't copy
|
||||
}
|
||||
|
||||
auto b = f.frontPtr();
|
||||
CHECK(*b == 1);
|
||||
CHECK(f.numFilledSlots() == 10);
|
||||
CHECK(f.numFreeSlots() == size - 10);
|
||||
f.next();
|
||||
auto c = f.frontPtr();
|
||||
CHECK(*c == 2);
|
||||
CHECK(f.numFilledSlots() == 9);
|
||||
CHECK(f.numFreeSlots() == size - 9);
|
||||
}
|
54
src/core/test/DType.test.cpp
Normal file
54
src/core/test/DType.test.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
|
||||
|
||||
#include "aare/core/DType.hpp"
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
using aare::DType;
|
||||
using aare::endian;
|
||||
|
||||
TEST_CASE("Construct from typeid") {
|
||||
REQUIRE(DType(typeid(int)) == typeid(int));
|
||||
REQUIRE(DType(typeid(int)) != typeid(double));
|
||||
}
|
||||
|
||||
TEST_CASE("Construct from string") {
|
||||
if (endian::native == endian::little) {
|
||||
REQUIRE(DType("<i1") == typeid(int8_t));
|
||||
REQUIRE(DType("<u1") == typeid(uint8_t));
|
||||
REQUIRE(DType("<i2") == typeid(int16_t));
|
||||
REQUIRE(DType("<u2") == typeid(uint16_t));
|
||||
REQUIRE(DType("<i4") == typeid(int));
|
||||
REQUIRE(DType("<u4") == typeid(unsigned));
|
||||
REQUIRE(DType("<i4") == typeid(int32_t));
|
||||
REQUIRE(DType("<i8") == typeid(long));
|
||||
REQUIRE(DType("<i8") == typeid(int64_t));
|
||||
REQUIRE(DType("<u4") == typeid(uint32_t));
|
||||
REQUIRE(DType("<u8") == typeid(uint64_t));
|
||||
REQUIRE(DType("f4") == typeid(float));
|
||||
REQUIRE(DType("f8") == typeid(double));
|
||||
}
|
||||
|
||||
if (endian::native == endian::big) {
|
||||
REQUIRE(DType(">i1") == typeid(int8_t));
|
||||
REQUIRE(DType(">u1") == typeid(uint8_t));
|
||||
REQUIRE(DType(">i2") == typeid(int16_t));
|
||||
REQUIRE(DType(">u2") == typeid(uint16_t));
|
||||
REQUIRE(DType(">i4") == typeid(int));
|
||||
REQUIRE(DType(">u4") == typeid(unsigned));
|
||||
REQUIRE(DType(">i4") == typeid(int32_t));
|
||||
REQUIRE(DType(">i8") == typeid(long));
|
||||
REQUIRE(DType(">i8") == typeid(int64_t));
|
||||
REQUIRE(DType(">u4") == typeid(uint32_t));
|
||||
REQUIRE(DType(">u8") == typeid(uint64_t));
|
||||
REQUIRE(DType("f4") == typeid(float));
|
||||
REQUIRE(DType("f8") == typeid(double));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Construct from string with endianess") {
|
||||
// TODO! handle big endian system in test!
|
||||
REQUIRE(DType("<i4") == typeid(int32_t));
|
||||
REQUIRE_THROWS(DType(">i4") == typeid(int32_t));
|
||||
}
|
||||
|
||||
TEST_CASE("Convert to string") { REQUIRE(DType(typeid(int)).str() == "<i4"); }
|
101
src/core/test/Frame.test.cpp
Normal file
101
src/core/test/Frame.test.cpp
Normal file
@ -0,0 +1,101 @@
|
||||
#include "aare/core/Frame.hpp"
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
using aare::Frame;
|
||||
|
||||
TEST_CASE("Construct a frame") {
|
||||
size_t rows = 10;
|
||||
size_t cols = 10;
|
||||
size_t bitdepth = 8;
|
||||
|
||||
Frame frame(rows, cols, bitdepth);
|
||||
|
||||
REQUIRE(frame.rows() == rows);
|
||||
REQUIRE(frame.cols() == cols);
|
||||
REQUIRE(frame.bitdepth() == bitdepth);
|
||||
REQUIRE(frame.size() == rows * cols * bitdepth / 8);
|
||||
|
||||
// data should be initialized to 0
|
||||
for (size_t i = 0; i < rows; i++) {
|
||||
for (size_t j = 0; j < cols; j++) {
|
||||
uint8_t *data = (uint8_t *)frame.get(i, j);
|
||||
REQUIRE(data != nullptr);
|
||||
REQUIRE(*data == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Set a value in a 8 bit frame") {
|
||||
size_t rows = 10;
|
||||
size_t cols = 10;
|
||||
size_t bitdepth = 8;
|
||||
|
||||
Frame frame(rows, cols, bitdepth);
|
||||
|
||||
// set a value
|
||||
uint8_t value = 255;
|
||||
frame.set(5, 7, value);
|
||||
|
||||
// only the value we did set should be non-zero
|
||||
for (size_t i = 0; i < rows; i++) {
|
||||
for (size_t j = 0; j < cols; j++) {
|
||||
uint8_t *data = (uint8_t *)frame.get(i, j);
|
||||
REQUIRE(data != nullptr);
|
||||
if (i == 5 && j == 7) {
|
||||
REQUIRE(*data == value);
|
||||
} else {
|
||||
REQUIRE(*data == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Set a value in a 64 bit frame") {
|
||||
size_t rows = 10;
|
||||
size_t cols = 10;
|
||||
size_t bitdepth = 64;
|
||||
|
||||
Frame frame(rows, cols, bitdepth);
|
||||
|
||||
// set a value
|
||||
uint64_t value = 255;
|
||||
frame.set(5, 7, value);
|
||||
|
||||
// only the value we did set should be non-zero
|
||||
for (size_t i = 0; i < rows; i++) {
|
||||
for (size_t j = 0; j < cols; j++) {
|
||||
uint64_t *data = (uint64_t *)frame.get(i, j);
|
||||
REQUIRE(data != nullptr);
|
||||
if (i == 5 && j == 7) {
|
||||
REQUIRE(*data == value);
|
||||
} else {
|
||||
REQUIRE(*data == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Move construct a frame") {
|
||||
size_t rows = 10;
|
||||
size_t cols = 10;
|
||||
size_t bitdepth = 8;
|
||||
|
||||
Frame frame(rows, cols, bitdepth);
|
||||
std::byte *data = frame.data();
|
||||
|
||||
Frame frame2(std::move(frame));
|
||||
|
||||
// state of the moved from object
|
||||
REQUIRE(frame.rows() == 0);
|
||||
REQUIRE(frame.cols() == 0);
|
||||
REQUIRE(frame.bitdepth() == 0);
|
||||
REQUIRE(frame.size() == 0);
|
||||
REQUIRE(frame.data() == nullptr);
|
||||
|
||||
// state of the moved to object
|
||||
REQUIRE(frame2.rows() == rows);
|
||||
REQUIRE(frame2.cols() == cols);
|
||||
REQUIRE(frame2.bitdepth() == bitdepth);
|
||||
REQUIRE(frame2.size() == rows * cols * bitdepth / 8);
|
||||
REQUIRE(frame2.data() == data);
|
||||
}
|
377
src/core/test/NDArray.test.cpp
Normal file
377
src/core/test/NDArray.test.cpp
Normal file
@ -0,0 +1,377 @@
|
||||
#include "aare/core/NDArray.hpp"
|
||||
#include <array>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
using aare::NDArray;
|
||||
using aare::NDView;
|
||||
using aare::Shape;
|
||||
|
||||
TEST_CASE("Initial size is zero if no size is specified") {
|
||||
NDArray<double> a;
|
||||
REQUIRE(a.size() == 0);
|
||||
REQUIRE(a.shape() == Shape<2>{0, 0});
|
||||
}
|
||||
|
||||
TEST_CASE("Construct from a DataSpan") {
|
||||
std::vector<int> some_data(9, 42);
|
||||
NDView<int, 2> view(some_data.data(), Shape<2>{3, 3});
|
||||
|
||||
NDArray<int, 2> image(view);
|
||||
|
||||
REQUIRE(image.shape() == view.shape());
|
||||
REQUIRE(image.size() == view.size());
|
||||
REQUIRE(image.data() != view.data());
|
||||
|
||||
for (int i = 0; i < image.size(); ++i) {
|
||||
REQUIRE(image(i) == view(i));
|
||||
}
|
||||
|
||||
// Changing the image doesn't change the view
|
||||
image = 43;
|
||||
for (int i = 0; i < image.size(); ++i) {
|
||||
REQUIRE(image(i) != view(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("1D image") {
|
||||
std::array<ssize_t, 1> shape{{20}};
|
||||
NDArray<short, 1> img(shape, 3);
|
||||
REQUIRE(img.size() == 20);
|
||||
REQUIRE(img(5) == 3);
|
||||
}
|
||||
|
||||
TEST_CASE("Accessing a const object") {
|
||||
const NDArray<double, 3> img({3, 4, 5}, 0);
|
||||
REQUIRE(img(1, 1, 1) == 0);
|
||||
REQUIRE(img.size() == 3 * 4 * 5);
|
||||
REQUIRE(img.shape() == Shape<3>{3, 4, 5});
|
||||
REQUIRE(img.shape(0) == 3);
|
||||
REQUIRE(img.shape(1) == 4);
|
||||
REQUIRE(img.shape(2) == 5);
|
||||
}
|
||||
|
||||
TEST_CASE("Indexing of a 2D image") {
|
||||
std::array<ssize_t, 2> shape{{3, 7}};
|
||||
NDArray<long> img(shape, 5);
|
||||
for (int i = 0; i != img.size(); ++i) {
|
||||
REQUIRE(img(i) == 5);
|
||||
}
|
||||
|
||||
for (int i = 0; i != img.size(); ++i) {
|
||||
img(i) = i;
|
||||
}
|
||||
REQUIRE(img(0, 0) == 0);
|
||||
REQUIRE(img(0, 1) == 1);
|
||||
REQUIRE(img(1, 0) == 7);
|
||||
}
|
||||
|
||||
TEST_CASE("Indexing of a 3D image") {
|
||||
NDArray<float, 3> img{{{3, 4, 2}}, 5.0f};
|
||||
for (int i = 0; i != img.size(); ++i) {
|
||||
REQUIRE(img(i) == 5.0f);
|
||||
}
|
||||
|
||||
// Double check general properties
|
||||
REQUIRE(img.size() == 3 * 4 * 2);
|
||||
|
||||
for (int i = 0; i != img.size(); ++i) {
|
||||
img(i) = float(i);
|
||||
}
|
||||
REQUIRE(img(0, 0, 0) == 0);
|
||||
REQUIRE(img(0, 0, 1) == 1);
|
||||
REQUIRE(img(0, 1, 1) == 3);
|
||||
REQUIRE(img(1, 2, 0) == 12);
|
||||
REQUIRE(img(2, 3, 1) == 23);
|
||||
}
|
||||
|
||||
TEST_CASE("Divide double by int") {
|
||||
NDArray<double, 1> a{{5}, 5};
|
||||
NDArray<int, 1> b{{5}, 5};
|
||||
a /= b;
|
||||
for (auto it : a) {
|
||||
REQUIRE(it == 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Elementwise multiplication of 3D image") {
|
||||
std::array<ssize_t, 3> shape{3, 4, 2};
|
||||
NDArray<double, 3> a{shape};
|
||||
NDArray<double, 3> b{shape};
|
||||
for (int i = 0; i != a.size(); ++i) {
|
||||
a(i) = i;
|
||||
b(i) = i;
|
||||
}
|
||||
auto c = a * b;
|
||||
REQUIRE(c(0, 0, 0) == 0 * 0);
|
||||
REQUIRE(c(0, 0, 1) == 1 * 1);
|
||||
REQUIRE(c(0, 1, 1) == 3 * 3);
|
||||
REQUIRE(c(1, 2, 0) == 12 * 12);
|
||||
REQUIRE(c(2, 3, 1) == 23 * 23);
|
||||
}
|
||||
|
||||
TEST_CASE("Compare two images") {
|
||||
NDArray<int> a;
|
||||
NDArray<int> b;
|
||||
CHECK(a == b);
|
||||
|
||||
a = NDArray<int>{{5, 10}, 0};
|
||||
CHECK(a != b);
|
||||
|
||||
b = NDArray<int>{{5, 10}, 0};
|
||||
CHECK(a == b);
|
||||
|
||||
b(3, 3) = 7;
|
||||
CHECK(a != b);
|
||||
}
|
||||
|
||||
TEST_CASE("Size and shape matches") {
|
||||
ssize_t w = 15;
|
||||
ssize_t h = 75;
|
||||
std::array<ssize_t, 2> shape{w, h};
|
||||
NDArray<double> a{shape};
|
||||
REQUIRE(a.size() == w * h);
|
||||
REQUIRE(a.shape() == shape);
|
||||
}
|
||||
|
||||
TEST_CASE("Initial value matches for all elements") {
|
||||
double v = 4.35;
|
||||
NDArray<double> a{{5, 5}, v};
|
||||
for (int i = 0; i < a.size(); ++i) {
|
||||
REQUIRE(a(i) == v);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Data layout of 3D image, fast index last") {
|
||||
NDArray<int, 3> a{{3, 3, 3}, 0};
|
||||
REQUIRE(a.size() == 27);
|
||||
int *ptr = a.data();
|
||||
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
*ptr++ = 10 + i;
|
||||
REQUIRE(a(0, 0, i) == 10 + i);
|
||||
REQUIRE(a(i) == 10 + i);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Bitwise and on data") {
|
||||
|
||||
NDArray<uint16_t, 1> a({3}, 0);
|
||||
uint16_t mask = 0x3FF;
|
||||
a(0) = 16684;
|
||||
a(1) = 33068;
|
||||
a(2) = 52608;
|
||||
|
||||
a &= mask;
|
||||
|
||||
REQUIRE(a(0) == 300);
|
||||
REQUIRE(a(1) == 300);
|
||||
REQUIRE(a(2) == 384);
|
||||
}
|
||||
|
||||
// TEST_CASE("Benchmarks")
|
||||
// {
|
||||
// NDArray<double> img;
|
||||
// std::array<ssize_t, 2> shape{ 512, 1024 };
|
||||
// BENCHMARK("Allocate 500k double image")
|
||||
// {
|
||||
// NDArray<double>im{ shape };
|
||||
// }
|
||||
// BENCHMARK("Allocate 500k double image with initial value")
|
||||
// {
|
||||
// NDArray<double>im{ shape, 3.14 };
|
||||
// }
|
||||
|
||||
// NDArray<double> a{ shape, 1.2 };
|
||||
// NDArray<double> b{ shape, 53. };
|
||||
// auto c = a + b;
|
||||
// c = a * b;
|
||||
// BENCHMARK("Multiply two images")
|
||||
// {
|
||||
// c = a * b;
|
||||
// }
|
||||
// BENCHMARK("Divide two images")
|
||||
// {
|
||||
// c = a / b;
|
||||
// }
|
||||
// BENCHMARK("Add two images")
|
||||
// {
|
||||
// c = a + b;
|
||||
// }
|
||||
// BENCHMARK("Subtract two images")
|
||||
// {
|
||||
// c = a - b;
|
||||
// }
|
||||
// }
|
||||
|
||||
TEST_CASE("Elementwise operatios on images") {
|
||||
std::array<ssize_t, 2> shape{5, 5};
|
||||
double a_val = 3.0;
|
||||
double b_val = 8.0;
|
||||
|
||||
SECTION("Add two images") {
|
||||
NDArray<double> A(shape, a_val);
|
||||
NDArray<double> B(shape, b_val);
|
||||
|
||||
auto C = A + B;
|
||||
|
||||
// Value of C matches
|
||||
for (int i = 0; i < C.size(); ++i) {
|
||||
REQUIRE(C(i) == a_val + b_val);
|
||||
}
|
||||
|
||||
// Value of A is not changed
|
||||
for (int i = 0; i < A.size(); ++i) {
|
||||
REQUIRE(A(i) == a_val);
|
||||
}
|
||||
|
||||
// Value of B is not changed
|
||||
for (int i = 0; i < B.size(); ++i) {
|
||||
REQUIRE(B(i) == b_val);
|
||||
}
|
||||
|
||||
// A, B and C referes to different data
|
||||
REQUIRE(A.data() != B.data());
|
||||
REQUIRE(B.data() != C.data());
|
||||
}
|
||||
SECTION("Subtract two images") {
|
||||
NDArray<double> A(shape, a_val);
|
||||
NDArray<double> B(shape, b_val);
|
||||
auto C = A - B;
|
||||
|
||||
// Value of C matches
|
||||
for (int i = 0; i < C.size(); ++i) {
|
||||
REQUIRE(C(i) == a_val - b_val);
|
||||
}
|
||||
|
||||
// Value of A is not changed
|
||||
for (int i = 0; i < A.size(); ++i) {
|
||||
REQUIRE(A(i) == a_val);
|
||||
}
|
||||
|
||||
// Value of B is not changed
|
||||
for (int i = 0; i < B.size(); ++i) {
|
||||
REQUIRE(B(i) == b_val);
|
||||
}
|
||||
|
||||
// A, B and C referes to different data
|
||||
REQUIRE(A.data() != B.data());
|
||||
REQUIRE(B.data() != C.data());
|
||||
}
|
||||
SECTION("Multiply two images") {
|
||||
NDArray<double> A(shape, a_val);
|
||||
NDArray<double> B(shape, b_val);
|
||||
auto C = A * B;
|
||||
|
||||
// Value of C matches
|
||||
for (int i = 0; i < C.size(); ++i) {
|
||||
REQUIRE(C(i) == a_val * b_val);
|
||||
}
|
||||
|
||||
// Value of A is not changed
|
||||
for (int i = 0; i < A.size(); ++i) {
|
||||
REQUIRE(A(i) == a_val);
|
||||
}
|
||||
|
||||
// Value of B is not changed
|
||||
for (int i = 0; i < B.size(); ++i) {
|
||||
REQUIRE(B(i) == b_val);
|
||||
}
|
||||
|
||||
// A, B and C referes to different data
|
||||
REQUIRE(A.data() != B.data());
|
||||
REQUIRE(B.data() != C.data());
|
||||
}
|
||||
SECTION("Divide two images") {
|
||||
NDArray<double> A(shape, a_val);
|
||||
NDArray<double> B(shape, b_val);
|
||||
auto C = A / B;
|
||||
|
||||
// Value of C matches
|
||||
for (int i = 0; i < C.size(); ++i) {
|
||||
REQUIRE(C(i) == a_val / b_val);
|
||||
}
|
||||
|
||||
// Value of A is not changed
|
||||
for (int i = 0; i < A.size(); ++i) {
|
||||
REQUIRE(A(i) == a_val);
|
||||
}
|
||||
|
||||
// Value of B is not changed
|
||||
for (int i = 0; i < B.size(); ++i) {
|
||||
REQUIRE(B(i) == b_val);
|
||||
}
|
||||
|
||||
// A, B and C referes to different data
|
||||
REQUIRE(A.data() != B.data());
|
||||
REQUIRE(B.data() != C.data());
|
||||
}
|
||||
|
||||
SECTION("subtract scalar") {
|
||||
NDArray<double> A(shape, a_val);
|
||||
NDArray<double> B(shape, b_val);
|
||||
double v = 1.0;
|
||||
auto C = A - v;
|
||||
REQUIRE(C.data() != A.data());
|
||||
|
||||
// Value of C matches
|
||||
for (int i = 0; i < C.size(); ++i) {
|
||||
REQUIRE(C(i) == a_val - v);
|
||||
}
|
||||
|
||||
// Value of A is not changed
|
||||
for (int i = 0; i < A.size(); ++i) {
|
||||
REQUIRE(A(i) == a_val);
|
||||
}
|
||||
}
|
||||
SECTION("add scalar") {
|
||||
NDArray<double> A(shape, a_val);
|
||||
NDArray<double> B(shape, b_val);
|
||||
double v = 1.0;
|
||||
auto C = A + v;
|
||||
REQUIRE(C.data() != A.data());
|
||||
|
||||
// Value of C matches
|
||||
for (int i = 0; i < C.size(); ++i) {
|
||||
REQUIRE(C(i) == a_val + v);
|
||||
}
|
||||
|
||||
// Value of A is not changed
|
||||
for (int i = 0; i < A.size(); ++i) {
|
||||
REQUIRE(A(i) == a_val);
|
||||
}
|
||||
}
|
||||
SECTION("divide with scalar") {
|
||||
NDArray<double> A(shape, a_val);
|
||||
NDArray<double> B(shape, b_val);
|
||||
double v = 3.7;
|
||||
auto C = A / v;
|
||||
REQUIRE(C.data() != A.data());
|
||||
|
||||
// Value of C matches
|
||||
for (int i = 0; i < C.size(); ++i) {
|
||||
REQUIRE(C(i) == a_val / v);
|
||||
}
|
||||
|
||||
// Value of A is not changed
|
||||
for (int i = 0; i < A.size(); ++i) {
|
||||
REQUIRE(A(i) == a_val);
|
||||
}
|
||||
}
|
||||
SECTION("multiply with scalar") {
|
||||
NDArray<double> A(shape, a_val);
|
||||
NDArray<double> B(shape, b_val);
|
||||
double v = 3.7;
|
||||
auto C = A / v;
|
||||
REQUIRE(C.data() != A.data());
|
||||
|
||||
// Value of C matches
|
||||
for (int i = 0; i < C.size(); ++i) {
|
||||
REQUIRE(C(i) == a_val / v);
|
||||
}
|
||||
|
||||
// Value of A is not changed
|
||||
for (int i = 0; i < A.size(); ++i) {
|
||||
REQUIRE(A(i) == a_val);
|
||||
}
|
||||
}
|
||||
}
|
193
src/core/test/NDView.test.cpp
Normal file
193
src/core/test/NDView.test.cpp
Normal file
@ -0,0 +1,193 @@
|
||||
#include "aare/core/NDView.hpp"
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
using aare::NDView;
|
||||
using aare::Shape;
|
||||
|
||||
TEST_CASE("Element reference 1D") {
|
||||
std::vector<int> vec;
|
||||
for (int i = 0; i != 10; ++i) {
|
||||
vec.push_back(i);
|
||||
}
|
||||
NDView<int, 1> data(vec.data(), Shape<1>{10});
|
||||
REQUIRE(vec.size() == static_cast<size_t>(data.size()));
|
||||
for (int i = 0; i != 10; ++i) {
|
||||
REQUIRE(data(i) == vec[i]);
|
||||
REQUIRE(data[i] == vec[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Element reference 2D") {
|
||||
std::vector<int> vec;
|
||||
for (int i = 0; i != 12; ++i) {
|
||||
vec.push_back(i);
|
||||
}
|
||||
|
||||
NDView<int, 2> data(vec.data(), Shape<2>{3, 4});
|
||||
REQUIRE(vec.size() == static_cast<size_t>(data.size()));
|
||||
int i = 0;
|
||||
for (int row = 0; row != 3; ++row) {
|
||||
for (int col = 0; col != 4; ++col) {
|
||||
REQUIRE(data(row, col) == i);
|
||||
REQUIRE(data[i] == vec[i]);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Element reference 3D") {
|
||||
std::vector<int> vec;
|
||||
for (int i = 0; i != 24; ++i) {
|
||||
vec.push_back(i);
|
||||
}
|
||||
NDView<int, 3> data(vec.data(), Shape<3>{2, 3, 4});
|
||||
REQUIRE(vec.size() == static_cast<size_t>(data.size()));
|
||||
int i = 0;
|
||||
for (int frame = 0; frame != 2; ++frame) {
|
||||
for (int row = 0; row != 3; ++row) {
|
||||
for (int col = 0; col != 4; ++col) {
|
||||
REQUIRE(data(frame, row, col) == i);
|
||||
REQUIRE(data[i] == vec[i]);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Plus and miuns with single value") {
|
||||
std::vector<int> vec;
|
||||
for (int i = 0; i != 12; ++i) {
|
||||
vec.push_back(i);
|
||||
}
|
||||
NDView<int, 2> data(vec.data(), Shape<2>{3, 4});
|
||||
data += 5;
|
||||
int i = 0;
|
||||
for (int row = 0; row != 3; ++row) {
|
||||
for (int col = 0; col != 4; ++col) {
|
||||
REQUIRE(data(row, col) == i + 5);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
data -= 3;
|
||||
i = 0;
|
||||
for (int row = 0; row != 3; ++row) {
|
||||
for (int col = 0; col != 4; ++col) {
|
||||
REQUIRE(data(row, col) == i + 2);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Multiply and divide with single value") {
|
||||
std::vector<int> vec;
|
||||
for (int i = 0; i != 12; ++i) {
|
||||
vec.push_back(i);
|
||||
}
|
||||
NDView<int, 2> data(vec.data(), Shape<2>{3, 4});
|
||||
data *= 5;
|
||||
int i = 0;
|
||||
for (int row = 0; row != 3; ++row) {
|
||||
for (int col = 0; col != 4; ++col) {
|
||||
REQUIRE(data(row, col) == i * 5);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
data /= 3;
|
||||
i = 0;
|
||||
for (int row = 0; row != 3; ++row) {
|
||||
for (int col = 0; col != 4; ++col) {
|
||||
REQUIRE(data(row, col) == (i * 5) / 3);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("elementwise assign") {
|
||||
std::vector<int> vec(25);
|
||||
NDView<int, 2> data(vec.data(), Shape<2>{5, 5});
|
||||
|
||||
data = 3;
|
||||
for (auto it : data) {
|
||||
REQUIRE(it == 3);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("iterators") {
|
||||
std::vector<int> vec;
|
||||
for (int i = 0; i != 12; ++i) {
|
||||
vec.push_back(i);
|
||||
}
|
||||
NDView<int, 1> data(vec.data(), Shape<1>{12});
|
||||
int i = 0;
|
||||
for (const auto item : data) {
|
||||
REQUIRE(item == vec[i]);
|
||||
++i;
|
||||
}
|
||||
REQUIRE(i == 12);
|
||||
|
||||
for (auto ptr = data.begin(); ptr != data.end(); ++ptr) {
|
||||
*ptr += 1;
|
||||
}
|
||||
for (auto &item : data) {
|
||||
++item;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
for (const auto item : data) {
|
||||
REQUIRE(item == i + 2);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
// TEST_CASE("shape from vector") {
|
||||
// std::vector<int> vec;
|
||||
// for (int i = 0; i != 12; ++i) {
|
||||
// vec.push_back(i);
|
||||
// }
|
||||
// std::vector<ssize_t> shape{3, 4};
|
||||
// NDView<int, 2> data(vec.data(), shape);
|
||||
// }
|
||||
|
||||
TEST_CASE("divide with another span") {
|
||||
std::vector<int> vec0{9, 12, 3};
|
||||
std::vector<int> vec1{3, 2, 1};
|
||||
std::vector<int> result{3, 6, 3};
|
||||
|
||||
NDView<int, 1> data0(vec0.data(), Shape<1>{static_cast<ssize_t>(vec0.size())});
|
||||
NDView<int, 1> data1(vec1.data(), Shape<1>{static_cast<ssize_t>(vec1.size())});
|
||||
|
||||
data0 /= data1;
|
||||
|
||||
for (size_t i = 0; i != vec0.size(); ++i) {
|
||||
REQUIRE(data0[i] == result[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Retrieve shape") {
|
||||
std::vector<int> vec;
|
||||
for (int i = 0; i != 12; ++i) {
|
||||
vec.push_back(i);
|
||||
}
|
||||
NDView<int, 2> data(vec.data(), Shape<2>{3, 4});
|
||||
REQUIRE(data.shape()[0] == 3);
|
||||
REQUIRE(data.shape()[1] == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("compare two views") {
|
||||
std::vector<int> vec1;
|
||||
for (int i = 0; i != 12; ++i) {
|
||||
vec1.push_back(i);
|
||||
}
|
||||
NDView<int, 2> view1(vec1.data(), Shape<2>{3, 4});
|
||||
|
||||
std::vector<int> vec2;
|
||||
for (int i = 0; i != 12; ++i) {
|
||||
vec2.push_back(i);
|
||||
}
|
||||
NDView<int, 2> view2(vec2.data(), Shape<2>{3, 4});
|
||||
|
||||
REQUIRE(view1 == view2);
|
||||
}
|
49
src/core/test/ProducerConsumerQueue.test.cpp
Normal file
49
src/core/test/ProducerConsumerQueue.test.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include "aare/core/ProducerConsumerQueue.hpp"
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
// using arve::SimpleQueue;
|
||||
TEST_CASE("push pop") {
|
||||
|
||||
folly::ProducerConsumerQueue<int> q(5);
|
||||
int a = 3;
|
||||
int b = 8;
|
||||
CHECK(q.sizeGuess() == 0);
|
||||
CHECK(q.write(a));
|
||||
CHECK(q.sizeGuess() == 1);
|
||||
CHECK(q.write(b));
|
||||
CHECK(q.sizeGuess() == 2);
|
||||
int c = 0;
|
||||
|
||||
CHECK(q.read(c));
|
||||
CHECK(c == 3);
|
||||
CHECK(q.sizeGuess() == 1);
|
||||
CHECK(q.read(c));
|
||||
CHECK(c == 8);
|
||||
CHECK(q.sizeGuess() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Cannot push to a full queue") {
|
||||
folly::ProducerConsumerQueue<int> q(3);
|
||||
int a = 3;
|
||||
int b = 4;
|
||||
int c = 0;
|
||||
CHECK(q.write(a));
|
||||
CHECK(q.write(b));
|
||||
CHECK_FALSE(q.write(a));
|
||||
|
||||
// values are still ok
|
||||
CHECK(q.read(c));
|
||||
CHECK(c == 3);
|
||||
CHECK(q.read(c));
|
||||
CHECK(c == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("Cannot pop from an empty queue") {
|
||||
folly::ProducerConsumerQueue<int> q(2);
|
||||
int a = 0;
|
||||
CHECK_FALSE(q.read(a));
|
||||
}
|
||||
|
||||
// TEST_CASE("fail"){
|
||||
// CHECK(false);
|
||||
// }
|
8
src/core/test/defs.test.cpp
Normal file
8
src/core/test/defs.test.cpp
Normal file
@ -0,0 +1,8 @@
|
||||
#include "aare/core/defs.hpp"
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <string>
|
||||
TEST_CASE("Enum to string conversion") {
|
||||
// By the way I don't think the enum string conversions should be in the defs.hpp file
|
||||
// but let's use this to show a test
|
||||
REQUIRE(toString(aare::DetectorType::Jungfrau) == "Jungfrau");
|
||||
}
|
73
src/core/test/wrappers.test.cpp
Normal file
73
src/core/test/wrappers.test.cpp
Normal file
@ -0,0 +1,73 @@
|
||||
#include <aare/core/Frame.hpp>
|
||||
#include <aare/core/NDView.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <cstdint>
|
||||
|
||||
using aare::Frame;
|
||||
using aare::NDArray;
|
||||
using aare::NDView;
|
||||
|
||||
TEST_CASE("Frame") {
|
||||
auto data = new uint16_t[100];
|
||||
for (int i = 0; i < 100; i++) {
|
||||
data[i] = i;
|
||||
}
|
||||
Frame f(reinterpret_cast<std::byte *>(data), 10, 10, 16);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
REQUIRE((uint16_t)*f.get(i / 10, i % 10) == data[i]);
|
||||
}
|
||||
REQUIRE(f.rows() == 10);
|
||||
REQUIRE(f.cols() == 10);
|
||||
REQUIRE(f.bitdepth() == 16);
|
||||
|
||||
uint16_t i = 44;
|
||||
f.set(0, 0, i);
|
||||
REQUIRE((uint16_t)*f.get(0, 0) == i);
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
TEST_CASE("NDView") {
|
||||
auto data = new uint16_t[100];
|
||||
for (int i = 0; i < 100; i++) {
|
||||
data[i] = i;
|
||||
}
|
||||
SECTION("constructors") {
|
||||
NDView<uint16_t, 2> ds(data, std::array<ssize_t, 2>({10, 10}));
|
||||
for (int i = 0; i < 100; i++) {
|
||||
REQUIRE(ds(i / 10, i % 10) == data[i]);
|
||||
}
|
||||
}
|
||||
SECTION("from Frame") {
|
||||
Frame f(reinterpret_cast<std::byte *>(data), 10, 10, 16);
|
||||
NDView<uint16_t> ds = f.view<uint16_t>();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
REQUIRE(ds(i / 10, i % 10) == data[i]);
|
||||
}
|
||||
|
||||
f.set(0, 0, (uint16_t)44);
|
||||
REQUIRE((uint16_t)*f.get(0, 0) == 44); // check that set worked
|
||||
REQUIRE(ds(0, 0) == 44); // check that ds is updated
|
||||
REQUIRE(data[0] == 0); // check that data is not updated
|
||||
}
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
TEST_CASE("NDArray") {
|
||||
auto data = new uint16_t[100];
|
||||
for (int i = 0; i < 100; i++) {
|
||||
data[i] = i;
|
||||
}
|
||||
SECTION("from Frame") {
|
||||
Frame f(reinterpret_cast<std::byte *>(data), 10, 10, 16);
|
||||
NDArray<uint16_t> img = f.image<uint16_t>();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
REQUIRE(img(i / 10, i % 10) == data[i]);
|
||||
}
|
||||
|
||||
f.set(0, 0, (uint16_t)44);
|
||||
REQUIRE((uint16_t)*f.get(0, 0) == 44); // check that set worked
|
||||
REQUIRE(img(0, 0) == 0); // check that ds is updated
|
||||
REQUIRE(data[0] == 0); // check that data is not updated
|
||||
}
|
||||
delete[] data;
|
||||
}
|
Reference in New Issue
Block a user