This commit is contained in:
Erik Fröjdh
2024-10-25 10:23:34 +02:00
parent ae71e23dd2
commit b1b020ad60
20 changed files with 2417 additions and 0 deletions

33
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,33 @@
set(SourceFiles
${CMAKE_CURRENT_SOURCE_DIR}/defs.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Dtype.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Frame.cpp
)
add_library(aare_core STATIC ${SourceFiles})
target_include_directories(aare_core PUBLIC ${CMAKE_SOURCE_DIR}/include)
target_link_libraries(aare_core PUBLIC fmt::fmt PRIVATE aare_compiler_flags )
if (AARE_PYTHON_BINDINGS)
set_property(TARGET aare_core PROPERTY POSITION_INDEPENDENT_CODE ON)
endif()
if(AARE_TESTS)
set(TestSources
${CMAKE_CURRENT_SOURCE_DIR}/defs.test.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Dtype.test.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Frame.test.cpp
# ${CMAKE_CURRENT_SOURCE_DIR}/test/ProducerConsumerQueue.test.cpp
${CMAKE_CURRENT_SOURCE_DIR}/NDArray.test.cpp
${CMAKE_CURRENT_SOURCE_DIR}/NDView.test.cpp
# ${CMAKE_CURRENT_SOURCE_DIR}/test/CircularFifo.test.cpp
# ${CMAKE_CURRENT_SOURCE_DIR}/test/wrappers.test.cpp
# ${CMAKE_CURRENT_SOURCE_DIR}/test/Transforms.test.cpp
)
target_sources(tests PRIVATE ${TestSources} )
target_link_libraries(tests PRIVATE aare_core)
endif()

191
src/Dtype.cpp Normal file
View File

@ -0,0 +1,191 @@
#include "aare/Dtype.hpp"
#include "aare/defs.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)) // 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::NONE:
return 0;
default:
throw std::runtime_error(LOCATION + "Could not get bitdepth. Type not supported.");
}
}
/**
* @brief Get the number of bytes of the data type
*/
size_t Dtype::bytes() const { return bitdepth() / 8; }
/**
* @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("Cannot construct data type from string.");
}
Dtype Dtype::from_bitdepth(uint8_t bitdepth) {
switch (bitdepth) {
case 8:
return Dtype(TypeIndex::UINT8);
case 16:
return Dtype(TypeIndex::UINT16);
case 32:
return Dtype(TypeIndex::UINT32);
case 64:
return Dtype(TypeIndex::UINT64);
default:
throw std::runtime_error("Could not construct data type from bitdepth.");
}
}
/**
* @brief Get the string representation of the data type
* @return string representation
*/
std::string Dtype::to_string() 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:
throw std::runtime_error("Could not get string representation. Type not supported.");
case TypeIndex::NONE:
throw std::runtime_error("Could not get string representation. Type not supported.");
}
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

54
src/Dtype.test.cpp Normal file
View File

@ -0,0 +1,54 @@
#include "aare/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)).to_string() == "<i4"); }

110
src/Frame.cpp Normal file
View File

@ -0,0 +1,110 @@
#include "aare/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(const std::byte *bytes, uint32_t rows, uint32_t cols, Dtype dtype)
: m_rows(rows), m_cols(cols), m_dtype(dtype), m_data(new std::byte[rows * cols * m_dtype.bytes()]) {
std::memcpy(m_data, bytes, rows * cols * m_dtype.bytes());
}
/**
* @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(uint32_t rows, uint32_t cols, Dtype dtype)
: m_rows(rows), m_cols(cols), m_dtype(dtype), m_data(new std::byte[rows * cols * dtype.bytes()]) {
std::memset(m_data, 0, rows * cols * dtype.bytes());
}
uint32_t Frame::rows() const { return m_rows; }
uint32_t Frame::cols() const { return m_cols; }
size_t Frame::bitdepth() const { return m_dtype.bitdepth(); }
Dtype Frame::dtype() const { return m_dtype; }
uint64_t Frame::size() const { return m_rows * m_cols; }
size_t Frame::bytes() const { return m_rows * m_cols * m_dtype.bytes(); }
std::byte *Frame::data() const { return m_data; }
/**
* @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(uint32_t row, uint32_t col) {
if ((row >= m_rows) || (col >= m_cols)) {
std::cerr << "Invalid row or column index" << '\n';
return nullptr;
}
return m_data + (row * m_cols + col) * (m_dtype.bytes());
}
// Frame &Frame::operator=(const Frame &other) {
// if (this == &other) {
// return *this;
// }
// m_rows = other.rows();
// m_cols = other.cols();
// m_dtype = other.dtype();
// m_data = new std::byte[m_rows * m_cols * m_dtype.bytes()];
// if (m_data == nullptr) {
// throw std::bad_alloc();
// }
// std::memcpy(m_data, other.m_data, m_rows * m_cols * m_dtype.bytes());
// return *this;
// }
Frame &Frame::operator=(Frame &&other) noexcept {
if (this == &other) {
return *this;
}
m_rows = other.rows();
m_cols = other.cols();
m_dtype = other.dtype();
if (m_data != nullptr) {
delete[] m_data;
}
m_data = other.m_data;
other.m_data = nullptr;
other.m_rows = other.m_cols = 0;
other.m_dtype = Dtype(Dtype::TypeIndex::ERROR);
return *this;
}
Frame::Frame(Frame &&other) noexcept
: m_rows(other.rows()), m_cols(other.cols()), m_dtype(other.dtype()), m_data(other.m_data) {
other.m_data = nullptr;
other.m_rows = other.m_cols = 0;
other.m_dtype = Dtype(Dtype::TypeIndex::ERROR);
}
// Frame::Frame(const Frame &other)
// : m_rows(other.rows()), m_cols(other.cols()), m_dtype(other.dtype()),
// m_data(new std::byte[m_rows * m_cols * m_dtype.bytes()]) {
// std::memcpy(m_data, other.m_data, m_rows * m_cols * m_dtype.bytes());
// }
Frame Frame::copy() const {
Frame frame(m_rows, m_cols, m_dtype);
std::memcpy(frame.m_data, m_data, m_rows * m_cols * m_dtype.bytes());
return frame;
}
Frame::~Frame() noexcept { delete[] m_data; }
} // namespace aare

152
src/Frame.test.cpp Normal file
View File

@ -0,0 +1,152 @@
#include "aare/Frame.hpp"
#include "aare/Dtype.hpp"
#include <catch2/catch_test_macros.hpp>
using namespace aare;
TEST_CASE("Construct a frame") {
size_t rows = 10;
size_t cols = 10;
size_t bitdepth = 8;
Frame frame(rows, cols, Dtype::from_bitdepth(bitdepth));
REQUIRE(frame.rows() == rows);
REQUIRE(frame.cols() == cols);
REQUIRE(frame.bitdepth() == bitdepth);
REQUIRE(frame.bytes() == 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, Dtype::from_bitdepth(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, Dtype::from_bitdepth(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, Dtype::from_bitdepth(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.dtype() == Dtype(Dtype::TypeIndex::ERROR));
REQUIRE(frame.data() == nullptr);
// state of the moved to object
REQUIRE(frame2.rows() == rows);
REQUIRE(frame2.cols() == cols);
REQUIRE(frame2.bitdepth() == bitdepth);
REQUIRE(frame2.bytes() == rows * cols * bitdepth / 8);
REQUIRE(frame2.data() == data);
}
TEST_CASE("Move assign a frame") {
size_t rows = 10;
size_t cols = 10;
size_t bitdepth = 8;
Frame frame(rows, cols, Dtype::from_bitdepth(bitdepth));
std::byte *data = frame.data();
Frame frame2(5, 5, Dtype::from_bitdepth(16));
frame2 = std::move(frame);
// state of the moved from object
REQUIRE(frame.rows() == 0);
REQUIRE(frame.cols() == 0);
REQUIRE(frame.dtype() == Dtype(Dtype::TypeIndex::ERROR));
REQUIRE(frame.data() == nullptr);
// state of the moved to object
REQUIRE(frame2.rows() == rows);
REQUIRE(frame2.cols() == cols);
REQUIRE(frame2.bitdepth() == bitdepth);
REQUIRE(frame2.bytes() == rows * cols * bitdepth / 8);
REQUIRE(frame2.data() == data);
}
TEST_CASE("test explicit copy constructor") {
size_t rows = 10;
size_t cols = 10;
size_t bitdepth = 8;
Frame frame(rows, cols, Dtype::from_bitdepth(bitdepth));
std::byte *data = frame.data();
Frame frame2 = frame.copy();
// state of the original object
REQUIRE(frame.rows() == rows);
REQUIRE(frame.cols() == cols);
REQUIRE(frame.bitdepth() == bitdepth);
REQUIRE(frame.bytes() == rows * cols * bitdepth / 8);
REQUIRE(frame.data() == data);
// state of the copied object
REQUIRE(frame2.rows() == rows);
REQUIRE(frame2.cols() == cols);
REQUIRE(frame2.bitdepth() == bitdepth);
REQUIRE(frame2.bytes() == rows * cols * bitdepth / 8);
REQUIRE(frame2.data() != data);
}

377
src/NDArray.test.cpp Normal file
View File

@ -0,0 +1,377 @@
#include "aare/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 (uint32_t i = 0; i < image.size(); ++i) {
REQUIRE(image(i) == view(i));
}
// Changing the image doesn't change the view
image = 43;
for (uint32_t i = 0; i < image.size(); ++i) {
REQUIRE(image(i) != view(i));
}
}
TEST_CASE("1D image") {
std::array<int64_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<int64_t, 2> shape{{3, 7}};
NDArray<long> img(shape, 5);
for (uint32_t i = 0; i != img.size(); ++i) {
REQUIRE(img(i) == 5);
}
for (uint32_t 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 (uint32_t i = 0; i != img.size(); ++i) {
REQUIRE(img(i) == 5.0f);
}
// Double check general properties
REQUIRE(img.size() == 3 * 4 * 2);
for (uint32_t 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<int64_t, 3> shape{3, 4, 2};
NDArray<double, 3> a{shape};
NDArray<double, 3> b{shape};
for (uint32_t 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") {
int64_t w = 15;
int64_t h = 75;
std::array<int64_t, 2> shape{w, h};
NDArray<double> a{shape};
REQUIRE(a.size() == static_cast<uint64_t>(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 (uint32_t 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<int64_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<int64_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 (uint32_t i = 0; i < C.size(); ++i) {
REQUIRE(C(i) == a_val + b_val);
}
// Value of A is not changed
for (uint32_t i = 0; i < A.size(); ++i) {
REQUIRE(A(i) == a_val);
}
// Value of B is not changed
for (uint32_t 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 (uint32_t i = 0; i < C.size(); ++i) {
REQUIRE(C(i) == a_val - b_val);
}
// Value of A is not changed
for (uint32_t i = 0; i < A.size(); ++i) {
REQUIRE(A(i) == a_val);
}
// Value of B is not changed
for (uint32_t 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 (uint32_t i = 0; i < C.size(); ++i) {
REQUIRE(C(i) == a_val * b_val);
}
// Value of A is not changed
for (uint32_t i = 0; i < A.size(); ++i) {
REQUIRE(A(i) == a_val);
}
// Value of B is not changed
for (uint32_t 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 (uint32_t i = 0; i < C.size(); ++i) {
REQUIRE(C(i) == a_val / b_val);
}
// Value of A is not changed
for (uint32_t i = 0; i < A.size(); ++i) {
REQUIRE(A(i) == a_val);
}
// Value of B is not changed
for (uint32_t 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 (uint32_t i = 0; i < C.size(); ++i) {
REQUIRE(C(i) == a_val - v);
}
// Value of A is not changed
for (uint32_t 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 (uint32_t i = 0; i < C.size(); ++i) {
REQUIRE(C(i) == a_val + v);
}
// Value of A is not changed
for (uint32_t 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 (uint32_t i = 0; i < C.size(); ++i) {
REQUIRE(C(i) == a_val / v);
}
// Value of A is not changed
for (uint32_t 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 (uint32_t i = 0; i < C.size(); ++i) {
REQUIRE(C(i) == a_val / v);
}
// Value of A is not changed
for (uint32_t i = 0; i < A.size(); ++i) {
REQUIRE(A(i) == a_val);
}
}
}

193
src/NDView.test.cpp Normal file
View File

@ -0,0 +1,193 @@
#include "aare/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<int64_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<int64_t>(vec0.size())});
NDView<int, 1> data1(vec1.data(), Shape<1>{static_cast<int64_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));
}

65
src/defs.cpp Normal file
View File

@ -0,0 +1,65 @@
#include "aare/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

42
src/defs.test.cpp Normal file
View File

@ -0,0 +1,42 @@
#include "aare/defs.hpp"
// #include "aare/utils/floats.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");
}
TEST_CASE("Cluster creation") {
aare::Cluster c(13, 15);
REQUIRE(c.cluster_sizeX == 13);
REQUIRE(c.cluster_sizeY == 15);
REQUIRE(c.dt == aare::Dtype(typeid(int32_t)));
REQUIRE(c.data() != nullptr);
aare::Cluster c2(c);
REQUIRE(c2.cluster_sizeX == 13);
REQUIRE(c2.cluster_sizeY == 15);
REQUIRE(c2.dt == aare::Dtype(typeid(int32_t)));
REQUIRE(c2.data() != nullptr);
}
// TEST_CASE("cluster set and get data") {
// aare::Cluster c2(33, 44, aare::Dtype(typeid(double)));
// REQUIRE(c2.cluster_sizeX == 33);
// REQUIRE(c2.cluster_sizeY == 44);
// REQUIRE(c2.dt == aare::Dtype::DOUBLE);
// double v = 3.14;
// c2.set<double>(0, v);
// double v2 = c2.get<double>(0);
// REQUIRE(aare::compare_floats<double>(v, v2));
// c2.set<double>(33 * 44 - 1, 123.11);
// double v3 = c2.get<double>(33 * 44 - 1);
// REQUIRE(aare::compare_floats<double>(123.11, v3));
// REQUIRE_THROWS_AS(c2.set(0, 1), std::invalid_argument); // set int to double
// }