mirror of
https://github.com/slsdetectorgroup/aare.git
synced 2025-06-20 18:57:59 +02:00
Taking v1 as the first release (#92)
- file reading - decoding master file
This commit is contained in:
71
src/ClusterFinder.test.cpp
Normal file
71
src/ClusterFinder.test.cpp
Normal file
@ -0,0 +1,71 @@
|
||||
#include "aare/ClusterFinder.hpp"
|
||||
#include "aare/Pedestal.hpp"
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <chrono>
|
||||
#include <random>
|
||||
|
||||
using namespace aare;
|
||||
|
||||
//TODO! Find a way to test the cluster finder
|
||||
|
||||
|
||||
|
||||
// class ClusterFinderUnitTest : public ClusterFinder {
|
||||
// public:
|
||||
// ClusterFinderUnitTest(int cluster_sizeX, int cluster_sizeY, double nSigma = 5.0, double threshold = 0.0)
|
||||
// : ClusterFinder(cluster_sizeX, cluster_sizeY, nSigma, threshold) {}
|
||||
// double get_c2() { return c2; }
|
||||
// double get_c3() { return c3; }
|
||||
// auto get_threshold() { return m_threshold; }
|
||||
// auto get_nSigma() { return m_nSigma; }
|
||||
// auto get_cluster_sizeX() { return m_cluster_sizeX; }
|
||||
// auto get_cluster_sizeY() { return m_cluster_sizeY; }
|
||||
// };
|
||||
|
||||
// TEST_CASE("test ClusterFinder constructor") {
|
||||
// ClusterFinderUnitTest cf(55, 100);
|
||||
// REQUIRE(cf.get_cluster_sizeX() == 55);
|
||||
// REQUIRE(cf.get_cluster_sizeY() == 100);
|
||||
// REQUIRE(cf.get_threshold() == 0.0);
|
||||
// REQUIRE(cf.get_nSigma() == 5.0);
|
||||
// double c2 = sqrt((100 + 1) / 2 * (55 + 1) / 2);
|
||||
// double c3 = sqrt(55 * 100);
|
||||
// // REQUIRE(compare_floats<double>(cf.get_c2(), c2));
|
||||
// // REQUIRE(compare_floats<double>(cf.get_c3(), c3));
|
||||
// REQUIRE_THAT(cf.get_c2(), Catch::Matchers::WithinRel(c2, 1e-9));
|
||||
// REQUIRE_THAT(cf.get_c3(), Catch::Matchers::WithinRel(c3, 1e-9));
|
||||
// }
|
||||
|
||||
TEST_CASE("Construct a cluster finder"){
|
||||
ClusterFinder clusterFinder({400,400}, {3,3});
|
||||
// REQUIRE(clusterFinder.get_cluster_sizeX() == 3);
|
||||
// REQUIRE(clusterFinder.get_cluster_sizeY() == 3);
|
||||
// REQUIRE(clusterFinder.get_threshold() == 1);
|
||||
// REQUIRE(clusterFinder.get_nSigma() == 1);
|
||||
}
|
||||
|
||||
// TEST_CASE("test cluster finder") {
|
||||
// aare::Pedestal pedestal(10, 10, 5);
|
||||
// NDArray<double, 2> frame({10, 10});
|
||||
// frame = 0;
|
||||
// ClusterFinder clusterFinder(3, 3, 1, 1); // 3x3 cluster, 1 nSigma, 1 threshold
|
||||
|
||||
// auto clusters = clusterFinder.find_clusters_without_threshold(frame.span(), pedestal);
|
||||
|
||||
// REQUIRE(clusters.size() == 0);
|
||||
|
||||
// frame(5, 5) = 10;
|
||||
// clusters = clusterFinder.find_clusters_without_threshold(frame.span(), pedestal);
|
||||
// REQUIRE(clusters.size() == 1);
|
||||
// REQUIRE(clusters[0].x == 5);
|
||||
// REQUIRE(clusters[0].y == 5);
|
||||
// for (int i = 0; i < 3; i++) {
|
||||
// for (int j = 0; j < 3; j++) {
|
||||
// if (i == 1 && j == 1)
|
||||
// REQUIRE(clusters[0].get<double>(i * 3 + j) == 10);
|
||||
// else
|
||||
// REQUIRE(clusters[0].get<double>(i * 3 + j) == 0);
|
||||
// }
|
||||
// }
|
||||
// }
|
74
src/CtbRawFile.cpp
Normal file
74
src/CtbRawFile.cpp
Normal file
@ -0,0 +1,74 @@
|
||||
#include "aare/CtbRawFile.hpp"
|
||||
|
||||
#include <fmt/format.h>
|
||||
namespace aare {
|
||||
|
||||
CtbRawFile::CtbRawFile(const std::filesystem::path &fname) : m_master(fname) {
|
||||
if (m_master.detector_type() != DetectorType::ChipTestBoard) {
|
||||
throw std::runtime_error(LOCATION + "Not a Ctb file");
|
||||
}
|
||||
|
||||
find_subfiles();
|
||||
|
||||
// open the first subfile
|
||||
m_file.open(m_master.data_fname(0, 0), std::ios::binary);
|
||||
}
|
||||
|
||||
void CtbRawFile::read_into(std::byte *image_buf, DetectorHeader* header) {
|
||||
if(m_current_frame >= m_master.frames_in_file()){
|
||||
throw std::runtime_error(LOCATION + "End of file reached");
|
||||
}
|
||||
|
||||
if(m_current_frame != 0 && m_current_frame % m_master.max_frames_per_file() == 0){
|
||||
open_data_file(m_current_subfile+1);
|
||||
}
|
||||
|
||||
if(header){
|
||||
m_file.read(reinterpret_cast<char *>(header), sizeof(DetectorHeader));
|
||||
}else{
|
||||
m_file.seekg(sizeof(DetectorHeader), std::ios::cur);
|
||||
}
|
||||
|
||||
m_file.read(reinterpret_cast<char *>(image_buf), m_master.image_size_in_bytes());
|
||||
m_current_frame++;
|
||||
}
|
||||
|
||||
void CtbRawFile::seek(size_t frame_number) {
|
||||
if (auto index = sub_file_index(frame_number); index != m_current_subfile) {
|
||||
open_data_file(index);
|
||||
}
|
||||
size_t frame_number_in_file = frame_number % m_master.max_frames_per_file();
|
||||
m_file.seekg((sizeof(DetectorHeader)+m_master.image_size_in_bytes()) * frame_number_in_file);
|
||||
m_current_frame = frame_number;
|
||||
}
|
||||
|
||||
size_t CtbRawFile::tell() const { return m_current_frame; }
|
||||
|
||||
size_t CtbRawFile::image_size_in_bytes() const { return m_master.image_size_in_bytes(); }
|
||||
|
||||
size_t CtbRawFile::frames_in_file() const { return m_master.frames_in_file(); }
|
||||
|
||||
RawMasterFile CtbRawFile::master() const { return m_master; }
|
||||
|
||||
void CtbRawFile::find_subfiles() {
|
||||
// we can semi safely assume that there is only one module for CTB
|
||||
while (std::filesystem::exists(m_master.data_fname(0, m_num_subfiles)))
|
||||
m_num_subfiles++;
|
||||
|
||||
fmt::print("Found {} subfiles\n", m_num_subfiles);
|
||||
}
|
||||
|
||||
void CtbRawFile::open_data_file(size_t subfile_index) {
|
||||
if (subfile_index >= m_num_subfiles) {
|
||||
throw std::runtime_error(LOCATION + "Subfile index out of range");
|
||||
}
|
||||
m_current_subfile = subfile_index;
|
||||
m_file = std::ifstream(m_master.data_fname(0, subfile_index), std::ios::binary); // only one module for CTB
|
||||
if (!m_file.is_open()) {
|
||||
throw std::runtime_error(LOCATION + "Could not open data file");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace aare
|
191
src/Dtype.cpp
Normal file
191
src/Dtype.cpp
Normal 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
54
src/Dtype.test.cpp
Normal 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"); }
|
77
src/File.cpp
Normal file
77
src/File.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
#include "aare/File.hpp"
|
||||
#include "aare/NumpyFile.hpp"
|
||||
#include "aare/RawFile.hpp"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace aare {
|
||||
|
||||
File::File(const std::filesystem::path &fname, const std::string &mode,
|
||||
const FileConfig &cfg)
|
||||
: file_impl(nullptr) {
|
||||
if (mode != "r") {
|
||||
throw std::invalid_argument("At the moment only reading is supported");
|
||||
}
|
||||
|
||||
if ((mode == "r") && !std::filesystem::exists(fname)) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("File does not exist: {}", fname.string()));
|
||||
}
|
||||
|
||||
// Assuming we are pointing at a master file?
|
||||
// TODO! How do we read raw files directly?
|
||||
if (fname.extension() == ".raw" || fname.extension() == ".json") {
|
||||
// file_impl = new RawFile(fname, mode, cfg);
|
||||
file_impl = std::make_unique<RawFile>(fname, mode);
|
||||
}
|
||||
else if (fname.extension() == ".npy") {
|
||||
// file_impl = new NumpyFile(fname, mode, cfg);
|
||||
file_impl = std::make_unique<NumpyFile>(fname, mode, cfg);
|
||||
} else {
|
||||
throw std::runtime_error("Unsupported file type");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File::File(File &&other) noexcept{
|
||||
std::swap(file_impl, other.file_impl);
|
||||
}
|
||||
|
||||
File& File::operator=(File &&other) noexcept {
|
||||
if (this != &other) {
|
||||
File tmp(std::move(other));
|
||||
std::swap(file_impl, tmp.file_impl);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Frame File::read_frame() { return file_impl->read_frame(); }
|
||||
Frame File::read_frame(size_t frame_index) {
|
||||
return file_impl->read_frame(frame_index);
|
||||
}
|
||||
size_t File::total_frames() const { return file_impl->total_frames(); }
|
||||
std::vector<Frame> File::read_n(size_t n_frames) {
|
||||
return file_impl->read_n(n_frames);
|
||||
}
|
||||
|
||||
void File::read_into(std::byte *image_buf) { file_impl->read_into(image_buf); }
|
||||
void File::read_into(std::byte *image_buf, size_t n_frames) {
|
||||
file_impl->read_into(image_buf, n_frames);
|
||||
}
|
||||
size_t File::frame_number(size_t frame_index) {
|
||||
return file_impl->frame_number(frame_index);
|
||||
}
|
||||
|
||||
size_t File::bytes_per_frame() const { return file_impl->bytes_per_frame(); }
|
||||
size_t File::pixels_per_frame() const{ return file_impl->pixels_per_frame(); }
|
||||
void File::seek(size_t frame_index) { file_impl->seek(frame_index); }
|
||||
size_t File::tell() const { return file_impl->tell(); }
|
||||
size_t File::rows() const { return file_impl->rows(); }
|
||||
size_t File::cols() const { return file_impl->cols(); }
|
||||
size_t File::bitdepth() const { return file_impl->bitdepth(); }
|
||||
size_t File::bytes_per_pixel() const { return file_impl->bitdepth() / 8; }
|
||||
|
||||
DetectorType File::detector_type() const { return file_impl->detector_type(); }
|
||||
|
||||
|
||||
} // namespace aare
|
74
src/Frame.cpp
Normal file
74
src/Frame.cpp
Normal file
@ -0,0 +1,74 @@
|
||||
#include "aare/Frame.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace aare {
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
|
||||
std::byte *Frame::pixel_ptr(uint32_t row, uint32_t col) const{
|
||||
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=(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::clone() 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;
|
||||
}
|
||||
|
||||
|
||||
} // namespace aare
|
152
src/Frame.test.cpp
Normal file
152
src/Frame.test.cpp
Normal 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.pixel_ptr(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.pixel_ptr(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.pixel_ptr(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.clone();
|
||||
|
||||
// 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
377
src/NDArray.test.cpp
Normal 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
193
src/NDView.test.cpp
Normal 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));
|
||||
}
|
200
src/NumpyFile.cpp
Normal file
200
src/NumpyFile.cpp
Normal file
@ -0,0 +1,200 @@
|
||||
|
||||
#include "aare/NumpyFile.hpp"
|
||||
#include "aare/NumpyHelpers.hpp"
|
||||
|
||||
namespace aare {
|
||||
|
||||
|
||||
|
||||
NumpyFile::NumpyFile(const std::filesystem::path &fname, const std::string &mode, FileConfig cfg) {
|
||||
// TODO! add opts to constructor
|
||||
|
||||
m_mode = mode;
|
||||
if (mode == "r") {
|
||||
fp = fopen(fname.string().c_str(), "rb");
|
||||
if (!fp) {
|
||||
throw std::runtime_error(fmt::format("Could not open: {} for reading", fname.string()));
|
||||
}
|
||||
load_metadata();
|
||||
} else if (mode == "w") {
|
||||
m_bitdepth = cfg.dtype.bitdepth();
|
||||
m_rows = cfg.rows;
|
||||
m_cols = cfg.cols;
|
||||
m_header = {cfg.dtype, false, {cfg.rows, cfg.cols}};
|
||||
m_header.shape = {0, cfg.rows, cfg.cols};
|
||||
fp = fopen(fname.string().c_str(), "wb");
|
||||
if (!fp) {
|
||||
throw std::runtime_error(fmt::format("Could not open: {} for reading", fname.string()));
|
||||
}
|
||||
initial_header_len = aare::NumpyHelpers::write_header(std::filesystem::path(fname.c_str()), m_header);
|
||||
}
|
||||
m_pixels_per_frame = std::accumulate(m_header.shape.begin() + 1, m_header.shape.end(), 1, std::multiplies<>());
|
||||
|
||||
m_bytes_per_frame = m_header.dtype.bitdepth() / 8 * m_pixels_per_frame;
|
||||
}
|
||||
void NumpyFile::write(Frame &frame) { write_impl(frame.data(), frame.bytes()); }
|
||||
void NumpyFile::write_impl(void *data, uint64_t size) {
|
||||
|
||||
if (fp == nullptr) {
|
||||
throw std::runtime_error("File not open");
|
||||
}
|
||||
if (!(m_mode == "w" || m_mode == "a")) {
|
||||
throw std::invalid_argument("File not open for writing");
|
||||
}
|
||||
if (fseek(fp, 0, SEEK_END))
|
||||
throw std::runtime_error("Could not seek to end of file");
|
||||
size_t const rc = fwrite(data, size, 1, fp);
|
||||
if (rc != 1) {
|
||||
throw std::runtime_error("Error writing frame to file");
|
||||
}
|
||||
|
||||
m_header.shape[0]++;
|
||||
}
|
||||
|
||||
Frame NumpyFile::get_frame(size_t frame_number) {
|
||||
Frame frame(m_header.shape[1], m_header.shape[2], m_header.dtype);
|
||||
get_frame_into(frame_number, frame.data());
|
||||
return frame;
|
||||
}
|
||||
void NumpyFile::get_frame_into(size_t frame_number, std::byte *image_buf) {
|
||||
if (fp == nullptr) {
|
||||
throw std::runtime_error("File not open");
|
||||
}
|
||||
if (frame_number > m_header.shape[0]) {
|
||||
throw std::invalid_argument("Frame number out of range");
|
||||
}
|
||||
if (fseek(fp, header_size + frame_number * m_bytes_per_frame, SEEK_SET)) // NOLINT
|
||||
throw std::runtime_error("Could not seek to frame");
|
||||
|
||||
size_t const rc = fread(image_buf, m_bytes_per_frame, 1, fp);
|
||||
if (rc != 1) {
|
||||
throw std::runtime_error("Error reading frame from file");
|
||||
}
|
||||
}
|
||||
|
||||
size_t NumpyFile::pixels_per_frame() { return m_pixels_per_frame; };
|
||||
size_t NumpyFile::bytes_per_frame() { return m_bytes_per_frame; };
|
||||
|
||||
std::vector<Frame> NumpyFile::read_n(size_t n_frames) {
|
||||
// TODO: implement this in a more efficient way
|
||||
std::vector<Frame> frames;
|
||||
for (size_t i = 0; i < n_frames; i++) {
|
||||
frames.push_back(get_frame(current_frame));
|
||||
current_frame++;
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
void NumpyFile::read_into(std::byte *image_buf, size_t n_frames) {
|
||||
// TODO: implement this in a more efficient way
|
||||
for (size_t i = 0; i < n_frames; i++) {
|
||||
get_frame_into(current_frame++, image_buf);
|
||||
image_buf += m_bytes_per_frame;
|
||||
}
|
||||
}
|
||||
|
||||
NumpyFile::~NumpyFile() noexcept {
|
||||
if (m_mode == "w" || m_mode == "a") {
|
||||
// determine number of frames
|
||||
if (fseek(fp, 0, SEEK_END)) {
|
||||
std::cout << "Could not seek to end of file" << std::endl;
|
||||
}
|
||||
size_t const file_size = ftell(fp);
|
||||
size_t const data_size = file_size - initial_header_len;
|
||||
size_t const n_frames = data_size / m_bytes_per_frame;
|
||||
// update number of frames in header (first element of shape)
|
||||
m_header.shape[0] = n_frames;
|
||||
if (fseek(fp, 0, SEEK_SET)) {
|
||||
std::cout << "Could not seek to beginning of file" << std::endl;
|
||||
}
|
||||
// create string stream to contain header
|
||||
std::stringstream ss;
|
||||
aare::NumpyHelpers::write_header(ss, m_header);
|
||||
std::string const header_str = ss.str();
|
||||
// write header
|
||||
size_t const rc = fwrite(header_str.c_str(), header_str.size(), 1, fp);
|
||||
if (rc != 1) {
|
||||
std::cout << "Error writing header to numpy file in destructor" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (fp != nullptr) {
|
||||
if (fclose(fp)) {
|
||||
std::cout << "Error closing file" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NumpyFile::load_metadata() {
|
||||
|
||||
// read magic number
|
||||
std::array<char, 6> tmp{};
|
||||
size_t rc = fread(tmp.data(), tmp.size(), 1, fp);
|
||||
if (rc != 1) {
|
||||
throw std::runtime_error("Error reading magic number");
|
||||
}
|
||||
if (tmp != aare::NumpyHelpers::magic_str) {
|
||||
for (auto item : tmp)
|
||||
fmt::print("{}, ", static_cast<int>(item));
|
||||
fmt::print("\n");
|
||||
throw std::runtime_error("Not a numpy file");
|
||||
}
|
||||
|
||||
// read version
|
||||
rc = fread(reinterpret_cast<char *>(&major_ver_), sizeof(major_ver_), 1, fp);
|
||||
rc += fread(reinterpret_cast<char *>(&minor_ver_), sizeof(minor_ver_), 1, fp);
|
||||
if (rc != 2) {
|
||||
throw std::runtime_error("Error reading numpy version");
|
||||
}
|
||||
|
||||
if (major_ver_ == 1) {
|
||||
header_len_size = 2;
|
||||
} else if (major_ver_ == 2) {
|
||||
header_len_size = 4;
|
||||
} else {
|
||||
throw std::runtime_error("Unsupported numpy version");
|
||||
}
|
||||
|
||||
// read header length
|
||||
rc = fread(reinterpret_cast<char *>(&header_len), header_len_size, 1, fp);
|
||||
if (rc != 1) {
|
||||
throw std::runtime_error("Error reading header length");
|
||||
}
|
||||
header_size = aare::NumpyHelpers::magic_string_length + 2 + header_len_size + header_len;
|
||||
if (header_size % 16 != 0) {
|
||||
fmt::print("Warning: header length is not a multiple of 16\n");
|
||||
}
|
||||
|
||||
// read header
|
||||
std::string header(header_len, '\0');
|
||||
rc = fread(header.data(), header_len, 1, fp);
|
||||
if (rc != 1) {
|
||||
throw std::runtime_error("Error reading header");
|
||||
}
|
||||
|
||||
// parse header
|
||||
std::vector<std::string> const keys{"descr", "fortran_order", "shape"};
|
||||
auto dict_map = aare::NumpyHelpers::parse_dict(header, keys);
|
||||
if (dict_map.empty())
|
||||
throw std::runtime_error("invalid dictionary in header");
|
||||
|
||||
std::string const descr_s = dict_map["descr"];
|
||||
std::string const fortran_s = dict_map["fortran_order"];
|
||||
std::string const shape_s = dict_map["shape"];
|
||||
|
||||
std::string const descr = aare::NumpyHelpers::parse_str(descr_s);
|
||||
aare::Dtype const dtype = aare::NumpyHelpers::parse_descr(descr);
|
||||
|
||||
// convert literal Python bool to C++ bool
|
||||
bool const fortran_order = aare::NumpyHelpers::parse_bool(fortran_s);
|
||||
|
||||
// parse the shape tuple
|
||||
auto shape_v = aare::NumpyHelpers::parse_tuple(shape_s);
|
||||
std::vector<size_t> shape;
|
||||
for (const auto &item : shape_v) {
|
||||
auto dim = static_cast<size_t>(std::stoul(item));
|
||||
shape.push_back(dim);
|
||||
}
|
||||
m_header = {dtype, fortran_order, shape};
|
||||
}
|
||||
|
||||
} // namespace aare
|
50
src/NumpyFile.test.cpp
Normal file
50
src/NumpyFile.test.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
#include "aare/NumpyFile.hpp"
|
||||
#include "aare/NDArray.hpp"
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include "test_config.hpp"
|
||||
|
||||
using aare::Dtype;
|
||||
using aare::NumpyFile;
|
||||
TEST_CASE("Read a 1D numpy file with int32 data type") {
|
||||
|
||||
auto fpath = test_data_path() / "numpy" / "test_1d_int32.npy";
|
||||
REQUIRE(std::filesystem::exists(fpath));
|
||||
|
||||
NumpyFile f(fpath);
|
||||
|
||||
// we know the file contains 10 elements of np.int32 containing values 0-9
|
||||
REQUIRE(f.dtype() == Dtype::INT32);
|
||||
REQUIRE(f.shape() == std::vector<size_t>{10});
|
||||
|
||||
// use the load function to read the full file into a NDArray
|
||||
auto data = f.load<int32_t, 1>();
|
||||
for (int32_t i = 0; i < 10; i++) {
|
||||
REQUIRE(data(i) == i);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Read a 3D numpy file with np.double data type") {
|
||||
|
||||
auto fpath = test_data_path() / "numpy" / "test_3d_double.npy";
|
||||
REQUIRE(std::filesystem::exists(fpath));
|
||||
|
||||
NumpyFile f(fpath);
|
||||
|
||||
// we know the file contains 10 elements of np.int32 containing values 0-9
|
||||
REQUIRE(f.dtype() == Dtype::DOUBLE);
|
||||
REQUIRE(f.shape() == std::vector<size_t>{3, 2, 5});
|
||||
|
||||
// use the load function to read the full file into a NDArray
|
||||
// numpy code to generate the array
|
||||
// arr2[0,0,0] = 1.0
|
||||
// arr2[0,0,1] = 2.0
|
||||
// arr2[0,1,0] = 72.0
|
||||
// arr2[2,0,4] = 63.0
|
||||
|
||||
auto data = f.load<double, 3>();
|
||||
REQUIRE(data(0, 0, 0) == 1.0);
|
||||
REQUIRE(data(0, 0, 1) == 2.0);
|
||||
REQUIRE(data(0, 1, 0) == 72.0);
|
||||
REQUIRE(data(2, 0, 4) == 63.0);
|
||||
}
|
269
src/NumpyHelpers.cpp
Normal file
269
src/NumpyHelpers.cpp
Normal file
@ -0,0 +1,269 @@
|
||||
/*
|
||||
28-03-2024 modified by: Bechir Braham <bechir.braham@psi.ch>
|
||||
|
||||
Copyright 2017-2023 Leon Merten Lohse
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "aare/NumpyHelpers.hpp"
|
||||
#include <iterator>
|
||||
|
||||
namespace aare {
|
||||
|
||||
std::string NumpyHeader::to_string() const {
|
||||
std::stringstream sstm;
|
||||
sstm << "dtype: " << dtype.to_string() << ", fortran_order: " << fortran_order << ' ';
|
||||
sstm << "shape: (";
|
||||
for (auto item : shape)
|
||||
sstm << item << ',';
|
||||
sstm << ')';
|
||||
return sstm.str();
|
||||
}
|
||||
|
||||
|
||||
namespace NumpyHelpers {
|
||||
|
||||
std::unordered_map<std::string, std::string> parse_dict(std::string in, const std::vector<std::string> &keys) {
|
||||
std::unordered_map<std::string, std::string> map;
|
||||
if (keys.empty())
|
||||
return map;
|
||||
|
||||
in = trim(in);
|
||||
|
||||
// unwrap dictionary
|
||||
if ((in.front() == '{') && (in.back() == '}'))
|
||||
in = in.substr(1, in.length() - 2);
|
||||
else
|
||||
throw std::runtime_error("Not a Python dictionary.");
|
||||
|
||||
std::vector<std::pair<size_t, std::string>> positions;
|
||||
|
||||
for (auto const &key : keys) {
|
||||
size_t const pos = in.find("'" + key + "'");
|
||||
|
||||
if (pos == std::string::npos)
|
||||
throw std::runtime_error("Missing '" + key + "' key.");
|
||||
|
||||
std::pair<size_t, std::string> const position_pair{pos, key};
|
||||
positions.push_back(position_pair);
|
||||
}
|
||||
|
||||
// sort by position in dict
|
||||
std::sort(positions.begin(), positions.end());
|
||||
|
||||
for (size_t i = 0; i < positions.size(); ++i) {
|
||||
std::string raw_value;
|
||||
size_t const begin{positions[i].first};
|
||||
size_t end{std::string::npos};
|
||||
|
||||
std::string const key = positions[i].second;
|
||||
|
||||
if (i + 1 < positions.size())
|
||||
end = positions[i + 1].first;
|
||||
|
||||
raw_value = in.substr(begin, end - begin);
|
||||
|
||||
raw_value = trim(raw_value);
|
||||
|
||||
if (raw_value.back() == ',')
|
||||
raw_value.pop_back();
|
||||
|
||||
map[key] = get_value_from_map(raw_value);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
aare::Dtype parse_descr(std::string typestring) {
|
||||
|
||||
if (typestring.length() < 3) {
|
||||
throw std::runtime_error("invalid typestring (length)");
|
||||
}
|
||||
|
||||
constexpr char little_endian_char = '<';
|
||||
constexpr char big_endian_char = '>';
|
||||
constexpr char no_endian_char = '|';
|
||||
constexpr std::array<char, 3> endian_chars = {little_endian_char, big_endian_char, no_endian_char};
|
||||
constexpr std::array<char, 4> numtype_chars = {'f', 'i', 'u', 'c'};
|
||||
|
||||
const char byteorder_c = typestring[0];
|
||||
const char kind_c = typestring[1];
|
||||
std::string const itemsize_s = typestring.substr(2);
|
||||
|
||||
if (!in_array(byteorder_c, endian_chars)) {
|
||||
throw std::runtime_error("invalid typestring (byteorder)");
|
||||
}
|
||||
|
||||
if (!in_array(kind_c, numtype_chars)) {
|
||||
throw std::runtime_error("invalid typestring (kind)");
|
||||
}
|
||||
|
||||
if (!is_digits(itemsize_s)) {
|
||||
throw std::runtime_error("invalid typestring (itemsize)");
|
||||
}
|
||||
|
||||
return aare::Dtype(typestring);
|
||||
}
|
||||
|
||||
bool parse_bool(const std::string &in) {
|
||||
if (in == "True")
|
||||
return true;
|
||||
if (in == "False")
|
||||
return false;
|
||||
throw std::runtime_error("Invalid python boolean.");
|
||||
}
|
||||
|
||||
std::string get_value_from_map(const std::string &mapstr) {
|
||||
size_t const sep_pos = mapstr.find_first_of(':');
|
||||
if (sep_pos == std::string::npos)
|
||||
return "";
|
||||
|
||||
std::string const tmp = mapstr.substr(sep_pos + 1);
|
||||
return trim(tmp);
|
||||
}
|
||||
|
||||
bool is_digits(const std::string &str) { return std::all_of(str.begin(), str.end(), ::isdigit); }
|
||||
|
||||
std::vector<std::string> parse_tuple(std::string in) {
|
||||
std::vector<std::string> v;
|
||||
const char separator = ',';
|
||||
|
||||
in = trim(in);
|
||||
|
||||
if ((in.front() == '(') && (in.back() == ')'))
|
||||
in = in.substr(1, in.length() - 2);
|
||||
else
|
||||
throw std::runtime_error("Invalid Python tuple.");
|
||||
|
||||
std::istringstream iss(in);
|
||||
|
||||
for (std::string token; std::getline(iss, token, separator);) {
|
||||
v.push_back(token);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
std::string trim(const std::string &str) {
|
||||
const std::string whitespace = " \t\n";
|
||||
auto begin = str.find_first_not_of(whitespace);
|
||||
|
||||
if (begin == std::string::npos)
|
||||
return "";
|
||||
|
||||
auto end = str.find_last_not_of(whitespace);
|
||||
return str.substr(begin, end - begin + 1);
|
||||
}
|
||||
|
||||
std::string parse_str(const std::string &in) {
|
||||
if ((in.front() == '\'') && (in.back() == '\''))
|
||||
return in.substr(1, in.length() - 2);
|
||||
|
||||
throw std::runtime_error("Invalid python string.");
|
||||
}
|
||||
|
||||
void write_magic(std::ostream &ostream, int version_major, int version_minor) {
|
||||
ostream.write(magic_str.data(), magic_string_length);
|
||||
ostream.put(static_cast<char>(version_major));
|
||||
ostream.put(static_cast<char>(version_minor));
|
||||
}
|
||||
template <typename T> inline std::string write_tuple(const std::vector<T> &v) {
|
||||
|
||||
if (v.empty())
|
||||
return "()";
|
||||
std::ostringstream ss;
|
||||
ss.imbue(std::locale("C"));
|
||||
|
||||
if (v.size() == 1) {
|
||||
ss << "(" << v.front() << ",)";
|
||||
} else {
|
||||
const std::string delimiter = ", ";
|
||||
// v.size() > 1
|
||||
ss << "(";
|
||||
// for (size_t i = 0; i < v.size() - 1; ++i) {
|
||||
// ss << v[i] << delimiter;
|
||||
// }
|
||||
// ss << v.back();
|
||||
std::copy(v.begin(), v.end() - 1, std::ostream_iterator<T>(ss, ", "));
|
||||
ss << v.back();
|
||||
ss << ")";
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
inline std::string write_boolean(bool b) {
|
||||
if (b)
|
||||
return "True";
|
||||
return "False";
|
||||
}
|
||||
|
||||
inline std::string write_header_dict(const std::string &descr, bool fortran_order, const std::vector<size_t> &shape) {
|
||||
std::string const s_fortran_order = write_boolean(fortran_order);
|
||||
std::string const shape_s = write_tuple(shape);
|
||||
|
||||
return "{'descr': '" + descr + "', 'fortran_order': " + s_fortran_order + ", 'shape': " + shape_s + ", }";
|
||||
}
|
||||
|
||||
size_t write_header(const std::filesystem::path &fname, const NumpyHeader &header) {
|
||||
std::ofstream out(fname, std::ios::binary | std::ios::out);
|
||||
return write_header(out, header);
|
||||
}
|
||||
|
||||
size_t write_header(std::ostream &out, const NumpyHeader &header) {
|
||||
std::string const header_dict = write_header_dict(header.dtype.to_string(), header.fortran_order, header.shape);
|
||||
|
||||
size_t length = magic_string_length + 2 + 2 + header_dict.length() + 1;
|
||||
|
||||
int version_major = 1;
|
||||
int version_minor = 0;
|
||||
if (length >= static_cast<size_t>(255) * 255) {
|
||||
length = magic_string_length + 2 + 4 + header_dict.length() + 1;
|
||||
version_major = 2;
|
||||
version_minor = 0;
|
||||
}
|
||||
size_t const padding_len = 16 - length % 16;
|
||||
std::string const padding(padding_len, ' ');
|
||||
|
||||
// write magic
|
||||
write_magic(out, version_major, version_minor);
|
||||
|
||||
// write header length
|
||||
if (version_major == 1 && version_minor == 0) {
|
||||
auto header_len = static_cast<uint16_t>(header_dict.length() + padding.length() + 1);
|
||||
|
||||
std::array<uint8_t, 2> header_len_le16{static_cast<uint8_t>((header_len >> 0) & 0xff),
|
||||
static_cast<uint8_t>((header_len >> 8) & 0xff)};
|
||||
out.write(reinterpret_cast<char *>(header_len_le16.data()), 2);
|
||||
} else {
|
||||
auto header_len = static_cast<uint32_t>(header_dict.length() + padding.length() + 1);
|
||||
|
||||
std::array<uint8_t, 4> header_len_le32{
|
||||
static_cast<uint8_t>((header_len >> 0) & 0xff), static_cast<uint8_t>((header_len >> 8) & 0xff),
|
||||
static_cast<uint8_t>((header_len >> 16) & 0xff), static_cast<uint8_t>((header_len >> 24) & 0xff)};
|
||||
out.write(reinterpret_cast<char *>(header_len_le32.data()), 4);
|
||||
}
|
||||
|
||||
out << header_dict << padding << '\n';
|
||||
return length;
|
||||
}
|
||||
|
||||
} // namespace NumpyHelpers
|
||||
} // namespace aare
|
62
src/NumpyHelpers.test.cpp
Normal file
62
src/NumpyHelpers.test.cpp
Normal file
@ -0,0 +1,62 @@
|
||||
#include "aare/NumpyHelpers.hpp" //Is this really a public header?
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
using namespace aare::NumpyHelpers;
|
||||
|
||||
TEST_CASE("is_digits with a few standard cases") {
|
||||
REQUIRE(is_digits(""));
|
||||
REQUIRE(is_digits("123"));
|
||||
REQUIRE(is_digits("0"));
|
||||
REQUIRE_FALSE(is_digits("hej123"));
|
||||
REQUIRE_FALSE(is_digits("a"));
|
||||
REQUIRE_FALSE(is_digits(" "));
|
||||
REQUIRE_FALSE(is_digits("abcdef"));
|
||||
}
|
||||
|
||||
TEST_CASE("Check for quotes and return stripped string") {
|
||||
REQUIRE(parse_str("'hej'") == "hej");
|
||||
REQUIRE(parse_str("'hej hej'") == "hej hej");
|
||||
REQUIRE(parse_str("''") == "");
|
||||
}
|
||||
|
||||
TEST_CASE("parsing a string without quotes throws") { REQUIRE_THROWS(parse_str("hej")); }
|
||||
|
||||
TEST_CASE("trim whitespace") {
|
||||
REQUIRE(trim(" hej ") == "hej");
|
||||
REQUIRE(trim("hej") == "hej");
|
||||
REQUIRE(trim(" hej") == "hej");
|
||||
REQUIRE(trim("hej ") == "hej");
|
||||
REQUIRE(trim(" ") == "");
|
||||
REQUIRE(trim(" \thej hej ") == "hej hej");
|
||||
}
|
||||
|
||||
TEST_CASE("parse data type descriptions") {
|
||||
REQUIRE(parse_descr("<i1") == aare::Dtype::INT8);
|
||||
REQUIRE(parse_descr("<i2") == aare::Dtype::INT16);
|
||||
REQUIRE(parse_descr("<i4") == aare::Dtype::INT32);
|
||||
REQUIRE(parse_descr("<i8") == aare::Dtype::INT64);
|
||||
|
||||
REQUIRE(parse_descr("<u1") == aare::Dtype::UINT8);
|
||||
REQUIRE(parse_descr("<u2") == aare::Dtype::UINT16);
|
||||
REQUIRE(parse_descr("<u4") == aare::Dtype::UINT32);
|
||||
REQUIRE(parse_descr("<u8") == aare::Dtype::UINT64);
|
||||
|
||||
REQUIRE(parse_descr("<f4") == aare::Dtype::FLOAT);
|
||||
REQUIRE(parse_descr("<f8") == aare::Dtype::DOUBLE);
|
||||
}
|
||||
|
||||
TEST_CASE("is element in array") {
|
||||
REQUIRE(in_array(1, std::array<int, 3>{1, 2, 3}));
|
||||
REQUIRE_FALSE(in_array(4, std::array<int, 3>{1, 2, 3}));
|
||||
REQUIRE(in_array(1, std::array<int, 1>{1}));
|
||||
REQUIRE_FALSE(in_array(1, std::array<int, 0>{}));
|
||||
}
|
||||
|
||||
TEST_CASE("Parse numpy dict") {
|
||||
std::string in = "{'descr': '<f4', 'fortran_order': False, 'shape': (3, 4)}";
|
||||
std::vector<std::string> keys{"descr", "fortran_order", "shape"};
|
||||
auto map = parse_dict(in, keys);
|
||||
REQUIRE(map["descr"] == "'<f4'");
|
||||
REQUIRE(map["fortran_order"] == "False");
|
||||
REQUIRE(map["shape"] == "(3, 4)");
|
||||
}
|
103
src/Pedestal.test.cpp
Normal file
103
src/Pedestal.test.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
#include "aare/Pedestal.hpp"
|
||||
|
||||
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <chrono>
|
||||
#include <random>
|
||||
|
||||
using namespace aare;
|
||||
TEST_CASE("test pedestal constructor") {
|
||||
aare::Pedestal pedestal(10, 10, 5);
|
||||
REQUIRE(pedestal.rows() == 10);
|
||||
REQUIRE(pedestal.cols() == 10);
|
||||
REQUIRE(pedestal.n_samples() == 5);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
REQUIRE(pedestal.get_sum()(i, j) == 0);
|
||||
REQUIRE(pedestal.get_sum2()(i, j) == 0);
|
||||
REQUIRE(pedestal.cur_samples()(i, j) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("test pedestal push") {
|
||||
aare::Pedestal pedestal(10, 10, 5);
|
||||
aare::Frame frame(10, 10, Dtype::UINT16);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
frame.set<uint16_t>(i, j, i + j);
|
||||
}
|
||||
}
|
||||
|
||||
// test single push
|
||||
pedestal.push<uint16_t>(frame);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
REQUIRE(pedestal.get_sum()(i, j) == i + j);
|
||||
REQUIRE(pedestal.get_sum2()(i, j) == (i + j) * (i + j));
|
||||
REQUIRE(pedestal.cur_samples()(i, j) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
// test clear
|
||||
pedestal.clear();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
REQUIRE(pedestal.get_sum()(i, j) == 0);
|
||||
REQUIRE(pedestal.get_sum2()(i, j) == 0);
|
||||
REQUIRE(pedestal.cur_samples()(i, j) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
// test number of samples after multiple push
|
||||
for (uint32_t k = 0; k < 50; k++) {
|
||||
pedestal.push<uint16_t>(frame);
|
||||
for (uint32_t i = 0; i < 10; i++) {
|
||||
for (uint32_t j = 0; j < 10; j++) {
|
||||
if (k < 5) {
|
||||
REQUIRE(pedestal.cur_samples()(i, j) == k + 1);
|
||||
REQUIRE(pedestal.get_sum()(i, j) == (k + 1) * (i + j));
|
||||
REQUIRE(pedestal.get_sum2()(i, j) == (k + 1) * (i + j) * (i + j));
|
||||
} else {
|
||||
REQUIRE(pedestal.cur_samples()(i, j) == 5);
|
||||
REQUIRE(pedestal.get_sum()(i, j) == 5 * (i + j));
|
||||
REQUIRE(pedestal.get_sum2()(i, j) == 5 * (i + j) * (i + j));
|
||||
}
|
||||
REQUIRE(pedestal.mean(i, j) == (i + j));
|
||||
REQUIRE(pedestal.variance(i, j) == 0);
|
||||
REQUIRE(pedestal.std(i, j) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("test pedestal with normal distribution") {
|
||||
const double MEAN = 5.0, STD = 2.0, VAR = STD * STD, TOLERANCE = 0.1;
|
||||
|
||||
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
|
||||
std::default_random_engine generator(seed);
|
||||
std::normal_distribution<double> distribution(MEAN, STD);
|
||||
|
||||
aare::Pedestal pedestal(3, 5, 10000);
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
aare::Frame frame(3, 5, Dtype::DOUBLE);
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int k = 0; k < 5; k++) {
|
||||
frame.set<double>(j, k, distribution(generator));
|
||||
}
|
||||
}
|
||||
pedestal.push<double>(frame);
|
||||
}
|
||||
auto mean = pedestal.mean();
|
||||
auto variance = pedestal.variance();
|
||||
auto standard_deviation = pedestal.std();
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 5; j++) {
|
||||
REQUIRE_THAT(mean(i, j), Catch::Matchers::WithinAbs(MEAN, MEAN * TOLERANCE));
|
||||
REQUIRE_THAT(variance(i, j), Catch::Matchers::WithinAbs(VAR, VAR * TOLERANCE));
|
||||
REQUIRE_THAT(standard_deviation(i, j), Catch::Matchers::WithinAbs(STD, STD * TOLERANCE));
|
||||
}
|
||||
}
|
||||
}
|
77
src/PixelMap.cpp
Normal file
77
src/PixelMap.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
#include "aare/PixelMap.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace aare {
|
||||
NDArray<ssize_t, 2> GenerateMoench03PixelMap() {
|
||||
std::array<int, 32> const adc_nr = {300, 325, 350, 375, 300, 325, 350, 375,
|
||||
200, 225, 250, 275, 200, 225, 250, 275,
|
||||
100, 125, 150, 175, 100, 125, 150, 175,
|
||||
0, 25, 50, 75, 0, 25, 50, 75};
|
||||
int const sc_width = 25;
|
||||
int const nadc = 32;
|
||||
int const pixels_per_sc = 5000;
|
||||
NDArray<ssize_t, 2> order_map({400, 400});
|
||||
|
||||
int pixel = 0;
|
||||
for (int i = 0; i != pixels_per_sc; ++i) {
|
||||
for (int i_adc = 0; i_adc != nadc; ++i_adc) {
|
||||
int const col = adc_nr[i_adc] + (i % sc_width);
|
||||
int row = 0;
|
||||
if ((i_adc / 4) % 2 == 0)
|
||||
row = 199 - (i / sc_width);
|
||||
else
|
||||
row = 200 + (i / sc_width);
|
||||
|
||||
order_map(row, col) = pixel;
|
||||
pixel++;
|
||||
}
|
||||
}
|
||||
return order_map;
|
||||
}
|
||||
|
||||
NDArray<ssize_t, 2> GenerateMoench05PixelMap() {
|
||||
std::array<int, 3> adc_numbers = {9, 13, 1};
|
||||
NDArray<ssize_t, 2> order_map({160, 150});
|
||||
int n_pixel = 0;
|
||||
for (int row = 0; row < 160; row++) {
|
||||
for (int i_col = 0; i_col < 50; i_col++) {
|
||||
n_pixel = row * 50 + i_col;
|
||||
for (int i_sc = 0; i_sc < 3; i_sc++) {
|
||||
int col = 50 * i_sc + i_col;
|
||||
int adc_nr = adc_numbers[i_sc];
|
||||
int i_analog = n_pixel * 32 + adc_nr;
|
||||
|
||||
|
||||
// analog_frame[row * 150 + col] = analog_data[i_analog] & 0x3FFF;
|
||||
order_map(row, col) = i_analog;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return order_map;
|
||||
}
|
||||
|
||||
NDArray<ssize_t, 2>GenerateMH02SingleCounterPixelMap(){
|
||||
NDArray<ssize_t, 2> order_map({48, 48});
|
||||
for(int row = 0; row < 48; row++){
|
||||
for(int col = 0; col < 48; col++){
|
||||
order_map(row, col) = row*48 + col;
|
||||
}
|
||||
}
|
||||
return order_map;
|
||||
}
|
||||
|
||||
NDArray<ssize_t, 3> GenerateMH02FourCounterPixelMap(){
|
||||
NDArray<ssize_t, 3> order_map({4, 48, 48});
|
||||
for (int counter=0; counter<4; counter++){
|
||||
for(int row = 0; row < 48; row++){
|
||||
for(int col = 0; col < 48; col++){
|
||||
order_map(counter, row, col) = counter*48*48 + row*48 + col;
|
||||
}
|
||||
}
|
||||
}
|
||||
return order_map;
|
||||
}
|
||||
|
||||
} // namespace aare
|
266
src/RawFile.cpp
Normal file
266
src/RawFile.cpp
Normal file
@ -0,0 +1,266 @@
|
||||
#include "aare/RawFile.hpp"
|
||||
#include "aare/PixelMap.hpp"
|
||||
#include "aare/defs.hpp"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace aare {
|
||||
|
||||
RawFile::RawFile(const std::filesystem::path &fname, const std::string &mode)
|
||||
: m_master(fname) {
|
||||
m_mode = mode;
|
||||
if (mode == "r") {
|
||||
find_number_of_subfiles();
|
||||
n_subfile_parts = m_master.geometry().col * m_master.geometry().row;
|
||||
find_geometry();
|
||||
open_subfiles();
|
||||
} else {
|
||||
throw std::runtime_error(LOCATION +
|
||||
"Unsupported mode. Can only read RawFiles.");
|
||||
}
|
||||
}
|
||||
|
||||
Frame RawFile::read_frame() { return get_frame(m_current_frame++); };
|
||||
|
||||
Frame RawFile::read_frame(size_t frame_number) {
|
||||
seek(frame_number);
|
||||
return read_frame();
|
||||
}
|
||||
|
||||
void RawFile::read_into(std::byte *image_buf, size_t n_frames) {
|
||||
// TODO: implement this in a more efficient way
|
||||
|
||||
for (size_t i = 0; i < n_frames; i++) {
|
||||
this->get_frame_into(m_current_frame++, image_buf);
|
||||
image_buf += bytes_per_frame();
|
||||
}
|
||||
}
|
||||
void RawFile::read_into(std::byte *image_buf) {
|
||||
return get_frame_into(m_current_frame++, image_buf);
|
||||
};
|
||||
|
||||
size_t RawFile::bytes_per_frame() {
|
||||
return m_rows * m_cols * m_master.bitdepth() / 8;
|
||||
}
|
||||
size_t RawFile::pixels_per_frame() { return m_rows * m_cols; }
|
||||
|
||||
DetectorType RawFile::detector_type() const { return m_master.detector_type(); }
|
||||
|
||||
void RawFile::seek(size_t frame_index) {
|
||||
// check if the frame number is greater than the total frames
|
||||
// if frame_number == total_frames, then the next read will throw an error
|
||||
if (frame_index > total_frames()) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("frame number {} is greater than total frames {}",
|
||||
frame_index, total_frames()));
|
||||
}
|
||||
m_current_frame = frame_index;
|
||||
};
|
||||
|
||||
size_t RawFile::tell() { return m_current_frame; };
|
||||
|
||||
size_t RawFile::total_frames() const { return m_master.frames_in_file(); }
|
||||
size_t RawFile::rows() const { return m_rows; }
|
||||
size_t RawFile::cols() const { return m_cols; }
|
||||
size_t RawFile::bitdepth() const { return m_master.bitdepth(); }
|
||||
xy RawFile::geometry() { return m_master.geometry(); }
|
||||
|
||||
void RawFile::open_subfiles() {
|
||||
if (m_mode == "r")
|
||||
for (size_t i = 0; i != n_subfiles; ++i) {
|
||||
auto v = std::vector<SubFile *>(n_subfile_parts);
|
||||
for (size_t j = 0; j != n_subfile_parts; ++j) {
|
||||
v[j] =
|
||||
new SubFile(m_master.data_fname(j, i),
|
||||
m_master.detector_type(), m_master.pixels_y(),
|
||||
m_master.pixels_x(), m_master.bitdepth());
|
||||
}
|
||||
subfiles.push_back(v);
|
||||
}
|
||||
else {
|
||||
throw std::runtime_error(LOCATION +
|
||||
"Unsupported mode. Can only read RawFiles.");
|
||||
}
|
||||
}
|
||||
|
||||
DetectorHeader RawFile::read_header(const std::filesystem::path &fname) {
|
||||
DetectorHeader h{};
|
||||
FILE *fp = fopen(fname.string().c_str(), "r");
|
||||
if (!fp)
|
||||
throw std::runtime_error(
|
||||
fmt::format("Could not open: {} for reading", fname.string()));
|
||||
|
||||
size_t const rc = fread(reinterpret_cast<char *>(&h), sizeof(h), 1, fp);
|
||||
if (rc != 1)
|
||||
throw std::runtime_error(LOCATION + "Could not read header from file");
|
||||
if (fclose(fp)) {
|
||||
throw std::runtime_error(LOCATION + "Could not close file");
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
bool RawFile::is_master_file(const std::filesystem::path &fpath) {
|
||||
std::string const stem = fpath.stem().string();
|
||||
return stem.find("_master_") != std::string::npos;
|
||||
}
|
||||
|
||||
void RawFile::find_number_of_subfiles() {
|
||||
int n_mod = 0;
|
||||
while (std::filesystem::exists(m_master.data_fname(0, ++n_mod)))
|
||||
;
|
||||
n_subfiles = n_mod;
|
||||
#ifdef AARE_VERBOSE
|
||||
fmt::print("Found: {} subfiles\n", n_subfiles);
|
||||
#endif
|
||||
}
|
||||
|
||||
void RawFile::find_geometry() {
|
||||
uint16_t r{};
|
||||
uint16_t c{};
|
||||
for (size_t i = 0; i < n_subfile_parts; i++) {
|
||||
for (size_t j = 0; j != n_subfiles; ++j) {
|
||||
auto h = this->read_header(m_master.data_fname(i, j));
|
||||
r = std::max(r, h.row);
|
||||
c = std::max(c, h.column);
|
||||
|
||||
positions.push_back({h.row, h.column});
|
||||
}
|
||||
}
|
||||
|
||||
r++;
|
||||
c++;
|
||||
|
||||
m_rows = (r * m_master.pixels_y());
|
||||
m_cols = (c * m_master.pixels_x());
|
||||
|
||||
m_rows += static_cast<size_t>((r - 1) * cfg.module_gap_row);
|
||||
}
|
||||
|
||||
Frame RawFile::get_frame(size_t frame_index) {
|
||||
auto f = Frame(m_rows, m_cols, Dtype::from_bitdepth(m_master.bitdepth()));
|
||||
std::byte *frame_buffer = f.data();
|
||||
get_frame_into(frame_index, frame_buffer);
|
||||
return f;
|
||||
}
|
||||
|
||||
void RawFile::get_frame_into(size_t frame_index, std::byte *frame_buffer) {
|
||||
if (frame_index > total_frames()) {
|
||||
throw std::runtime_error(LOCATION + "Frame number out of range");
|
||||
}
|
||||
std::vector<size_t> frame_numbers(n_subfile_parts);
|
||||
std::vector<size_t> frame_indices(n_subfile_parts, frame_index);
|
||||
|
||||
if (n_subfile_parts != 1) {
|
||||
for (size_t part_idx = 0; part_idx != n_subfile_parts; ++part_idx) {
|
||||
auto subfile_id = frame_index / m_master.max_frames_per_file();
|
||||
frame_numbers[part_idx] =
|
||||
subfiles[subfile_id][part_idx]->frame_number(
|
||||
frame_index % m_master.max_frames_per_file());
|
||||
}
|
||||
// 1. if frame number vector is the same break
|
||||
while (std::adjacent_find(frame_numbers.begin(), frame_numbers.end(),
|
||||
std::not_equal_to<>()) !=
|
||||
frame_numbers.end()) {
|
||||
// 2. find the index of the minimum frame number,
|
||||
auto min_frame_idx = std::distance(
|
||||
frame_numbers.begin(),
|
||||
std::min_element(frame_numbers.begin(), frame_numbers.end()));
|
||||
// 3. increase its index and update its respective frame number
|
||||
frame_indices[min_frame_idx]++;
|
||||
// 4. if we can't increase its index => throw error
|
||||
if (frame_indices[min_frame_idx] >= total_frames()) {
|
||||
throw std::runtime_error(LOCATION +
|
||||
"Frame number out of range");
|
||||
}
|
||||
auto subfile_id =
|
||||
frame_indices[min_frame_idx] / m_master.max_frames_per_file();
|
||||
frame_numbers[min_frame_idx] =
|
||||
subfiles[subfile_id][min_frame_idx]->frame_number(
|
||||
frame_indices[min_frame_idx] %
|
||||
m_master.max_frames_per_file());
|
||||
}
|
||||
}
|
||||
|
||||
if (m_master.geometry().col == 1) {
|
||||
// get the part from each subfile and copy it to the frame
|
||||
for (size_t part_idx = 0; part_idx != n_subfile_parts; ++part_idx) {
|
||||
auto corrected_idx = frame_indices[part_idx];
|
||||
auto subfile_id = corrected_idx / m_master.max_frames_per_file();
|
||||
auto part_offset = subfiles[subfile_id][part_idx]->bytes_per_part();
|
||||
subfiles[subfile_id][part_idx]->get_part(
|
||||
frame_buffer + part_idx * part_offset,
|
||||
corrected_idx % m_master.max_frames_per_file());
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// create a buffer that will hold a the frame part
|
||||
auto bytes_per_part = m_master.pixels_y() * m_master.pixels_x() *
|
||||
m_master.bitdepth() /
|
||||
8; // TODO! replace with image_size_in_bytes
|
||||
auto *part_buffer = new std::byte[bytes_per_part];
|
||||
|
||||
// TODO! if we have many submodules we should reorder them on the module
|
||||
// level
|
||||
|
||||
for (size_t part_idx = 0; part_idx != n_subfile_parts; ++part_idx) {
|
||||
auto corrected_idx = frame_indices[part_idx];
|
||||
auto subfile_id = corrected_idx / m_master.max_frames_per_file();
|
||||
|
||||
subfiles[subfile_id][part_idx]->get_part(
|
||||
part_buffer, corrected_idx % m_master.max_frames_per_file());
|
||||
for (size_t cur_row = 0; cur_row < (m_master.pixels_y());
|
||||
cur_row++) {
|
||||
auto irow = cur_row + (part_idx / m_master.geometry().col) *
|
||||
m_master.pixels_y();
|
||||
auto icol =
|
||||
(part_idx % m_master.geometry().col) * m_master.pixels_x();
|
||||
auto dest = (irow * this->m_cols + icol);
|
||||
dest = dest * m_master.bitdepth() / 8;
|
||||
memcpy(frame_buffer + dest,
|
||||
part_buffer + cur_row * m_master.pixels_x() *
|
||||
m_master.bitdepth() / 8,
|
||||
m_master.pixels_x() * m_master.bitdepth() / 8);
|
||||
}
|
||||
}
|
||||
delete[] part_buffer;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Frame> RawFile::read_n(size_t n_frames) {
|
||||
// TODO: implement this in a more efficient way
|
||||
std::vector<Frame> frames;
|
||||
for (size_t i = 0; i < n_frames; i++) {
|
||||
frames.push_back(this->get_frame(m_current_frame));
|
||||
m_current_frame++;
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
size_t RawFile::frame_number(size_t frame_index) {
|
||||
if (frame_index >= m_master.frames_in_file()) {
|
||||
throw std::runtime_error(LOCATION + " Frame number out of range");
|
||||
}
|
||||
size_t subfile_id = frame_index / m_master.max_frames_per_file();
|
||||
if (subfile_id >= subfiles.size()) {
|
||||
throw std::runtime_error(
|
||||
LOCATION + " Subfile out of range. Possible missing data.");
|
||||
}
|
||||
return subfiles[subfile_id][0]->frame_number(
|
||||
frame_index % m_master.max_frames_per_file());
|
||||
}
|
||||
|
||||
RawFile::~RawFile() {
|
||||
|
||||
// TODO! Fix this, for file closing
|
||||
for (auto &vec : subfiles) {
|
||||
for (auto *subfile : vec) {
|
||||
delete subfile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace aare
|
150
src/RawFile.test.cpp
Normal file
150
src/RawFile.test.cpp
Normal file
@ -0,0 +1,150 @@
|
||||
#include "aare/File.hpp"
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <filesystem>
|
||||
|
||||
#include "test_config.hpp"
|
||||
|
||||
using aare::File;
|
||||
|
||||
TEST_CASE("Read number of frames from a jungfrau raw file") {
|
||||
|
||||
auto fpath = test_data_path() / "jungfrau" / "jungfrau_single_master_0.json";
|
||||
REQUIRE(std::filesystem::exists(fpath));
|
||||
|
||||
File f(fpath, "r");
|
||||
REQUIRE(f.total_frames() == 10);
|
||||
}
|
||||
|
||||
TEST_CASE("Read frame numbers from a jungfrau raw file") {
|
||||
auto fpath = test_data_path() / "jungfrau" / "jungfrau_single_master_0.json";
|
||||
REQUIRE(std::filesystem::exists(fpath));
|
||||
|
||||
File f(fpath, "r");
|
||||
|
||||
// we know this file has 10 frames with frame numbers 1 to 10
|
||||
// f0 1,2,3
|
||||
// f1 4,5,6
|
||||
// f2 7,8,9
|
||||
// f3 10
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
CHECK(f.frame_number(i) == i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Read a frame number too high throws") {
|
||||
auto fpath = test_data_path() / "jungfrau" / "jungfrau_single_master_0.json";
|
||||
REQUIRE(std::filesystem::exists(fpath));
|
||||
|
||||
File f(fpath, "r");
|
||||
|
||||
// we know this file has 10 frames with frame numbers 1 to 10
|
||||
// f0 1,2,3
|
||||
// f1 4,5,6
|
||||
// f2 7,8,9
|
||||
// f3 10
|
||||
REQUIRE_THROWS(f.frame_number(10));
|
||||
}
|
||||
|
||||
TEST_CASE("Read a frame numbers where the subfile is missing throws") {
|
||||
auto fpath = test_data_path() / "jungfrau" / "jungfrau_missing_subfile_master_0.json";
|
||||
REQUIRE(std::filesystem::exists(fpath));
|
||||
|
||||
File f(fpath, "r");
|
||||
|
||||
// we know this file has 10 frames with frame numbers 1 to 10
|
||||
// f0 1,2,3
|
||||
// f1 4,5,6 - but files f1-f3 are missing
|
||||
// f2 7,8,9 - gone
|
||||
// f3 10 - gone
|
||||
REQUIRE(f.frame_number(0) == 1);
|
||||
REQUIRE(f.frame_number(1) == 2);
|
||||
REQUIRE(f.frame_number(2) == 3);
|
||||
REQUIRE_THROWS(f.frame_number(4));
|
||||
REQUIRE_THROWS(f.frame_number(7));
|
||||
REQUIRE_THROWS(f.frame_number(937));
|
||||
REQUIRE_THROWS(f.frame_number(10));
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Read data from a jungfrau 500k single port raw file") {
|
||||
auto fpath = test_data_path() / "jungfrau" / "jungfrau_single_master_0.json";
|
||||
REQUIRE(std::filesystem::exists(fpath));
|
||||
|
||||
File f(fpath, "r");
|
||||
|
||||
// we know this file has 10 frames with pixel 0,0 being: 2123, 2051, 2109, 2117, 2089, 2095, 2072, 2126, 2097, 2102
|
||||
std::vector<uint16_t> pixel_0_0 = {2123, 2051, 2109, 2117, 2089, 2095, 2072, 2126, 2097, 2102};
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
auto frame = f.read_frame();
|
||||
CHECK(frame.rows() == 512);
|
||||
CHECK(frame.cols() == 1024);
|
||||
CHECK(frame.view<uint16_t>()(0, 0) == pixel_0_0[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Read frame numbers from a raw file") {
|
||||
auto fpath = test_data_path() / "eiger" / "eiger_500k_16bit_master_0.json";
|
||||
REQUIRE(std::filesystem::exists(fpath));
|
||||
|
||||
// we know this file has 3 frames with frame numbers 14, 15, 16
|
||||
std::vector<size_t> frame_numbers = {14, 15, 16};
|
||||
|
||||
File f(fpath, "r");
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
CHECK(f.frame_number(i) == frame_numbers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Compare reading from a numpy file with a raw file") {
|
||||
auto fpath_raw = test_data_path() / "jungfrau" / "jungfrau_single_master_0.json";
|
||||
REQUIRE(std::filesystem::exists(fpath_raw));
|
||||
|
||||
auto fpath_npy = test_data_path() / "jungfrau" / "jungfrau_single_0.npy";
|
||||
REQUIRE(std::filesystem::exists(fpath_npy));
|
||||
|
||||
File raw(fpath_raw, "r");
|
||||
File npy(fpath_npy, "r");
|
||||
|
||||
CHECK(raw.total_frames() == 10);
|
||||
CHECK(npy.total_frames() == 10);
|
||||
|
||||
for (size_t i = 0; i < 10; ++i) {
|
||||
auto raw_frame = raw.read_frame();
|
||||
auto npy_frame = npy.read_frame();
|
||||
CHECK((raw_frame.view<uint16_t>() == npy_frame.view<uint16_t>()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Read multipart files") {
|
||||
auto fpath = test_data_path() / "jungfrau" / "jungfrau_double_master_0.json";
|
||||
REQUIRE(std::filesystem::exists(fpath));
|
||||
|
||||
File f(fpath, "r");
|
||||
|
||||
// we know this file has 10 frames check read_multiport.py for the values
|
||||
std::vector<uint16_t> pixel_0_0 = {2099, 2121, 2108, 2084, 2084, 2118, 2066, 2108, 2112, 2116};
|
||||
std::vector<uint16_t> pixel_0_1 = {2842, 2796, 2865, 2798, 2805, 2817, 2852, 2789, 2792, 2833};
|
||||
std::vector<uint16_t> pixel_255_1023 = {2149, 2037, 2115, 2102, 2118, 2090, 2036, 2071, 2073, 2142};
|
||||
std::vector<uint16_t> pixel_511_1023 = {3231, 3169, 3167, 3162, 3168, 3160, 3171, 3171, 3169, 3171};
|
||||
std::vector<uint16_t> pixel_1_0 = {2748, 2614, 2665, 2629, 2618, 2630, 2631, 2634, 2577, 2598};
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
auto frame = f.read_frame();
|
||||
CHECK(frame.rows() == 512);
|
||||
CHECK(frame.cols() == 1024);
|
||||
CHECK(frame.view<uint16_t>()(0, 0) == pixel_0_0[i]);
|
||||
CHECK(frame.view<uint16_t>()(0, 1) == pixel_0_1[i]);
|
||||
CHECK(frame.view<uint16_t>()(1, 0) == pixel_1_0[i]);
|
||||
CHECK(frame.view<uint16_t>()(255, 1023) == pixel_255_1023[i]);
|
||||
CHECK(frame.view<uint16_t>()(511, 1023) == pixel_511_1023[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Read file with unordered frames") {
|
||||
//TODO! Better explanation and error message
|
||||
auto fpath = test_data_path() / "mythen" / "scan242_master_3.raw";
|
||||
REQUIRE(std::filesystem::exists(fpath));
|
||||
File f(fpath);
|
||||
REQUIRE_THROWS((f.read_frame()));
|
||||
}
|
328
src/RawMasterFile.cpp
Normal file
328
src/RawMasterFile.cpp
Normal file
@ -0,0 +1,328 @@
|
||||
#include "aare/RawMasterFile.hpp"
|
||||
#include <sstream>
|
||||
namespace aare {
|
||||
|
||||
RawFileNameComponents::RawFileNameComponents(
|
||||
const std::filesystem::path &fname) {
|
||||
m_base_path = fname.parent_path();
|
||||
m_base_name = fname.stem();
|
||||
m_ext = fname.extension();
|
||||
|
||||
if (m_ext != ".json" && m_ext != ".raw") {
|
||||
throw std::runtime_error(LOCATION +
|
||||
"Unsupported file type. (only .json or .raw)");
|
||||
}
|
||||
|
||||
// parse file index
|
||||
try {
|
||||
auto pos = m_base_name.rfind('_');
|
||||
m_file_index = std::stoi(m_base_name.substr(pos + 1));
|
||||
} catch (const std::invalid_argument &e) {
|
||||
throw std::runtime_error(LOCATION + "Could not parse file index");
|
||||
}
|
||||
|
||||
// remove master from base name
|
||||
auto pos = m_base_name.find("_master_");
|
||||
if (pos != std::string::npos) {
|
||||
m_base_name.erase(pos);
|
||||
} else {
|
||||
throw std::runtime_error(LOCATION +
|
||||
"Could not find _master_ in file name");
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path RawFileNameComponents::master_fname() const {
|
||||
return m_base_path /
|
||||
fmt::format("{}_master_{}{}", m_base_name, m_file_index, m_ext);
|
||||
}
|
||||
|
||||
std::filesystem::path RawFileNameComponents::data_fname(size_t mod_id,
|
||||
size_t file_id) const {
|
||||
return m_base_path / fmt::format("{}_d{}_f{}_{}.raw", m_base_name, mod_id,
|
||||
file_id, m_file_index);
|
||||
}
|
||||
|
||||
const std::filesystem::path &RawFileNameComponents::base_path() const {
|
||||
return m_base_path;
|
||||
}
|
||||
const std::string &RawFileNameComponents::base_name() const {
|
||||
return m_base_name;
|
||||
}
|
||||
const std::string &RawFileNameComponents::ext() const { return m_ext; }
|
||||
int RawFileNameComponents::file_index() const { return m_file_index; }
|
||||
|
||||
// "[enabled\ndac dac 4\nstart 500\nstop 2200\nstep 5\nsettleTime 100us\n]"
|
||||
ScanParameters::ScanParameters(const std::string& par){
|
||||
std::istringstream iss(par.substr(1, par.size()-2));
|
||||
std::string line;
|
||||
while(std::getline(iss, line)){
|
||||
if(line == "enabled"){
|
||||
m_enabled = true;
|
||||
}else if(line.find("dac") != std::string::npos){
|
||||
m_dac = line.substr(4);
|
||||
}else if(line.find("start") != std::string::npos){
|
||||
m_start = std::stoi(line.substr(6));
|
||||
}else if(line.find("stop") != std::string::npos){
|
||||
m_stop = std::stoi(line.substr(5));
|
||||
}else if(line.find("step") != std::string::npos){
|
||||
m_step = std::stoi(line.substr(5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ScanParameters::start() const { return m_start; }
|
||||
int ScanParameters::stop() const { return m_stop; }
|
||||
int ScanParameters::step() const { return m_step; }
|
||||
const std::string &ScanParameters::dac() const { return m_dac; }
|
||||
bool ScanParameters::enabled() const { return m_enabled; }
|
||||
|
||||
|
||||
RawMasterFile::RawMasterFile(const std::filesystem::path &fpath)
|
||||
: m_fnc(fpath) {
|
||||
if (!std::filesystem::exists(fpath)) {
|
||||
throw std::runtime_error(LOCATION + " File does not exist");
|
||||
}
|
||||
if (m_fnc.ext() == ".json") {
|
||||
parse_json(fpath);
|
||||
} else if (m_fnc.ext() == ".raw") {
|
||||
parse_raw(fpath);
|
||||
} else {
|
||||
throw std::runtime_error(LOCATION + "Unsupported file type");
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path RawMasterFile::data_fname(size_t mod_id,
|
||||
size_t file_id) const {
|
||||
return m_fnc.data_fname(mod_id, file_id);
|
||||
}
|
||||
|
||||
const std::string &RawMasterFile::version() const { return m_version; }
|
||||
const DetectorType &RawMasterFile::detector_type() const { return m_type; }
|
||||
const TimingMode &RawMasterFile::timing_mode() const { return m_timing_mode; }
|
||||
size_t RawMasterFile::image_size_in_bytes() const {
|
||||
return m_image_size_in_bytes;
|
||||
}
|
||||
size_t RawMasterFile::frames_in_file() const { return m_frames_in_file; }
|
||||
size_t RawMasterFile::pixels_y() const { return m_pixels_y; }
|
||||
size_t RawMasterFile::pixels_x() const { return m_pixels_x; }
|
||||
size_t RawMasterFile::max_frames_per_file() const {
|
||||
return m_max_frames_per_file;
|
||||
}
|
||||
size_t RawMasterFile::bitdepth() const { return m_bitdepth; }
|
||||
size_t RawMasterFile::frame_padding() const { return m_frame_padding; }
|
||||
const FrameDiscardPolicy &RawMasterFile::frame_discard_policy() const {
|
||||
return m_frame_discard_policy;
|
||||
}
|
||||
|
||||
size_t RawMasterFile::total_frames_expected() const {
|
||||
return m_total_frames_expected;
|
||||
}
|
||||
|
||||
std::optional<size_t> RawMasterFile::number_of_rows() const {
|
||||
return m_number_of_rows;
|
||||
}
|
||||
|
||||
xy RawMasterFile::geometry() const { return m_geometry; }
|
||||
|
||||
std::optional<uint8_t> RawMasterFile::quad() const { return m_quad; }
|
||||
|
||||
// optional values, these may or may not be present in the master file
|
||||
// and are therefore modeled as std::optional
|
||||
std::optional<size_t> RawMasterFile::analog_samples() const {
|
||||
return m_analog_samples;
|
||||
}
|
||||
std::optional<size_t> RawMasterFile::digital_samples() const {
|
||||
return m_digital_samples;
|
||||
}
|
||||
|
||||
std::optional<size_t> RawMasterFile::transceiver_samples() const {
|
||||
return m_transceiver_samples;
|
||||
}
|
||||
|
||||
ScanParameters RawMasterFile::scan_parameters() const {
|
||||
return m_scan_parameters;
|
||||
}
|
||||
|
||||
void RawMasterFile::parse_json(const std::filesystem::path &fpath) {
|
||||
std::ifstream ifs(fpath);
|
||||
json j;
|
||||
ifs >> j;
|
||||
double v = j["Version"];
|
||||
m_version = fmt::format("{:.1f}", v);
|
||||
|
||||
m_type = StringTo<DetectorType>(j["Detector Type"].get<std::string>());
|
||||
m_timing_mode = StringTo<TimingMode>(j["Timing Mode"].get<std::string>());
|
||||
|
||||
m_geometry = {j["Geometry"]["y"], j["Geometry"]["x"]};
|
||||
|
||||
m_image_size_in_bytes = j["Image Size in bytes"];
|
||||
m_frames_in_file = j["Frames in File"];
|
||||
m_pixels_y = j["Pixels"]["y"];
|
||||
m_pixels_x = j["Pixels"]["x"];
|
||||
|
||||
m_max_frames_per_file = j["Max Frames Per File"];
|
||||
|
||||
// Not all detectors write the bitdepth but in case
|
||||
// its not there it is 16
|
||||
try {
|
||||
m_bitdepth = j.at("Dynamic Range");
|
||||
} catch (const json::out_of_range &e) {
|
||||
m_bitdepth = 16;
|
||||
}
|
||||
m_total_frames_expected = j["Total Frames"];
|
||||
|
||||
m_frame_padding = j["Frame Padding"];
|
||||
m_frame_discard_policy = StringTo<FrameDiscardPolicy>(
|
||||
j["Frame Discard Policy"].get<std::string>());
|
||||
|
||||
try {
|
||||
m_number_of_rows = j.at("Number of rows");
|
||||
} catch (const json::out_of_range &e) {
|
||||
// keep the optional empty
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Special treatment of analog flag because of Moench03
|
||||
try{
|
||||
m_analog_flag = j.at("Analog Flag");
|
||||
}catch (const json::out_of_range &e) {
|
||||
// if it doesn't work still set it to one
|
||||
// to try to decode analog samples (Old Moench03)
|
||||
m_analog_flag = 1;
|
||||
}
|
||||
try {
|
||||
if (m_analog_flag) {
|
||||
m_analog_samples = j.at("Analog Samples");
|
||||
}
|
||||
|
||||
} catch (const json::out_of_range &e) {
|
||||
// keep the optional empty
|
||||
// and set analog flag to 0
|
||||
m_analog_flag = 0;
|
||||
}
|
||||
//-----------------------------------------------------------------
|
||||
|
||||
try {
|
||||
m_quad = j.at("Quad");
|
||||
} catch (const json::out_of_range &e) {
|
||||
// keep the optional empty
|
||||
}
|
||||
// try{
|
||||
// std::string adc_mask = j.at("ADC Mask");
|
||||
// m_adc_mask = std::stoul(adc_mask, nullptr, 16);
|
||||
// }catch (const json::out_of_range &e) {
|
||||
// m_adc_mask = 0;
|
||||
// }
|
||||
|
||||
try {
|
||||
int digital_flag = j.at("Digital Flag");
|
||||
if (digital_flag) {
|
||||
m_digital_samples = j.at("Digital Samples");
|
||||
}
|
||||
} catch (const json::out_of_range &e) {
|
||||
// keep the optional empty
|
||||
}
|
||||
|
||||
try{
|
||||
m_transceiver_flag = j.at("Transceiver Flag");
|
||||
if(m_transceiver_flag){
|
||||
m_transceiver_samples = j.at("Transceiver Samples");
|
||||
}
|
||||
}catch (const json::out_of_range &e) {
|
||||
// keep the optional empty
|
||||
}
|
||||
|
||||
try{
|
||||
std::string scan_parameters = j.at("Scan Parameters");
|
||||
m_scan_parameters = ScanParameters(scan_parameters);
|
||||
}catch (const json::out_of_range &e) {
|
||||
// not a scan
|
||||
}
|
||||
|
||||
// Update detector type for Moench
|
||||
// TODO! How does this work with old .raw master files?
|
||||
#ifdef AARE_VERBOSE
|
||||
fmt::print("Detecting Moench03: m_pixels_y: {}, m_analog_samples: {}\n",
|
||||
m_pixels_y, m_analog_samples.value_or(0));
|
||||
#endif
|
||||
if (m_type == DetectorType::Moench && !m_analog_samples &&
|
||||
m_pixels_y == 400) {
|
||||
m_type = DetectorType::Moench03;
|
||||
} else if (m_type == DetectorType::Moench && m_pixels_y == 400 &&
|
||||
m_analog_samples == 5000) {
|
||||
m_type = DetectorType::Moench03_old;
|
||||
}
|
||||
}
|
||||
void RawMasterFile::parse_raw(const std::filesystem::path &fpath) {
|
||||
|
||||
std::ifstream ifs(fpath);
|
||||
for (std::string line; std::getline(ifs, line);) {
|
||||
if (line == "#Frame Header")
|
||||
break;
|
||||
auto pos = line.find(':');
|
||||
auto key_pos = pos;
|
||||
while (key_pos != std::string::npos && std::isspace(line[--key_pos]))
|
||||
;
|
||||
if (key_pos != std::string::npos) {
|
||||
auto key = line.substr(0, key_pos + 1);
|
||||
auto value = line.substr(pos + 2);
|
||||
// do the actual parsing
|
||||
if (key == "Version") {
|
||||
m_version = value;
|
||||
} else if (key == "TimeStamp") {
|
||||
|
||||
} else if (key == "Detector Type") {
|
||||
m_type = StringTo<DetectorType>(value);
|
||||
} else if (key == "Timing Mode") {
|
||||
m_timing_mode = StringTo<TimingMode>(value);
|
||||
} else if (key == "Image Size") {
|
||||
m_image_size_in_bytes = std::stoi(value);
|
||||
} else if (key == "Frame Padding") {
|
||||
m_frame_padding = std::stoi(value);
|
||||
// } else if (key == "Frame Discard Policy"){
|
||||
// m_frame_discard_policy =
|
||||
// StringTo<FrameDiscardPolicy>(value);
|
||||
// } else if (key == "Number of rows"){
|
||||
// m_number_of_rows = std::stoi(value);
|
||||
} else if (key == "Analog Flag") {
|
||||
m_analog_flag = std::stoi(value);
|
||||
} else if (key == "Digital Flag") {
|
||||
m_digital_flag = std::stoi(value);
|
||||
|
||||
} else if (key == "Analog Samples") {
|
||||
if (m_analog_flag == 1) {
|
||||
m_analog_samples = std::stoi(value);
|
||||
}
|
||||
} else if (key == "Digital Samples") {
|
||||
if (m_digital_flag == 1) {
|
||||
m_digital_samples = std::stoi(value);
|
||||
}
|
||||
} else if (key == "Frames in File") {
|
||||
m_frames_in_file = std::stoi(value);
|
||||
// } else if (key == "ADC Mask") {
|
||||
// m_adc_mask = std::stoi(value, nullptr, 16);
|
||||
} else if (key == "Pixels") {
|
||||
// Total number of pixels cannot be found yet looking at
|
||||
// submodule
|
||||
pos = value.find(',');
|
||||
m_pixels_x = std::stoi(value.substr(1, pos));
|
||||
m_pixels_y = std::stoi(value.substr(pos + 1));
|
||||
} else if (key == "Total Frames") {
|
||||
m_total_frames_expected = std::stoi(value);
|
||||
} else if (key == "Dynamic Range") {
|
||||
m_bitdepth = std::stoi(value);
|
||||
} else if (key == "Quad") {
|
||||
m_quad = std::stoi(value);
|
||||
} else if (key == "Max Frames Per File") {
|
||||
m_max_frames_per_file = std::stoi(value);
|
||||
} else if (key == "Geometry") {
|
||||
pos = value.find(',');
|
||||
m_geometry = {
|
||||
static_cast<uint32_t>(std::stoi(value.substr(1, pos))),
|
||||
static_cast<uint32_t>(std::stoi(value.substr(pos + 1)))};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace aare
|
278
src/RawMasterFile.test.cpp
Normal file
278
src/RawMasterFile.test.cpp
Normal file
@ -0,0 +1,278 @@
|
||||
#include "aare/RawMasterFile.hpp"
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include "test_config.hpp"
|
||||
|
||||
using namespace aare;
|
||||
|
||||
|
||||
TEST_CASE("Parse a master file fname"){
|
||||
RawFileNameComponents m("test_master_1.json");
|
||||
REQUIRE(m.base_name() == "test");
|
||||
REQUIRE(m.ext() == ".json");
|
||||
REQUIRE(m.file_index() == 1);
|
||||
REQUIRE(m.base_path() == "");
|
||||
}
|
||||
|
||||
TEST_CASE("Extraction of base path works"){
|
||||
RawFileNameComponents m("some/path/test_master_73.json");
|
||||
REQUIRE(m.base_name() == "test");
|
||||
REQUIRE(m.ext() == ".json");
|
||||
REQUIRE(m.file_index() == 73);
|
||||
REQUIRE(m.base_path() == "some/path");
|
||||
}
|
||||
|
||||
TEST_CASE("Construction of master file name and data files"){
|
||||
RawFileNameComponents m("test_master_1.json");
|
||||
REQUIRE(m.master_fname() == "test_master_1.json");
|
||||
REQUIRE(m.data_fname(0, 0) == "test_d0_f0_1.raw");
|
||||
REQUIRE(m.data_fname(1, 0) == "test_d1_f0_1.raw");
|
||||
REQUIRE(m.data_fname(0, 1) == "test_d0_f1_1.raw");
|
||||
REQUIRE(m.data_fname(1, 1) == "test_d1_f1_1.raw");
|
||||
}
|
||||
|
||||
TEST_CASE("Master file name does not fit pattern"){
|
||||
REQUIRE_THROWS(RawFileNameComponents("somefile.json"));
|
||||
REQUIRE_THROWS(RawFileNameComponents("another_test_d0_f0_1.raw"));
|
||||
REQUIRE_THROWS(RawFileNameComponents("test_master_1.txt"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
TEST_CASE("Parse scan parameters"){
|
||||
ScanParameters s("[enabled\ndac dac 4\nstart 500\nstop 2200\nstep 5\nsettleTime 100us\n]");
|
||||
REQUIRE(s.enabled());
|
||||
REQUIRE(s.dac() == "dac 4");
|
||||
REQUIRE(s.start() == 500);
|
||||
REQUIRE(s.stop() == 2200);
|
||||
REQUIRE(s.step() == 5);
|
||||
}
|
||||
|
||||
TEST_CASE("A disabled scan"){
|
||||
ScanParameters s("[disabled]");
|
||||
REQUIRE_FALSE(s.enabled());
|
||||
REQUIRE(s.dac() == "");
|
||||
REQUIRE(s.start() == 0);
|
||||
REQUIRE(s.stop() == 0);
|
||||
REQUIRE(s.step() == 0);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Parse a master file in .json format"){
|
||||
auto fpath = test_data_path() / "jungfrau" / "jungfrau_single_master_0.json";
|
||||
REQUIRE(std::filesystem::exists(fpath));
|
||||
RawMasterFile f(fpath);
|
||||
|
||||
// "Version": 7.2,
|
||||
REQUIRE(f.version() == "7.2");
|
||||
// "Timestamp": "Tue Feb 20 08:28:24 2024",
|
||||
// "Detector Type": "Jungfrau",
|
||||
REQUIRE(f.detector_type() == DetectorType::Jungfrau);
|
||||
// "Timing Mode": "auto",
|
||||
REQUIRE(f.timing_mode() == TimingMode::Auto);
|
||||
|
||||
// "Geometry": {
|
||||
// "x": 1,
|
||||
// "y": 1
|
||||
// },
|
||||
REQUIRE(f.geometry().col == 1);
|
||||
REQUIRE(f.geometry().row == 1);
|
||||
|
||||
// "Image Size in bytes": 1048576,
|
||||
REQUIRE(f.image_size_in_bytes() == 1048576);
|
||||
// "Pixels": {
|
||||
// "x": 1024,
|
||||
REQUIRE(f.pixels_x() == 1024);
|
||||
// "y": 512
|
||||
REQUIRE(f.pixels_y() == 512);
|
||||
// },
|
||||
|
||||
// "Max Frames Per File": 3,
|
||||
REQUIRE(f.max_frames_per_file() == 3);
|
||||
|
||||
//Jungfrau doesn't write but it is 16
|
||||
REQUIRE(f.bitdepth() == 16);
|
||||
|
||||
|
||||
// "Frame Discard Policy": "nodiscard",
|
||||
|
||||
// "Frame Padding": 1,
|
||||
REQUIRE(f.frame_padding() == 1);
|
||||
// "Scan Parameters": "[disabled]",
|
||||
// "Total Frames": 10,
|
||||
REQUIRE(f.total_frames_expected() == 10);
|
||||
// "Receiver Roi": {
|
||||
// "xmin": 4294967295,
|
||||
// "xmax": 4294967295,
|
||||
// "ymin": 4294967295,
|
||||
// "ymax": 4294967295
|
||||
// },
|
||||
// "Exptime": "10us",
|
||||
// "Period": "1ms",
|
||||
// "Number of UDP Interfaces": 1,
|
||||
// "Number of rows": 512,
|
||||
REQUIRE(f.number_of_rows() == 512);
|
||||
// "Frames in File": 10,
|
||||
REQUIRE(f.frames_in_file() == 10);
|
||||
|
||||
//TODO! Should we parse this?
|
||||
// "Frame Header Format": {
|
||||
// "Frame Number": "8 bytes",
|
||||
// "SubFrame Number/ExpLength": "4 bytes",
|
||||
// "Packet Number": "4 bytes",
|
||||
// "Bunch ID": "8 bytes",
|
||||
// "Timestamp": "8 bytes",
|
||||
// "Module Id": "2 bytes",
|
||||
// "Row": "2 bytes",
|
||||
// "Column": "2 bytes",
|
||||
// "Reserved": "2 bytes",
|
||||
// "Debug": "4 bytes",
|
||||
// "Round Robin Number": "2 bytes",
|
||||
// "Detector Type": "1 byte",
|
||||
// "Header Version": "1 byte",
|
||||
// "Packets Caught Mask": "64 bytes"
|
||||
// }
|
||||
// }
|
||||
|
||||
REQUIRE_FALSE(f.analog_samples());
|
||||
REQUIRE_FALSE(f.digital_samples());
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("Parse a master file in .raw format"){
|
||||
|
||||
auto fpath = test_data_path() / "moench/moench04_noise_200V_sto_both_100us_no_light_thresh_900_master_0.raw";
|
||||
REQUIRE(std::filesystem::exists(fpath));
|
||||
RawMasterFile f(fpath);
|
||||
|
||||
// Version : 6.4
|
||||
REQUIRE(f.version() == "6.4");
|
||||
// TimeStamp : Wed Aug 31 09:08:49 2022
|
||||
|
||||
// Detector Type : ChipTestBoard
|
||||
REQUIRE(f.detector_type() == DetectorType::ChipTestBoard);
|
||||
// Timing Mode : auto
|
||||
REQUIRE(f.timing_mode() == TimingMode::Auto);
|
||||
// Geometry : [1, 1]
|
||||
REQUIRE(f.geometry().col == 1);
|
||||
REQUIRE(f.geometry().row == 1);
|
||||
// Image Size : 360000 bytes
|
||||
REQUIRE(f.image_size_in_bytes() == 360000);
|
||||
// Pixels : [96, 1]
|
||||
REQUIRE(f.pixels_x() == 96);
|
||||
REQUIRE(f.pixels_y() == 1);
|
||||
// Max Frames Per File : 20000
|
||||
REQUIRE(f.max_frames_per_file() == 20000);
|
||||
// Frame Discard Policy : nodiscard
|
||||
// Frame Padding : 1
|
||||
REQUIRE(f.frame_padding() == 1);
|
||||
// Scan Parameters : [disabled]
|
||||
// Total Frames : 100
|
||||
REQUIRE(f.total_frames_expected() == 100);
|
||||
// Exptime : 100us
|
||||
// Period : 4ms
|
||||
// Ten Giga : 1
|
||||
// ADC Mask : 0xffffffff
|
||||
// Analog Flag : 1
|
||||
// Analog Samples : 5000
|
||||
REQUIRE(f.analog_samples() == 5000);
|
||||
// Digital Flag : 1
|
||||
// Digital Samples : 5000
|
||||
REQUIRE(f.digital_samples() == 5000);
|
||||
// Dbit Offset : 0
|
||||
// Dbit Bitset : 0
|
||||
// Frames in File : 100
|
||||
REQUIRE(f.frames_in_file() == 100);
|
||||
|
||||
// #Frame Header
|
||||
// Frame Number : 8 bytes
|
||||
// SubFrame Number/ExpLength : 4 bytes
|
||||
// Packet Number : 4 bytes
|
||||
// Bunch ID : 8 bytes
|
||||
// Timestamp : 8 bytes
|
||||
// Module Id : 2 bytes
|
||||
// Row : 2 bytes
|
||||
// Column : 2 bytes
|
||||
// Reserved : 2 bytes
|
||||
// Debug : 4 bytes
|
||||
// Round Robin Number : 2 bytes
|
||||
// Detector Type : 1 byte
|
||||
// Header Version : 1 byte
|
||||
// Packets Caught Mask : 64 bytes
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Read eiger master file"){
|
||||
auto fpath = test_data_path() / "eiger" / "eiger_500k_32bit_master_0.json";
|
||||
REQUIRE(std::filesystem::exists(fpath));
|
||||
RawMasterFile f(fpath);
|
||||
|
||||
// {
|
||||
// "Version": 7.2,
|
||||
REQUIRE(f.version() == "7.2");
|
||||
// "Timestamp": "Tue Mar 26 17:24:34 2024",
|
||||
// "Detector Type": "Eiger",
|
||||
REQUIRE(f.detector_type() == DetectorType::Eiger);
|
||||
// "Timing Mode": "auto",
|
||||
REQUIRE(f.timing_mode() == TimingMode::Auto);
|
||||
// "Geometry": {
|
||||
// "x": 2,
|
||||
// "y": 2
|
||||
// },
|
||||
// "Image Size in bytes": 524288,
|
||||
REQUIRE(f.image_size_in_bytes() == 524288);
|
||||
// "Pixels": {
|
||||
// "x": 512,
|
||||
REQUIRE(f.pixels_x() == 512);
|
||||
// "y": 256
|
||||
REQUIRE(f.pixels_y() == 256);
|
||||
// },
|
||||
// "Max Frames Per File": 10000,
|
||||
REQUIRE(f.max_frames_per_file() == 10000);
|
||||
// "Frame Discard Policy": "nodiscard",
|
||||
REQUIRE(f.frame_discard_policy() == FrameDiscardPolicy::NoDiscard);
|
||||
// "Frame Padding": 1,
|
||||
REQUIRE(f.frame_padding() == 1);
|
||||
|
||||
// "Scan Parameters": "[disabled]",
|
||||
// "Total Frames": 3,
|
||||
// "Receiver Roi": {
|
||||
// "xmin": 4294967295,
|
||||
// "xmax": 4294967295,
|
||||
// "ymin": 4294967295,
|
||||
// "ymax": 4294967295
|
||||
// },
|
||||
// "Dynamic Range": 32,
|
||||
// "Ten Giga": 0,
|
||||
// "Exptime": "5s",
|
||||
// "Period": "1s",
|
||||
// "Threshold Energy": -1,
|
||||
// "Sub Exptime": "2.62144ms",
|
||||
// "Sub Period": "2.62144ms",
|
||||
// "Quad": 0,
|
||||
// "Number of rows": 256,
|
||||
// "Rate Corrections": "[0, 0]",
|
||||
// "Frames in File": 3,
|
||||
// "Frame Header Format": {
|
||||
// "Frame Number": "8 bytes",
|
||||
// "SubFrame Number/ExpLength": "4 bytes",
|
||||
// "Packet Number": "4 bytes",
|
||||
// "Bunch ID": "8 bytes",
|
||||
// "Timestamp": "8 bytes",
|
||||
// "Module Id": "2 bytes",
|
||||
// "Row": "2 bytes",
|
||||
// "Column": "2 bytes",
|
||||
// "Reserved": "2 bytes",
|
||||
// "Debug": "4 bytes",
|
||||
// "Round Robin Number": "2 bytes",
|
||||
// "Detector Type": "1 byte",
|
||||
// "Header Version": "1 byte",
|
||||
// "Packets Caught Mask": "64 bytes"
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
}
|
91
src/SubFile.cpp
Normal file
91
src/SubFile.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
#include "aare/SubFile.hpp"
|
||||
#include "aare/PixelMap.hpp"
|
||||
#include <cstring> // memcpy
|
||||
#include <fmt/core.h>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
namespace aare {
|
||||
|
||||
SubFile::SubFile(const std::filesystem::path &fname, DetectorType detector, size_t rows, size_t cols, size_t bitdepth,
|
||||
const std::string &mode)
|
||||
: m_bitdepth(bitdepth), m_fname(fname), m_rows(rows), m_cols(cols), m_mode(mode), m_detector_type(detector) {
|
||||
|
||||
|
||||
if (m_detector_type == DetectorType::Moench03_old) {
|
||||
pixel_map = GenerateMoench03PixelMap();
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(fname)) {
|
||||
n_frames = std::filesystem::file_size(fname) / (sizeof(DetectorHeader) + rows * cols * bitdepth / 8);
|
||||
} else {
|
||||
n_frames = 0;
|
||||
}
|
||||
|
||||
if (mode == "r") {
|
||||
fp = fopen(m_fname.string().c_str(), "rb");
|
||||
} else {
|
||||
throw std::runtime_error(LOCATION + "Unsupported mode. Can only read RawFiles.");
|
||||
}
|
||||
if (fp == nullptr) {
|
||||
throw std::runtime_error(LOCATION + fmt::format("Could not open file {}", m_fname.string()));
|
||||
}
|
||||
#ifdef AARE_VERBOSE
|
||||
fmt::print("Opened file: {} with {} frames\n", m_fname.string(), n_frames);
|
||||
fmt::print("m_rows: {}, m_cols: {}, m_bitdepth: {}\n", m_rows, m_cols, m_bitdepth);
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t SubFile::get_part(std::byte *buffer, size_t frame_index) {
|
||||
if (frame_index >= n_frames) {
|
||||
throw std::runtime_error("Frame number out of range");
|
||||
}
|
||||
fseek(fp, sizeof(DetectorHeader) + (sizeof(DetectorHeader) + bytes_per_part()) * frame_index, // NOLINT
|
||||
SEEK_SET);
|
||||
|
||||
if (pixel_map){
|
||||
// read into a temporary buffer and then copy the data to the buffer
|
||||
// in the correct order
|
||||
auto part_buffer = new std::byte[bytes_per_part()];
|
||||
auto wc = fread(part_buffer, bytes_per_part(), 1, fp);
|
||||
auto *data = reinterpret_cast<uint16_t *>(buffer);
|
||||
auto *part_data = reinterpret_cast<uint16_t *>(part_buffer);
|
||||
for (size_t i = 0; i < pixels_per_part(); i++) {
|
||||
data[i] = part_data[(*pixel_map)(i)];
|
||||
}
|
||||
delete[] part_buffer;
|
||||
return wc;
|
||||
}else{
|
||||
// read directly into the buffer
|
||||
return fread(buffer, this->bytes_per_part(), 1, this->fp);
|
||||
}
|
||||
|
||||
}
|
||||
size_t SubFile::write_part(std::byte *buffer, DetectorHeader header, size_t frame_index) {
|
||||
if (frame_index > n_frames) {
|
||||
throw std::runtime_error("Frame number out of range");
|
||||
}
|
||||
fseek(fp, static_cast<int64_t>((sizeof(DetectorHeader) + bytes_per_part()) * frame_index), SEEK_SET);
|
||||
auto wc = fwrite(reinterpret_cast<char *>(&header), sizeof(header), 1, fp);
|
||||
wc += fwrite(buffer, bytes_per_part(), 1, fp);
|
||||
|
||||
return wc;
|
||||
}
|
||||
|
||||
size_t SubFile::frame_number(size_t frame_index) {
|
||||
DetectorHeader h{};
|
||||
fseek(fp, (sizeof(DetectorHeader) + bytes_per_part()) * frame_index, SEEK_SET); // NOLINT
|
||||
size_t const rc = fread(reinterpret_cast<char *>(&h), sizeof(h), 1, fp);
|
||||
if (rc != 1)
|
||||
throw std::runtime_error(LOCATION + "Could not read header from file");
|
||||
|
||||
return h.frameNumber;
|
||||
}
|
||||
|
||||
SubFile::~SubFile() {
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace aare
|
83
src/defs.cpp
Normal file
83
src/defs.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
#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::Moench03:
|
||||
return "Moench03";
|
||||
case DetectorType::Moench03_old:
|
||||
return "Moench03_old";
|
||||
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 == "Moench03")
|
||||
return DetectorType::Moench03;
|
||||
if (arg == "Moench03_old")
|
||||
return DetectorType::Moench03_old;
|
||||
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 <> FrameDiscardPolicy StringTo(const std::string &arg) {
|
||||
if (arg == "nodiscard")
|
||||
return FrameDiscardPolicy::NoDiscard;
|
||||
if (arg == "discard")
|
||||
return FrameDiscardPolicy::Discard;
|
||||
if (arg == "discardpartial")
|
||||
return FrameDiscardPolicy::DiscardPartial;
|
||||
throw std::runtime_error("Could not decode frame discard policy from: \"" + arg + "\"");
|
||||
}
|
||||
|
||||
// template <> TimingMode StringTo<TimingMode>(std::string mode);
|
||||
|
||||
} // namespace aare
|
42
src/defs.test.cpp
Normal file
42
src/defs.test.cpp
Normal 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
|
||||
// }
|
Reference in New Issue
Block a user