Taking v1 as the first release (#92)

- file reading
- decoding master file
This commit is contained in:
Erik Fröjdh
2024-11-07 10:14:20 +01:00
committed by GitHub
parent ae71e23dd2
commit d8d1f0c517
87 changed files with 9860 additions and 0 deletions

View 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
View 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
View File

@ -0,0 +1,191 @@
#include "aare/Dtype.hpp"
#include "aare/defs.hpp"
#include <fmt/core.h>
namespace aare {
/**
* @brief Construct a DType object from a type_info object
* @param t type_info object
* @throw runtime_error if the type is not supported
* @note supported types are: int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t, float, double
* @note the type_info object is obtained using typeid (e.g. typeid(int))
*/
Dtype::Dtype(const std::type_info &t) {
if (t == typeid(int8_t))
m_type = TypeIndex::INT8;
else if (t == typeid(uint8_t))
m_type = TypeIndex::UINT8;
else if (t == typeid(int16_t))
m_type = TypeIndex::INT16;
else if (t == typeid(uint16_t))
m_type = TypeIndex::UINT16;
else if (t == typeid(int32_t))
m_type = TypeIndex::INT32;
else if (t == typeid(uint32_t))
m_type = TypeIndex::UINT32;
else if (t == typeid(int64_t)) // NOLINT
m_type = TypeIndex::INT64;
else if (t == typeid(uint64_t))
m_type = TypeIndex::UINT64;
else if (t == typeid(float))
m_type = TypeIndex::FLOAT;
else if (t == typeid(double))
m_type = TypeIndex::DOUBLE;
else
throw std::runtime_error("Could not construct data type. Type not supported.");
}
/**
* @brief Get the bitdepth of the data type
* @return bitdepth
*/
uint8_t Dtype::bitdepth() const {
switch (m_type) {
case TypeIndex::INT8:
case TypeIndex::UINT8:
return 8;
case TypeIndex::INT16:
case TypeIndex::UINT16:
return 16;
case TypeIndex::INT32:
case TypeIndex::UINT32:
return 32;
case TypeIndex::INT64:
case TypeIndex::UINT64:
return 64;
case TypeIndex::FLOAT:
return 32;
case TypeIndex::DOUBLE:
return 64;
case TypeIndex::NONE:
return 0;
default:
throw std::runtime_error(LOCATION + "Could not get bitdepth. Type not supported.");
}
}
/**
* @brief Get the number of bytes of the data type
*/
size_t Dtype::bytes() const { return bitdepth() / 8; }
/**
* @brief Construct a DType object from a TypeIndex
* @param ti TypeIndex
*
*/
Dtype::Dtype(Dtype::TypeIndex ti) : m_type(ti) {}
/**
* @brief Construct a DType object from a string
* @param sv string_view
* @throw runtime_error if the type is not supported
* @note example strings: "<i4", "u8", "f4"
* @note the endianess is checked and only native endianess is supported
*/
Dtype::Dtype(std::string_view sv) {
// Check if the file is using our native endianess
if (auto pos = sv.find_first_of("<>"); pos != std::string_view::npos) {
const auto endianess = [](const char c) {
if (c == '<')
return endian::little;
return endian::big;
}(sv[pos]);
if (endianess != endian::native) {
throw std::runtime_error("Non native endianess not supported");
}
}
// we are done with the endianess so we can remove the prefix
sv.remove_prefix(std::min(sv.find_first_not_of("<>"), sv.size()));
if (sv == "i1")
m_type = TypeIndex::INT8;
else if (sv == "u1")
m_type = TypeIndex::UINT8;
else if (sv == "i2")
m_type = TypeIndex::INT16;
else if (sv == "u2")
m_type = TypeIndex::UINT16;
else if (sv == "i4")
m_type = TypeIndex::INT32;
else if (sv == "u4")
m_type = TypeIndex::UINT32;
else if (sv == "i8")
m_type = TypeIndex::INT64;
else if (sv == "u8")
m_type = TypeIndex::UINT64;
else if (sv == "f4")
m_type = TypeIndex::FLOAT;
else if (sv == "f8")
m_type = TypeIndex::DOUBLE;
else
throw std::runtime_error("Cannot construct data type from string.");
}
Dtype Dtype::from_bitdepth(uint8_t bitdepth) {
switch (bitdepth) {
case 8:
return Dtype(TypeIndex::UINT8);
case 16:
return Dtype(TypeIndex::UINT16);
case 32:
return Dtype(TypeIndex::UINT32);
case 64:
return Dtype(TypeIndex::UINT64);
default:
throw std::runtime_error("Could not construct data type from bitdepth.");
}
}
/**
* @brief Get the string representation of the data type
* @return string representation
*/
std::string Dtype::to_string() const {
char ec{};
if (endian::native == endian::little)
ec = '<';
else
ec = '>';
switch (m_type) {
case TypeIndex::INT8:
return fmt::format("{}i1", ec);
case TypeIndex::UINT8:
return fmt::format("{}u1", ec);
case TypeIndex::INT16:
return fmt::format("{}i2", ec);
case TypeIndex::UINT16:
return fmt::format("{}u2", ec);
case TypeIndex::INT32:
return fmt::format("{}i4", ec);
case TypeIndex::UINT32:
return fmt::format("{}u4", ec);
case TypeIndex::INT64:
return fmt::format("{}i8", ec);
case TypeIndex::UINT64:
return fmt::format("{}u8", ec);
case TypeIndex::FLOAT:
return "f4";
case TypeIndex::DOUBLE:
return "f8";
case TypeIndex::ERROR:
throw std::runtime_error("Could not get string representation. Type not supported.");
case TypeIndex::NONE:
throw std::runtime_error("Could not get string representation. Type not supported.");
}
return {};
}
bool Dtype::operator==(const Dtype &other) const noexcept { return m_type == other.m_type; }
bool Dtype::operator!=(const Dtype &other) const noexcept { return !(*this == other); }
bool Dtype::operator==(const std::type_info &t) const { return Dtype(t) == *this; }
bool Dtype::operator!=(const std::type_info &t) const { return Dtype(t) != *this; }
} // namespace aare

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

@ -0,0 +1,54 @@
#include "aare/Dtype.hpp"
#include <catch2/catch_test_macros.hpp>
using aare::Dtype;
using aare::endian;
TEST_CASE("Construct from typeid") {
REQUIRE(Dtype(typeid(int)) == typeid(int));
REQUIRE(Dtype(typeid(int)) != typeid(double));
}
TEST_CASE("Construct from string") {
if (endian::native == endian::little) {
REQUIRE(Dtype("<i1") == typeid(int8_t));
REQUIRE(Dtype("<u1") == typeid(uint8_t));
REQUIRE(Dtype("<i2") == typeid(int16_t));
REQUIRE(Dtype("<u2") == typeid(uint16_t));
REQUIRE(Dtype("<i4") == typeid(int));
REQUIRE(Dtype("<u4") == typeid(unsigned));
REQUIRE(Dtype("<i4") == typeid(int32_t));
// REQUIRE(Dtype("<i8") == typeid(long));
REQUIRE(Dtype("<i8") == typeid(int64_t));
REQUIRE(Dtype("<u4") == typeid(uint32_t));
REQUIRE(Dtype("<u8") == typeid(uint64_t));
REQUIRE(Dtype("f4") == typeid(float));
REQUIRE(Dtype("f8") == typeid(double));
}
if (endian::native == endian::big) {
REQUIRE(Dtype(">i1") == typeid(int8_t));
REQUIRE(Dtype(">u1") == typeid(uint8_t));
REQUIRE(Dtype(">i2") == typeid(int16_t));
REQUIRE(Dtype(">u2") == typeid(uint16_t));
REQUIRE(Dtype(">i4") == typeid(int));
REQUIRE(Dtype(">u4") == typeid(unsigned));
REQUIRE(Dtype(">i4") == typeid(int32_t));
// REQUIRE(Dtype(">i8") == typeid(long));
REQUIRE(Dtype(">i8") == typeid(int64_t));
REQUIRE(Dtype(">u4") == typeid(uint32_t));
REQUIRE(Dtype(">u8") == typeid(uint64_t));
REQUIRE(Dtype("f4") == typeid(float));
REQUIRE(Dtype("f8") == typeid(double));
}
}
TEST_CASE("Construct from string with endianess") {
// TODO! handle big endian system in test!
REQUIRE(Dtype("<i4") == typeid(int32_t));
REQUIRE_THROWS(Dtype(">i4") == typeid(int32_t));
}
TEST_CASE("Convert to string") { REQUIRE(Dtype(typeid(int)).to_string() == "<i4"); }

77
src/File.cpp Normal file
View 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
View 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
View File

@ -0,0 +1,152 @@
#include "aare/Frame.hpp"
#include "aare/Dtype.hpp"
#include <catch2/catch_test_macros.hpp>
using namespace aare;
TEST_CASE("Construct a frame") {
size_t rows = 10;
size_t cols = 10;
size_t bitdepth = 8;
Frame frame(rows, cols, Dtype::from_bitdepth(bitdepth));
REQUIRE(frame.rows() == rows);
REQUIRE(frame.cols() == cols);
REQUIRE(frame.bitdepth() == bitdepth);
REQUIRE(frame.bytes() == rows * cols * bitdepth / 8);
// data should be initialized to 0
for (size_t i = 0; i < rows; i++) {
for (size_t j = 0; j < cols; j++) {
uint8_t *data = (uint8_t *)frame.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
View File

@ -0,0 +1,377 @@
#include "aare/NDArray.hpp"
#include <array>
#include <catch2/catch_test_macros.hpp>
using aare::NDArray;
using aare::NDView;
using aare::Shape;
TEST_CASE("Initial size is zero if no size is specified") {
NDArray<double> a;
REQUIRE(a.size() == 0);
REQUIRE(a.shape() == Shape<2>{0, 0});
}
TEST_CASE("Construct from a DataSpan") {
std::vector<int> some_data(9, 42);
NDView<int, 2> view(some_data.data(), Shape<2>{3, 3});
NDArray<int, 2> image(view);
REQUIRE(image.shape() == view.shape());
REQUIRE(image.size() == view.size());
REQUIRE(image.data() != view.data());
for (uint32_t i = 0; i < image.size(); ++i) {
REQUIRE(image(i) == view(i));
}
// Changing the image doesn't change the view
image = 43;
for (uint32_t i = 0; i < image.size(); ++i) {
REQUIRE(image(i) != view(i));
}
}
TEST_CASE("1D image") {
std::array<int64_t, 1> shape{{20}};
NDArray<short, 1> img(shape, 3);
REQUIRE(img.size() == 20);
REQUIRE(img(5) == 3);
}
TEST_CASE("Accessing a const object") {
const NDArray<double, 3> img({3, 4, 5}, 0);
REQUIRE(img(1, 1, 1) == 0);
REQUIRE(img.size() == 3 * 4 * 5);
REQUIRE(img.shape() == Shape<3>{3, 4, 5});
REQUIRE(img.shape(0) == 3);
REQUIRE(img.shape(1) == 4);
REQUIRE(img.shape(2) == 5);
}
TEST_CASE("Indexing of a 2D image") {
std::array<int64_t, 2> shape{{3, 7}};
NDArray<long> img(shape, 5);
for (uint32_t i = 0; i != img.size(); ++i) {
REQUIRE(img(i) == 5);
}
for (uint32_t i = 0; i != img.size(); ++i) {
img(i) = i;
}
REQUIRE(img(0, 0) == 0);
REQUIRE(img(0, 1) == 1);
REQUIRE(img(1, 0) == 7);
}
TEST_CASE("Indexing of a 3D image") {
NDArray<float, 3> img{{{3, 4, 2}}, 5.0f};
for (uint32_t i = 0; i != img.size(); ++i) {
REQUIRE(img(i) == 5.0f);
}
// Double check general properties
REQUIRE(img.size() == 3 * 4 * 2);
for (uint32_t i = 0; i != img.size(); ++i) {
img(i) = float(i);
}
REQUIRE(img(0, 0, 0) == 0);
REQUIRE(img(0, 0, 1) == 1);
REQUIRE(img(0, 1, 1) == 3);
REQUIRE(img(1, 2, 0) == 12);
REQUIRE(img(2, 3, 1) == 23);
}
TEST_CASE("Divide double by int") {
NDArray<double, 1> a{{5}, 5};
NDArray<int, 1> b{{5}, 5};
a /= b;
for (auto it : a) {
REQUIRE(it == 1.0);
}
}
TEST_CASE("Elementwise multiplication of 3D image") {
std::array<int64_t, 3> shape{3, 4, 2};
NDArray<double, 3> a{shape};
NDArray<double, 3> b{shape};
for (uint32_t i = 0; i != a.size(); ++i) {
a(i) = i;
b(i) = i;
}
auto c = a * b;
REQUIRE(c(0, 0, 0) == 0 * 0);
REQUIRE(c(0, 0, 1) == 1 * 1);
REQUIRE(c(0, 1, 1) == 3 * 3);
REQUIRE(c(1, 2, 0) == 12 * 12);
REQUIRE(c(2, 3, 1) == 23 * 23);
}
TEST_CASE("Compare two images") {
NDArray<int> a;
NDArray<int> b;
CHECK((a == b));
a = NDArray<int>{{5, 10}, 0};
CHECK((a != b));
b = NDArray<int>{{5, 10}, 0};
CHECK((a == b));
b(3, 3) = 7;
CHECK((a != b));
}
TEST_CASE("Size and shape matches") {
int64_t w = 15;
int64_t h = 75;
std::array<int64_t, 2> shape{w, h};
NDArray<double> a{shape};
REQUIRE(a.size() == static_cast<uint64_t>(w * h));
REQUIRE(a.shape() == shape);
}
TEST_CASE("Initial value matches for all elements") {
double v = 4.35;
NDArray<double> a{{5, 5}, v};
for (uint32_t i = 0; i < a.size(); ++i) {
REQUIRE(a(i) == v);
}
}
TEST_CASE("Data layout of 3D image, fast index last") {
NDArray<int, 3> a{{3, 3, 3}, 0};
REQUIRE(a.size() == 27);
int *ptr = a.data();
for (int i = 0; i < 9; ++i) {
*ptr++ = 10 + i;
REQUIRE(a(0, 0, i) == 10 + i);
REQUIRE(a(i) == 10 + i);
}
}
TEST_CASE("Bitwise and on data") {
NDArray<uint16_t, 1> a({3}, 0);
uint16_t mask = 0x3FF;
a(0) = 16684;
a(1) = 33068;
a(2) = 52608;
a &= mask;
REQUIRE(a(0) == 300);
REQUIRE(a(1) == 300);
REQUIRE(a(2) == 384);
}
// TEST_CASE("Benchmarks")
// {
// NDArray<double> img;
// std::array<int64_t, 2> shape{ 512, 1024 };
// BENCHMARK("Allocate 500k double image")
// {
// NDArray<double>im{ shape };
// }
// BENCHMARK("Allocate 500k double image with initial value")
// {
// NDArray<double>im{ shape, 3.14 };
// }
// NDArray<double> a{ shape, 1.2 };
// NDArray<double> b{ shape, 53. };
// auto c = a + b;
// c = a * b;
// BENCHMARK("Multiply two images")
// {
// c = a * b;
// }
// BENCHMARK("Divide two images")
// {
// c = a / b;
// }
// BENCHMARK("Add two images")
// {
// c = a + b;
// }
// BENCHMARK("Subtract two images")
// {
// c = a - b;
// }
// }
TEST_CASE("Elementwise operatios on images") {
std::array<int64_t, 2> shape{5, 5};
double a_val = 3.0;
double b_val = 8.0;
SECTION("Add two images") {
NDArray<double> A(shape, a_val);
NDArray<double> B(shape, b_val);
auto C = A + B;
// Value of C matches
for (uint32_t i = 0; i < C.size(); ++i) {
REQUIRE(C(i) == a_val + b_val);
}
// Value of A is not changed
for (uint32_t i = 0; i < A.size(); ++i) {
REQUIRE(A(i) == a_val);
}
// Value of B is not changed
for (uint32_t i = 0; i < B.size(); ++i) {
REQUIRE(B(i) == b_val);
}
// A, B and C referes to different data
REQUIRE(A.data() != B.data());
REQUIRE(B.data() != C.data());
}
SECTION("Subtract two images") {
NDArray<double> A(shape, a_val);
NDArray<double> B(shape, b_val);
auto C = A - B;
// Value of C matches
for (uint32_t i = 0; i < C.size(); ++i) {
REQUIRE(C(i) == a_val - b_val);
}
// Value of A is not changed
for (uint32_t i = 0; i < A.size(); ++i) {
REQUIRE(A(i) == a_val);
}
// Value of B is not changed
for (uint32_t i = 0; i < B.size(); ++i) {
REQUIRE(B(i) == b_val);
}
// A, B and C referes to different data
REQUIRE(A.data() != B.data());
REQUIRE(B.data() != C.data());
}
SECTION("Multiply two images") {
NDArray<double> A(shape, a_val);
NDArray<double> B(shape, b_val);
auto C = A * B;
// Value of C matches
for (uint32_t i = 0; i < C.size(); ++i) {
REQUIRE(C(i) == a_val * b_val);
}
// Value of A is not changed
for (uint32_t i = 0; i < A.size(); ++i) {
REQUIRE(A(i) == a_val);
}
// Value of B is not changed
for (uint32_t i = 0; i < B.size(); ++i) {
REQUIRE(B(i) == b_val);
}
// A, B and C referes to different data
REQUIRE(A.data() != B.data());
REQUIRE(B.data() != C.data());
}
SECTION("Divide two images") {
NDArray<double> A(shape, a_val);
NDArray<double> B(shape, b_val);
auto C = A / B;
// Value of C matches
for (uint32_t i = 0; i < C.size(); ++i) {
REQUIRE(C(i) == a_val / b_val);
}
// Value of A is not changed
for (uint32_t i = 0; i < A.size(); ++i) {
REQUIRE(A(i) == a_val);
}
// Value of B is not changed
for (uint32_t i = 0; i < B.size(); ++i) {
REQUIRE(B(i) == b_val);
}
// A, B and C referes to different data
REQUIRE(A.data() != B.data());
REQUIRE(B.data() != C.data());
}
SECTION("subtract scalar") {
NDArray<double> A(shape, a_val);
NDArray<double> B(shape, b_val);
double v = 1.0;
auto C = A - v;
REQUIRE(C.data() != A.data());
// Value of C matches
for (uint32_t i = 0; i < C.size(); ++i) {
REQUIRE(C(i) == a_val - v);
}
// Value of A is not changed
for (uint32_t i = 0; i < A.size(); ++i) {
REQUIRE(A(i) == a_val);
}
}
SECTION("add scalar") {
NDArray<double> A(shape, a_val);
NDArray<double> B(shape, b_val);
double v = 1.0;
auto C = A + v;
REQUIRE(C.data() != A.data());
// Value of C matches
for (uint32_t i = 0; i < C.size(); ++i) {
REQUIRE(C(i) == a_val + v);
}
// Value of A is not changed
for (uint32_t i = 0; i < A.size(); ++i) {
REQUIRE(A(i) == a_val);
}
}
SECTION("divide with scalar") {
NDArray<double> A(shape, a_val);
NDArray<double> B(shape, b_val);
double v = 3.7;
auto C = A / v;
REQUIRE(C.data() != A.data());
// Value of C matches
for (uint32_t i = 0; i < C.size(); ++i) {
REQUIRE(C(i) == a_val / v);
}
// Value of A is not changed
for (uint32_t i = 0; i < A.size(); ++i) {
REQUIRE(A(i) == a_val);
}
}
SECTION("multiply with scalar") {
NDArray<double> A(shape, a_val);
NDArray<double> B(shape, b_val);
double v = 3.7;
auto C = A / v;
REQUIRE(C.data() != A.data());
// Value of C matches
for (uint32_t i = 0; i < C.size(); ++i) {
REQUIRE(C(i) == a_val / v);
}
// Value of A is not changed
for (uint32_t i = 0; i < A.size(); ++i) {
REQUIRE(A(i) == a_val);
}
}
}

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

@ -0,0 +1,193 @@
#include "aare/NDView.hpp"
#include <catch2/catch_test_macros.hpp>
#include <iostream>
#include <vector>
using aare::NDView;
using aare::Shape;
TEST_CASE("Element reference 1D") {
std::vector<int> vec;
for (int i = 0; i != 10; ++i) {
vec.push_back(i);
}
NDView<int, 1> data(vec.data(), Shape<1>{10});
REQUIRE(vec.size() == static_cast<size_t>(data.size()));
for (int i = 0; i != 10; ++i) {
REQUIRE(data(i) == vec[i]);
REQUIRE(data[i] == vec[i]);
}
}
TEST_CASE("Element reference 2D") {
std::vector<int> vec;
for (int i = 0; i != 12; ++i) {
vec.push_back(i);
}
NDView<int, 2> data(vec.data(), Shape<2>{3, 4});
REQUIRE(vec.size() == static_cast<size_t>(data.size()));
int i = 0;
for (int row = 0; row != 3; ++row) {
for (int col = 0; col != 4; ++col) {
REQUIRE(data(row, col) == i);
REQUIRE(data[i] == vec[i]);
++i;
}
}
}
TEST_CASE("Element reference 3D") {
std::vector<int> vec;
for (int i = 0; i != 24; ++i) {
vec.push_back(i);
}
NDView<int, 3> data(vec.data(), Shape<3>{2, 3, 4});
REQUIRE(vec.size() == static_cast<size_t>(data.size()));
int i = 0;
for (int frame = 0; frame != 2; ++frame) {
for (int row = 0; row != 3; ++row) {
for (int col = 0; col != 4; ++col) {
REQUIRE(data(frame, row, col) == i);
REQUIRE(data[i] == vec[i]);
++i;
}
}
}
}
TEST_CASE("Plus and miuns with single value") {
std::vector<int> vec;
for (int i = 0; i != 12; ++i) {
vec.push_back(i);
}
NDView<int, 2> data(vec.data(), Shape<2>{3, 4});
data += 5;
int i = 0;
for (int row = 0; row != 3; ++row) {
for (int col = 0; col != 4; ++col) {
REQUIRE(data(row, col) == i + 5);
++i;
}
}
data -= 3;
i = 0;
for (int row = 0; row != 3; ++row) {
for (int col = 0; col != 4; ++col) {
REQUIRE(data(row, col) == i + 2);
++i;
}
}
}
TEST_CASE("Multiply and divide with single value") {
std::vector<int> vec;
for (int i = 0; i != 12; ++i) {
vec.push_back(i);
}
NDView<int, 2> data(vec.data(), Shape<2>{3, 4});
data *= 5;
int i = 0;
for (int row = 0; row != 3; ++row) {
for (int col = 0; col != 4; ++col) {
REQUIRE(data(row, col) == i * 5);
++i;
}
}
data /= 3;
i = 0;
for (int row = 0; row != 3; ++row) {
for (int col = 0; col != 4; ++col) {
REQUIRE(data(row, col) == (i * 5) / 3);
++i;
}
}
}
TEST_CASE("elementwise assign") {
std::vector<int> vec(25);
NDView<int, 2> data(vec.data(), Shape<2>{5, 5});
data = 3;
for (auto it : data) {
REQUIRE(it == 3);
}
}
TEST_CASE("iterators") {
std::vector<int> vec;
for (int i = 0; i != 12; ++i) {
vec.push_back(i);
}
NDView<int, 1> data(vec.data(), Shape<1>{12});
int i = 0;
for (const auto item : data) {
REQUIRE(item == vec[i]);
++i;
}
REQUIRE(i == 12);
for (auto ptr = data.begin(); ptr != data.end(); ++ptr) {
*ptr += 1;
}
for (auto &item : data) {
++item;
}
i = 0;
for (const auto item : data) {
REQUIRE(item == i + 2);
++i;
}
}
// TEST_CASE("shape from vector") {
// std::vector<int> vec;
// for (int i = 0; i != 12; ++i) {
// vec.push_back(i);
// }
// std::vector<int64_t> shape{3, 4};
// NDView<int, 2> data(vec.data(), shape);
// }
TEST_CASE("divide with another span") {
std::vector<int> vec0{9, 12, 3};
std::vector<int> vec1{3, 2, 1};
std::vector<int> result{3, 6, 3};
NDView<int, 1> data0(vec0.data(), Shape<1>{static_cast<int64_t>(vec0.size())});
NDView<int, 1> data1(vec1.data(), Shape<1>{static_cast<int64_t>(vec1.size())});
data0 /= data1;
for (size_t i = 0; i != vec0.size(); ++i) {
REQUIRE(data0[i] == result[i]);
}
}
TEST_CASE("Retrieve shape") {
std::vector<int> vec;
for (int i = 0; i != 12; ++i) {
vec.push_back(i);
}
NDView<int, 2> data(vec.data(), Shape<2>{3, 4});
REQUIRE(data.shape()[0] == 3);
REQUIRE(data.shape()[1] == 4);
}
TEST_CASE("compare two views") {
std::vector<int> vec1;
for (int i = 0; i != 12; ++i) {
vec1.push_back(i);
}
NDView<int, 2> view1(vec1.data(), Shape<2>{3, 4});
std::vector<int> vec2;
for (int i = 0; i != 12; ++i) {
vec2.push_back(i);
}
NDView<int, 2> view2(vec2.data(), Shape<2>{3, 4});
REQUIRE((view1 == view2));
}

200
src/NumpyFile.cpp Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,42 @@
#include "aare/defs.hpp"
// #include "aare/utils/floats.hpp"
#include <catch2/catch_test_macros.hpp>
#include <string>
TEST_CASE("Enum to string conversion") {
// By the way I don't think the enum string conversions should be in the defs.hpp file
// but let's use this to show a test
REQUIRE(ToString(aare::DetectorType::Jungfrau) == "Jungfrau");
}
TEST_CASE("Cluster creation") {
aare::Cluster c(13, 15);
REQUIRE(c.cluster_sizeX == 13);
REQUIRE(c.cluster_sizeY == 15);
REQUIRE(c.dt == aare::Dtype(typeid(int32_t)));
REQUIRE(c.data() != nullptr);
aare::Cluster c2(c);
REQUIRE(c2.cluster_sizeX == 13);
REQUIRE(c2.cluster_sizeY == 15);
REQUIRE(c2.dt == aare::Dtype(typeid(int32_t)));
REQUIRE(c2.data() != nullptr);
}
// TEST_CASE("cluster set and get data") {
// aare::Cluster c2(33, 44, aare::Dtype(typeid(double)));
// REQUIRE(c2.cluster_sizeX == 33);
// REQUIRE(c2.cluster_sizeY == 44);
// REQUIRE(c2.dt == aare::Dtype::DOUBLE);
// double v = 3.14;
// c2.set<double>(0, v);
// double v2 = c2.get<double>(0);
// REQUIRE(aare::compare_floats<double>(v, v2));
// c2.set<double>(33 * 44 - 1, 123.11);
// double v3 = c2.get<double>(33 * 44 - 1);
// REQUIRE(aare::compare_floats<double>(123.11, v3));
// REQUIRE_THROWS_AS(c2.set(0, 1), std::invalid_argument); // set int to double
// }