McStas Readout Master 0.8.0
Loading...
Searching...
No Matches
CollectorClass.h
Go to the documentation of this file.
1// Copyright (C) 2026 European Spallation Source, ERIC. See LICENSE file
2//===----------------------------------------------------------------------===//
7//===----------------------------------------------------------------------===//
8#pragma once
9
10#include <filesystem>
11#include <cstdlib>
12#include <sstream>
13#include <string>
14#include <iomanip>
15#include <utility>
16#include <optional>
17#include <random>
18#include <map>
19#include <set>
20
21#include "Structs.h"
22#include "Readout.h"
23#include "enums.h"
24#include "hdf_interface.h"
25#include "version.hpp"
26#include "efu_time.h"
28
29
30#ifdef WIN32
31// Export symbols if compile flags "READOUT_SHARED" and "READOUT_EXPORT" are set on Windows.
32 #ifdef READOUT_SHARED
33 #ifdef READOUT_EXPORT
34 #define RL_API __declspec(dllexport)
35 #else
36 #define RL_API __declspec(dllimport)
37 #endif
38 #else
39 // Disable definition if linking statically.
40 #define RL_API
41 #endif
42#else
43// Disable definition for non-Win32 systems.
44#define RL_API
45#endif
46
50RL_API int validate_collector_file(const std::string & filename);
51RL_API int validate_collector_file_impl(const HighFive::File & file, const std::string & filename);
52
61RL_API bool warn_about_another_build(const HighFive::File & file, const std::string & filename);
62
63RL_API void ensure_file_attributes(HighFive::File & file);
64RL_API void ensure_collector_group_attributes(HighFive::Group & group);
65RL_API void ensure_parameter_group_attributes(HighFive::Group & group);
66
67RL_API std::string validate_file_attributes(const HighFive::File & file);
68RL_API std::string validate_collector_group(const HighFive::Group & group);
69RL_API std::string validate_collector_root(const HighFive::Group & group) ;
70
71RL_API bool validate_collector_files(
72 const std::vector<std::string> & in_filenames,
73 std::set<std::string> & datasets,
74 std::map<std::string, size_t> & sizes,
75 std::set<std::string> & valid_files,
76 std::string & collector_name,
77 std::set<std::string> & parameters
78 );
79
86RL_API bool append_collector_files(const std::string & out_filename, const std::vector<std::string> & in_filenames, bool reset_datasets);
87
93RL_API bool concatenate_collector_files(const std::string & out_filename, const std::vector<std::string> & in_filenames);
94
101RL_API bool merge_collector_files(const std::string & out_filename, const std::vector<std::string> & in_filenames, bool remove_after_merge);
102
108RL_API bool combine_collector_files(const std::string & out_filename, const std::vector<std::string> & in_filenames);
109
117RL_API void merge_collector_datasets(const std::string & out_filename, const std::vector<std::string> & in_filenames, const std::string & which_dataset, bool remove_after_merge);
118
126RL_API void copy_collector_parameters(const std::string & out_filename, const std::vector<std::string> & in_filenames);
127
128// A singleton object to hold the current runtime's output file for the collector
130protected:
131 CollectorSink() = default;
132
133 std::set<std::string> users_;
135 bool appending_{false};
137 size_t points_before_{0};
138 std::optional<std::string> filename_{std::nullopt};
139 std::optional<HighFive::File> file_{std::nullopt};
140 std::optional<HighFive::Group> collector_, parameters_{std::nullopt};
141
142public:
143 static const std::string & type_attribute() {
144 static const std::string name{"type"};
145 return name;
146 }
147 static const std::string & collector_group_type() {
148 static const std::string name{"Readouts"};
149 return name;
150 }
151 static const std::string & readout_dataset_name() {
152 static const std::string name{"readouts"};
153 return name;
154 }
155 static const std::string & cue_dataset_name() {
156 static const std::string name{"cues"};
157 return name;
158 }
159 static const std::string & weight_dataset_name() {
160 static const std::string name{"weights"};
161 return name;
162 }
163 static const std::string & normalization_dataset_name() {
164 static const std::string name{"normalizations"};
165 return name;
166 }
167
172 static const std::string & detector_attribute_name() {
173 static const std::string name{"detector"};
174 return name;
175 }
176 static const std::string & efu_address_attribute_name() {
177 static const std::string name{"efu_address"};
178 return name;
179 }
180 static const std::string & efu_port_attribute_name() {
181 static const std::string name{"efu_port"};
182 return name;
183 }
184
185 static const std::string & parameter_group_type() {
186 static const std::string name{"Parameters"};
187 return name;
188 }
189 static const std::string & parameter_group_name() {
190 static const std::string name{"parameters"};
191 return name;
192 }
193 static const std::string & parameter_unit_attribute_name() {
194 static const std::string name{"unit"};
195 return name;
196 }
197 static const std::string & parameter_description_attribute_name() {
198 static const std::string name{"description"};
199 return name;
200 }
201 static const std::string & program_attribute_name() {
202 static const std::string name{"program"};
203 return name;
204 }
205 static const std::string & version_attribute_name() {
206 static const std::string name{"version"};
207 return name;
208 }
209 static const std::string & revision_attribute_name() {
210 static const std::string name{"revision"};
211 return name;
212 }
213 static const std::string & program_attribute_value() {
214 static const std::string name{"libreadout"};
215 return name;
216 }
217 static const std::string & version_attribute_value() {
218 static const std::string name{reinterpret_cast<const char *>(libreadout::version::version_number)};
219 return name;
220 }
221 static const std::string & revision_attribute_value() {
222 static const std::string name{reinterpret_cast<const char *>(libreadout::version::git_revision)};
223 return name;
224 }
225
226 CollectorSink(CollectorSink &other) = delete;
227 void operator=(const CollectorSink &) = delete;
228 // Defined in CollectorClass.cpp and exported: the singleton must live in
229 // libreadout so that every module of a process (the library itself, any C++
230 // consumer executing these header-inline methods) shares ONE sink. A
231 // header-inline definition would give each Windows module its own copy.
232 RL_API static CollectorSink * instance();
233 static void destroy() {
234 instance()->teardown();
235 }
236
237 bool is_setup() const { return filename_.has_value(); }
238 [[nodiscard]] std::string current_filename() const { return filename_.value_or(""); }
239 [[nodiscard]] size_t user_count() const { return users_.size(); }
240
244 static bool allow_existing_file() {
245 const char * const flag = std::getenv("READOUT_ALLOW_EXISTING_FILE");
246 return flag != nullptr && flag[0] != '\0' && flag[0] != '0';
247 }
248
258 void setup(const std::string& filename) {
259 const bool existing = std::filesystem::exists(filename);
260 if (existing && !allow_existing_file()) {
261 std::stringstream message;
262 message << "Collector file '" << filename << "' already exists.\n"
263 << " A second run into one file merges its rays into the first run's scan\n"
264 << " point, leaving one set of parameter values standing for all of them.\n"
265 << " Write to a new output directory (--dir), or merge files deliberately\n"
266 << " with readout-combine. Set READOUT_ALLOW_EXISTING_FILE=1 to add to it\n"
267 << " anyway.";
268 throw std::runtime_error(message.str());
269 }
270 filename_ = filename;
271 try {
272 // If the file doesn't exist, create it, otherwise open it for read/write so we can add to it
273 file_ = HighFive::File(filename, HighFive::File::OpenOrCreate);
274 } catch (HighFive::Exception & ex) {
275 std::cout << "Error opening file " << filename << " for writing:\n" << ex.what();
276 throw;
277 }
278 collector_ = file_->getGroup("/");
279 ensure_file_attributes(*file_);
280 // Adding to a file that already holds points means adding a *point*, not more rays
281 // to the last one: this run had its own parameter values and its own ray count, and
282 // folding them into a previous run's point loses both.
283 appending_ = existing;
285 }
286
291 [[nodiscard]] size_t count_points() const {
292 if (!collector_.has_value()) {
293 return 0;
294 }
295 for (const auto & name : collector_->listObjectNames()) {
296 if (name == parameter_group_name()) {
297 continue;
298 }
299 if (collector_->getObjectType(name) != HighFive::ObjectType::Group) {
300 continue;
301 }
302 if (auto group = collector_->getGroup(name); group.exist(cue_dataset_name())) {
303 return group.getDataSet(cue_dataset_name()).getDimensions().back();
304 }
305 }
306 return 0;
307 }
308
310 template<class T>
311 static void extend_points(HighFive::DataSet ds, const size_t length, const T value) {
312 const auto current = ds.getDimensions().back();
313 if (current >= length) {
314 return;
315 }
316 ds.resize({length});
317 for (size_t i = current; i < length; ++i) {
318 ds.select({i}, {1}).write(value);
319 }
320 }
321
322 void teardown() {
323 if (users_.empty()) {
324 if (file_.has_value()) {
325 file_->flush();
326 file_ = std::nullopt;
327 }
328 appending_ = false;
329 points_before_ = 0;
330 filename_ = std::nullopt;
331 collector_ = std::nullopt;
332 parameters_ = std::nullopt;
333 }
334 }
335
336
337 std::optional<HighFive::Group> getCollector(
338 const std::string& name,
339 const DetectorType& detector,
340 const ReadoutType& readout
341 ) {
342 return getCollector(name, hdf_compound_type(readout), detector);
343 }
344
352 std::optional<HighFive::Group> getCollector(
353 const std::string& name,
354 const HighFive::DataType& datatype,
355 const std::optional<DetectorType>& detector = std::nullopt,
356 const std::optional<std::string>& description = std::nullopt
357 ) {
358 using namespace HighFive;
359 if (!file_.has_value()) {
360 std::cerr << "File not initialized, cannot get dataset!" << std::endl;
361 return std::nullopt;
362 }
363 if (!collector_.has_value()) {
364 collector_ = file_->getGroup("/");
365 }
366 if (!collector_->exist(name)) {
367 auto group = collector_->createGroup(name);
368 group.createAttribute<std::string>(type_attribute(), collector_group_type());
369 if (detector.has_value()) {
370 group.createAttribute<std::string>(detector_attribute_name(), detectorType_name(detector.value()));
371 }
372
373 // but should chunk file operations to avoid too much disk IO?
374 DataSetCreateProps props;
375 props.add(Chunking(std::vector<hsize_t>{100}));
376
377 // create the readouts dataset, set to an empty vector []
378 auto ds = group.createDataSet(readout_dataset_name(), DataSpace({0}, {DataSpace::UNLIMITED}), datatype, props);
379 if (description.has_value()) {
380 ds.createAttribute<std::string>(parameter_description_attribute_name(), description.value());
381 }
382 // One element per point, the last of which is the point being collected now.
383 // A group first seen partway through a file's life is padded with empty points,
384 // so every collector group carries the same number as the file does.
385 const auto points = points_before_ + 1;
386 const auto ws = group.createDataSet(weight_dataset_name(), DataSpace({points}, {DataSpace::UNLIMITED}), AtomicType<double>(), props);
387 for (size_t i = 0; i < points; ++i) ws.select({i},{1}).write(0.);
388 const auto cs = group.createDataSet(cue_dataset_name(), DataSpace({points}, {DataSpace::UNLIMITED}), AtomicType<uint32_t>(), props);
389 for (size_t i = 0; i < points; ++i) cs.select({i},{1}).write(0);
390 const auto ns = group.createDataSet(normalization_dataset_name(), DataSpace({points}, {DataSpace::UNLIMITED}), AtomicType<uint64_t>(), props);
391 for (size_t i = 0; i < points; ++i) ns.select({i},{1}).write(0);
392
393 } else if (appending_) {
394 // The group is already here from an earlier run, so open a new point on it: a
395 // fresh weight and normalization to accumulate into, and a cue seeded with the
396 // records already stored, which is where this point would end if it stored none.
397 auto group = collector_->getGroup(name);
398 const auto points = points_before_ + 1;
399 const auto cds = group.getDataSet(cue_dataset_name());
400 const auto stored = cds.getDimensions().back()
401 ? cds.select({cds.getDimensions().back() - 1}, {1}).read<uint32_t>() : 0u;
402 extend_points(group.getDataSet(weight_dataset_name()), points, 0.);
403 extend_points(cds, points, stored);
404 extend_points(group.getDataSet(normalization_dataset_name()), points,
405 static_cast<uint64_t>(0));
406 }
407 // Outside the branch above: a collector whose group is already in the file is still
408 // a user of it. Registering only new groups meant that re-opening a file left the
409 // sink with no users at all -- so the first-user gate never fired, ~Collector's
410 // removeCollector found nothing to remove, and teardown could close the file while
411 // another collector was still writing.
412 users_.insert(name);
413 // TODO verify that the group has the right components?
414 return collector_->getGroup(name);
415 }
416
417 [[nodiscard]] auto removeCollector(const std::string& name) {
418 return users_.erase(name);
419 }
420
421 template<class T>
422 void addParameter(
423 const std::string& name,
424 T value,
425 const std::optional<std::string> & unit = std::nullopt,
426 const std::optional<std::string> & description = std::nullopt
427 ) {
428 using namespace HighFive;
429 if (!file_.has_value()) {
430 std::cerr << "File not initialized, cannot add parameter!" << std::endl;
431 return;
432 }
433 if (!parameters_.has_value()) {
434 // Opened rather than created when it is already there, as getCollector does for
435 // collector groups: HighFive throws on createGroup for a name that exists, and
436 // the per-parameter datasets below already handle being written twice.
437 const auto & group_name = parameter_group_name();
438 parameters_ = collector_->exist(group_name) ? collector_->getGroup(group_name)
439 : collector_->createGroup(group_name);
440 ensure_parameter_group_attributes(parameters_.value());
441 }
442 if (!parameters_->exist(name)) {
443 DataSetCreateProps props;
444 props.add(Chunking(std::vector<hsize_t>{100}));
445 // One value per point. A parameter first recorded partway through a file's life
446 // is padded, so its length still matches the number of points.
447 const auto points = points_before_ + 1;
448 auto ds = parameters_->createDataSet(name, DataSpace({points}, {DataSpace::UNLIMITED}), create_datatype<T>(), props);
449 ds.select({points - 1},{1}).write(value);
450 if (unit.has_value()) {
451 ds.createAttribute(parameter_unit_attribute_name(), unit.value());
452 }
453 if (description.has_value()) {
454 ds.createAttribute(parameter_description_attribute_name(), description.value());
455 }
456 } else {
457 // verify that the existing dataset matches the new value's type
458 auto existing_ds = parameters_->getDataSet(name);
459 if (existing_ds.getDataType() != create_datatype<T>()) {
460 std::cerr << "Parameter " << name << " already exists with a different type!" << std::endl;
461 }
462 if (const auto n = parameter_unit_attribute_name();
463 unit.has_value()
464 && (!existing_ds.hasAttribute(n) || existing_ds.getAttribute(n).read<std::string>() != unit.value())
465 ) {
466 std::cerr << "Parameter " << name << " already exists with a different unit!" << std::endl;
467 }
468 if (const auto n = parameter_description_attribute_name();
469 description.has_value()
470 && (!existing_ds.hasAttribute(n) || existing_ds.getAttribute(n).read<std::string>() != description.value())
471 ) {
472 std::cerr << "Parameter " << name << " already exists with a different description!" << std::endl;
473 }
474 if (appending_) {
475 // This run is a new point, so the value belongs beside the earlier ones rather
476 // than on top of them -- the whole reason a parameter dataset is per-point.
477 extend_points(existing_ds, points_before_ + 1, value);
478 existing_ds.select({points_before_},{1}).write(value);
479 } else if (existing_ds.select({existing_ds.getDimensions().back()-1},{1}).read<T>() != value) {
480 // Written to the current point rather than over the whole dataset, which would
481 // overwrite every earlier point's value with this one.
482 std::cerr << "Parameter " << name << " already exists with a different value! Overwriting." << std::endl;
483 existing_ds.select({existing_ds.getDimensions().back()-1},{1}).write(value);
484 }
485 }
486 }
487};
488
489
491 std::string name_;
492 std::optional<HighFive::Group> group_;
493 std::optional<HighFive::DataSet> dataset_;
494 double weight_{0};
495 uint64_t normalization_{0};
496 std::optional<DetectorType> detector_{DetectorType::Reserved};
497 std::optional<ReadoutType> readout_{ReadoutType::CAEN};
498 std::optional<HighFive::DataType> datatype_{std::nullopt};
499 size_t record_size_{0};
500
501public:
502
503 explicit Collector(
504 const std::string &filename,
505 std::string name,
506 const int Type=0x34,
507 const uint64_t normalization=0
508 ): name_{std::move(name)}, normalization_{normalization}, detector_{detectorType_from_int(Type)}, readout_{readoutType_from_detectorType(detector_.value())} {
509 datatype_ = hdf_compound_type(readout_.value());
510 record_size_ = datatype_->getSize();
511 const auto sink = CollectorSink::instance();
512 if (!sink->is_setup()) {
513 sink->setup(filename);
514 }
515 group_ = sink->getCollector(name_, detector_.value(), readout_.value());
516 dataset_ = group_->getDataSet(CollectorSink::readout_dataset_name());
517 }
518
529 explicit Collector(
530 const std::string &filename,
531 std::string name,
532 const std::string &type_description,
533 const uint64_t normalization=0,
534 const int Type=0
535 ): name_{std::move(name)}, normalization_{normalization},
536 detector_{Type > 0 ? std::optional(detectorType_from_int(Type)) : std::nullopt},
537 readout_{std::nullopt} {
538 const auto schema = parse_type_description(type_description);
539 datatype_ = build_hdf5_compound_type(schema);
540 record_size_ = schema.total_size;
541 const auto sink = CollectorSink::instance();
542 if (!sink->is_setup()) {
543 sink->setup(filename);
544 }
545 group_ = sink->getCollector(name_, datatype_.value(), detector_, type_description);
546 dataset_ = group_->getDataSet(CollectorSink::readout_dataset_name());
547 }
548
549 [[nodiscard]] size_t record_size() const { return record_size_; }
550
555 void addRecord(const double weight, const void * data) {
556 if (!dataset_.has_value() || !datatype_.has_value()) {
557 std::cerr << "Dataset not initialized, cannot save record!" << std::endl;
558 return;
559 }
560 weight_ += weight;
561 auto & d = dataset_.value();
562 const auto pos = d.getDimensions().back();
563 d.resize({pos + 1});
564 d.select({pos}, {1}).write_raw(static_cast<const uint8_t *>(data), datatype_.value());
565 }
566
567 // Add a readout with time and weight information to the writer's storage
568 void addReadout(const uint8_t Ring, const uint8_t FEN, const double tof, const double weight, const void * data) {
569 if (!readout_.has_value()) {
570 throw std::runtime_error("addReadout requires a typed Collector; use addRecord with a description-based Collector");
571 }
572 weight_ += weight;
573 switch (readout_.value()) {
574 case ReadoutType::CAEN: {
575 const CAEN_event event{Ring, FEN, tof, weight, static_cast<const CAEN_readout_t*>(data)};
576 return saveReadout(event);
577 }
578 case ReadoutType::CDT: {
579 const CDT_event event{Ring, FEN, tof, weight, static_cast<const CDT_readout_t*>(data)};
580 return saveReadout(event);
581 }
582 case ReadoutType::VMM3: {
583 const VMM3_event event{Ring, FEN, tof, weight, static_cast<const VMM3_readout_t*>(data)};
584 return saveReadout(event);
585 }
586 case ReadoutType::BM0: {
587 const BM0_event event{Ring, FEN, tof, weight, static_cast<const BM0_readout_t*>(data)};
588 return saveReadout(event);
589 }
590 case ReadoutType::BM2: {
591 const BM2_event event{Ring, FEN, tof, weight, static_cast<const BM2_readout_t*>(data)};
592 return saveReadout(event);
593 }
594 case ReadoutType::BMI: {
595 const BMI_event event{Ring, FEN, tof, weight, static_cast<const BMI_readout_t*>(data)};
596 return saveReadout(event);
597 }
598 default:
599 throw std::runtime_error("Saving this readout type is not implemented yet!");
600 }
601 }
602
603 template<class ... Args> void addParameter(Args && ... args) {
604 CollectorSink::instance()->addParameter(std::forward<Args>(args)...);
605 }
606
609 void setEFU(const std::string & address, int port) {
610 if (!group_.has_value() || address.empty() || port <= 0) return;
611 if (!group_->hasAttribute(CollectorSink::efu_address_attribute_name())) {
612 group_->createAttribute<std::string>(CollectorSink::efu_address_attribute_name(), address);
613 }
614 if (!group_->hasAttribute(CollectorSink::efu_port_attribute_name())) {
615 group_->createAttribute<int>(CollectorSink::efu_port_attribute_name(), port);
616 }
617 }
618
619 ~Collector() {
620 // Add the accumulated weight to the existing dataset value -- which was initialized to [0.]
621 const auto wds = group_->getDataSet(CollectorSink::weight_dataset_name());
622 auto selection = wds.select({wds.getDimensions().back() - 1}, {1});
623 selection.write(weight_ + selection.read<double>());
624 // Record the number of readouts written -- where this points' readouts *end* (was initialized to [0u])
625 if (dataset_.has_value()) {
626 const auto count = dataset_->getDimensions().back();
627 const auto cds = group_->getDataSet(CollectorSink::cue_dataset_name());
628 auto cs = cds.select({cds.getDimensions().back()-1}, {1});
629 cs.write<uint32_t>(static_cast<uint32_t>(count));
630 }
631 // Add the normalization to the existing dataset value -- which was initialized to [0]
632 const auto nds = group_->getDataSet(CollectorSink::normalization_dataset_name());
633 auto ns = nds.select({nds.getDimensions().back() - 1}, {1});
634 ns.write(normalization_ + ns.read<uint64_t>());
635
636 const auto sink = CollectorSink::instance();
637 if (const auto count = sink->removeCollector(name_); count != 1) {
638 std::cerr << "Warning: removed collector " << name_ << " from sink " << count << " times, but expected to remove exactly one." << std::endl;
639 }
640 // try closing the sink file (internally it checks if any collectors are still using it):
641 sink->teardown();
642 }
643
644private:
645 template<class T>
646 void saveReadout(T data) {
647 //TODO Consider buffering this internally and only writing when the buffer is full (or closing the file)
648 if (!dataset_.has_value() || !datatype_.has_value()) {
649 std::cerr << "Dataset not initialized, cannot save readout!" << std::endl;
650 return;
651 }
652 auto & d = dataset_.value();
653 // the dataset should be 1-D ... hopefully that's true
654 auto pos = d.getDimensions().back();
655 auto size = pos + 1;
656 d.resize({size});
657 d.select({pos}, {1}).write_raw(reinterpret_cast<const uint8_t *>(&data), datatype_.value());
658 }
659};
660
661
662std::string filename_for_collector(const std::string & basepath, const std::string & basename, const std::string & extension = "h5");
663std::string filename_for_collector_node(const std::string & basepath, const std::string & basename, int node, int nodes);
RL_API bool merge_collector_files(const std::string &out_filename, const std::vector< std::string > &in_filenames, bool remove_after_merge)
Merge (append) readouts from multiple same-point collector files, optionally removing the inputs afte...
RL_API bool combine_collector_files(const std::string &out_filename, const std::vector< std::string > &in_filenames)
Combine multiple collector files automatically choosing append vs concatenate based on parameter iden...
RL_API void copy_collector_parameters(const std::string &out_filename, const std::vector< std::string > &in_filenames)
Copy parameters from multiple collector files into a single output file, for a specified scan point i...
RL_API bool warn_about_another_build(const HighFive::File &file, const std::string &filename)
Warn on stderr if a collector file was written by another libreadout build.
RL_API void merge_collector_datasets(const std::string &out_filename, const std::vector< std::string > &in_filenames, const std::string &which_dataset, bool remove_after_merge)
Merge dataset(s) from multiple collector files into a single output file, for a specified scan point ...
RL_API bool append_collector_files(const std::string &out_filename, const std::vector< std::string > &in_filenames, bool reset_datasets)
Append readouts from multiple same-point collector files into a single output file.
RL_API int validate_collector_file(const std::string &filename)
Validate that a given file is a valid collector file, and return the number of scan points it contain...
RL_API bool concatenate_collector_files(const std::string &out_filename, const std::vector< std::string > &in_filenames)
Concatenate readouts from multiple different-point collector files into a single multi-point output f...
Umbrella header for the C API used by the McStas components.
ESS and Bifrost readout data structures.
Parser for C struct type descriptions into a runtime type schema.
RL_API TypeSchema parse_type_description(const std::string &description)
Parse a C struct type description string into a TypeSchema.
Minimal beam-monitor readout event: channel only.
Position-resolving beam-monitor readout event: channel, x, y.
Integrating beam-monitor readout event: channel, sum, 24-bit ADC.
CAEN readout event: group channel and amplitudes A-D.
CDT (DREAM-family) readout event: output module, cathode, anode.
size_t count_points() const
How many scan points the file already holds, read from any collector group.
std::optional< HighFive::Group > getCollector(const std::string &name, const HighFive::DataType &datatype, const std::optional< DetectorType > &detector=std::nullopt, const std::optional< std::string > &description=std::nullopt)
Get (or create) a collector group for records of an arbitrary compound datatype.
void setup(const std::string &filename)
Open (or create) the one file this process collects into.
static void extend_points(HighFive::DataSet ds, const size_t length, const T value)
Grow a per-point dataset to length, filling any new elements with value.
bool appending_
Whether this run is adding a scan point to a file that already held some.
size_t points_before_
How many points the file held when this run opened it.
static bool allow_existing_file()
Whether a run may add to a collector file that already existed.
static const std::string & detector_attribute_name()
The detector identity is a GROUP attribute, alongside the EFU routing attributes: together they say w...
Collector(const std::string &filename, std::string name, const std::string &type_description, const uint64_t normalization=0, const int Type=0)
A description-based collector: records of a user-defined C struct layout.
void setEFU(const std::string &address, int port)
Write optional EFU destination attributes onto this collector group. Only writes if address is non-em...
void addRecord(const double weight, const void *data)
Store one record of the collector's compound type, accumulating its weight.
VMM3 readout event: BC, OTADC, GEO, TDC, VMM, channel.