diff --git a/core/include/aare/Frame.hpp b/core/include/aare/Frame.hpp index 27e1709..69b9792 100644 --- a/core/include/aare/Frame.hpp +++ b/core/include/aare/Frame.hpp @@ -1,5 +1,5 @@ #pragma once -#include "aare/View.hpp" +#include "aare/Image.hpp" #include "aare/defs.hpp" #include #include @@ -46,6 +46,11 @@ class Frame { return View(data, shape); } + template + Image image() { + return Image(this->view()); + } + ~Frame() { delete[] m_data; } }; diff --git a/core/include/aare/Image.hpp b/core/include/aare/Image.hpp new file mode 100644 index 0000000..54deddf --- /dev/null +++ b/core/include/aare/Image.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/View.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + + +template class Image { + public: + Image() + : shape_(), strides_(c_strides(shape_)), size_(0), + data_(nullptr){}; + + explicit Image(std::array shape) + : shape_(shape), strides_(c_strides(shape_)), + size_(std::accumulate(shape_.begin(), shape_.end(), 1, + std::multiplies())), + data_(new T[size_]){}; + + Image(std::array shape, T value) : Image(shape) { + this->operator=(value); + } + + /* When constructing from a View we need to copy the data since + Image expect to own its data, and span is just a view*/ + Image(View span):Image(span.shape()){ + std::copy(span.begin(), span.end(), begin()); + // fmt::print("Image(View span)\n"); + } + + // Move constructor + Image(Image &&other) + : shape_(other.shape_), strides_(c_strides(shape_)), + size_(other.size_), data_(nullptr) { + data_ = other.data_; + other.reset(); + // fmt::print("Image(Image &&other)\n"); + } + + // Copy constructor + Image(const Image &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("Image(const Image &other)\n"); + } + + ~Image() { + delete[] data_; + } + + auto begin() { return data_; } + auto end() { return data_ + size_; } + + using value_type = T; + + Image &operator=(Image &&other); // Move assign + Image &operator=(const Image &other); // Copy assign + + Image operator+(const Image &other); + Image &operator+=(const Image &other); + Image operator-(const Image &other); + Image &operator-=(const Image &other); + Image operator*(const Image &other); + Image &operator*=(const Image &other); + Image operator/(const Image &other); + // Image& operator/=(const Image& other); + template + Image &operator/=(const Image &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 Image must match")); + } + } + + Image operator>(const Image &other); + + bool operator==(const Image &other) const; + bool operator!=(const Image &other) const; + + Image &operator=(const T &); + Image &operator+=(const T &); + Image operator+(const T &); + Image &operator-=(const T &); + Image operator-(const T &); + Image &operator*=(const T &); + Image operator*(const T &); + Image &operator/=(const T &); + Image operator/(const T &); + + Image &operator&=(const T &); + + void sqrt() { + for (int i = 0; i < size_; ++i) { + data_[i] = std::sqrt(data_[i]); + } + } + + Image &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_; + } + + View span() const { return View{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 +Image &Image::operator=(Image &&other) { + if (this != &other) { + delete[] data_; + data_ = other.data_; + shape_ = other.shape_; + size_ = other.size_; + strides_ = other.strides_; + other.reset(); + } + return *this; +} + +template +Image Image::operator+(const Image &other) { + Image result(*this); + result += other; + return result; +} +template +Image & +Image::operator+=(const Image &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 +Image Image::operator-(const Image &other) { + Image result{*this}; + result -= other; + return result; +} + +template +Image & +Image::operator-=(const Image &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 +Image Image::operator*(const Image &other) { + Image result = *this; + result *= other; + return result; +} + +template +Image & +Image::operator*=(const Image &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 +Image Image::operator/(const Image &other) { + Image result = *this; + result /= other; + return result; +} + +template +Image &Image::operator&=(const T &mask) { + for (auto it = begin(); it != end(); ++it) + *it &= mask; + return *this; +} + +// template +// Image& Image::operator/=(const Image& +// 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 +Image Image::operator>(const Image &other) { + if (shape_ == other.shape_) { + Image 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 +Image & +Image::operator=(const Image &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 Image::operator==(const Image &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 Image::operator!=(const Image &other) const { + return !((*this) == other); +} +template +Image &Image::operator++() { + for (int i = 0; i < size_; ++i) + data_[i] += 1; + return *this; +} +template +Image &Image::operator=(const T &value) { + std::fill_n(data_, size_, value); + return *this; +} + +template +Image &Image::operator+=(const T &value) { + for (int i = 0; i < size_; ++i) + data_[i] += value; + return *this; +} + +template +Image Image::operator+(const T &value) { + Image result = *this; + result += value; + return result; +} +template +Image &Image::operator-=(const T &value) { + for (int i = 0; i < size_; ++i) + data_[i] -= value; + return *this; +} +template +Image Image::operator-(const T &value) { + Image result = *this; + result -= value; + return result; +} + +template +Image &Image::operator/=(const T &value) { + for (int i = 0; i < size_; ++i) + data_[i] /= value; + return *this; +} +template +Image Image::operator/(const T &value) { + Image result = *this; + result /= value; + return result; +} +template +Image &Image::operator*=(const T &value) { + for (int i = 0; i < size_; ++i) + data_[i] *= value; + return *this; +} +template +Image Image::operator*(const T &value) { + Image result = *this; + result *= value; + return result; +} +template void Image::Print() { + if (shape_[0] < 20 && shape_[1] < 20) + Print_all(); + else + Print_some(); +} +template void Image::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 Image::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(Image &img, std::string pathname) { + std::ofstream f; + f.open(pathname, std::ios::binary); + f.write(img.buffer(), img.size() * sizeof(T)); + f.close(); +} + +template +Image load(const std::string &pathname, + std::array shape) { + Image 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 af3fd7a..953cd87 100644 --- a/core/include/aare/VariableSizeClusterFinder.hpp +++ b/core/include/aare/VariableSizeClusterFinder.hpp @@ -6,7 +6,7 @@ #include -#include "aare/ImageData.hpp" +#include "aare/Image.hpp" const int MAX_CLUSTER_SIZE = 200; namespace pl { @@ -31,9 +31,9 @@ template class ClusterFinder { private: const std::array shape_; View original_; - ImageData labeled_; - ImageData peripheral_labeled_; - ImageData binary_; // over threshold flag + Image labeled_; + Image peripheral_labeled_; + Image binary_; // over threshold flag T threshold_; View noiseMap; bool use_noise_map = false; @@ -56,7 +56,7 @@ template class ClusterFinder { hits.reserve(2000); } - ImageData labeled() { return labeled_; } + Image labeled() { return labeled_; } void set_noiseMap(View 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 e2983c3..2ffd267 100644 --- a/core/test/wrappers.test.cpp +++ b/core/test/wrappers.test.cpp @@ -1,5 +1,5 @@ -#include #include +#include #include #include @@ -33,17 +33,37 @@ TEST_CASE("View") { REQUIRE(ds(i / 10, i % 10) == data[i]); } } + // SECTION("from Frame") { + // Frame f(reinterpret_cast(data), 10, 10, 16); + // View ds = f.view(); + // 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("Image") { + 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); - View ds = f.view(); + Image img = f.image(); for (int i = 0; i < 100; i++) { - REQUIRE(ds(i / 10, i % 10) == data[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(ds(0, 0) == 44);// check that ds is updated - REQUIRE(data[0] == 0); // check that data is not updated + 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