diff --git a/core/include/aare/Frame.hpp b/core/include/aare/Frame.hpp index af7b111..b6e8446 100644 --- a/core/include/aare/Frame.hpp +++ b/core/include/aare/Frame.hpp @@ -1,5 +1,5 @@ #pragma once -#include "aare/NDView.hpp" +#include "aare/NDArray.hpp" #include "aare/defs.hpp" #include #include @@ -46,6 +46,11 @@ class Frame { return NDView(data, shape); } + template + NDArray image() { + return NDArray(this->view()); + } + ~Frame() { delete[] m_data; } }; diff --git a/core/include/aare/NDArray.hpp b/core/include/aare/NDArray.hpp new file mode 100644 index 0000000..fb3378a --- /dev/null +++ b/core/include/aare/NDArray.hpp @@ -0,0 +1,429 @@ +#pragma once +/* +Container holding image data, or a time series of image data in contigious +memory. + + +TODO! Add expression templates for operators + +*/ +#include "aare/NDView.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + + +template class NDArray { + public: + NDArray() + : shape_(), strides_(c_strides(shape_)), size_(0), + data_(nullptr){}; + + explicit NDArray(std::array shape) + : shape_(shape), strides_(c_strides(shape_)), + size_(std::accumulate(shape_.begin(), shape_.end(), 1, + std::multiplies())), + data_(new T[size_]){}; + + NDArray(std::array 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*/ + NDArray(NDView span):NDArray(span.shape()){ + std::copy(span.begin(), span.end(), begin()); + // fmt::print("NDArray(NDView span)\n"); + } + + // Move constructor + NDArray(NDArray &&other) + : shape_(other.shape_), strides_(c_strides(shape_)), + size_(other.size_), data_(nullptr) { + data_ = other.data_; + other.reset(); + // fmt::print("NDArray(NDArray &&other)\n"); + } + + // Copy constructor + NDArray(const NDArray &other) + : shape_(other.shape_), strides_(c_strides(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); // 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 + NDArray &operator/=(const NDArray &other) { + // check shape + if (shape_ == other.shape()) { + for (int i = 0; i < size_; ++i) { + data_[i] /= other(i); + } + return *this; + } else { + throw(std::runtime_error("Shape of NDArray must match")); + } + } + + NDArray operator>(const NDArray &other); + + bool operator==(const NDArray &other) const; + bool operator!=(const NDArray &other) const; + + NDArray &operator=(const T &); + NDArray &operator+=(const T &); + NDArray operator+(const T &); + NDArray &operator-=(const T &); + NDArray operator-(const T &); + NDArray &operator*=(const T &); + NDArray operator*(const T &); + NDArray &operator/=(const T &); + NDArray operator/(const T &); + + NDArray &operator&=(const T &); + + void sqrt() { + for (int i = 0; i < size_; ++i) { + data_[i] = std::sqrt(data_[i]); + } + } + + NDArray &operator++(); // pre inc + + template + typename std::enable_if::type + operator()(Ix... index) { + return data_[element_offset(strides_, index...)]; + } + + template + typename std::enable_if::type + operator()(Ix... index) const{ + return data_[element_offset(strides_, index...)]; + } + + + template + typename std::enable_if::type 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(data_); } + ssize_t size() const { return size_; } + size_t total_bytes() const {return size_*sizeof(T);} + std::array shape() const noexcept { return shape_; } + ssize_t shape(ssize_t i) const noexcept { return shape_[i]; } + std::array strides() const noexcept { return strides_; } + std::array byte_strides() const noexcept { + auto byte_strides = strides_; + for (auto &val : byte_strides) + val *= sizeof(T); + return byte_strides; + // return strides_; + } + + NDView span() const { return NDView{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 shape_; + std::array strides_; + ssize_t size_; + T *data_; +}; + +// Move assign +template +NDArray &NDArray::operator=(NDArray &&other) { + if (this != &other) { + delete[] data_; + data_ = other.data_; + shape_ = other.shape_; + size_ = other.size_; + strides_ = other.strides_; + other.reset(); + } + return *this; +} + +template +NDArray NDArray::operator+(const NDArray &other) { + NDArray result(*this); + result += other; + return result; +} +template +NDArray & +NDArray::operator+=(const NDArray &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 +NDArray NDArray::operator-(const NDArray &other) { + NDArray result{*this}; + result -= other; + return result; +} + +template +NDArray & +NDArray::operator-=(const NDArray &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 +NDArray NDArray::operator*(const NDArray &other) { + NDArray result = *this; + result *= other; + return result; +} + +template +NDArray & +NDArray::operator*=(const NDArray &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 +NDArray NDArray::operator/(const NDArray &other) { + NDArray result = *this; + result /= other; + return result; +} + +template +NDArray &NDArray::operator&=(const T &mask) { + for (auto it = begin(); it != end(); ++it) + *it &= mask; + return *this; +} + +// template +// NDArray& NDArray::operator/=(const NDArray& +// 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 +NDArray NDArray::operator>(const NDArray &other) { + if (shape_ == other.shape_) { + NDArray result{shape_}; + for (int i = 0; i < size_; ++i) { + result(i) = (data_[i] > other.data_[i]); + } + return result; + } else { + throw(std::runtime_error("Shape of ImageDatas must match")); + } +} + +template +NDArray & +NDArray::operator=(const NDArray &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 +bool NDArray::operator==(const NDArray &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 +bool NDArray::operator!=(const NDArray &other) const { + return !((*this) == other); +} +template +NDArray &NDArray::operator++() { + for (int i = 0; i < size_; ++i) + data_[i] += 1; + return *this; +} +template +NDArray &NDArray::operator=(const T &value) { + std::fill_n(data_, size_, value); + return *this; +} + +template +NDArray &NDArray::operator+=(const T &value) { + for (int i = 0; i < size_; ++i) + data_[i] += value; + return *this; +} + +template +NDArray NDArray::operator+(const T &value) { + NDArray result = *this; + result += value; + return result; +} +template +NDArray &NDArray::operator-=(const T &value) { + for (int i = 0; i < size_; ++i) + data_[i] -= value; + return *this; +} +template +NDArray NDArray::operator-(const T &value) { + NDArray result = *this; + result -= value; + return result; +} + +template +NDArray &NDArray::operator/=(const T &value) { + for (int i = 0; i < size_; ++i) + data_[i] /= value; + return *this; +} +template +NDArray NDArray::operator/(const T &value) { + NDArray result = *this; + result /= value; + return result; +} +template +NDArray &NDArray::operator*=(const T &value) { + for (int i = 0; i < size_; ++i) + data_[i] *= value; + return *this; +} +template +NDArray NDArray::operator*(const T &value) { + NDArray result = *this; + result *= value; + return result; +} +template void NDArray::Print() { + if (shape_[0] < 20 && shape_[1] < 20) + Print_all(); + else + Print_some(); +} +template void NDArray::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 void NDArray::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 +void save(NDArray &img, std::string pathname) { + std::ofstream f; + f.open(pathname, std::ios::binary); + f.write(img.buffer(), img.size() * sizeof(T)); + f.close(); +} + +template +NDArray load(const std::string &pathname, + std::array shape) { + NDArray img{shape}; + std::ifstream f; + f.open(pathname, std::ios::binary); + f.read(img.buffer(), img.size() * sizeof(T)); + f.close(); + return img; +} + + diff --git a/core/include/aare/VariableSizeClusterFinder.hpp b/core/include/aare/VariableSizeClusterFinder.hpp index fcd2fc5..dcf95f0 100644 --- a/core/include/aare/VariableSizeClusterFinder.hpp +++ b/core/include/aare/VariableSizeClusterFinder.hpp @@ -6,7 +6,7 @@ #include -#include "aare/ImageData.hpp" +#include "aare/NDArray.hpp" const int MAX_CLUSTER_SIZE = 200; namespace pl { @@ -31,9 +31,9 @@ template class ClusterFinder { private: const std::array shape_; NDView original_; - ImageData labeled_; - ImageData peripheral_labeled_; - ImageData binary_; // over threshold flag + NDArray labeled_; + NDArray peripheral_labeled_; + NDArray binary_; // over threshold flag T threshold_; NDView noiseMap; bool use_noise_map = false; @@ -56,7 +56,7 @@ template class ClusterFinder { hits.reserve(2000); } - ImageData labeled() { return labeled_; } + NDArray labeled() { return labeled_; } void set_noiseMap(NDView noise_map) { noiseMap = noise_map; use_noise_map = true; } void set_peripheralThresholdFactor(int factor) { peripheralThresholdFactor_ = factor; } diff --git a/core/test/wrappers.test.cpp b/core/test/wrappers.test.cpp index 32f3046..ae0f12e 100644 --- a/core/test/wrappers.test.cpp +++ b/core/test/wrappers.test.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -42,8 +43,28 @@ TEST_CASE("NDView") { 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 + 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(data), 10, 10, 16); + NDArray img = f.image(); + 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; } \ No newline at end of file