24#include "hdf_interface.h"
34 #define RL_API __declspec(dllexport)
36 #define RL_API __declspec(dllimport)
51RL_API
int validate_collector_file_impl(
const HighFive::File & file,
const std::string & filename);
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);
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) ;
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
86RL_API
bool append_collector_files(
const std::string & out_filename,
const std::vector<std::string> & in_filenames,
bool reset_datasets);
101RL_API
bool merge_collector_files(
const std::string & out_filename,
const std::vector<std::string> & in_filenames,
bool remove_after_merge);
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);
133 std::set<std::string> users_;
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};
143 static const std::string & type_attribute() {
144 static const std::string name{
"type"};
147 static const std::string & collector_group_type() {
148 static const std::string name{
"Readouts"};
151 static const std::string & readout_dataset_name() {
152 static const std::string name{
"readouts"};
155 static const std::string & cue_dataset_name() {
156 static const std::string name{
"cues"};
159 static const std::string & weight_dataset_name() {
160 static const std::string name{
"weights"};
163 static const std::string & normalization_dataset_name() {
164 static const std::string name{
"normalizations"};
173 static const std::string name{
"detector"};
176 static const std::string & efu_address_attribute_name() {
177 static const std::string name{
"efu_address"};
180 static const std::string & efu_port_attribute_name() {
181 static const std::string name{
"efu_port"};
185 static const std::string & parameter_group_type() {
186 static const std::string name{
"Parameters"};
189 static const std::string & parameter_group_name() {
190 static const std::string name{
"parameters"};
193 static const std::string & parameter_unit_attribute_name() {
194 static const std::string name{
"unit"};
197 static const std::string & parameter_description_attribute_name() {
198 static const std::string name{
"description"};
201 static const std::string & program_attribute_name() {
202 static const std::string name{
"program"};
205 static const std::string & version_attribute_name() {
206 static const std::string name{
"version"};
209 static const std::string & revision_attribute_name() {
210 static const std::string name{
"revision"};
213 static const std::string & program_attribute_value() {
214 static const std::string name{
"libreadout"};
217 static const std::string & version_attribute_value() {
218 static const std::string name{
reinterpret_cast<const char *
>(libreadout::version::version_number)};
221 static const std::string & revision_attribute_value() {
222 static const std::string name{
reinterpret_cast<const char *
>(libreadout::version::git_revision)};
233 static void destroy() {
234 instance()->teardown();
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(); }
245 const char *
const flag = std::getenv(
"READOUT_ALLOW_EXISTING_FILE");
246 return flag !=
nullptr && flag[0] !=
'\0' && flag[0] !=
'0';
258 void setup(
const std::string& filename) {
259 const bool existing = std::filesystem::exists(filename);
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"
268 throw std::runtime_error(message.str());
270 filename_ = filename;
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();
278 collector_ = file_->getGroup(
"/");
279 ensure_file_attributes(*file_);
292 if (!collector_.has_value()) {
295 for (
const auto & name : collector_->listObjectNames()) {
296 if (name == parameter_group_name()) {
299 if (collector_->getObjectType(name) != HighFive::ObjectType::Group) {
302 if (
auto group = collector_->getGroup(name); group.exist(cue_dataset_name())) {
303 return group.getDataSet(cue_dataset_name()).getDimensions().back();
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) {
317 for (
size_t i = current; i < length; ++i) {
318 ds.select({i}, {1}).write(value);
323 if (users_.empty()) {
324 if (file_.has_value()) {
326 file_ = std::nullopt;
330 filename_ = std::nullopt;
331 collector_ = std::nullopt;
332 parameters_ = std::nullopt;
337 std::optional<HighFive::Group> getCollector(
338 const std::string& name,
339 const DetectorType& detector,
340 const ReadoutType& readout
342 return getCollector(name, hdf_compound_type(readout), detector);
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
358 using namespace HighFive;
359 if (!file_.has_value()) {
360 std::cerr <<
"File not initialized, cannot get dataset!" << std::endl;
363 if (!collector_.has_value()) {
364 collector_ = file_->getGroup(
"/");
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()) {
374 DataSetCreateProps props;
375 props.add(Chunking(std::vector<hsize_t>{100}));
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());
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);
393 }
else if (appending_) {
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));
414 return collector_->getGroup(name);
417 [[nodiscard]]
auto removeCollector(
const std::string& name) {
418 return users_.erase(name);
423 const std::string& name,
425 const std::optional<std::string> & unit = std::nullopt,
426 const std::optional<std::string> & description = std::nullopt
428 using namespace HighFive;
429 if (!file_.has_value()) {
430 std::cerr <<
"File not initialized, cannot add parameter!" << std::endl;
433 if (!parameters_.has_value()) {
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());
442 if (!parameters_->exist(name)) {
443 DataSetCreateProps props;
444 props.add(Chunking(std::vector<hsize_t>{100}));
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());
453 if (description.has_value()) {
454 ds.createAttribute(parameter_description_attribute_name(), description.value());
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;
462 if (
const auto n = parameter_unit_attribute_name();
464 && (!existing_ds.hasAttribute(n) || existing_ds.getAttribute(n).read<std::string>() != unit.value())
466 std::cerr <<
"Parameter " << name <<
" already exists with a different unit!" << std::endl;
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())
472 std::cerr <<
"Parameter " << name <<
" already exists with a different description!" << std::endl;
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) {
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);
492 std::optional<HighFive::Group> group_;
493 std::optional<HighFive::DataSet> dataset_;
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};
504 const std::string &filename,
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);
515 group_ = sink->getCollector(name_, detector_.value(), readout_.value());
516 dataset_ = group_->getDataSet(CollectorSink::readout_dataset_name());
530 const std::string &filename,
532 const std::string &type_description,
533 const uint64_t normalization=0,
535 ): name_{std::move(name)}, normalization_{normalization},
536 detector_{Type > 0 ? std::optional(detectorType_from_int(Type)) : std::nullopt},
537 readout_{std::nullopt} {
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);
545 group_ = sink->getCollector(name_, datatype_.value(), detector_, type_description);
546 dataset_ = group_->getDataSet(CollectorSink::readout_dataset_name());
549 [[nodiscard]]
size_t record_size()
const {
return record_size_; }
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;
561 auto & d = dataset_.value();
562 const auto pos = d.getDimensions().back();
564 d.select({pos}, {1}).write_raw(
static_cast<const uint8_t *
>(data), datatype_.value());
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");
573 switch (readout_.value()) {
574 case ReadoutType::CAEN: {
576 return saveReadout(event);
578 case ReadoutType::CDT: {
580 return saveReadout(event);
582 case ReadoutType::VMM3: {
584 return saveReadout(event);
586 case ReadoutType::BM0: {
588 return saveReadout(event);
590 case ReadoutType::BM2: {
592 return saveReadout(event);
594 case ReadoutType::BMI: {
596 return saveReadout(event);
599 throw std::runtime_error(
"Saving this readout type is not implemented yet!");
603 template<
class ... Args>
void addParameter(Args && ... args) {
604 CollectorSink::instance()->addParameter(std::forward<Args>(args)...);
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);
614 if (!group_->hasAttribute(CollectorSink::efu_port_attribute_name())) {
615 group_->createAttribute<
int>(CollectorSink::efu_port_attribute_name(), port);
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>());
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));
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>());
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;
646 void saveReadout(T data) {
648 if (!dataset_.has_value() || !datatype_.has_value()) {
649 std::cerr <<
"Dataset not initialized, cannot save readout!" << std::endl;
652 auto & d = dataset_.value();
654 auto pos = d.getDimensions().back();
657 d.select({pos}, {1}).write_raw(
reinterpret_cast<const uint8_t *
>(&data), datatype_.value());
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.