mirror of
https://github.com/slsdetectorgroup/aare.git
synced 2025-06-05 12:30:39 +02:00
read write cluster file (#60)
* Read and write cluster files (save work) * add reading test * use define for examples env variable and fix ci * read and write cluster files (working) * fix cluster CI
This commit is contained in:
parent
9dfd388927
commit
28d7e8c07a
14
.clang-tidy
14
.clang-tidy
@ -11,6 +11,15 @@ Checks: '
|
||||
# - cppcoreguidelines-owning-memory
|
||||
# - bugprone-easily-swappable-parameters
|
||||
# - cppcoreguidelines-non-private-member-variables-in-classes
|
||||
|
||||
# cppcoreguidelines-special-member-functions
|
||||
# hicpp-special-member-functions
|
||||
|
||||
# cert-err33-c # too much with c file api
|
||||
|
||||
# modernize-pass-by-value # contradicts with other checks
|
||||
|
||||
|
||||
# abseil-cleanup-ctad
|
||||
# abseil-duration-addition
|
||||
# abseil-duration-comparison
|
||||
@ -31,8 +40,7 @@ Checks: '
|
||||
# abseil-time-subtraction
|
||||
# abseil-upgrade-duration-conversions
|
||||
android-cloexec-accept
|
||||
cppcoreguidelines-special-member-functions
|
||||
hicpp-special-member-functions
|
||||
|
||||
android-cloexec-accept4
|
||||
android-cloexec-creat
|
||||
android-cloexec-dup
|
||||
@ -131,7 +139,6 @@ Checks: '
|
||||
cert-dcl59-cpp
|
||||
cert-env33-c
|
||||
cert-err09-cpp
|
||||
cert-err33-c
|
||||
cert-err34-c
|
||||
cert-err52-cpp
|
||||
cert-err58-cpp
|
||||
@ -384,7 +391,6 @@ Checks: '
|
||||
modernize-macro-to-enum
|
||||
modernize-make-shared
|
||||
modernize-make-unique
|
||||
modernize-pass-by-value
|
||||
modernize-raw-string-literal
|
||||
modernize-redundant-void-arg
|
||||
modernize-replace-auto-ptr
|
||||
|
2
.github/workflows/common-workflow.yml
vendored
2
.github/workflows/common-workflow.yml
vendored
@ -43,7 +43,7 @@ jobs:
|
||||
# find all examples in build/examples and run them
|
||||
run: |
|
||||
pwd
|
||||
export PROJECT_ROOT_DIR="."
|
||||
export AARE_ROOT_DIR="$PWD"
|
||||
ls build/examples/*_example
|
||||
find build/examples -name "*_example" -not -name "zmq_*" | xargs -I {} -n 1 -t bash -c {}
|
||||
|
||||
|
@ -1,5 +1,10 @@
|
||||
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17) #TODO! Global or per target?
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
project(aare
|
||||
VERSION 0.1
|
||||
DESCRIPTION "Data processing library for PSI detectors"
|
||||
@ -206,10 +211,9 @@ add_custom_target(
|
||||
COMMENT "Formatting with clang-format"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
clang-tidy
|
||||
COMMAND find \( -path "./core/*" -o -path "./file_io/*" -path "./network_io/*" -path "./utils/*" \) \( -name "*.cpp" -o -name "*.hpp" \) -not -path "./python/*" -not -name "*.test.cpp" -not -name "CircularFifo.hpp" -not -name "ProducerConsumerQueue.hpp" -not -name "VariableSizeClusterFinder.hpp" | xargs -I {} -n 1 -P 10 bash -c "clang-tidy --config-file=.clang-tidy -p build {}"
|
||||
COMMAND find \( -path "./core/*" -o -path "./file_io/*" -o -path "./network_io/*" -o -path "./utils/*" \) \( -name "*.cpp" \) -not -path "./python/*" -not -name "*.test.cpp" -not -name "CircularFifo.hpp" -not -name "ProducerConsumerQueue.hpp" -not -name "VariableSizeClusterFinder.hpp" | xargs -I {} -n 1 -P 10 bash -c "clang-tidy --config-file=.clang-tidy -p build {}"
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMENT "linting with clang-tidy"
|
||||
VERBATIM
|
||||
|
@ -11,6 +11,20 @@
|
||||
|
||||
namespace aare {
|
||||
|
||||
struct Cluster {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
std::array<int32_t, 9> data;
|
||||
std::string to_string() const {
|
||||
std::string s = "x: " + std::to_string(x) + " y: " + std::to_string(y) + "\ndata: [";
|
||||
for (auto d : data) {
|
||||
s += std::to_string(d) + " ";
|
||||
}
|
||||
s += "]";
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief header contained in parts of frames
|
||||
*/
|
||||
|
BIN
data/clusters/beam_En700eV_-40deg_300V_10us_d0_f0_100.clust
Executable file
BIN
data/clusters/beam_En700eV_-40deg_300V_10us_d0_f0_100.clust
Executable file
Binary file not shown.
BIN
data/clusters/single_frame_97_clustrers.clust
Executable file
BIN
data/clusters/single_frame_97_clustrers.clust
Executable file
Binary file not shown.
BIN
data/clusters/test_writing.clust
Normal file
BIN
data/clusters/test_writing.clust
Normal file
Binary file not shown.
@ -1,8 +1,10 @@
|
||||
|
||||
set(EXAMPLE_LIST "json_example;logger_example;numpy_read_example;multiport_example;raw_example;zmq_restream_example")
|
||||
set(EXAMPLE_LIST "${EXAMPLE_LIST};mythen_example;numpy_write_example;zmq_receiver_example;zmq_sender_example;")
|
||||
set(EXAMPLE_LIST "${EXAMPLE_LIST};cluster_example")
|
||||
foreach(example ${EXAMPLE_LIST})
|
||||
add_executable(${example} ${example}.cpp)
|
||||
target_include_directories(${example} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
target_link_libraries(${example} PUBLIC aare PRIVATE aare_compiler_flags)
|
||||
endforeach()
|
||||
|
||||
|
53
examples/cluster_example.cpp
Normal file
53
examples/cluster_example.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
#include "aare/core/defs.hpp"
|
||||
#include "aare/file_io/ClusterFile.hpp"
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
|
||||
using namespace aare;
|
||||
int main() {
|
||||
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv("AARE_ROOT_DIR"));
|
||||
std::filesystem::path const fpath(PROJECT_ROOT_DIR / "data" / "clusters" / "single_frame_97_clustrers.clust");
|
||||
|
||||
// reading a file
|
||||
aare::ClusterFile cf(fpath, "r");
|
||||
std::cout << "file opened " << '\n';
|
||||
std::cout << "n_clusters " << cf.count() << '\n';
|
||||
std::cout << "frame_number " << cf.frame() << '\n';
|
||||
std::cout << "file size: " << cf.count() << '\n';
|
||||
cf.seek(0); // seek to the beginning of the file (this is the default behavior of the constructor)
|
||||
|
||||
auto cluster = cf.read(97);
|
||||
|
||||
std::cout << "read 10 clusters" << '\n';
|
||||
int offset = 0;
|
||||
int data_offset = 0;
|
||||
for (auto c : cluster) {
|
||||
assert(c.y == offset + 200);
|
||||
for (int i = 0; i < 9; i++) {
|
||||
assert(c.data[i] == data_offset + i);
|
||||
}
|
||||
|
||||
offset++;
|
||||
data_offset += 9;
|
||||
}
|
||||
|
||||
// writing a file
|
||||
std::filesystem::path const fpath_out("/tmp/cluster_example_file.clust");
|
||||
aare::ClusterFile cf_out(fpath_out, "w", ClusterFileConfig(1, 44));
|
||||
std::cout << "file opened for writing" << '\n';
|
||||
std::vector<Cluster> clusters;
|
||||
for (int i = 0; i < 1084; i++) {
|
||||
Cluster c;
|
||||
c.x = i;
|
||||
c.y = i + 200;
|
||||
for (int j = 0; j < 9; j++) {
|
||||
c.data[j] = j;
|
||||
}
|
||||
clusters.push_back(c);
|
||||
}
|
||||
cf_out.write(clusters);
|
||||
std::cout << "wrote 10 clusters" << '\n';
|
||||
cf_out.update_header();
|
||||
|
||||
return 0;
|
||||
}
|
2
examples/include/aare/examples/defs.hpp
Normal file
2
examples/include/aare/examples/defs.hpp
Normal file
@ -0,0 +1,2 @@
|
||||
#pragma once
|
||||
#define AARE_ROOT_DIR "AARE_ROOT_DIR"
|
@ -1,10 +1,9 @@
|
||||
// Your First C++ Program
|
||||
#include "aare/examples/defs.hpp"
|
||||
#include "aare/file_io/File.hpp"
|
||||
#include "aare/utils/logger.hpp"
|
||||
#include <iostream>
|
||||
|
||||
#define AARE_ROOT_DIR_VAR "PROJECT_ROOT_DIR"
|
||||
|
||||
using aare::File;
|
||||
using aare::Frame;
|
||||
|
||||
@ -18,7 +17,7 @@ void test(File &f, int frame_number) {
|
||||
}
|
||||
|
||||
int main() {
|
||||
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv(AARE_ROOT_DIR_VAR));
|
||||
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv(AARE_ROOT_DIR));
|
||||
std::filesystem::path const fpath(PROJECT_ROOT_DIR / "data" / "jungfrau" / "jungfrau_single_master_0.json");
|
||||
std::cout << fpath << '\n';
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
#include "aare/examples/defs.hpp"
|
||||
#include "aare/utils/logger.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
@ -11,7 +13,7 @@ int main() {
|
||||
|
||||
// writing to file
|
||||
std::ofstream textfile;
|
||||
textfile.open("Test.txt");
|
||||
textfile.open("/tmp/Test.txt");
|
||||
aare::logger::set_streams(textfile.rdbuf());
|
||||
aare::logger::info(LOCATION, "info printed to file");
|
||||
|
||||
@ -26,7 +28,7 @@ int main() {
|
||||
|
||||
// setting file output by path
|
||||
// user doesn't have to close file
|
||||
aare::logger::set_output_file("Test2.txt");
|
||||
aare::logger::set_output_file("/tmp/Test2.txt");
|
||||
aare::logger::info(LOCATION, "info printed to Test2.txt");
|
||||
return 0;
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
// Your First C++ Program
|
||||
#include "aare/examples/defs.hpp"
|
||||
#include "aare/file_io/File.hpp"
|
||||
#include "aare/utils/logger.hpp"
|
||||
#include <iostream>
|
||||
|
||||
#define AARE_ROOT_DIR_VAR "PROJECT_ROOT_DIR"
|
||||
#include <iostream>
|
||||
|
||||
using aare::File;
|
||||
using aare::Frame;
|
||||
@ -19,7 +19,7 @@ void test(File &f, int frame_number) {
|
||||
}
|
||||
|
||||
int main() {
|
||||
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv(AARE_ROOT_DIR_VAR));
|
||||
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv(AARE_ROOT_DIR));
|
||||
std::filesystem::path const fpath(PROJECT_ROOT_DIR / "data" / "jungfrau" / "jungfrau_double_master_0.json");
|
||||
std::cout << fpath << '\n';
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
// Your First C++ Program
|
||||
#include "aare/examples/defs.hpp"
|
||||
#include "aare/file_io/File.hpp"
|
||||
#include "aare/utils/logger.hpp"
|
||||
#include <iostream>
|
||||
|
||||
#define AARE_ROOT_DIR_VAR "PROJECT_ROOT_DIR"
|
||||
#include <iostream>
|
||||
|
||||
using aare::File;
|
||||
using aare::Frame;
|
||||
@ -32,7 +32,7 @@ void test2(File &f, int frame_number) {
|
||||
}
|
||||
|
||||
int main() {
|
||||
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv(AARE_ROOT_DIR_VAR));
|
||||
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv(AARE_ROOT_DIR));
|
||||
if (PROJECT_ROOT_DIR.empty()) {
|
||||
throw std::runtime_error("environment variable PROJECT_ROOT_DIR is not set");
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
// Your First C++ Program
|
||||
#include "aare/examples/defs.hpp"
|
||||
#include "aare/file_io/File.hpp"
|
||||
#include <iostream>
|
||||
|
||||
#define AARE_ROOT_DIR_VAR "PROJECT_ROOT_DIR"
|
||||
#include <iostream>
|
||||
|
||||
using aare::File;
|
||||
using aare::Frame;
|
||||
@ -18,7 +18,7 @@ void test(File &f, int frame_number) {
|
||||
|
||||
int main() {
|
||||
|
||||
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv(AARE_ROOT_DIR_VAR));
|
||||
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv(AARE_ROOT_DIR));
|
||||
std::filesystem::path const fpath(PROJECT_ROOT_DIR / "data" / "numpy" / "test_numpy_file.npy");
|
||||
std::cout << fpath << '\n';
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
// Your First C++ Program
|
||||
#include "aare/core/Frame.hpp"
|
||||
#include "aare/examples/defs.hpp"
|
||||
#include "aare/file_io/File.hpp"
|
||||
#include <iostream>
|
||||
|
||||
#define AARE_ROOT_DIR_VAR "PROJECT_ROOT_DIR"
|
||||
#include <iostream>
|
||||
|
||||
using aare::File;
|
||||
using aare::FileConfig;
|
||||
|
@ -1,10 +1,9 @@
|
||||
// Your First C++ Program
|
||||
#include "aare/examples/defs.hpp"
|
||||
#include "aare/file_io/File.hpp"
|
||||
#include "aare/utils/logger.hpp"
|
||||
#include <iostream>
|
||||
|
||||
#define AARE_ROOT_DIR_VAR "PROJECT_ROOT_DIR"
|
||||
|
||||
using aare::File;
|
||||
using aare::Frame;
|
||||
|
||||
@ -17,7 +16,7 @@ void test(File &f, int frame_number) {
|
||||
}
|
||||
|
||||
int main() {
|
||||
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv(AARE_ROOT_DIR_VAR));
|
||||
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv(AARE_ROOT_DIR));
|
||||
if (PROJECT_ROOT_DIR.empty()) {
|
||||
throw std::runtime_error("environment variable PROJECT_ROOT_DIR is not set");
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
#include "aare/examples/defs.hpp"
|
||||
#include "aare/network_io/ZmqSocketReceiver.hpp"
|
||||
#include "aare/network_io/defs.hpp"
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
#include "aare/examples/defs.hpp"
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "aare/core/Frame.hpp"
|
||||
#include "aare/examples/defs.hpp"
|
||||
#include "aare/network_io/ZmqHeader.hpp"
|
||||
#include "aare/network_io/ZmqSocketSender.hpp"
|
||||
#include "aare/network_io/defs.hpp"
|
||||
|
@ -15,6 +15,7 @@ set(SourceFiles
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/SubFile.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/NumpyFile.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/NumpyHelpers.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/ClusterFile.cpp
|
||||
)
|
||||
|
||||
add_library(file_io STATIC ${SourceFiles})
|
||||
@ -31,6 +32,7 @@ if(AARE_TESTS)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/NumpyFile.test.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/NumpyHelpers.test.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/RawFile.test.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/ClusterFile.test.cpp
|
||||
)
|
||||
target_sources(tests PRIVATE ${TestSources} )
|
||||
target_link_libraries(tests PRIVATE core file_io)
|
||||
|
70
file_io/include/aare/file_io/ClusterFile.hpp
Normal file
70
file_io/include/aare/file_io/ClusterFile.hpp
Normal file
@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
#include "aare/core/defs.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* cluster file format:
|
||||
* header: [int32 frame_number][int32 n_clusters]
|
||||
* data: [clusters....]
|
||||
* where each cluster is of the form:
|
||||
* typedef struct {
|
||||
* int16_t x;
|
||||
* int16_t y;
|
||||
* int32_t data[9];
|
||||
*} Cluster ;
|
||||
*
|
||||
*/
|
||||
|
||||
namespace aare {
|
||||
|
||||
/**
|
||||
* @brief Configuration of the ClusterFile
|
||||
* can be use as the header of the cluster file
|
||||
*/
|
||||
struct ClusterFileConfig {
|
||||
int32_t frame_number;
|
||||
int32_t n_clusters;
|
||||
ClusterFileConfig(int32_t frame_number_, int32_t n_clusters_)
|
||||
: frame_number(frame_number_), n_clusters(n_clusters_) {}
|
||||
ClusterFileConfig() : frame_number(0), n_clusters(0) {}
|
||||
bool operator==(const ClusterFileConfig &other) const {
|
||||
return frame_number == other.frame_number && n_clusters == other.n_clusters;
|
||||
}
|
||||
bool operator!=(const ClusterFileConfig &other) const { return !(*this == other); }
|
||||
std::string to_string() const {
|
||||
return "frame_number: " + std::to_string(frame_number) + " n_clusters: " + std::to_string(n_clusters) + "\n";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Class to read and write clusters to a file
|
||||
*/
|
||||
class ClusterFile {
|
||||
|
||||
public:
|
||||
ClusterFile(const std::filesystem::path &fname, const std::string &mode, ClusterFileConfig config = {});
|
||||
void write(std::vector<Cluster> &clusters);
|
||||
void write(Cluster &cluster);
|
||||
Cluster read();
|
||||
Cluster iread(size_t cluster_number);
|
||||
std::vector<Cluster> read(size_t n_clusters);
|
||||
void seek(size_t cluster_number);
|
||||
size_t tell() const;
|
||||
size_t count() noexcept;
|
||||
int32_t frame() const;
|
||||
void update_header() /* throws */;
|
||||
~ClusterFile() noexcept;
|
||||
|
||||
private:
|
||||
FILE *fp = nullptr;
|
||||
// size_t current_cluster{};
|
||||
std::filesystem::path fname{};
|
||||
std::string mode{};
|
||||
int32_t frame_number{};
|
||||
int32_t n_clusters{};
|
||||
static const int HEADER_BYTES = 8;
|
||||
};
|
||||
|
||||
} // namespace aare
|
@ -24,7 +24,7 @@ class RawFile : public FileInterface {
|
||||
* @brief write function is not implemented for RawFile
|
||||
* @param frame frame to write
|
||||
*/
|
||||
void write(Frame & /*frame*/) override { throw std::runtime_error("Not implemented"); };
|
||||
void write([[maybe_unused]] Frame &frame) override { throw std::runtime_error("Not implemented"); };
|
||||
Frame read() override { return get_frame(this->current_frame++); };
|
||||
std::vector<Frame> read(size_t n_frames) override;
|
||||
void read_into(std::byte *image_buf) override { return get_frame_into(this->current_frame++, image_buf); };
|
||||
|
218
file_io/src/ClusterFile.cpp
Normal file
218
file_io/src/ClusterFile.cpp
Normal file
@ -0,0 +1,218 @@
|
||||
#include "aare/file_io/ClusterFile.hpp"
|
||||
#include "aare/core/defs.hpp"
|
||||
#include "aare/utils/logger.hpp"
|
||||
#include "fmt/core.h"
|
||||
namespace aare {
|
||||
|
||||
/**
|
||||
* @brief Constructor for the ClusterFile class.
|
||||
*
|
||||
* Opens the file with the given mode ('r' for read, 'w' for write).
|
||||
* Throws an exception if the mode is not 'r' or 'w', or if the file cannot be opened.
|
||||
*
|
||||
* @param fname_ The name of the file to open.
|
||||
* @param mode_ The mode to open the file in.
|
||||
* @param config Configuration for the file header.
|
||||
*/
|
||||
ClusterFile::ClusterFile(const std::filesystem::path &fname_, const std::string &mode_, ClusterFileConfig config)
|
||||
: fname{fname_}, mode{mode_}, frame_number{config.frame_number}, n_clusters{config.n_clusters} {
|
||||
|
||||
// check if the file has the .clust extension
|
||||
if (fname.extension() != ".clust") {
|
||||
aare::logger::warn("file", fname, "does not have .clust extension");
|
||||
}
|
||||
|
||||
if (mode == "r") {
|
||||
// check if the file exists and is a regular file
|
||||
if (not std::filesystem::exists(fname)) {
|
||||
throw std::invalid_argument(fmt::format("file {} does not exist", fname.c_str()));
|
||||
}
|
||||
if (not std::filesystem::is_regular_file(fname)) {
|
||||
throw std::invalid_argument(fmt::format("file {} is not a regular file", fname.c_str()));
|
||||
}
|
||||
// check if the file size is a multiple of the cluster size
|
||||
if ((std::filesystem::file_size(fname) - HEADER_BYTES) % sizeof(Cluster) != 0) {
|
||||
aare::logger::warn("file", fname, "size is not a multiple of cluster size");
|
||||
}
|
||||
if (config != ClusterFileConfig()) {
|
||||
aare::logger::warn("ignored ClusterFileConfig for read mode");
|
||||
}
|
||||
// open file
|
||||
fp = fopen(fname.c_str(), "rb");
|
||||
if (fp == nullptr) {
|
||||
throw std::runtime_error(fmt::format("could not open file {}", fname.c_str()));
|
||||
}
|
||||
// read header
|
||||
const size_t rc = fread(&config, sizeof(config), 1, fp);
|
||||
if (rc != 1) {
|
||||
throw std::runtime_error(fmt::format("could not read header from file {}", fname.c_str()));
|
||||
}
|
||||
frame_number = config.frame_number;
|
||||
n_clusters = config.n_clusters;
|
||||
} else if (mode == "w") {
|
||||
// open file
|
||||
fp = fopen(fname.c_str(), "wb");
|
||||
if (fp == nullptr) {
|
||||
throw std::runtime_error(fmt::format("could not open file {}", fname.c_str()));
|
||||
}
|
||||
|
||||
// write header
|
||||
if (fwrite(&config, sizeof(config), 1, fp) != 1) {
|
||||
throw std::runtime_error(fmt::format("could not write header to file {}", fname.c_str()));
|
||||
}
|
||||
} else {
|
||||
throw std::invalid_argument("mode must be 'r' or 'w'");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @brief Writes a vector of clusters to the file.
|
||||
*
|
||||
* Each cluster is written as a binary block of size sizeof(Cluster).
|
||||
*
|
||||
* @param clusters The vector of clusters to write to the file.
|
||||
*/
|
||||
void ClusterFile::write(std::vector<Cluster> &clusters) {
|
||||
fwrite(clusters.data(), sizeof(Cluster), clusters.size(), fp);
|
||||
}
|
||||
/**
|
||||
* @brief Writes a single cluster to the file.
|
||||
*
|
||||
* The cluster is written as a binary block of size sizeof(Cluster).
|
||||
*
|
||||
* @param cluster The cluster to write to the file.
|
||||
*/
|
||||
void ClusterFile::write(Cluster &cluster) { fwrite(&cluster, sizeof(Cluster), 1, fp); }
|
||||
/**
|
||||
* @brief Reads a single cluster from the file.
|
||||
*
|
||||
* The cluster is read as a binary block of size sizeof(Cluster).
|
||||
*
|
||||
* @return The cluster read from the file.
|
||||
*/
|
||||
Cluster ClusterFile::read() {
|
||||
if (tell() >= count()) {
|
||||
throw std::runtime_error("cluster number out of range");
|
||||
}
|
||||
|
||||
Cluster cluster{};
|
||||
fread(&cluster, sizeof(Cluster), 1, fp);
|
||||
return cluster;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads a specific cluster from the file.
|
||||
*
|
||||
* The file pointer is moved to the specific cluster, and the cluster is read as a binary block of size sizeof(Cluster).
|
||||
*
|
||||
* @param cluster_number The number of the cluster to read from the file.
|
||||
* @return The cluster read from the file.
|
||||
*/
|
||||
Cluster ClusterFile::iread(size_t cluster_number) {
|
||||
if (cluster_number >= count()) {
|
||||
throw std::runtime_error("cluster number out of range");
|
||||
}
|
||||
|
||||
auto old_pos = ftell(fp);
|
||||
this->seek(cluster_number);
|
||||
Cluster cluster{};
|
||||
fread(&cluster, sizeof(Cluster), 1, fp);
|
||||
fseek(fp, old_pos, SEEK_SET); // restore the file position
|
||||
return cluster;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads a specific number of clusters from the file.
|
||||
*
|
||||
* Each cluster is read as a binary block of size sizeof(Cluster).
|
||||
*
|
||||
* @param n_clusters The number of clusters to read from the file.
|
||||
* @return A vector of clusters read from the file.
|
||||
*/
|
||||
std::vector<Cluster> ClusterFile::read(size_t n_clusters_) {
|
||||
if (n_clusters_ + tell() > count()) {
|
||||
throw std::runtime_error("cluster number out of range");
|
||||
}
|
||||
std::vector<Cluster> clusters(n_clusters_);
|
||||
fread(clusters.data(), sizeof(Cluster), n_clusters, fp);
|
||||
return clusters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Moves the file pointer to a specific cluster.
|
||||
*
|
||||
* The file pointer is moved to the start of the specific cluster, based on the size of a cluster.
|
||||
*
|
||||
* @param cluster_number The number of the cluster to move the file pointer to.
|
||||
*/
|
||||
void ClusterFile::seek(size_t cluster_number) {
|
||||
if (cluster_number > count()) {
|
||||
throw std::runtime_error("cluster number out of range");
|
||||
}
|
||||
|
||||
const auto offset = static_cast<int64_t>(sizeof(ClusterFileConfig) + cluster_number * sizeof(Cluster));
|
||||
|
||||
fseek(fp, offset, SEEK_SET);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the current position of the file pointer in terms of clusters.
|
||||
*
|
||||
* The position is calculated as the number of clusters from the beginning of the file to the current position of the
|
||||
* file pointer.
|
||||
*
|
||||
* @return The current position of the file pointer in terms of clusters.
|
||||
*/
|
||||
size_t ClusterFile::tell() const { return ftell(fp) / sizeof(Cluster); }
|
||||
|
||||
/**
|
||||
* @brief Counts the number of clusters in the file.
|
||||
*
|
||||
* The count is calculated as the size of the file divided by the size of a cluster.
|
||||
*
|
||||
* @return The number of clusters in the file.
|
||||
*/
|
||||
size_t ClusterFile::count() noexcept {
|
||||
if (mode == "r") {
|
||||
return n_clusters;
|
||||
}
|
||||
// save the current position
|
||||
auto old_pos = ftell(fp);
|
||||
fseek(fp, 0, SEEK_END);
|
||||
const size_t n_clusters_ = ftell(fp) / sizeof(Cluster);
|
||||
// restore the file position
|
||||
fseek(fp, old_pos, SEEK_SET);
|
||||
return n_clusters_;
|
||||
}
|
||||
|
||||
int32_t ClusterFile::frame() const { return frame_number; }
|
||||
void ClusterFile::update_header() {
|
||||
if (mode == "r") {
|
||||
throw std::runtime_error("update header is not implemented for read mode");
|
||||
}
|
||||
// update the header with the correct number of clusters
|
||||
aare::logger::debug("updating header with correct number of clusters", count());
|
||||
auto tmp_n_clusters = count();
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
ClusterFileConfig config(frame_number, static_cast<int32_t>(tmp_n_clusters));
|
||||
if (fwrite(&config, sizeof(config), 1, fp) != 1) {
|
||||
throw std::runtime_error("could not write header to file");
|
||||
}
|
||||
if (fflush(fp) != 0) {
|
||||
throw std::runtime_error("could not flush file");
|
||||
}
|
||||
}
|
||||
ClusterFile::~ClusterFile() noexcept {
|
||||
if (mode == "w") {
|
||||
try {
|
||||
update_header();
|
||||
} catch (std::runtime_error &e) {
|
||||
aare::logger::error("error updating header", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
if (fp != nullptr) {
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace aare
|
114
file_io/test/ClusterFile.test.cpp
Normal file
114
file_io/test/ClusterFile.test.cpp
Normal file
@ -0,0 +1,114 @@
|
||||
#include "aare/file_io/ClusterFile.hpp"
|
||||
#include "aare/utils/compare_files.hpp"
|
||||
#include "test_config.hpp"
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
using aare::Cluster;
|
||||
using aare::ClusterFile;
|
||||
using aare::ClusterFileConfig;
|
||||
|
||||
TEST_CASE("Read a cluster file") {
|
||||
auto fpath = test_data_path() / "clusters" / "single_frame_97_clustrers.clust";
|
||||
REQUIRE(std::filesystem::exists(fpath));
|
||||
ClusterFile cf(fpath, "r");
|
||||
|
||||
SECTION("Read the header of the file") {
|
||||
REQUIRE(cf.count() == 97);
|
||||
REQUIRE(cf.frame() == 135);
|
||||
}
|
||||
SECTION("Read a single cluster") {
|
||||
Cluster c = cf.read();
|
||||
REQUIRE(c.x == 1);
|
||||
REQUIRE(c.y == 200);
|
||||
for (int i = 0; i < 9; i++) {
|
||||
REQUIRE(c.data[i] == i);
|
||||
}
|
||||
}
|
||||
SECTION("Read a single cluster using iread") {
|
||||
Cluster c = cf.iread(0);
|
||||
REQUIRE(c.x == 1);
|
||||
REQUIRE(c.y == 200);
|
||||
for (int i = 0; i < 9; i++) {
|
||||
REQUIRE(c.data[i] == i);
|
||||
}
|
||||
}
|
||||
SECTION("Read a cluster using seek") {
|
||||
cf.seek(0);
|
||||
Cluster c = cf.read();
|
||||
REQUIRE(c.x == 1);
|
||||
REQUIRE(c.y == 200);
|
||||
for (int i = 0; i < 9; i++) {
|
||||
REQUIRE(c.data[i] == i);
|
||||
}
|
||||
c = cf.read();
|
||||
REQUIRE(c.x == 2);
|
||||
REQUIRE(c.y == 201);
|
||||
for (int i = 0; i < 9; i++) {
|
||||
REQUIRE(c.data[i] == i + 9);
|
||||
}
|
||||
}
|
||||
SECTION("check out of bound reading") {
|
||||
REQUIRE_THROWS_AS(cf.iread(97), std::runtime_error);
|
||||
REQUIRE_NOTHROW(cf.seek(97));
|
||||
REQUIRE_THROWS_AS(cf.read(), std::runtime_error);
|
||||
REQUIRE_THROWS_AS(cf.read(1), std::runtime_error);
|
||||
REQUIRE_NOTHROW(cf.seek(0));
|
||||
REQUIRE_NOTHROW(cf.read(97));
|
||||
}
|
||||
|
||||
SECTION("test read multiple clusters") {
|
||||
std::vector<Cluster> cluster = cf.read(97);
|
||||
REQUIRE(cluster.size() == 97);
|
||||
int offset = 0;
|
||||
int data_offset = 0;
|
||||
for (auto c : cluster) {
|
||||
REQUIRE(c.x == offset + 1);
|
||||
REQUIRE(c.y == offset + 200);
|
||||
for (int i = 0; i < 9; i++) {
|
||||
REQUIRE(c.data[i] == data_offset + i);
|
||||
}
|
||||
|
||||
offset++;
|
||||
data_offset += 9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("write a cluster file") {
|
||||
|
||||
auto const FRAME_NUMBER = 1461041991;
|
||||
auto const TOTAL_CLUSTERS = 214748;
|
||||
|
||||
std::filesystem::path const fpath_out("/tmp/file.clust");
|
||||
ClusterFile cf_out(fpath_out, "w", ClusterFileConfig(FRAME_NUMBER, TOTAL_CLUSTERS));
|
||||
REQUIRE(cf_out.count() == 0);
|
||||
REQUIRE(cf_out.frame() == FRAME_NUMBER);
|
||||
|
||||
// write file with random close to bounds values
|
||||
int32_t offset = 0;
|
||||
std::vector<Cluster> clusters(TOTAL_CLUSTERS);
|
||||
for (int32_t i = 0; i < TOTAL_CLUSTERS; i++) {
|
||||
Cluster c;
|
||||
c.x = INT16_MAX - offset;
|
||||
c.y = INT16_MAX - (offset + 200);
|
||||
for (int32_t j = 0; j < 9; j++) {
|
||||
if (j % 2 == 0)
|
||||
c.data[j] = -(offset * 2);
|
||||
else
|
||||
c.data[j] = (offset * 2);
|
||||
}
|
||||
clusters[i] = c;
|
||||
offset++;
|
||||
offset %= INT16_MAX - 200;
|
||||
}
|
||||
cf_out.write(clusters);
|
||||
REQUIRE(cf_out.count() == TOTAL_CLUSTERS);
|
||||
REQUIRE(cf_out.frame() == FRAME_NUMBER);
|
||||
cf_out.update_header();
|
||||
REQUIRE(cf_out.count() == TOTAL_CLUSTERS);
|
||||
REQUIRE(cf_out.frame() == FRAME_NUMBER);
|
||||
|
||||
auto data_file = test_data_path() / "clusters" / "test_writing.clust";
|
||||
REQUIRE(aare::compare_files(fpath_out, data_file));
|
||||
}
|
37
utils/include/aare/utils/compare_files.hpp
Normal file
37
utils/include/aare/utils/compare_files.hpp
Normal file
@ -0,0 +1,37 @@
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
|
||||
namespace aare {
|
||||
|
||||
/**
|
||||
* @brief Compare two files
|
||||
*
|
||||
* @param p1 path to the first file
|
||||
* @param p2 path to the second file
|
||||
* @return true if the files are the same, false otherwise
|
||||
*/
|
||||
bool compare_files(const std::string &p1, const std::string &p2) {
|
||||
std::ifstream f1(p1, std::ifstream::binary | std::ifstream::ate);
|
||||
std::ifstream f2(p2, std::ifstream::binary | std::ifstream::ate);
|
||||
|
||||
if (f1.fail() || f2.fail()) {
|
||||
return false; // file problem
|
||||
}
|
||||
|
||||
if (f1.tellg() != f2.tellg()) {
|
||||
return false; // size mismatch
|
||||
}
|
||||
|
||||
// seek back to beginning and use std::equal to compare contents
|
||||
f1.seekg(0, std::ifstream::beg);
|
||||
f2.seekg(0, std::ifstream::beg);
|
||||
return std::equal(std::istreambuf_iterator<char>(f1.rdbuf()), std::istreambuf_iterator<char>(),
|
||||
std::istreambuf_iterator<char>(f2.rdbuf()));
|
||||
}
|
||||
bool compare_files(const std::filesystem::path &p1, const std::filesystem::path &p2) {
|
||||
return compare_files(p1.string(), p2.string());
|
||||
}
|
||||
} // namespace aare
|
@ -244,7 +244,7 @@ extern aare::logger::Logger logger_instance; // NOLINT
|
||||
* @param s arguments to log
|
||||
* @return void
|
||||
*/
|
||||
template <LOGGING_LEVEL level, typename... Strings> void log(const Strings... s) {
|
||||
template <LOGGING_LEVEL level, typename... Strings> void log(const Strings... s) noexcept {
|
||||
internal::logger_instance.log<level>(s...);
|
||||
}
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user