From c815c524e8e59c7c9c19c44a1fc546b24f57baaa Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Sat, 6 May 2023 12:47:53 +0200 Subject: [PATCH 01/23] make naming of measurment id consistent in cluster writer --- Examples/Io/Csv/src/CsvMeasurementWriter.cpp | 2 +- Examples/Io/Csv/src/CsvOutputData.hpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Examples/Io/Csv/src/CsvMeasurementWriter.cpp b/Examples/Io/Csv/src/CsvMeasurementWriter.cpp index 724b305489b..3404f8cfe99 100644 --- a/Examples/Io/Csv/src/CsvMeasurementWriter.cpp +++ b/Examples/Io/Csv/src/CsvMeasurementWriter.cpp @@ -133,7 +133,7 @@ ActsExamples::ProcessCode ActsExamples::CsvMeasurementWriter::writeT( if (not clusters.empty() && writerCells) { auto cluster = clusters[hitIdx]; cell.geometry_id = meas.geometry_id; - cell.hit_id = meas.measurement_id; + cell.measurement_id = meas.measurement_id; for (auto& c : cluster.channels) { cell.channel0 = c.bin[0]; cell.channel1 = c.bin[1]; diff --git a/Examples/Io/Csv/src/CsvOutputData.hpp b/Examples/Io/Csv/src/CsvOutputData.hpp index cd58b22b1a6..fdf3a22caf0 100644 --- a/Examples/Io/Csv/src/CsvOutputData.hpp +++ b/Examples/Io/Csv/src/CsvOutputData.hpp @@ -139,10 +139,10 @@ struct MeasurementData { struct CellData { /// Hit surface identifier. uint64_t geometry_id = 0u; - /// Event-unique hit identifier. As defined for the simulated hit above and + /// Event-unique measurement identifier. As defined for the measurement above and /// used to link back to it; same value can appear multiple times for clusters /// with more than one active cell. - uint64_t hit_id = 0; + uint64_t measurement_id = 0; /// Digital cell address/ channel int32_t channel0 = 0, channel1 = 0; /// Digital cell timestamp. Not available in the TrackML datasets. @@ -150,7 +150,7 @@ struct CellData { /// (Digital) measured cell value, e.g. amplitude or time-over-threshold. float value = 0; - DFE_NAMEDTUPLE(CellData, geometry_id, hit_id, channel0, channel1, timestamp, + DFE_NAMEDTUPLE(CellData, geometry_id, measurement_id, channel0, channel1, timestamp, value); }; From 54c055cff4baa9acce5e2e97fde9b5b7de5f2b98 Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Sat, 6 May 2023 12:48:52 +0200 Subject: [PATCH 02/23] clang-format --- Examples/Io/Csv/src/CsvOutputData.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Examples/Io/Csv/src/CsvOutputData.hpp b/Examples/Io/Csv/src/CsvOutputData.hpp index fdf3a22caf0..42a0ac3bbfd 100644 --- a/Examples/Io/Csv/src/CsvOutputData.hpp +++ b/Examples/Io/Csv/src/CsvOutputData.hpp @@ -139,9 +139,9 @@ struct MeasurementData { struct CellData { /// Hit surface identifier. uint64_t geometry_id = 0u; - /// Event-unique measurement identifier. As defined for the measurement above and - /// used to link back to it; same value can appear multiple times for clusters - /// with more than one active cell. + /// Event-unique measurement identifier. As defined for the measurement above + /// and used to link back to it; same value can appear multiple times for + /// clusters with more than one active cell. uint64_t measurement_id = 0; /// Digital cell address/ channel int32_t channel0 = 0, channel1 = 0; @@ -150,8 +150,8 @@ struct CellData { /// (Digital) measured cell value, e.g. amplitude or time-over-threshold. float value = 0; - DFE_NAMEDTUPLE(CellData, geometry_id, measurement_id, channel0, channel1, timestamp, - value); + DFE_NAMEDTUPLE(CellData, geometry_id, measurement_id, channel0, channel1, + timestamp, value); }; struct SurfaceData { From a744feb1a41e6d645e7d9b571261c11036a4432f Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Sat, 6 May 2023 12:55:28 +0200 Subject: [PATCH 03/23] some more renaming --- Examples/Io/Csv/src/CsvMeasurementWriter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/Io/Csv/src/CsvMeasurementWriter.cpp b/Examples/Io/Csv/src/CsvMeasurementWriter.cpp index 3404f8cfe99..c63078e5043 100644 --- a/Examples/Io/Csv/src/CsvMeasurementWriter.cpp +++ b/Examples/Io/Csv/src/CsvMeasurementWriter.cpp @@ -83,7 +83,7 @@ ActsExamples::ProcessCode ActsExamples::CsvMeasurementWriter::writeT( MeasurementData meas; CellData cell; - // Will be reused as hit counter + // Will be reused as measurment counter meas.measurement_id = 0; ACTS_VERBOSE("Writing " << measurements.size() From 495bc4757b607f62799b1c5a7201f4f0a522771b Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Sat, 6 May 2023 12:57:09 +0200 Subject: [PATCH 04/23] forgot to save... --- Examples/Io/Csv/src/CsvMeasurementWriter.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Examples/Io/Csv/src/CsvMeasurementWriter.cpp b/Examples/Io/Csv/src/CsvMeasurementWriter.cpp index c63078e5043..78875e2eac9 100644 --- a/Examples/Io/Csv/src/CsvMeasurementWriter.cpp +++ b/Examples/Io/Csv/src/CsvMeasurementWriter.cpp @@ -83,18 +83,18 @@ ActsExamples::ProcessCode ActsExamples::CsvMeasurementWriter::writeT( MeasurementData meas; CellData cell; - // Will be reused as measurment counter + // Will be reused as measurement counter meas.measurement_id = 0; ACTS_VERBOSE("Writing " << measurements.size() << " measurements in this event."); - for (Index hitIdx = 0u; hitIdx < measurements.size(); ++hitIdx) { - const auto& measurement = measurements[hitIdx]; + for (Index measIdx = 0u; measIdx < measurements.size(); ++measIdx) { + const auto& measurement = measurements[measIdx]; - auto simHitIndices = makeRange(measurementSimHitsMap.equal_range(hitIdx)); + auto simHitIndices = makeRange(measurementSimHitsMap.equal_range(measIdx)); for (auto [_, simHitIdx] : simHitIndices) { - writerMeasurementSimHitMap.append({hitIdx, simHitIdx}); + writerMeasurementSimHitMap.append({measIdx, simHitIdx}); } std::visit( @@ -131,7 +131,7 @@ ActsExamples::ProcessCode ActsExamples::CsvMeasurementWriter::writeT( // CLUSTER / channel information ------------------------------ if (not clusters.empty() && writerCells) { - auto cluster = clusters[hitIdx]; + auto cluster = clusters[measIdx]; cell.geometry_id = meas.geometry_id; cell.measurement_id = meas.measurement_id; for (auto& c : cluster.channels) { From b28fccbbffe309db219dc8b28a807898b2bb2703 Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Thu, 15 Jun 2023 18:00:17 +0200 Subject: [PATCH 05/23] start to change reader and writer for clusters and started with unit test --- Examples/Io/Csv/src/CsvMeasurementReader.cpp | 52 ++++++++++++----- Examples/Io/Csv/src/CsvOutputData.hpp | 21 +++++++ .../Io/Csv/src/CsvPlanarClusterReader.cpp | 4 +- .../Io/Csv/src/CsvPlanarClusterWriter.cpp | 4 +- Tests/UnitTests/Examples/CMakeLists.txt | 2 + Tests/UnitTests/Examples/Csv/CMakeLists.txt | 3 + .../Csv/MeasurementReaderWriterTests.cpp | 56 +++++++++++++++++++ 7 files changed, 125 insertions(+), 17 deletions(-) create mode 100644 Tests/UnitTests/Examples/Csv/CMakeLists.txt create mode 100644 Tests/UnitTests/Examples/Csv/MeasurementReaderWriterTests.cpp diff --git a/Examples/Io/Csv/src/CsvMeasurementReader.cpp b/Examples/Io/Csv/src/CsvMeasurementReader.cpp index 66e770bcb29..a233cafa032 100644 --- a/Examples/Io/Csv/src/CsvMeasurementReader.cpp +++ b/Examples/Io/Csv/src/CsvMeasurementReader.cpp @@ -107,16 +107,6 @@ std::vector readMeasurementsByGeometryId( return measurements; } -std::vector readCellsByHitId( - const std::string& inputDir, size_t event) { - // timestamp is an optional element - auto cells = readEverything(inputDir, "cells.csv", - {"timestamp"}, event); - // sort for fast hit id look up - std::sort(cells.begin(), cells.end(), CompareHitId{}); - return cells; -} - } // namespace ActsExamples::ProcessCode ActsExamples::CsvMeasurementReader::read( @@ -133,7 +123,8 @@ ActsExamples::ProcessCode ActsExamples::CsvMeasurementReader::read( std::vector cellData = {}; if (not m_cfg.outputClusters.empty()) { - cellData = readCellsByHitId(m_cfg.inputDir, ctx.eventNumber); + cellData = readEverything( + m_cfg.inputDir, "cells.csv", {"timestamp"}, ctx.eventNumber); } // Prepare containers for the hit data using the framework event data types @@ -194,8 +185,8 @@ ActsExamples::ProcessCode ActsExamples::CsvMeasurementReader::read( // The measurement container is unordered and the index under which // the measurement will be stored is known before adding it. - Index hitIdx = orderedMeasurements.size(); - IndexSourceLink& sourceLink = sourceLinkStorage.emplace_back(geoId, hitIdx); + const Index index = orderedMeasurements.size(); + IndexSourceLink& sourceLink = sourceLinkStorage.emplace_back(geoId, index); auto measurement = createMeasurement(dParameters, sourceLink); // Due to the previous sorting of the raw hit data by geometry id, new @@ -210,6 +201,41 @@ ActsExamples::ProcessCode ActsExamples::CsvMeasurementReader::read( } sourceLinks.insert(sourceLinks.end(), std::cref(sourceLink)); + + // Get all cell data for this measurment + std::vector thisCellData; + std::copy_if(cellData.begin(), cellData.end(), thisCellData.begin(), + [&](const auto& cd) { return cd.measurement_id == index; }); + + // Fill the channels + Cluster cluster; + cluster.channels.reserve(thisCellData.size()); + for (const auto& cd : thisCellData) { + ActsFatras::Channelizer::Segment2D dummySegment; + ActsFatras::Channelizer::Bin2D bin{ + static_cast(cd.channel0), + static_cast(cd.channel1)}; + cluster.channels.emplace_back(bin, dummySegment, cd.value); + } + + // Compute cluster size + if (not cluster.channels.empty()) { + auto compareX = [](const auto& a, const auto& b) { + return a.bin[0] < b.bin[0]; + }; + auto compareY = [](const auto& a, const auto& b) { + return a.bin[1] < b.bin[1]; + }; + + auto [minX, maxX] = std::minmax_element(cluster.channels.begin(), + cluster.channels.end(), compareX); + auto [minY, maxY] = std::minmax_element(cluster.channels.begin(), + cluster.channels.end(), compareY); + cluster.sizeLoc0 = maxX->bin[0] - minX->bin[0]; + cluster.sizeLoc1 = maxY->bin[1] - minY->bin[1]; + } + + clusters.push_back(cluster); } MeasurementContainer measurements; diff --git a/Examples/Io/Csv/src/CsvOutputData.hpp b/Examples/Io/Csv/src/CsvOutputData.hpp index 42a0ac3bbfd..98e046ffec8 100644 --- a/Examples/Io/Csv/src/CsvOutputData.hpp +++ b/Examples/Io/Csv/src/CsvOutputData.hpp @@ -154,6 +154,27 @@ struct CellData { timestamp, value); }; + +// uses hit id +struct CellDataLegacy { + /// Hit surface identifier. + uint64_t geometry_id = 0u; + /// Event-unique measurement identifier. As defined for the measurement above + /// and used to link back to it; same value can appear multiple times for + /// clusters with more than one active cell. + uint64_t hit_id = 0; + /// Digital cell address/ channel + int32_t channel0 = 0, channel1 = 0; + /// Digital cell timestamp. Not available in the TrackML datasets. + float timestamp = 0; + /// (Digital) measured cell value, e.g. amplitude or time-over-threshold. + float value = 0; + + DFE_NAMEDTUPLE(CellDataLegacy, geometry_id, hit_id, channel0, channel1, + timestamp, value); +}; + + struct SurfaceData { /// Surface identifier. Not available in the TrackML datasets. uint64_t geometry_id = 0; diff --git a/Examples/Io/Csv/src/CsvPlanarClusterReader.cpp b/Examples/Io/Csv/src/CsvPlanarClusterReader.cpp index ae83b206715..2be5d17a8bb 100644 --- a/Examples/Io/Csv/src/CsvPlanarClusterReader.cpp +++ b/Examples/Io/Csv/src/CsvPlanarClusterReader.cpp @@ -123,10 +123,10 @@ std::vector readHitsByGeometryId( return hits; } -std::vector readCellsByHitId( +std::vector readCellsByHitId( const std::string& inputDir, size_t event) { // timestamp is an optional element - auto cells = readEverything(inputDir, "cells.csv", + auto cells = readEverything(inputDir, "cells.csv", {"timestamp"}, event); // sort for fast hit id look up std::sort(cells.begin(), cells.end(), CompareHitId{}); diff --git a/Examples/Io/Csv/src/CsvPlanarClusterWriter.cpp b/Examples/Io/Csv/src/CsvPlanarClusterWriter.cpp index ceea76ac238..12e3272101d 100644 --- a/Examples/Io/Csv/src/CsvPlanarClusterWriter.cpp +++ b/Examples/Io/Csv/src/CsvPlanarClusterWriter.cpp @@ -55,13 +55,13 @@ ActsExamples::ProcessCode ActsExamples::CsvPlanarClusterWriter::writeT( perEventFilepath(m_cfg.outputDir, "truth.csv", ctx.eventNumber); dfe::NamedTupleCsvWriter writerHits(pathHits, m_cfg.outputPrecision); - dfe::NamedTupleCsvWriter writerCells(pathCells, + dfe::NamedTupleCsvWriter writerCells(pathCells, m_cfg.outputPrecision); dfe::NamedTupleCsvWriter writerTruth(pathTruth, m_cfg.outputPrecision); HitData hit; - CellData cell; + CellDataLegacy cell; TruthHitData truth; // will be reused as hit counter hit.hit_id = 0; diff --git a/Tests/UnitTests/Examples/CMakeLists.txt b/Tests/UnitTests/Examples/CMakeLists.txt index fd13a837780..b08eb1f9246 100644 --- a/Tests/UnitTests/Examples/CMakeLists.txt +++ b/Tests/UnitTests/Examples/CMakeLists.txt @@ -1 +1,3 @@ add_subdirectory_if(Json ACTS_BUILD_PLUGIN_JSON) +add_subdirectory(Csv) + diff --git a/Tests/UnitTests/Examples/Csv/CMakeLists.txt b/Tests/UnitTests/Examples/Csv/CMakeLists.txt new file mode 100644 index 00000000000..f6d76c22b41 --- /dev/null +++ b/Tests/UnitTests/Examples/Csv/CMakeLists.txt @@ -0,0 +1,3 @@ +set(unittest_extra_libraries ActsExamplesDigitization ActsExamplesIoCsv) + +add_unittest(MeasurementReaderWriter MeasurementReaderWriterTests.cpp) diff --git a/Tests/UnitTests/Examples/Csv/MeasurementReaderWriterTests.cpp b/Tests/UnitTests/Examples/Csv/MeasurementReaderWriterTests.cpp new file mode 100644 index 00000000000..55abc322322 --- /dev/null +++ b/Tests/UnitTests/Examples/Csv/MeasurementReaderWriterTests.cpp @@ -0,0 +1,56 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2021 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include "Acts/Definitions/TrackParametrization.hpp" +#include "Acts/Utilities/BinUtility.hpp" +#include "ActsExamples/Digitization/DigitizationConfig.hpp" +#include "ActsExamples/Digitization/Smearers.hpp" +#include "ActsExamples/Io/Csv/CsvMeasurementReader.hpp" +#include "ActsExamples/Io/Csv/CsvMeasurementWriter.hpp" +#include "ActsExamples/Framework/WhiteBoard.hpp" +#include "ActsExamples/EventData/Measurement.hpp" +#include "ActsExamples/EventData/Cluster.hpp" + +#include +#include + +using namespace ActsExamples; + +const CsvMeasurementWriter::Config writerConfig; +const CsvMeasurementReader::Config readerConfig{ + writerConfig.outputDir, + writerConfig.inputMeasurements, + writerConfig.inputMeasurementSimHitsMap, + "sourcelinks", + writerConfig.inputClusters +}; + +// void write(const MeasurementContainer &m, const ClusterContainer &b;) { +// ActsExamples::WhiteBoard board; +// board.add +// +// auto cfg = CsvMeasurementWriter::Config(); +// +// +// } + +BOOST_AUTO_TEST_CASE(RoundTrip) { + + + // A: Write + { + + } + // B: Read + { + + } + +} From 90a4812c5037401cf7d90e559f3f493d8642a355 Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Fri, 16 Jun 2023 16:22:46 +0200 Subject: [PATCH 06/23] add round trip test --- Examples/Io/Csv/src/CsvMeasurementReader.cpp | 10 +- Examples/Io/Csv/src/CsvOutputData.hpp | 2 - .../Io/Csv/src/CsvPlanarClusterReader.cpp | 4 +- .../Io/Csv/src/CsvPlanarClusterWriter.cpp | 2 +- .../CommonHelpers/WhiteBoardUtilities.hpp | 109 ++++++++++++ .../Csv/MeasurementReaderWriterTests.cpp | 167 +++++++++++++++--- 6 files changed, 259 insertions(+), 35 deletions(-) create mode 100644 Tests/CommonHelpers/Acts/Tests/CommonHelpers/WhiteBoardUtilities.hpp diff --git a/Examples/Io/Csv/src/CsvMeasurementReader.cpp b/Examples/Io/Csv/src/CsvMeasurementReader.cpp index a233cafa032..21ab5094197 100644 --- a/Examples/Io/Csv/src/CsvMeasurementReader.cpp +++ b/Examples/Io/Csv/src/CsvMeasurementReader.cpp @@ -204,14 +204,16 @@ ActsExamples::ProcessCode ActsExamples::CsvMeasurementReader::read( // Get all cell data for this measurment std::vector thisCellData; - std::copy_if(cellData.begin(), cellData.end(), thisCellData.begin(), + std::copy_if(cellData.begin(), cellData.end(), + std::back_inserter(thisCellData), [&](const auto& cd) { return cd.measurement_id == index; }); // Fill the channels Cluster cluster; cluster.channels.reserve(thisCellData.size()); for (const auto& cd : thisCellData) { - ActsFatras::Channelizer::Segment2D dummySegment; + ActsFatras::Channelizer::Segment2D dummySegment = {Acts::Vector2::Zero(), + Acts::Vector2::Zero()}; ActsFatras::Channelizer::Bin2D bin{ static_cast(cd.channel0), static_cast(cd.channel1)}; @@ -231,8 +233,8 @@ ActsExamples::ProcessCode ActsExamples::CsvMeasurementReader::read( cluster.channels.end(), compareX); auto [minY, maxY] = std::minmax_element(cluster.channels.begin(), cluster.channels.end(), compareY); - cluster.sizeLoc0 = maxX->bin[0] - minX->bin[0]; - cluster.sizeLoc1 = maxY->bin[1] - minY->bin[1]; + cluster.sizeLoc0 = 1 + maxX->bin[0] - minX->bin[0]; + cluster.sizeLoc1 = 1 + maxY->bin[1] - minY->bin[1]; } clusters.push_back(cluster); diff --git a/Examples/Io/Csv/src/CsvOutputData.hpp b/Examples/Io/Csv/src/CsvOutputData.hpp index 98e046ffec8..1903306fb0c 100644 --- a/Examples/Io/Csv/src/CsvOutputData.hpp +++ b/Examples/Io/Csv/src/CsvOutputData.hpp @@ -154,7 +154,6 @@ struct CellData { timestamp, value); }; - // uses hit id struct CellDataLegacy { /// Hit surface identifier. @@ -174,7 +173,6 @@ struct CellDataLegacy { timestamp, value); }; - struct SurfaceData { /// Surface identifier. Not available in the TrackML datasets. uint64_t geometry_id = 0; diff --git a/Examples/Io/Csv/src/CsvPlanarClusterReader.cpp b/Examples/Io/Csv/src/CsvPlanarClusterReader.cpp index 2be5d17a8bb..2835983febe 100644 --- a/Examples/Io/Csv/src/CsvPlanarClusterReader.cpp +++ b/Examples/Io/Csv/src/CsvPlanarClusterReader.cpp @@ -126,8 +126,8 @@ std::vector readHitsByGeometryId( std::vector readCellsByHitId( const std::string& inputDir, size_t event) { // timestamp is an optional element - auto cells = readEverything(inputDir, "cells.csv", - {"timestamp"}, event); + auto cells = readEverything( + inputDir, "cells.csv", {"timestamp"}, event); // sort for fast hit id look up std::sort(cells.begin(), cells.end(), CompareHitId{}); return cells; diff --git a/Examples/Io/Csv/src/CsvPlanarClusterWriter.cpp b/Examples/Io/Csv/src/CsvPlanarClusterWriter.cpp index 12e3272101d..dbe862abadd 100644 --- a/Examples/Io/Csv/src/CsvPlanarClusterWriter.cpp +++ b/Examples/Io/Csv/src/CsvPlanarClusterWriter.cpp @@ -56,7 +56,7 @@ ActsExamples::ProcessCode ActsExamples::CsvPlanarClusterWriter::writeT( dfe::NamedTupleCsvWriter writerHits(pathHits, m_cfg.outputPrecision); dfe::NamedTupleCsvWriter writerCells(pathCells, - m_cfg.outputPrecision); + m_cfg.outputPrecision); dfe::NamedTupleCsvWriter writerTruth(pathTruth, m_cfg.outputPrecision); diff --git a/Tests/CommonHelpers/Acts/Tests/CommonHelpers/WhiteBoardUtilities.hpp b/Tests/CommonHelpers/Acts/Tests/CommonHelpers/WhiteBoardUtilities.hpp new file mode 100644 index 00000000000..1af67661741 --- /dev/null +++ b/Tests/CommonHelpers/Acts/Tests/CommonHelpers/WhiteBoardUtilities.hpp @@ -0,0 +1,109 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2021 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "ActsExamples/Framework/DataHandle.hpp" +#include "ActsExamples/Framework/SequenceElement.hpp" +#include "ActsExamples/Framework/WhiteBoard.hpp" + +#include + +namespace Acts::Test { +struct DummySequenceElement : public ActsExamples::SequenceElement { + ActsExamples::ProcessCode initialize() override { return {}; }; + ActsExamples::ProcessCode finalize() override { return {}; }; + ActsExamples::ProcessCode internalExecute( + const ActsExamples::AlgorithmContext &) override { + return {}; + }; + std::string name() const override { return {}; }; +}; + +template +void addToWhiteBoard(std::string name, T &&data, ActsExamples::WhiteBoard &wb) { + DummySequenceElement dummyElement; + + ActsExamples::WriteDataHandle handle(&dummyElement, name + "handle"); + handle.initialize(name); + handle(wb, std::forward(data)); +} + +template +T getFromWhiteBoard(std::string name, ActsExamples::WhiteBoard &wb) { + DummySequenceElement dummyElement; + + ActsExamples::ReadDataHandle handle(&dummyElement, name + "handle"); + handle.initialize(name); + return handle(wb); +} + +template , + typename str_tuple_t = std::tuple<>> +struct GenericReadWriteTool { + val_tuple_t tuple; + str_tuple_t strings; + + constexpr static std::size_t kSize = std::tuple_size_v; + static_assert(kSize == std::tuple_size_v); + + template + auto add(const std::string &name, T value) { + auto newTuple = std::tuple_cat(tuple, std::tuple{value}); + auto newStrings = std::tuple_cat(strings, std::tuple{name}); + + GenericReadWriteTool newInstance; + newInstance.tuple = std::move(newTuple); + newInstance.strings = std::move(newStrings); + + return newInstance; + } + + template + auto write(typename writer_t::Config cfg) { + ActsExamples::WhiteBoard board; + ActsExamples::AlgorithmContext ctx(0, 0, board); + + auto add = [&](auto &self, auto N) { + if constexpr (N() < kSize) { + addToWhiteBoard(std::get(strings), std::get(tuple), board); + self(self, std::integral_constant{}); + } + }; + + add(add, std::integral_constant{}); + + writer_t writer(cfg, Acts::Logging::Level::FATAL); + writer.internalExecute(ctx); + writer.finalize(); + } + + template + auto read(typename reader_t::Config cfg) { + ActsExamples::WhiteBoard board; + ActsExamples::AlgorithmContext ctx(0, 0, board); + + reader_t reader(cfg, Acts::Logging::Level::FATAL); + reader.internalExecute(ctx); + reader.finalize(); + + auto get = [&](auto &self, auto res, auto N) { + if constexpr (N() < kSize) { + using T = std::tuple_element_t; + auto val = getFromWhiteBoard(std::get(strings), board); + return self(self, std::tuple_cat(res, std::make_tuple(val)), + std::integral_constant{}); + } else { + return res; + } + }; + + return get(get, std::tuple<>{}, std::integral_constant{}); + } +}; +} // namespace Acts::Test diff --git a/Tests/UnitTests/Examples/Csv/MeasurementReaderWriterTests.cpp b/Tests/UnitTests/Examples/Csv/MeasurementReaderWriterTests.cpp index 55abc322322..9be789118e3 100644 --- a/Tests/UnitTests/Examples/Csv/MeasurementReaderWriterTests.cpp +++ b/Tests/UnitTests/Examples/Csv/MeasurementReaderWriterTests.cpp @@ -3,54 +3,169 @@ // Copyright (C) 2021 CERN for the benefit of the Acts project // // This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this +// License, v. 2.0. If a copy of the MPL was not distiributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include #include "Acts/Definitions/TrackParametrization.hpp" +#include "Acts/Tests/CommonHelpers/FloatComparisons.hpp" +#include "Acts/Tests/CommonHelpers/WhiteBoardUtilities.hpp" #include "Acts/Utilities/BinUtility.hpp" +#include "Acts/Utilities/Zip.hpp" #include "ActsExamples/Digitization/DigitizationConfig.hpp" #include "ActsExamples/Digitization/Smearers.hpp" +#include "ActsExamples/EventData/Cluster.hpp" +#include "ActsExamples/EventData/Measurement.hpp" #include "ActsExamples/Io/Csv/CsvMeasurementReader.hpp" #include "ActsExamples/Io/Csv/CsvMeasurementWriter.hpp" -#include "ActsExamples/Framework/WhiteBoard.hpp" -#include "ActsExamples/EventData/Measurement.hpp" -#include "ActsExamples/EventData/Cluster.hpp" #include #include +#include using namespace ActsExamples; +using namespace Acts::Test; -const CsvMeasurementWriter::Config writerConfig; -const CsvMeasurementReader::Config readerConfig{ - writerConfig.outputDir, - writerConfig.inputMeasurements, - writerConfig.inputMeasurementSimHitsMap, - "sourcelinks", - writerConfig.inputClusters -}; - -// void write(const MeasurementContainer &m, const ClusterContainer &b;) { -// ActsExamples::WhiteBoard board; -// board.add -// -// auto cfg = CsvMeasurementWriter::Config(); -// -// -// } +BOOST_AUTO_TEST_CASE(CsvMeasurmentRoundTrip) { + IndexSourceLinkContainer sourceLinksOriginal; + MeasurementContainer measOriginal; + ClusterContainer clusterOriginal; + IndexMultimap mapOriginal; + + //////////////////////////// + // Create some dummy data // + //////////////////////////// + const std::size_t nMeasurements = 3; + Acts::GeometryIdentifier someGeoId{298453}; + + std::mt19937 gen(23); + std::uniform_int_distribution disti(1, 10); + std::uniform_real_distribution distf(0.0, 1.0); + + for (auto i = 0ul; i < nMeasurements; ++i) { + IndexSourceLink sl(someGeoId, static_cast(i)); + sourceLinksOriginal.insert(sl); + + Acts::Vector2 p = Acts::Vector2::Random(); + Acts::SymMatrix2 c = Acts::SymMatrix2::Random(); + + auto m = Acts::makeMeasurement(Acts::SourceLink{sl}, p, c, Acts::eBoundLoc0, + Acts::eBoundLoc1); + + // TODO this fails! + // auto m = Acts::makeMeasurement(Acts::SourceLink{sl}, p, c, + // Acts::eBoundLoc0, + // Acts::eBoundTime); -BOOST_AUTO_TEST_CASE(RoundTrip) { + measOriginal.push_back(m); + ActsExamples::Cluster cl; - // A: Write - { + using Bin2D = ActsFatras::Channelizer::Bin2D; + using Seg2D = ActsFatras::Channelizer::Segment2D; + // We have two cluster shapes which are displaced randomly + const auto o = disti(gen); + cl.channels.emplace_back( + Bin2D{o + 0, o + 0}, + Seg2D{Acts::Vector2::Random(), Acts::Vector2::Random()}, distf(gen)); + cl.channels.emplace_back( + Bin2D{o + 0, o + 1}, + Seg2D{Acts::Vector2::Random(), Acts::Vector2::Random()}, distf(gen)); + if (distf(gen) < 0.5) { + cl.channels.emplace_back( + Bin2D{o + 0, o + 2}, + Seg2D{Acts::Vector2::Random(), Acts::Vector2::Random()}, distf(gen)); + cl.sizeLoc0 = 1; + cl.sizeLoc1 = 3; + } else { + cl.channels.emplace_back( + Bin2D{o + 1, o + 0}, + Seg2D{Acts::Vector2::Random(), Acts::Vector2::Random()}, distf(gen)); + cl.sizeLoc0 = 2; + cl.sizeLoc1 = 2; } - // B: Read - { + clusterOriginal.push_back(cl); + + mapOriginal.insert({i, disti(gen)}); + } + + ////////////////// + // Write & Read // + ////////////////// + CsvMeasurementWriter::Config writerConfig; + writerConfig.inputClusters = "clusters"; + writerConfig.inputMeasurements = "meas"; + writerConfig.inputMeasurementSimHitsMap = "map"; + writerConfig.outputDir = ""; + + CsvMeasurementReader::Config readerConfig; + readerConfig.inputDir = writerConfig.outputDir; + readerConfig.outputMeasurements = writerConfig.inputMeasurements; + readerConfig.outputMeasurementSimHitsMap = + writerConfig.inputMeasurementSimHitsMap; + readerConfig.outputClusters = writerConfig.inputClusters; + readerConfig.outputSourceLinks = "sourcelinks"; + + auto writeTool = + GenericReadWriteTool() + .add(writerConfig.inputMeasurements, measOriginal) + .add(writerConfig.inputClusters, clusterOriginal) + .add(writerConfig.inputMeasurementSimHitsMap, mapOriginal); + + writeTool.write(writerConfig); + + auto readTool = + writeTool.add(readerConfig.outputSourceLinks, sourceLinksOriginal); + + const auto [measRead, clusterRead, mapRead, sourceLinksRead] = + readTool.read(readerConfig); + + /////////// + // Check // + /////////// + auto checkMeasurementClose = [](const auto &m1, const auto &m2) { + constexpr auto SizeA = std::decay_t::size(); + constexpr auto SizeB = std::decay_t::size(); + if constexpr (SizeA == SizeB) { + CHECK_CLOSE_REL(m1.parameters(), m2.parameters(), 1e-4); + } + }; + + static_assert( + std::is_same_v, decltype(measOriginal)>); + BOOST_REQUIRE(measRead.size() == measOriginal.size()); + for (const auto &[a, b] : Acts::zip(measRead, measOriginal)) { + std::visit(checkMeasurementClose, a, b); + } + + static_assert(std::is_same_v, + decltype(clusterOriginal)>); + BOOST_REQUIRE(clusterRead.size() == clusterOriginal.size()); + for (const auto &[a, b] : Acts::zip(clusterRead, clusterOriginal)) { + BOOST_REQUIRE(a.sizeLoc0 == b.sizeLoc0); + BOOST_REQUIRE(a.sizeLoc1 == b.sizeLoc1); + for (const auto &[ca, cb] : Acts::zip(a.channels, b.channels)) { + BOOST_REQUIRE(ca.bin[0] == cb.bin[0]); + BOOST_REQUIRE(ca.bin[1] == cb.bin[1]); + CHECK_CLOSE_REL(ca.activation, cb.activation, 1.e-4); } + } + + static_assert( + std::is_same_v, decltype(mapOriginal)>); + BOOST_REQUIRE(mapRead.size() == mapOriginal.size()); + for (const auto &[a, b] : Acts::zip(mapRead, mapOriginal)) { + BOOST_REQUIRE(a == b); + } + static_assert(std::is_same_v, + decltype(sourceLinksOriginal)>); + BOOST_REQUIRE(sourceLinksRead.size() == sourceLinksOriginal.size()); + for (const auto &[a, b] : Acts::zip(sourceLinksRead, sourceLinksOriginal)) { + BOOST_REQUIRE(a.geometryId() == b.geometryId()); + BOOST_REQUIRE(a.index() == b.index()); + } } From 1a766e8086e1ac7b9a62c5edf181857701001e42 Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Fri, 16 Jun 2023 16:43:53 +0200 Subject: [PATCH 07/23] fix --- .../Examples/Csv/MeasurementReaderWriterTests.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Tests/UnitTests/Examples/Csv/MeasurementReaderWriterTests.cpp b/Tests/UnitTests/Examples/Csv/MeasurementReaderWriterTests.cpp index 9be789118e3..29b34341348 100644 --- a/Tests/UnitTests/Examples/Csv/MeasurementReaderWriterTests.cpp +++ b/Tests/UnitTests/Examples/Csv/MeasurementReaderWriterTests.cpp @@ -1,9 +1,9 @@ // This file is part of the Acts project. // -// Copyright (C) 2021 CERN for the benefit of the Acts project +// Copyright (C) 2023 CERN for the benefit of the Acts project // // This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distiributed with this +// License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include @@ -89,7 +89,8 @@ BOOST_AUTO_TEST_CASE(CsvMeasurmentRoundTrip) { clusterOriginal.push_back(cl); - mapOriginal.insert({i, disti(gen)}); + // Just generate some random hitid + mapOriginal.insert(std::pair{i, disti(gen)}); } ////////////////// From d2614a25ac34605a1c57c0b3f9970c6c9350a89e Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Tue, 20 Jun 2023 11:36:50 +0200 Subject: [PATCH 08/23] add log failing thresholds --- .../Acts/Tests/CommonHelpers/WhiteBoardUtilities.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/CommonHelpers/Acts/Tests/CommonHelpers/WhiteBoardUtilities.hpp b/Tests/CommonHelpers/Acts/Tests/CommonHelpers/WhiteBoardUtilities.hpp index 1af67661741..2e195f9fb5c 100644 --- a/Tests/CommonHelpers/Acts/Tests/CommonHelpers/WhiteBoardUtilities.hpp +++ b/Tests/CommonHelpers/Acts/Tests/CommonHelpers/WhiteBoardUtilities.hpp @@ -78,7 +78,7 @@ struct GenericReadWriteTool { add(add, std::integral_constant{}); - writer_t writer(cfg, Acts::Logging::Level::FATAL); + writer_t writer(cfg, Acts::Logging::Level::WARNING); writer.internalExecute(ctx); writer.finalize(); } @@ -88,7 +88,7 @@ struct GenericReadWriteTool { ActsExamples::WhiteBoard board; ActsExamples::AlgorithmContext ctx(0, 0, board); - reader_t reader(cfg, Acts::Logging::Level::FATAL); + reader_t reader(cfg, Acts::Logging::Level::WARNING); reader.internalExecute(ctx); reader.finalize(); From 4543e763c688906d353b32c681904e7f1d8ce98d Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Tue, 20 Jun 2023 12:24:07 +0200 Subject: [PATCH 09/23] make clang tidy happy --- .../Acts/Tests/CommonHelpers/WhiteBoardUtilities.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/CommonHelpers/Acts/Tests/CommonHelpers/WhiteBoardUtilities.hpp b/Tests/CommonHelpers/Acts/Tests/CommonHelpers/WhiteBoardUtilities.hpp index 2e195f9fb5c..759459e1761 100644 --- a/Tests/CommonHelpers/Acts/Tests/CommonHelpers/WhiteBoardUtilities.hpp +++ b/Tests/CommonHelpers/Acts/Tests/CommonHelpers/WhiteBoardUtilities.hpp @@ -19,7 +19,7 @@ struct DummySequenceElement : public ActsExamples::SequenceElement { ActsExamples::ProcessCode initialize() override { return {}; }; ActsExamples::ProcessCode finalize() override { return {}; }; ActsExamples::ProcessCode internalExecute( - const ActsExamples::AlgorithmContext &) override { + const ActsExamples::AlgorithmContext &/*context*/) override { return {}; }; std::string name() const override { return {}; }; From 69b1fda5cfa8c116630020e6f70ec73e6908739a Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Fri, 28 Jul 2023 10:44:06 +0200 Subject: [PATCH 10/23] add reader and unit test --- Examples/Io/Root/CMakeLists.txt | 1 + .../ActsExamples/Io/Root/RootSimHitReader.hpp | 111 ++++++++++++ Examples/Io/Root/src/RootSimHitReader.cpp | 168 ++++++++++++++++++ Examples/Python/src/Input.cpp | 5 + .../CommonHelpers/WhiteBoardUtilities.hpp | 105 +++++++++++ Tests/UnitTests/Examples/Io/CMakeLists.txt | 1 + .../UnitTests/Examples/Io/Root/CMakeLists.txt | 3 + .../Io/Root/SimhitReaderWriterTests.cpp | 113 ++++++++++++ 8 files changed, 507 insertions(+) create mode 100644 Examples/Io/Root/include/ActsExamples/Io/Root/RootSimHitReader.hpp create mode 100644 Examples/Io/Root/src/RootSimHitReader.cpp create mode 100644 Tests/CommonHelpers/Acts/Tests/CommonHelpers/WhiteBoardUtilities.hpp create mode 100644 Tests/UnitTests/Examples/Io/Root/CMakeLists.txt create mode 100644 Tests/UnitTests/Examples/Io/Root/SimhitReaderWriterTests.cpp diff --git a/Examples/Io/Root/CMakeLists.txt b/Examples/Io/Root/CMakeLists.txt index 9814a67b357..a2a82ed1ba7 100644 --- a/Examples/Io/Root/CMakeLists.txt +++ b/Examples/Io/Root/CMakeLists.txt @@ -10,6 +10,7 @@ add_library( src/RootParticleReader.cpp src/RootPropagationStepsWriter.cpp src/RootSimHitWriter.cpp + src/RootSimHitReader.cpp src/RootSpacepointWriter.cpp src/RootTrackParameterWriter.cpp src/RootTrajectoryStatesWriter.cpp diff --git a/Examples/Io/Root/include/ActsExamples/Io/Root/RootSimHitReader.hpp b/Examples/Io/Root/include/ActsExamples/Io/Root/RootSimHitReader.hpp new file mode 100644 index 00000000000..acc5c03601b --- /dev/null +++ b/Examples/Io/Root/include/ActsExamples/Io/Root/RootSimHitReader.hpp @@ -0,0 +1,111 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2023 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/Definitions/Algebra.hpp" +#include "Acts/Propagator/MaterialInteractor.hpp" +#include "Acts/Utilities/Logger.hpp" +#include "ActsExamples/EventData/SimHit.hpp" +#include "ActsExamples/Framework/DataHandle.hpp" +#include "ActsExamples/Framework/IReader.hpp" +#include "ActsExamples/Framework/ProcessCode.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +class TChain; + +namespace ActsExamples { +struct AlgorithmContext; + +/// @class RootParticleReader +/// +/// @brief Reads in Particles information from a root file +class RootSimHitReader : public IReader { + public: + /// @brief The nested configuration struct + struct Config { + /// name of the whiteboard entry + std::string simHitCollection = "simhits"; + /// name of the output tree + std::string treeName = "hits"; + ///< The name of the input file + std::string filePath; + /// Whether the events are ordered or not + bool orderedEvents = true; + }; + + RootSimHitReader(const RootSimHitReader &) = delete; + RootSimHitReader(const RootSimHitReader &&) = delete; + + /// Constructor + /// @param config The Configuration struct + RootSimHitReader(const Config &config, Acts::Logging::Level level); + + /// Framework name() method + std::string name() const override { return "RootSimHitReader"; } + + /// Return the available events range. + std::pair availableEvents() const override; + + /// Read out data from the input stream + /// + /// @param context The algorithm context + ProcessCode read(const ActsExamples::AlgorithmContext &context) override; + + /// Readonly access to the config + const Config &config() const { return m_cfg; } + + private: + /// Private access to the logging instance + const Acts::Logger &logger() const { return *m_logger; } + + /// The config class + Config m_cfg; + + WriteDataHandle m_outputSimHits{this, "OutputSimHits"}; + std::unique_ptr m_logger; + + /// mutex used to protect multi-threaded reads + std::mutex m_read_mutex; + + /// Vector of {eventNr, entryMin, entryMax} + std::vector> m_eventMap; + + /// The input tree name + TChain *m_inputChain = nullptr; + + /// The entry numbers for accessing events in increased order (there could be + /// multiple entries corresponding to one event number) + std::vector m_entryNumbers = {}; + + /// The keys we have in the ROOT file + constexpr static std::array m_floatKeys = { + "tx", "ty", "tz", "tt", "tpx", "tpy", + "tpz", "te", "deltapx", "deltapy", "deltapz", "deltae"}; + constexpr static std::array m_uint64Keys = {"geometry_id", + "particle_id"}; + constexpr static std::array m_uint32Keys = { + "event_id", "volume_id", "boundary_id", + "layer_id", "approach_id", "sensitive_id"}; + constexpr static std::array m_int32Keys = {"index"}; + + std::unordered_map m_floatColumns; + std::unordered_map m_uint64Columns; + std::unordered_map m_uint32Columns; + std::unordered_map m_int32Columns; +}; + +} // namespace ActsExamples diff --git a/Examples/Io/Root/src/RootSimHitReader.cpp b/Examples/Io/Root/src/RootSimHitReader.cpp new file mode 100644 index 00000000000..a3de0592249 --- /dev/null +++ b/Examples/Io/Root/src/RootSimHitReader.cpp @@ -0,0 +1,168 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2023 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "ActsExamples/Io/Root/RootSimHitReader.hpp" + +#include "Acts/Definitions/PdgParticle.hpp" +#include "Acts/Utilities/Logger.hpp" +#include "ActsExamples/EventData/SimParticle.hpp" +#include "ActsExamples/Framework/AlgorithmContext.hpp" +#include "ActsFatras/EventData/ProcessType.hpp" + +#include +#include +#include +#include + +#include +#include + +ActsExamples::RootSimHitReader::RootSimHitReader( + const ActsExamples::RootSimHitReader::Config& config, + Acts::Logging::Level level) + : ActsExamples::IReader(), + m_cfg(config), + m_logger(Acts::getDefaultLogger(name(), level)) { + m_inputChain = new TChain(m_cfg.treeName.c_str()); + + if (m_cfg.filePath.empty()) { + throw std::invalid_argument("Missing input filename"); + } + if (m_cfg.treeName.empty()) { + throw std::invalid_argument("Missing tree name"); + } + + m_outputSimHits.initialize(m_cfg.simHitCollection); + + // Set the branches + int f = 0; + auto setBranches = [&](const auto& keys, auto& columns) { + for (auto key : keys) { + columns.insert({key, f++}); + } + for (auto key : keys) { + m_inputChain->SetBranchAddress(key, &columns.at(key)); + } + }; + + setBranches(m_floatKeys, m_floatColumns); + setBranches(m_uint32Keys, m_uint32Columns); + setBranches(m_uint64Keys, m_uint64Columns); + setBranches(m_int32Keys, m_int32Columns); + + // add file to the input chain + m_inputChain->Add(m_cfg.filePath.c_str()); + m_inputChain->LoadTree(0); + ACTS_DEBUG("Adding File " << m_cfg.filePath << " to tree '" << m_cfg.treeName + << "'."); + + // Because each hit is stored in a single entry in the root file, we need to + // scan the file first for the positions of the events in the file in order to + // efficiently read the events later on. + // TODO change the file format to store one event per entry + + // Disable all branches and only enable event-id for a first scan of the file + m_inputChain->SetBranchStatus("*", false); + m_inputChain->SetBranchStatus("event_id", true); + + auto nEntries = static_cast(m_inputChain->GetEntriesFast()); + + // Add the first entry + m_inputChain->GetEntry(0); + m_eventMap.push_back({m_uint32Columns.at("event_id"), 0ul, 0ul}); + + // Go through all entries and store the position of new events + for (auto i = 1ul; i < nEntries; ++i) { + m_inputChain->GetEntry(i); + const auto evtId = m_uint32Columns.at("event_id"); + + if (evtId != std::get<0>(m_eventMap.back())) { + std::get<2>(m_eventMap.back()) = i; + m_eventMap.push_back({evtId, i, 0ul}); + } + } + + std::get<2>(m_eventMap.back()) = nEntries; + + // Sort by event id + std::sort(m_eventMap.begin(), m_eventMap.end(), + [](const auto& a, const auto& b) { + return std::get<0>(a) < std::get<0>(b); + }); + + // Re-Enable all branches + m_inputChain->SetBranchStatus("*", true); +} + +std::pair ActsExamples::RootSimHitReader::availableEvents() + const { + return {std::get<0>(m_eventMap.front()), std::get<0>(m_eventMap.back())}; +} + +ActsExamples::ProcessCode ActsExamples::RootSimHitReader::read( + const ActsExamples::AlgorithmContext& context) { + auto it = std::find_if( + m_eventMap.begin(), m_eventMap.end(), + [&](const auto& a) { return std::get<0>(a) == context.eventNumber; }); + + if (m_inputChain == nullptr || it == m_eventMap.end()) { + ACTS_ERROR("Cannot read hits of event " << context.eventNumber); + return ActsExamples::ProcessCode::ABORT; + } + + // lock the mutex + std::lock_guard lock(m_read_mutex); + + ACTS_DEBUG("Reading event: " << std::get<0>(*it) + << " stored in entries: " << std::get<1>(*it) + << " - " << std::get<2>(*it)); + + SimHitContainer hits; + for (auto entry = std::get<1>(*it); entry < std::get<2>(*it); ++entry) { + m_inputChain->GetEntry(entry); + + auto eventId = m_uint32Columns.at("event_id"); + if (eventId != context.eventNumber) { + break; + } + + const Acts::GeometryIdentifier geoid = m_uint64Columns.at("geometry_id"); + const SimBarcode pid = m_uint64Columns.at("particle_id"); + const auto index = m_int32Columns.at("index"); + + const Acts::Vector4 pos4 = { + m_floatColumns.at("tx") * Acts::UnitConstants::mm, + m_floatColumns.at("ty") * Acts::UnitConstants::mm, + m_floatColumns.at("tz") * Acts::UnitConstants::mm, + m_floatColumns.at("tt") * Acts::UnitConstants::ns, + }; + + const Acts::Vector4 before4 = { + m_floatColumns.at("tpx") * Acts::UnitConstants::GeV, + m_floatColumns.at("tpy") * Acts::UnitConstants::GeV, + m_floatColumns.at("tpz") * Acts::UnitConstants::GeV, + m_floatColumns.at("te") * Acts::UnitConstants::GeV, + }; + + const Acts::Vector4 delta = { + m_floatColumns.at("deltapx") * Acts::UnitConstants::GeV, + m_floatColumns.at("deltapy") * Acts::UnitConstants::GeV, + m_floatColumns.at("deltapz") * Acts::UnitConstants::GeV, + m_floatColumns.at("deltae") * Acts::UnitConstants::GeV, + }; + + SimHit hit(geoid, pid, pos4, before4, before4 + delta, index); + + hits.insert(hit); + } + + m_outputSimHits(context, std::move(hits)); + + // Return success flag + return ActsExamples::ProcessCode::SUCCESS; +} diff --git a/Examples/Python/src/Input.cpp b/Examples/Python/src/Input.cpp index 1f91610fa4e..c73bed7f28b 100644 --- a/Examples/Python/src/Input.cpp +++ b/Examples/Python/src/Input.cpp @@ -17,6 +17,7 @@ #include "ActsExamples/Io/Root/RootAthenaNTupleReader.hpp" #include "ActsExamples/Io/Root/RootMaterialTrackReader.hpp" #include "ActsExamples/Io/Root/RootParticleReader.hpp" +#include "ActsExamples/Io/Root/RootSimHitReader.hpp" #include "ActsExamples/Io/Root/RootTrajectorySummaryReader.hpp" #include @@ -81,5 +82,9 @@ void addInput(Context& ctx) { inputFilePath, outputTrackParameters, outputTruthVtxParameters, outputRecoVtxParameters, outputBeamspotConstraint); + + ACTS_PYTHON_DECLARE_READER(ActsExamples::RootSimHitReader, mex, + "RootSimHitReader", treeName, filePath, + simHitCollection); } } // namespace Acts::Python diff --git a/Tests/CommonHelpers/Acts/Tests/CommonHelpers/WhiteBoardUtilities.hpp b/Tests/CommonHelpers/Acts/Tests/CommonHelpers/WhiteBoardUtilities.hpp new file mode 100644 index 00000000000..461f90759d5 --- /dev/null +++ b/Tests/CommonHelpers/Acts/Tests/CommonHelpers/WhiteBoardUtilities.hpp @@ -0,0 +1,105 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2023 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "ActsExamples/Framework/DataHandle.hpp" +#include "ActsExamples/Framework/SequenceElement.hpp" +#include "ActsExamples/Framework/WhiteBoard.hpp" + +#include + +namespace Acts::Test { +struct DummySequenceElement : public ActsExamples::SequenceElement { + ActsExamples::ProcessCode initialize() override { return {}; }; + ActsExamples::ProcessCode finalize() override { return {}; }; + ActsExamples::ProcessCode internalExecute( + const ActsExamples::AlgorithmContext & /*context*/) override { + return {}; + }; + std::string name() const override { return {}; }; +}; + +template +void addToWhiteBoard(std::string name, T data, ActsExamples::WhiteBoard &wb) { + DummySequenceElement dummyElement; + + ActsExamples::WriteDataHandle handle(&dummyElement, name + "handle"); + handle.initialize(name); + handle(wb, std::move(data)); +} + +template +T getFromWhiteBoard(std::string name, ActsExamples::WhiteBoard &wb) { + DummySequenceElement dummyElement; + + ActsExamples::ReadDataHandle handle(&dummyElement, name + "handle"); + handle.initialize(name); + return handle(wb); +} + +template , + typename str_tuple_t = std::tuple<>> +struct GenericReadWriteTool { + val_tuple_t tuple; + str_tuple_t strings; + + constexpr static std::size_t kSize = std::tuple_size_v; + static_assert(kSize == std::tuple_size_v); + + template + auto add(const std::string &name, T value) { + auto newTuple = std::tuple_cat(tuple, std::tuple{value}); + auto newStrings = std::tuple_cat(strings, std::tuple{name}); + + GenericReadWriteTool newInstance; + newInstance.tuple = std::move(newTuple); + newInstance.strings = std::move(newStrings); + + return newInstance; + } + + template + auto write(writer_t &writer, std::size_t eventId = 0) { + ActsExamples::WhiteBoard board; + ActsExamples::AlgorithmContext ctx(0, eventId, board); + + auto add = [&](auto &self, auto N) { + if constexpr (N() < kSize) { + addToWhiteBoard(std::get(strings), std::get(tuple), board); + self(self, std::integral_constant{}); + } + }; + + add(add, std::integral_constant{}); + + writer.internalExecute(ctx); + } + + template + auto read(reader_t &reader, std::size_t eventId = 0) { + ActsExamples::WhiteBoard board; + ActsExamples::AlgorithmContext ctx(0, eventId, board); + + reader.internalExecute(ctx); + + auto get = [&](auto &self, auto res, auto N) { + if constexpr (N() < kSize) { + using T = std::tuple_element_t; + auto val = getFromWhiteBoard(std::get(strings), board); + return self(self, std::tuple_cat(res, std::make_tuple(val)), + std::integral_constant{}); + } else { + return res; + } + }; + + return get(get, std::tuple<>{}, std::integral_constant{}); + } +}; +} // namespace Acts::Test diff --git a/Tests/UnitTests/Examples/Io/CMakeLists.txt b/Tests/UnitTests/Examples/Io/CMakeLists.txt index fd13a837780..f4a992fe62c 100644 --- a/Tests/UnitTests/Examples/Io/CMakeLists.txt +++ b/Tests/UnitTests/Examples/Io/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory_if(Json ACTS_BUILD_PLUGIN_JSON) +add_subdirectory(Root) diff --git a/Tests/UnitTests/Examples/Io/Root/CMakeLists.txt b/Tests/UnitTests/Examples/Io/Root/CMakeLists.txt new file mode 100644 index 00000000000..c94c393d6ca --- /dev/null +++ b/Tests/UnitTests/Examples/Io/Root/CMakeLists.txt @@ -0,0 +1,3 @@ +set(unittest_extra_libraries ActsExamplesIoRoot) + +add_unittest(RootSimhitReaderWriter SimhitReaderWriterTests.cpp) diff --git a/Tests/UnitTests/Examples/Io/Root/SimhitReaderWriterTests.cpp b/Tests/UnitTests/Examples/Io/Root/SimhitReaderWriterTests.cpp new file mode 100644 index 00000000000..f2d2eade9ca --- /dev/null +++ b/Tests/UnitTests/Examples/Io/Root/SimhitReaderWriterTests.cpp @@ -0,0 +1,113 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2023 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include "Acts/Tests/CommonHelpers/FloatComparisons.hpp" +#include "Acts/Tests/CommonHelpers/WhiteBoardUtilities.hpp" +#include "Acts/Utilities/Zip.hpp" +#include "ActsExamples/EventData/SimHit.hpp" +#include "ActsExamples/Io/Root/RootSimHitReader.hpp" +#include "ActsExamples/Io/Root/RootSimHitWriter.hpp" + +#include +#include +#include + +using namespace ActsExamples; +using namespace Acts::Test; + +std::mt19937 gen(23); + +auto makeTestSimhits(std::size_t nSimHits) { + std::uniform_int_distribution distIds( + 1, std::numeric_limits::max()); + std::uniform_int_distribution distIndex(1, 20); + + SimHitContainer simhits; + for (auto i = 0ul; i < nSimHits; ++i) { + Acts::GeometryIdentifier geoid(distIds(gen)); + SimBarcode pid(distIds(gen)); + + Acts::Vector4 pos4 = Acts::Vector4::Random(); + Acts::Vector4 before4 = Acts::Vector4::Random(); + Acts::Vector4 after4 = Acts::Vector4::Random(); + + auto index = distIndex(gen); + + simhits.insert(SimHit(geoid, pid, pos4, before4, after4, index)); + } + + return simhits; +} + +BOOST_AUTO_TEST_SUITE(RootSimHitReaderWriter) + +BOOST_AUTO_TEST_CASE(RoundTripTest) { + //////////////////////////// + // Create some dummy data // + //////////////////////////// + auto simhits1 = makeTestSimhits(20); + auto simhits2 = makeTestSimhits(15); + + /////////// + // Write // + /////////// + RootSimHitWriter::Config writerConfig; + writerConfig.inputSimHits = "hits"; + writerConfig.filePath = "./testhits.root"; + + RootSimHitWriter writer(writerConfig, Acts::Logging::WARNING); + + auto readWriteTool = + GenericReadWriteTool().add(writerConfig.inputSimHits, simhits1); + + // Write two different events + readWriteTool.write(writer, 11); + + std::get<0>(readWriteTool.tuple) = simhits2; + readWriteTool.write(writer, 22); + + writer.finalize(); + + ////////// + // Read // + ////////// + RootSimHitReader::Config readerConfig; + readerConfig.simHitCollection = "hits"; + readerConfig.filePath = "./testhits.root"; + + RootSimHitReader reader(readerConfig, Acts::Logging::WARNING); + // Read two different events + const auto [hitsRead2] = readWriteTool.read(reader, 22); + const auto [hitsRead1] = readWriteTool.read(reader, 11); + reader.finalize(); + + /////////// + // Check // + /////////// + + auto check = [](const auto &testhits, const auto &refhits, auto tol) { + BOOST_CHECK(testhits.size() == refhits.size()); + + for (const auto &[ref, test] : Acts::zip(refhits, testhits)) { + CHECK_CLOSE_ABS(test.fourPosition(), ref.fourPosition(), tol); + CHECK_CLOSE_ABS(test.momentum4After(), ref.momentum4After(), tol); + CHECK_CLOSE_ABS(test.momentum4Before(), ref.momentum4Before(), tol); + + BOOST_CHECK(ref.geometryId() == test.geometryId()); + BOOST_CHECK(ref.particleId() == test.particleId()); + BOOST_CHECK(ref.index() == test.index()); + } + }; + + check(hitsRead1, simhits1, 1.e-6); + check(hitsRead2, simhits2, 1.e-6); +} + +BOOST_AUTO_TEST_SUITE_END() From 23f4f3fe4333580ac50e7ad58779c909ac5c09dd Mon Sep 17 00:00:00 2001 From: Benjamin Huth <37871400+benjaminhuth@users.noreply.github.com> Date: Fri, 28 Jul 2023 13:12:21 +0200 Subject: [PATCH 11/23] Apply suggestions from code review Co-authored-by: Andreas Stefl --- .../Io/Root/include/ActsExamples/Io/Root/RootSimHitReader.hpp | 4 ++-- Examples/Io/Root/src/RootSimHitReader.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Examples/Io/Root/include/ActsExamples/Io/Root/RootSimHitReader.hpp b/Examples/Io/Root/include/ActsExamples/Io/Root/RootSimHitReader.hpp index acc5c03601b..73f2faacb5b 100644 --- a/Examples/Io/Root/include/ActsExamples/Io/Root/RootSimHitReader.hpp +++ b/Examples/Io/Root/include/ActsExamples/Io/Root/RootSimHitReader.hpp @@ -104,8 +104,8 @@ class RootSimHitReader : public IReader { std::unordered_map m_floatColumns; std::unordered_map m_uint64Columns; - std::unordered_map m_uint32Columns; - std::unordered_map m_int32Columns; + std::unordered_map m_uint32Columns; + std::unordered_map m_int32Columns; }; } // namespace ActsExamples diff --git a/Examples/Io/Root/src/RootSimHitReader.cpp b/Examples/Io/Root/src/RootSimHitReader.cpp index a3de0592249..2cce943f7de 100644 --- a/Examples/Io/Root/src/RootSimHitReader.cpp +++ b/Examples/Io/Root/src/RootSimHitReader.cpp @@ -83,7 +83,7 @@ ActsExamples::RootSimHitReader::RootSimHitReader( if (evtId != std::get<0>(m_eventMap.back())) { std::get<2>(m_eventMap.back()) = i; - m_eventMap.push_back({evtId, i, 0ul}); + m_eventMap.push_back({evtId, i, i}); } } From fd4276739966135435ffc32d5ac2f462bbab2725 Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Fri, 28 Jul 2023 13:41:35 +0200 Subject: [PATCH 12/23] add comment --- .../Root/include/ActsExamples/Io/Root/RootSimHitReader.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Examples/Io/Root/include/ActsExamples/Io/Root/RootSimHitReader.hpp b/Examples/Io/Root/include/ActsExamples/Io/Root/RootSimHitReader.hpp index 73f2faacb5b..c5ecabdfe5f 100644 --- a/Examples/Io/Root/include/ActsExamples/Io/Root/RootSimHitReader.hpp +++ b/Examples/Io/Root/include/ActsExamples/Io/Root/RootSimHitReader.hpp @@ -103,9 +103,12 @@ class RootSimHitReader : public IReader { constexpr static std::array m_int32Keys = {"index"}; std::unordered_map m_floatColumns; - std::unordered_map m_uint64Columns; std::unordered_map m_uint32Columns; std::unordered_map m_int32Columns; + + // For some reason I need to use here `unsigned long long` instead of + // `uint64_t` to prevent a internal ROOT type mismatch... + std::unordered_map m_uint64Columns; }; } // namespace ActsExamples From af2a79847a69401b9efb56c61da344cd6416abe0 Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Fri, 28 Jul 2023 16:55:13 +0200 Subject: [PATCH 13/23] small fixes and changes --- .../Io/Root/include/ActsExamples/Io/Root/RootSimHitReader.hpp | 4 ---- Tests/UnitTests/Examples/Io/Root/SimhitReaderWriterTests.cpp | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Examples/Io/Root/include/ActsExamples/Io/Root/RootSimHitReader.hpp b/Examples/Io/Root/include/ActsExamples/Io/Root/RootSimHitReader.hpp index c5ecabdfe5f..9bd38da6876 100644 --- a/Examples/Io/Root/include/ActsExamples/Io/Root/RootSimHitReader.hpp +++ b/Examples/Io/Root/include/ActsExamples/Io/Root/RootSimHitReader.hpp @@ -87,10 +87,6 @@ class RootSimHitReader : public IReader { /// The input tree name TChain *m_inputChain = nullptr; - /// The entry numbers for accessing events in increased order (there could be - /// multiple entries corresponding to one event number) - std::vector m_entryNumbers = {}; - /// The keys we have in the ROOT file constexpr static std::array m_floatKeys = { "tx", "ty", "tz", "tt", "tpx", "tpy", diff --git a/Tests/UnitTests/Examples/Io/Root/SimhitReaderWriterTests.cpp b/Tests/UnitTests/Examples/Io/Root/SimhitReaderWriterTests.cpp index f2d2eade9ca..ab321185b5b 100644 --- a/Tests/UnitTests/Examples/Io/Root/SimhitReaderWriterTests.cpp +++ b/Tests/UnitTests/Examples/Io/Root/SimhitReaderWriterTests.cpp @@ -65,7 +65,7 @@ BOOST_AUTO_TEST_CASE(RoundTripTest) { RootSimHitWriter writer(writerConfig, Acts::Logging::WARNING); auto readWriteTool = - GenericReadWriteTool().add(writerConfig.inputSimHits, simhits1); + GenericReadWriteTool<>().add(writerConfig.inputSimHits, simhits1); // Write two different events readWriteTool.write(writer, 11); From f83fed9a7c06d3b15e667be4a8b9ee910fb8fb95 Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Fri, 28 Jul 2023 17:46:13 +0200 Subject: [PATCH 14/23] update and harmonize with root simhit reader PR --- Tests/UnitTests/Examples/Io/CMakeLists.txt | 1 + .../Io/Csv/MeasurementReaderWriterTests.cpp | 29 ++++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/Tests/UnitTests/Examples/Io/CMakeLists.txt b/Tests/UnitTests/Examples/Io/CMakeLists.txt index f4a992fe62c..2c5d082a6c9 100644 --- a/Tests/UnitTests/Examples/Io/CMakeLists.txt +++ b/Tests/UnitTests/Examples/Io/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory_if(Json ACTS_BUILD_PLUGIN_JSON) add_subdirectory(Root) +add_subdirectory(Csv) diff --git a/Tests/UnitTests/Examples/Io/Csv/MeasurementReaderWriterTests.cpp b/Tests/UnitTests/Examples/Io/Csv/MeasurementReaderWriterTests.cpp index 29b34341348..23ff1ecf8c8 100644 --- a/Tests/UnitTests/Examples/Io/Csv/MeasurementReaderWriterTests.cpp +++ b/Tests/UnitTests/Examples/Io/Csv/MeasurementReaderWriterTests.cpp @@ -93,15 +93,28 @@ BOOST_AUTO_TEST_CASE(CsvMeasurmentRoundTrip) { mapOriginal.insert(std::pair{i, disti(gen)}); } - ////////////////// - // Write & Read // - ////////////////// + /////////// + // Write // + /////////// CsvMeasurementWriter::Config writerConfig; writerConfig.inputClusters = "clusters"; writerConfig.inputMeasurements = "meas"; writerConfig.inputMeasurementSimHitsMap = "map"; writerConfig.outputDir = ""; + CsvMeasurementWriter writer(writerConfig, Acts::Logging::WARNING); + + auto writeTool = + GenericReadWriteTool<>() + .add(writerConfig.inputMeasurements, measOriginal) + .add(writerConfig.inputClusters, clusterOriginal) + .add(writerConfig.inputMeasurementSimHitsMap, mapOriginal); + + writeTool.write(writer); + + ////////////////// + // Write & Read // + ////////////////// CsvMeasurementReader::Config readerConfig; readerConfig.inputDir = writerConfig.outputDir; readerConfig.outputMeasurements = writerConfig.inputMeasurements; @@ -110,19 +123,13 @@ BOOST_AUTO_TEST_CASE(CsvMeasurmentRoundTrip) { readerConfig.outputClusters = writerConfig.inputClusters; readerConfig.outputSourceLinks = "sourcelinks"; - auto writeTool = - GenericReadWriteTool() - .add(writerConfig.inputMeasurements, measOriginal) - .add(writerConfig.inputClusters, clusterOriginal) - .add(writerConfig.inputMeasurementSimHitsMap, mapOriginal); - - writeTool.write(writerConfig); + CsvMeasurementReader reader(readerConfig, Acts::Logging::WARNING); auto readTool = writeTool.add(readerConfig.outputSourceLinks, sourceLinksOriginal); const auto [measRead, clusterRead, mapRead, sourceLinksRead] = - readTool.read(readerConfig); + readTool.read(reader); /////////// // Check // From 65b7d27ef2387e71014697ea62be80e7bd1689d1 Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Tue, 1 Aug 2023 09:45:51 +0200 Subject: [PATCH 15/23] spelling --- Examples/Io/Csv/src/CsvMeasurementReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/Io/Csv/src/CsvMeasurementReader.cpp b/Examples/Io/Csv/src/CsvMeasurementReader.cpp index 637dc460c3f..bbb74396f69 100644 --- a/Examples/Io/Csv/src/CsvMeasurementReader.cpp +++ b/Examples/Io/Csv/src/CsvMeasurementReader.cpp @@ -207,7 +207,7 @@ ActsExamples::ProcessCode ActsExamples::CsvMeasurementReader::read( sourceLinks.insert(sourceLinks.end(), std::cref(sourceLink)); - // Get all cell data for this measurment + // Get all cell data for this measurement std::vector thisCellData; std::copy_if(cellData.begin(), cellData.end(), std::back_inserter(thisCellData), From 87c852b6c90e0220985291f975e29c5c850f405b Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Tue, 1 Aug 2023 10:46:33 +0200 Subject: [PATCH 16/23] update --- Examples/Io/Csv/src/CsvMeasurementReader.cpp | 42 ++++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/Examples/Io/Csv/src/CsvMeasurementReader.cpp b/Examples/Io/Csv/src/CsvMeasurementReader.cpp index bbb74396f69..8334637fce6 100644 --- a/Examples/Io/Csv/src/CsvMeasurementReader.cpp +++ b/Examples/Io/Csv/src/CsvMeasurementReader.cpp @@ -37,7 +37,6 @@ ActsExamples::CsvMeasurementReader::CsvMeasurementReader( const ActsExamples::CsvMeasurementReader::Config& config, Acts::Logging::Level level) : m_cfg(config), - // TODO check that all files (hits,cells,truth) exists m_eventsRange( determineEventFilesRange(m_cfg.inputDir, "measurements.csv")), m_logger(Acts::getDefaultLogger("CsvMeasurementReader", level)) { @@ -49,6 +48,21 @@ ActsExamples::CsvMeasurementReader::CsvMeasurementReader( m_outputMeasurementSimHitsMap.initialize(m_cfg.outputMeasurementSimHitsMap); m_outputSourceLinks.initialize(m_cfg.outputSourceLinks); m_outputClusters.maybeInitialize(m_cfg.outputClusters); + + // Check if event ranges match (should also catch missing files) + auto checkRange = [&](const std::string& fileStem) { + const auto hitmapRange = determineEventFilesRange(m_cfg.inputDir, fileStem); + if (hitmapRange.first > m_eventsRange.first or + hitmapRange.second < m_eventsRange.second) { + throw std::runtime_error("event range mismatch for 'event**-" + fileStem + + "'"); + } + }; + + checkRange("measurement-simhit-map.csv"); + if (not m_cfg.outputClusters.empty()) { + checkRange("cells.csv"); + } } std::string ActsExamples::CsvMeasurementReader::CsvMeasurementReader::name() @@ -128,8 +142,30 @@ ActsExamples::ProcessCode ActsExamples::CsvMeasurementReader::read( std::vector cellData = {}; if (not m_cfg.outputClusters.empty()) { - cellData = readEverything( - m_cfg.inputDir, "cells.csv", {"timestamp"}, ctx.eventNumber); + // This allows seamless import of files created with a older version where + // the measurment_id-column is still named hit_id + try { + cellData = readEverything( + m_cfg.inputDir, "cells.csv", {"timestamp"}, ctx.eventNumber); + } catch (std::runtime_error& e) { + // Rethrow exception if it is not about the measurement_id-column + if (not std::string(e.what()).find( + "Missing header column 'measurement_id'")) { + throw; + } + + const auto oldCellData = readEverything( + m_cfg.inputDir, "cells.csv", {"timestamp"}, ctx.eventNumber); + + auto fromLegacy = [](const CellDataLegacy& old) { + return CellData{old.geometry_id, old.hit_id, old.channel0, + old.channel1, old.timestamp, old.value}; + }; + + cellData.resize(oldCellData.size()); + std::transform(oldCellData.begin(), oldCellData.end(), cellData.begin(), + fromLegacy); + } } // Prepare containers for the hit data using the framework event data types From 3386e7a0ef419a73de367002ecb570cfe2f7a15d Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Tue, 1 Aug 2023 11:07:22 +0200 Subject: [PATCH 17/23] fix --- Examples/Io/Csv/src/CsvMeasurementReader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Examples/Io/Csv/src/CsvMeasurementReader.cpp b/Examples/Io/Csv/src/CsvMeasurementReader.cpp index 8334637fce6..bdf0641ca90 100644 --- a/Examples/Io/Csv/src/CsvMeasurementReader.cpp +++ b/Examples/Io/Csv/src/CsvMeasurementReader.cpp @@ -149,8 +149,8 @@ ActsExamples::ProcessCode ActsExamples::CsvMeasurementReader::read( m_cfg.inputDir, "cells.csv", {"timestamp"}, ctx.eventNumber); } catch (std::runtime_error& e) { // Rethrow exception if it is not about the measurement_id-column - if (not std::string(e.what()).find( - "Missing header column 'measurement_id'")) { + if (std::string(e.what()).find( + "Missing header column 'measurement_id'") == std::string::npos) { throw; } From ab2beb38117796666c1785fe985d806bf3fdc70a Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Tue, 1 Aug 2023 15:38:46 +0200 Subject: [PATCH 18/23] speed up reading and refactor --- Examples/Io/Csv/src/CsvMeasurementReader.cpp | 168 +++++++++++-------- 1 file changed, 100 insertions(+), 68 deletions(-) diff --git a/Examples/Io/Csv/src/CsvMeasurementReader.cpp b/Examples/Io/Csv/src/CsvMeasurementReader.cpp index bdf0641ca90..42553ac882c 100644 --- a/Examples/Io/Csv/src/CsvMeasurementReader.cpp +++ b/Examples/Io/Csv/src/CsvMeasurementReader.cpp @@ -126,6 +126,64 @@ std::vector readMeasurementsByGeometryId( return measurements; } +// Build clusters from the cell data vector. Assumes the cell data to be sorted +// by measurment id! +ActsExamples::ClusterContainer makeClusters( + const std::vector& cellData, + std::size_t nMeasurments) { + using namespace ActsExamples; + ClusterContainer clusters; + + auto cellDataIt = cellData.begin(); + for (auto index = 0ul; index < nMeasurments; ++index) { + // Search for the range containing the index + cellDataIt = std::find_if(cellDataIt, cellData.end(), [&](const auto& cd) { + return cd.measurement_id == index; + }); + + auto nextCellDataIt = std::find_if( + cellDataIt, cellData.end(), + [&](const auto& cd) { return cd.measurement_id != index; }); + + // Fill the channels with the iterators + Cluster cluster; + cluster.channels.reserve(std::distance(cellDataIt, nextCellDataIt)); + + for (auto it = cellDataIt; it != nextCellDataIt; ++it) { + ActsFatras::Channelizer::Segment2D dummySegment = {Acts::Vector2::Zero(), + Acts::Vector2::Zero()}; + + ActsFatras::Channelizer::Bin2D bin{ + static_cast(it->channel0), + static_cast(it->channel1)}; + + cluster.channels.emplace_back(bin, dummySegment, it->value); + } + + // update the iterator + + // Compute cluster size + if (not cluster.channels.empty()) { + auto compareX = [](const auto& a, const auto& b) { + return a.bin[0] < b.bin[0]; + }; + auto compareY = [](const auto& a, const auto& b) { + return a.bin[1] < b.bin[1]; + }; + + auto [minX, maxX] = std::minmax_element(cluster.channels.begin(), + cluster.channels.end(), compareX); + auto [minY, maxY] = std::minmax_element(cluster.channels.begin(), + cluster.channels.end(), compareY); + cluster.sizeLoc0 = 1 + maxX->bin[0] - minX->bin[0]; + cluster.sizeLoc1 = 1 + maxY->bin[1] - minY->bin[1]; + } + + clusters.push_back(cluster); + } + return clusters; +} + } // namespace ActsExamples::ProcessCode ActsExamples::CsvMeasurementReader::read( @@ -140,37 +198,8 @@ ActsExamples::ProcessCode ActsExamples::CsvMeasurementReader::read( auto measurementData = readMeasurementsByGeometryId(m_cfg.inputDir, ctx.eventNumber); - std::vector cellData = {}; - if (not m_cfg.outputClusters.empty()) { - // This allows seamless import of files created with a older version where - // the measurment_id-column is still named hit_id - try { - cellData = readEverything( - m_cfg.inputDir, "cells.csv", {"timestamp"}, ctx.eventNumber); - } catch (std::runtime_error& e) { - // Rethrow exception if it is not about the measurement_id-column - if (std::string(e.what()).find( - "Missing header column 'measurement_id'") == std::string::npos) { - throw; - } - - const auto oldCellData = readEverything( - m_cfg.inputDir, "cells.csv", {"timestamp"}, ctx.eventNumber); - - auto fromLegacy = [](const CellDataLegacy& old) { - return CellData{old.geometry_id, old.hit_id, old.channel0, - old.channel1, old.timestamp, old.value}; - }; - - cellData.resize(oldCellData.size()); - std::transform(oldCellData.begin(), oldCellData.end(), cellData.begin(), - fromLegacy); - } - } - // Prepare containers for the hit data using the framework event data types GeometryIdMultimap orderedMeasurements; - ClusterContainer clusters; IndexMultimap measurementSimHitsMap; IndexSourceLinkContainer sourceLinks; // need list here for stable addresses @@ -242,43 +271,6 @@ ActsExamples::ProcessCode ActsExamples::CsvMeasurementReader::read( } sourceLinks.insert(sourceLinks.end(), std::cref(sourceLink)); - - // Get all cell data for this measurement - std::vector thisCellData; - std::copy_if(cellData.begin(), cellData.end(), - std::back_inserter(thisCellData), - [&](const auto& cd) { return cd.measurement_id == index; }); - - // Fill the channels - Cluster cluster; - cluster.channels.reserve(thisCellData.size()); - for (const auto& cd : thisCellData) { - ActsFatras::Channelizer::Segment2D dummySegment = {Acts::Vector2::Zero(), - Acts::Vector2::Zero()}; - ActsFatras::Channelizer::Bin2D bin{ - static_cast(cd.channel0), - static_cast(cd.channel1)}; - cluster.channels.emplace_back(bin, dummySegment, cd.value); - } - - // Compute cluster size - if (not cluster.channels.empty()) { - auto compareX = [](const auto& a, const auto& b) { - return a.bin[0] < b.bin[0]; - }; - auto compareY = [](const auto& a, const auto& b) { - return a.bin[1] < b.bin[1]; - }; - - auto [minX, maxX] = std::minmax_element(cluster.channels.begin(), - cluster.channels.end(), compareX); - auto [minY, maxY] = std::minmax_element(cluster.channels.begin(), - cluster.channels.end(), compareY); - cluster.sizeLoc0 = 1 + maxX->bin[0] - minX->bin[0]; - cluster.sizeLoc1 = 1 + maxY->bin[1] - minY->bin[1]; - } - - clusters.push_back(cluster); } MeasurementContainer measurements; @@ -290,9 +282,49 @@ ActsExamples::ProcessCode ActsExamples::CsvMeasurementReader::read( m_outputMeasurements(ctx, std::move(measurements)); m_outputMeasurementSimHitsMap(ctx, std::move(measurementSimHitsMap)); m_outputSourceLinks(ctx, std::move(sourceLinks)); - if (not clusters.empty()) { - m_outputClusters(ctx, std::move(clusters)); + + ///////////////////////// + // Cluster information // + ///////////////////////// + + if (m_cfg.outputClusters.empty()) { + return ActsExamples::ProcessCode::SUCCESS; + } + + std::vector cellData; + + // This allows seamless import of files created with a older version where + // the measurment_id-column is still named hit_id + try { + cellData = readEverything( + m_cfg.inputDir, "cells.csv", {"timestamp"}, ctx.eventNumber); + } catch (std::runtime_error& e) { + // Rethrow exception if it is not about the measurement_id-column + if (std::string(e.what()).find("Missing header column 'measurement_id'") == + std::string::npos) { + throw; + } + + const auto oldCellData = readEverything( + m_cfg.inputDir, "cells.csv", {"timestamp"}, ctx.eventNumber); + + auto fromLegacy = [](const CellDataLegacy& old) { + return CellData{old.geometry_id, old.hit_id, old.channel0, + old.channel1, old.timestamp, old.value}; + }; + + cellData.resize(oldCellData.size()); + std::transform(oldCellData.begin(), oldCellData.end(), cellData.begin(), + fromLegacy); } + // Sort cell data so we can go trough the cell data efficiently afterwards + std::sort(cellData.begin(), cellData.end(), [](const auto& a, const auto& b) { + return a.measurement_id < b.measurement_id; + }); + + auto clusters = makeClusters(cellData, orderedMeasurements.size()); + m_outputClusters(ctx, std::move(clusters)); + return ActsExamples::ProcessCode::SUCCESS; } From 6b70309218c9dd686fa625a256147b6ab4ba740c Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Mon, 14 Aug 2023 11:28:53 +0200 Subject: [PATCH 19/23] add particle measurement map reading capability --- .../Io/Csv/CsvMeasurementReader.hpp | 13 +++++++++++++ Examples/Io/Csv/src/CsvMeasurementReader.cpp | 17 +++++++++++++++++ Examples/Python/src/Input.cpp | 8 ++++---- Examples/Python/tests/test_reader.py | 4 +++- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvMeasurementReader.hpp b/Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvMeasurementReader.hpp index 1386ff1575c..a6cbcaa99f9 100644 --- a/Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvMeasurementReader.hpp +++ b/Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvMeasurementReader.hpp @@ -15,6 +15,8 @@ #include "ActsExamples/EventData/Index.hpp" #include "ActsExamples/EventData/IndexSourceLink.hpp" #include "ActsExamples/EventData/Measurement.hpp" +#include "ActsExamples/EventData/SimHit.hpp" +#include "ActsExamples/EventData/SimParticle.hpp" #include "ActsExamples/Framework/DataHandle.hpp" #include "ActsExamples/Framework/IReader.hpp" #include "ActsExamples/Framework/ProcessCode.hpp" @@ -62,6 +64,12 @@ class CsvMeasurementReader final : public IReader { std::string outputSourceLinks; /// Output cluster collection (optional). std::string outputClusters; + + /// Input SimHits for measurment-particle map (optional) + std::string inputSimHits; + /// Output measurement to particle collection (optional) + /// @note Only filled if inputSimHits is given + std::string outputMeasurementParticlesMap; }; /// Construct the cluster reader. @@ -98,6 +106,11 @@ class CsvMeasurementReader final : public IReader { this, "OutputSourceLinks"}; WriteDataHandle m_outputClusters{this, "OutputClusters"}; + + WriteDataHandle> + m_outputMeasurementParticlesMap{this, "OutputMeasurementParticlesMap"}; + + ReadDataHandle m_inputHits{this, "InputHits"}; }; } // namespace ActsExamples diff --git a/Examples/Io/Csv/src/CsvMeasurementReader.cpp b/Examples/Io/Csv/src/CsvMeasurementReader.cpp index 42553ac882c..2acc79ce819 100644 --- a/Examples/Io/Csv/src/CsvMeasurementReader.cpp +++ b/Examples/Io/Csv/src/CsvMeasurementReader.cpp @@ -48,6 +48,9 @@ ActsExamples::CsvMeasurementReader::CsvMeasurementReader( m_outputMeasurementSimHitsMap.initialize(m_cfg.outputMeasurementSimHitsMap); m_outputSourceLinks.initialize(m_cfg.outputSourceLinks); m_outputClusters.maybeInitialize(m_cfg.outputClusters); + m_outputMeasurementParticlesMap.maybeInitialize( + m_cfg.outputMeasurementParticlesMap); + m_inputHits.maybeInitialize(m_cfg.inputSimHits); // Check if event ranges match (should also catch missing files) auto checkRange = [&](const std::string& fileStem) { @@ -278,6 +281,20 @@ ActsExamples::ProcessCode ActsExamples::CsvMeasurementReader::read( measurements.emplace_back(std::move(meas)); } + if (m_inputHits.isInitialized() && + m_outputMeasurementParticlesMap.isInitialized()) { + const auto hits = m_inputHits(ctx); + + IndexMultimap outputMap; + + for (const auto& [measIdx, hitIdx] : measurementSimHitsMap) { + const auto& hit = hits.nth(hitIdx); + outputMap.emplace(measIdx, hit->particleId()); + } + + m_outputMeasurementParticlesMap(ctx, std::move(outputMap)); + } + // Write the data to the EventStore m_outputMeasurements(ctx, std::move(measurements)); m_outputMeasurementSimHitsMap(ctx, std::move(measurementSimHitsMap)); diff --git a/Examples/Python/src/Input.cpp b/Examples/Python/src/Input.cpp index c73bed7f28b..4d365e9cc3b 100644 --- a/Examples/Python/src/Input.cpp +++ b/Examples/Python/src/Input.cpp @@ -55,10 +55,10 @@ void addInput(Context& ctx) { "CsvParticleReader", inputDir, inputStem, outputParticles); - ACTS_PYTHON_DECLARE_READER(ActsExamples::CsvMeasurementReader, mex, - "CsvMeasurementReader", inputDir, - outputMeasurements, outputMeasurementSimHitsMap, - outputSourceLinks, outputClusters); + ACTS_PYTHON_DECLARE_READER( + ActsExamples::CsvMeasurementReader, mex, "CsvMeasurementReader", inputDir, + outputMeasurements, outputMeasurementSimHitsMap, outputSourceLinks, + outputClusters, outputMeasurementParticlesMap, inputSimHits); ACTS_PYTHON_DECLARE_READER(ActsExamples::CsvPlanarClusterReader, mex, "CsvPlanarClusterReader", inputDir, outputClusters, diff --git a/Examples/Python/tests/test_reader.py b/Examples/Python/tests/test_reader.py index e032c3679dd..36cf305d71b 100644 --- a/Examples/Python/tests/test_reader.py +++ b/Examples/Python/tests/test_reader.py @@ -192,13 +192,15 @@ def test_csv_meas_reader(tmp_path, fatras, trk_geo, conf_const): outputMeasurements="measurements", outputMeasurementSimHitsMap="simhitsmap", outputSourceLinks="sourcelinks", + outputMeasurementParticlesMap="meas_ptcl_map", + inputSimHits=simAlg.config.outputSimHits, inputDir=str(out), ) ) algs = [ AssertCollectionExistsAlg(k, f"check_alg_{k}", acts.logging.WARNING) - for k in ("measurements", "simhitsmap", "sourcelinks") + for k in ("measurements", "simhitsmap", "sourcelinks", "meas_ptcl_map") ] for alg in algs: s.addAlgorithm(alg) From f824557e232b10b2ef23c2f3d3e0b3c4190d10c2 Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Mon, 14 Aug 2023 11:44:36 +0200 Subject: [PATCH 20/23] improve comment --- .../Examples/Io/Csv/MeasurementReaderWriterTests.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Tests/UnitTests/Examples/Io/Csv/MeasurementReaderWriterTests.cpp b/Tests/UnitTests/Examples/Io/Csv/MeasurementReaderWriterTests.cpp index 23ff1ecf8c8..99a8cba1aeb 100644 --- a/Tests/UnitTests/Examples/Io/Csv/MeasurementReaderWriterTests.cpp +++ b/Tests/UnitTests/Examples/Io/Csv/MeasurementReaderWriterTests.cpp @@ -50,14 +50,12 @@ BOOST_AUTO_TEST_CASE(CsvMeasurmentRoundTrip) { Acts::Vector2 p = Acts::Vector2::Random(); Acts::SymMatrix2 c = Acts::SymMatrix2::Random(); + // NOTE this fails: + // auto m = Acts::makeMeasurement(sl, p, c, eBoundLoc0, eBoundTime) + // because we dont support non-consecutive parameters here for now auto m = Acts::makeMeasurement(Acts::SourceLink{sl}, p, c, Acts::eBoundLoc0, Acts::eBoundLoc1); - // TODO this fails! - // auto m = Acts::makeMeasurement(Acts::SourceLink{sl}, p, c, - // Acts::eBoundLoc0, - // Acts::eBoundTime); - measOriginal.push_back(m); ActsExamples::Cluster cl; From a0c719ea06abc3a0d1616cd68497a4ac47b6226d Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Tue, 15 Aug 2023 09:13:25 +0200 Subject: [PATCH 21/23] apply suggestion --- Examples/Io/Csv/src/CsvMeasurementReader.cpp | 37 ++++++++------------ 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/Examples/Io/Csv/src/CsvMeasurementReader.cpp b/Examples/Io/Csv/src/CsvMeasurementReader.cpp index 2acc79ce819..5ab071cb001 100644 --- a/Examples/Io/Csv/src/CsvMeasurementReader.cpp +++ b/Examples/Io/Csv/src/CsvMeasurementReader.cpp @@ -129,38 +129,30 @@ std::vector readMeasurementsByGeometryId( return measurements; } -// Build clusters from the cell data vector. Assumes the cell data to be sorted -// by measurment id! ActsExamples::ClusterContainer makeClusters( - const std::vector& cellData, + const std::unordered_multimap& + cellDataMap, std::size_t nMeasurments) { using namespace ActsExamples; ClusterContainer clusters; - auto cellDataIt = cellData.begin(); for (auto index = 0ul; index < nMeasurments; ++index) { - // Search for the range containing the index - cellDataIt = std::find_if(cellDataIt, cellData.end(), [&](const auto& cd) { - return cd.measurement_id == index; - }); - - auto nextCellDataIt = std::find_if( - cellDataIt, cellData.end(), - [&](const auto& cd) { return cd.measurement_id != index; }); + auto [begin, end] = cellDataMap.equal_range(index); // Fill the channels with the iterators Cluster cluster; - cluster.channels.reserve(std::distance(cellDataIt, nextCellDataIt)); + cluster.channels.reserve(std::distance(begin, end)); - for (auto it = cellDataIt; it != nextCellDataIt; ++it) { + for (auto it = begin; it != end; ++it) { + const auto& cellData = it->second; ActsFatras::Channelizer::Segment2D dummySegment = {Acts::Vector2::Zero(), Acts::Vector2::Zero()}; ActsFatras::Channelizer::Bin2D bin{ - static_cast(it->channel0), - static_cast(it->channel1)}; + static_cast(cellData.channel0), + static_cast(cellData.channel1)}; - cluster.channels.emplace_back(bin, dummySegment, it->value); + cluster.channels.emplace_back(bin, dummySegment, cellData.value); } // update the iterator @@ -281,6 +273,7 @@ ActsExamples::ProcessCode ActsExamples::CsvMeasurementReader::read( measurements.emplace_back(std::move(meas)); } + // Generate measurment-particles-map if (m_inputHits.isInitialized() && m_outputMeasurementParticlesMap.isInitialized()) { const auto hits = m_inputHits(ctx); @@ -335,12 +328,12 @@ ActsExamples::ProcessCode ActsExamples::CsvMeasurementReader::read( fromLegacy); } - // Sort cell data so we can go trough the cell data efficiently afterwards - std::sort(cellData.begin(), cellData.end(), [](const auto& a, const auto& b) { - return a.measurement_id < b.measurement_id; - }); + std::unordered_multimap cellDataMap; + for (const auto& cd : cellData) { + cellDataMap.emplace(cd.measurement_id, cd); + } - auto clusters = makeClusters(cellData, orderedMeasurements.size()); + auto clusters = makeClusters(cellDataMap, orderedMeasurements.size()); m_outputClusters(ctx, std::move(clusters)); return ActsExamples::ProcessCode::SUCCESS; From 3adb835d4ea2ce8df8f9e2c7f33e0045e2517102 Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Tue, 15 Aug 2023 12:17:54 +0200 Subject: [PATCH 22/23] fix unit test --- .../Io/Csv/MeasurementReaderWriterTests.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Tests/UnitTests/Examples/Io/Csv/MeasurementReaderWriterTests.cpp b/Tests/UnitTests/Examples/Io/Csv/MeasurementReaderWriterTests.cpp index 99a8cba1aeb..1c8013130f1 100644 --- a/Tests/UnitTests/Examples/Io/Csv/MeasurementReaderWriterTests.cpp +++ b/Tests/UnitTests/Examples/Io/Csv/MeasurementReaderWriterTests.cpp @@ -150,13 +150,17 @@ BOOST_AUTO_TEST_CASE(CsvMeasurmentRoundTrip) { static_assert(std::is_same_v, decltype(clusterOriginal)>); BOOST_REQUIRE(clusterRead.size() == clusterOriginal.size()); - for (const auto &[a, b] : Acts::zip(clusterRead, clusterOriginal)) { + for (auto [a, b] : Acts::zip(clusterRead, clusterOriginal)) { BOOST_REQUIRE(a.sizeLoc0 == b.sizeLoc0); BOOST_REQUIRE(a.sizeLoc1 == b.sizeLoc1); - for (const auto &[ca, cb] : Acts::zip(a.channels, b.channels)) { - BOOST_REQUIRE(ca.bin[0] == cb.bin[0]); - BOOST_REQUIRE(ca.bin[1] == cb.bin[1]); - CHECK_CLOSE_REL(ca.activation, cb.activation, 1.e-4); + + for (const auto &ca : a.channels) { + auto match = [&](const auto &cb) { + return ca.bin == cb.bin && + std::abs(ca.activation - cb.activation) < 1.e-4; + }; + + BOOST_CHECK(std::any_of(b.channels.begin(), b.channels.end(), match)); } } From 45f11165ae583d17ec8b2333a0e067f6b6b8d717 Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Tue, 15 Aug 2023 14:23:35 +0200 Subject: [PATCH 23/23] fix python test --- Examples/Python/tests/test_reader.py | 34 +++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/Examples/Python/tests/test_reader.py b/Examples/Python/tests/test_reader.py index 36cf305d71b..f745fd75320 100644 --- a/Examples/Python/tests/test_reader.py +++ b/Examples/Python/tests/test_reader.py @@ -173,18 +173,40 @@ def test_csv_meas_reader(tmp_path, fatras, trk_geo, conf_const): out = tmp_path / "csv" out.mkdir() - config = CsvMeasurementWriter.Config( - inputMeasurements=digiAlg.config.outputMeasurements, - inputClusters=digiAlg.config.outputClusters, - inputMeasurementSimHitsMap=digiAlg.config.outputMeasurementSimHitsMap, - outputDir=str(out), + s.addWriter( + CsvMeasurementWriter( + level=acts.logging.INFO, + inputMeasurements=digiAlg.config.outputMeasurements, + inputClusters=digiAlg.config.outputClusters, + inputMeasurementSimHitsMap=digiAlg.config.outputMeasurementSimHitsMap, + outputDir=str(out), + ) + ) + + # Write hits, so we can later construct the measurment-particles-map + s.addWriter( + CsvSimHitWriter( + level=acts.logging.INFO, + inputSimHits=simAlg.config.outputSimHits, + outputDir=str(out), + outputStem="hits", + ) ) - s.addWriter(CsvMeasurementWriter(level=acts.logging.INFO, config=config)) + s.run() # read back in s = Sequencer(numThreads=1) + s.addReader( + CsvSimHitReader( + level=acts.logging.INFO, + outputSimHits=simAlg.config.outputSimHits, + inputDir=str(out), + inputStem="hits", + ) + ) + s.addReader( conf_const( CsvMeasurementReader,