diff --git a/.clang-tidy b/.clang-tidy index 0f70e14..87c3908 100644 --- a/.clang-tidy +++ b/.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 diff --git a/.env.dev b/.env.dev new file mode 100644 index 0000000..b24c6c2 --- /dev/null +++ b/.env.dev @@ -0,0 +1 @@ +export AARE_ROOT_DIR="/home/l_bechir/github/aare/" diff --git a/.github/workflows/common-workflow.yml b/.github/workflows/common-workflow.yml index 3e28268..958f4aa 100644 --- a/.github/workflows/common-workflow.yml +++ b/.github/workflows/common-workflow.yml @@ -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 {} diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bf7b36..7907d4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/core/include/aare/core/defs.hpp b/core/include/aare/core/defs.hpp index 6bc0d54..73e9688 100644 --- a/core/include/aare/core/defs.hpp +++ b/core/include/aare/core/defs.hpp @@ -11,6 +11,20 @@ namespace aare { +struct Cluster { + int16_t x; + int16_t y; + std::array 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 */ diff --git a/data/clusters/beam_En700eV_-40deg_300V_10us_d0_f0_100.clust b/data/clusters/beam_En700eV_-40deg_300V_10us_d0_f0_100.clust new file mode 100755 index 0000000..5dca44a Binary files /dev/null and b/data/clusters/beam_En700eV_-40deg_300V_10us_d0_f0_100.clust differ diff --git a/data/clusters/single_frame_97_clustrers.clust b/data/clusters/single_frame_97_clustrers.clust new file mode 100755 index 0000000..08b2742 Binary files /dev/null and b/data/clusters/single_frame_97_clustrers.clust differ diff --git a/data/clusters/test_writing.clust b/data/clusters/test_writing.clust new file mode 100644 index 0000000..e3cca06 Binary files /dev/null and b/data/clusters/test_writing.clust differ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 12a7569..66e403d 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -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() diff --git a/examples/cluster_example.cpp b/examples/cluster_example.cpp new file mode 100644 index 0000000..b86679e --- /dev/null +++ b/examples/cluster_example.cpp @@ -0,0 +1,53 @@ +#include "aare/core/defs.hpp" +#include "aare/file_io/ClusterFile.hpp" +#include +#include + +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 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; +} \ No newline at end of file diff --git a/examples/include/aare/examples/defs.hpp b/examples/include/aare/examples/defs.hpp new file mode 100644 index 0000000..4c61b36 --- /dev/null +++ b/examples/include/aare/examples/defs.hpp @@ -0,0 +1,2 @@ +#pragma once +#define AARE_ROOT_DIR "AARE_ROOT_DIR" \ No newline at end of file diff --git a/examples/json_example.cpp b/examples/json_example.cpp index 833293a..5a8e16d 100644 --- a/examples/json_example.cpp +++ b/examples/json_example.cpp @@ -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 -#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'; diff --git a/examples/logger_example.cpp b/examples/logger_example.cpp index bb83411..a1b9a63 100644 --- a/examples/logger_example.cpp +++ b/examples/logger_example.cpp @@ -1,4 +1,6 @@ +#include "aare/examples/defs.hpp" #include "aare/utils/logger.hpp" + #include #include @@ -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; } \ No newline at end of file diff --git a/examples/multiport_example.cpp b/examples/multiport_example.cpp index 86da66e..cc7ac0c 100644 --- a/examples/multiport_example.cpp +++ b/examples/multiport_example.cpp @@ -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 -#define AARE_ROOT_DIR_VAR "PROJECT_ROOT_DIR" +#include 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'; diff --git a/examples/mythen_example.cpp b/examples/mythen_example.cpp index 79ad7b4..01bc021 100644 --- a/examples/mythen_example.cpp +++ b/examples/mythen_example.cpp @@ -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 -#define AARE_ROOT_DIR_VAR "PROJECT_ROOT_DIR" +#include 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"); } diff --git a/examples/numpy_read_example.cpp b/examples/numpy_read_example.cpp index d5708e6..906274d 100644 --- a/examples/numpy_read_example.cpp +++ b/examples/numpy_read_example.cpp @@ -1,8 +1,8 @@ // Your First C++ Program +#include "aare/examples/defs.hpp" #include "aare/file_io/File.hpp" -#include -#define AARE_ROOT_DIR_VAR "PROJECT_ROOT_DIR" +#include 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'; diff --git a/examples/numpy_write_example.cpp b/examples/numpy_write_example.cpp index fad2a77..6dea24e 100644 --- a/examples/numpy_write_example.cpp +++ b/examples/numpy_write_example.cpp @@ -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 -#define AARE_ROOT_DIR_VAR "PROJECT_ROOT_DIR" +#include using aare::File; using aare::FileConfig; diff --git a/examples/raw_example.cpp b/examples/raw_example.cpp index 9348fa6..c59ce32 100644 --- a/examples/raw_example.cpp +++ b/examples/raw_example.cpp @@ -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 -#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"); } diff --git a/examples/zmq_receiver_example.cpp b/examples/zmq_receiver_example.cpp index 27e7bd5..4f50b01 100644 --- a/examples/zmq_receiver_example.cpp +++ b/examples/zmq_receiver_example.cpp @@ -1,3 +1,4 @@ +#include "aare/examples/defs.hpp" #include "aare/network_io/ZmqSocketReceiver.hpp" #include "aare/network_io/defs.hpp" diff --git a/examples/zmq_restream_example.cpp b/examples/zmq_restream_example.cpp index 0e58321..28da551 100644 --- a/examples/zmq_restream_example.cpp +++ b/examples/zmq_restream_example.cpp @@ -1,3 +1,4 @@ +#include "aare/examples/defs.hpp" #include #include diff --git a/examples/zmq_sender_example.cpp b/examples/zmq_sender_example.cpp index 6dc179c..1e22503 100644 --- a/examples/zmq_sender_example.cpp +++ b/examples/zmq_sender_example.cpp @@ -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" diff --git a/file_io/CMakeLists.txt b/file_io/CMakeLists.txt index f8095af..8efb324 100644 --- a/file_io/CMakeLists.txt +++ b/file_io/CMakeLists.txt @@ -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) diff --git a/file_io/include/aare/file_io/ClusterFile.hpp b/file_io/include/aare/file_io/ClusterFile.hpp new file mode 100644 index 0000000..71d4970 --- /dev/null +++ b/file_io/include/aare/file_io/ClusterFile.hpp @@ -0,0 +1,70 @@ +#pragma once +#include "aare/core/defs.hpp" + +#include +#include + +/** + * 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 &clusters); + void write(Cluster &cluster); + Cluster read(); + Cluster iread(size_t cluster_number); + std::vector 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 \ No newline at end of file diff --git a/file_io/include/aare/file_io/RawFile.hpp b/file_io/include/aare/file_io/RawFile.hpp index 29980d8..cc7b399 100644 --- a/file_io/include/aare/file_io/RawFile.hpp +++ b/file_io/include/aare/file_io/RawFile.hpp @@ -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 read(size_t n_frames) override; void read_into(std::byte *image_buf) override { return get_frame_into(this->current_frame++, image_buf); }; diff --git a/file_io/src/ClusterFile.cpp b/file_io/src/ClusterFile.cpp new file mode 100644 index 0000000..877e974 --- /dev/null +++ b/file_io/src/ClusterFile.cpp @@ -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 &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 ClusterFile::read(size_t n_clusters_) { + if (n_clusters_ + tell() > count()) { + throw std::runtime_error("cluster number out of range"); + } + std::vector 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(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(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 \ No newline at end of file diff --git a/file_io/test/ClusterFile.test.cpp b/file_io/test/ClusterFile.test.cpp new file mode 100644 index 0000000..581b054 --- /dev/null +++ b/file_io/test/ClusterFile.test.cpp @@ -0,0 +1,114 @@ +#include "aare/file_io/ClusterFile.hpp" +#include "aare/utils/compare_files.hpp" +#include "test_config.hpp" +#include +#include +#include +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 = 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 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)); +} \ No newline at end of file diff --git a/utils/include/aare/utils/compare_files.hpp b/utils/include/aare/utils/compare_files.hpp new file mode 100644 index 0000000..eaaea37 --- /dev/null +++ b/utils/include/aare/utils/compare_files.hpp @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include + +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(f1.rdbuf()), std::istreambuf_iterator(), + std::istreambuf_iterator(f2.rdbuf())); +} +bool compare_files(const std::filesystem::path &p1, const std::filesystem::path &p2) { + return compare_files(p1.string(), p2.string()); +} +} // namespace aare \ No newline at end of file diff --git a/utils/include/aare/utils/logger.hpp b/utils/include/aare/utils/logger.hpp index 6e60fc2..858426a 100644 --- a/utils/include/aare/utils/logger.hpp +++ b/utils/include/aare/utils/logger.hpp @@ -244,7 +244,7 @@ extern aare::logger::Logger logger_instance; // NOLINT * @param s arguments to log * @return void */ -template void log(const Strings... s) { +template void log(const Strings... s) noexcept { internal::logger_instance.log(s...); } /**