From 86ef97ee4699f877bc3b8f62c4c06b1eee021bde Mon Sep 17 00:00:00 2001 From: Nathan Brei Date: Mon, 8 Sep 2025 14:23:29 -0400 Subject: [PATCH 1/6] Add TranslationTable_service example --- .../11_calibrations/CMakeLists.txt | 24 ++++++++++- .../11_calibrations/RecHit_factory.cc | 0 .../11_calibrations/RecHit_factory.h | 0 .../11_calibrations/RecHit_factory_tests.cc | 34 +++++++++++++++ .../TranslationTable_service.cc | 41 +++++++++++++++++++ .../TranslationTable_service.h | 40 ++++++++++++++++++ .../11_calibrations/hit_reco_plugin.cc | 0 7 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.cc create mode 100644 src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.h create mode 100644 src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory_tests.cc create mode 100644 src/examples/tutorial_with_lightweight_datamodel/11_calibrations/TranslationTable_service.cc create mode 100644 src/examples/tutorial_with_lightweight_datamodel/11_calibrations/TranslationTable_service.h create mode 100644 src/examples/tutorial_with_lightweight_datamodel/11_calibrations/hit_reco_plugin.cc diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/CMakeLists.txt b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/CMakeLists.txt index 4c2fc5ff6..53937d432 100644 --- a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/CMakeLists.txt +++ b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/CMakeLists.txt @@ -1,2 +1,24 @@ -#add_jana_plugin(lw_calibrations) +# `liblw_hit_reco_common` is a library that includes our hit reconstruction +# machinery, specifically the TranslationTable service and RecoHit factory. +# The algorithm is simple enough that we keep it in the factory for now. + +add_jana_library(lw_hit_reco_common + SOURCES + RecHit_factory.cc + TranslationTable_service.cc + PUBLIC_HEADER + RecHit_factory.h + TranslationTable_service.h + TESTS + RecHit_factory_tests.cc +) + +# `add_jana_library` will create a target for us, but we still need to link it against +# its various dependencies by ourselves. + +target_link_libraries(lw_hit_reco_common PUBLIC lw_datamodel) + +add_jana_plugin(lw_hit_reco SOURCES hit_reco_plugin.cc) +target_link_libraries(lw_hit_reco PUBLIC lw_hit_reco_common) + diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.cc b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.cc new file mode 100644 index 000000000..e69de29bb diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.h b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.h new file mode 100644 index 000000000..e69de29bb diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory_tests.cc b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory_tests.cc new file mode 100644 index 000000000..2caf97710 --- /dev/null +++ b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory_tests.cc @@ -0,0 +1,34 @@ + +#define CATCH_CONFIG_MAIN +#include +#include +#include "TranslationTable_service.h" + +TEST_CASE("TranslationTable_service_BasicTests") { + JApplication app; + + auto sut = std::make_shared(); + sut->AddRow(22, + {.crate=1, .slot=1, .channel=1}, + {.detector_id = 7, .cell_id = 9, .indices = {1,2,3}, .x = 0.4, .y = 0.8, .z = 0.0}); + + sut->AddRow(22, + {.crate=1, .slot=1, .channel=2}, + {.detector_id = 7, .cell_id = 10, .indices = {1,2,4}, .x = 0.5, .y = 0.8, .z = 0.0}); + + sut->AddRow(22, + {.crate=1, .slot=2, .channel=1}, + {.detector_id = 7, .cell_id = 11, .indices = {1,2,5}, .x = 0.6, .y = 0.8, .z = 0.0}); + + app.ProvideService(sut); + app.Initialize(); + auto tt = app.template GetService(); + auto row = tt->TranslateDAQCoordinates(22, {1, 1, 2}); + REQUIRE(row.detector_id == 7); + REQUIRE(row.cell_id== 10); + REQUIRE(row.indices.at(2) == 4); +} + +TEST_CASE("RecHit_factory_BasicTests") { + //REQUIRE(0 == 1); +} diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/TranslationTable_service.cc b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/TranslationTable_service.cc new file mode 100644 index 000000000..345821795 --- /dev/null +++ b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/TranslationTable_service.cc @@ -0,0 +1,41 @@ +#include "TranslationTable_service.h" + +void TranslationTable_service::Init() { +} + +void TranslationTable_service::AddRow(int run_number, + TranslationTable_service::DAQCoordinates daq_coords, + TranslationTable_service::DetectorCoordinates det_coords) { + + std::map* table = nullptr; + auto table_it = m_daq_to_det_lookup.find(run_number); + if (table_it == m_daq_to_det_lookup.end()) { + auto owned_table = std::make_unique>(); + table = owned_table.get(); + m_daq_to_det_lookup[run_number] = std::move(owned_table); + } + else { + table = table_it->second.get(); + } + (*table)[daq_coords] = det_coords; +} + +const TranslationTable_service::DetectorCoordinates& TranslationTable_service::TranslateDAQCoordinates(int run_number, + const DAQCoordinates& daq_coords) { + std::map* table = nullptr; + auto table_it = m_daq_to_det_lookup.find(run_number); + if (table_it == m_daq_to_det_lookup.end()) { + auto owned_table = FetchTable(run_number); + table = owned_table.get(); + m_daq_to_det_lookup[run_number] = std::move(owned_table); + } + else { + table = table_it->second.get(); + } + return table->at(daq_coords); +} + +std::unique_ptr> TranslationTable_service::FetchTable(int /*run_number*/) { + throw JException("FetchTable() not implemented yet! Use hardcoded table instead. (Hint: Are you using a different run number from what you hardcoded?)"); +} diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/TranslationTable_service.h b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/TranslationTable_service.h new file mode 100644 index 000000000..d2dfca467 --- /dev/null +++ b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/TranslationTable_service.h @@ -0,0 +1,40 @@ + +#pragma once +#include +#include + +class TranslationTable_service : public JService { +public: + struct DAQCoordinates { + int crate; + int slot; + int channel; + }; + struct DetectorCoordinates { + int detector_id; + int cell_id; + std::vector indices; + double x; + double y; + double z; + }; + +private: + + std::map>> m_daq_to_det_lookup; + // std::map m_cell_id_to_daq_lookup; + // std::map m_cell_id_to_det_lookup; + +public: + void Init() override; + const DetectorCoordinates& TranslateDAQCoordinates(int run_number, const DAQCoordinates&); + // const DetectorCoordinates& Translate(int run_number, const DAQCoordinates&) const; + void AddRow(int run_number, DAQCoordinates daq_coords, DetectorCoordinates det_coords); + + std::unique_ptr> FetchTable(int run_number); + +}; + +inline bool operator<(const TranslationTable_service::DAQCoordinates& lhs, const TranslationTable_service::DAQCoordinates& rhs) { + return (lhs.crate < rhs.crate || lhs.slot < rhs.slot || lhs.channel < rhs.channel); +} diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/hit_reco_plugin.cc b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/hit_reco_plugin.cc new file mode 100644 index 000000000..e69de29bb From bfa1d24616bd421986f9361ba8858d781e04ac04 Mon Sep 17 00:00:00 2001 From: Nathan Brei Date: Tue, 9 Sep 2025 00:06:18 -0400 Subject: [PATCH 2/6] Update ADC{Pulse,Waveform} datamodel objects --- .../01_datamodel/ADCHit.h | 28 -------------- .../01_datamodel/ADCPulse.h | 33 +++++++++++++++++ .../01_datamodel/ADCWaveform.h | 37 +++++++++++++++++++ .../05_csv_file_writer/CsvWriter.h | 2 +- 4 files changed, 71 insertions(+), 29 deletions(-) delete mode 100644 src/examples/tutorial_with_lightweight_datamodel/01_datamodel/ADCHit.h create mode 100644 src/examples/tutorial_with_lightweight_datamodel/01_datamodel/ADCPulse.h create mode 100644 src/examples/tutorial_with_lightweight_datamodel/01_datamodel/ADCWaveform.h diff --git a/src/examples/tutorial_with_lightweight_datamodel/01_datamodel/ADCHit.h b/src/examples/tutorial_with_lightweight_datamodel/01_datamodel/ADCHit.h deleted file mode 100644 index b29781cba..000000000 --- a/src/examples/tutorial_with_lightweight_datamodel/01_datamodel/ADCHit.h +++ /dev/null @@ -1,28 +0,0 @@ - -// Copyright 2020, Jefferson Science Associates, LLC. -// Subject to the terms in the LICENSE file found in the top-level directory. - -#pragma once -#include -#include - -struct ADCHit : public JObject { - - JOBJECT_PUBLIC(ADCHit) - - uint32_t crate; - uint32_t slot; - uint32_t channel; - uint32_t energy; - uint32_t timestamp; - - void Summarize(JObjectSummary& summary) const override { - summary.add(crate, NAME_OF(crate), "%f"); - summary.add(slot, NAME_OF(slot), "%f"); - summary.add(channel, NAME_OF(channel), "%f"); - summary.add(energy, NAME_OF(energy), "%f", "Energy in GeV"); - summary.add(timestamp, NAME_OF(timestamp), "%f", "Time in ticks since timeframe start"); - } -}; - - diff --git a/src/examples/tutorial_with_lightweight_datamodel/01_datamodel/ADCPulse.h b/src/examples/tutorial_with_lightweight_datamodel/01_datamodel/ADCPulse.h new file mode 100644 index 000000000..401529b97 --- /dev/null +++ b/src/examples/tutorial_with_lightweight_datamodel/01_datamodel/ADCPulse.h @@ -0,0 +1,33 @@ + +// Copyright 2020, Jefferson Science Associates, LLC. +// Subject to the terms in the LICENSE file found in the top-level directory. + +#pragma once +#include +#include + +struct ADCPulse: public JObject { + + JOBJECT_PUBLIC(ADCPulse) + + uint32_t crate; + uint32_t slot; + uint32_t channel; + + uint32_t amplitude; + uint32_t pedestal; + uint32_t integral; + uint32_t timestamp; + + void Summarize(JObjectSummary& summary) const override { + summary.add(crate, NAME_OF(crate), "%d"); + summary.add(slot, NAME_OF(slot), "%d"); + summary.add(channel, NAME_OF(channel), "%d"); + summary.add(amplitude, NAME_OF(amplitude), "%d", "Amplitude"); + summary.add(pedestal, NAME_OF(pedestal), "%d", "Pedestal"); + summary.add(integral, NAME_OF(integral), "%d", "Integral"); + summary.add(timestamp, NAME_OF(timestamp), "%d", "Timestamp"); + } +}; + + diff --git a/src/examples/tutorial_with_lightweight_datamodel/01_datamodel/ADCWaveform.h b/src/examples/tutorial_with_lightweight_datamodel/01_datamodel/ADCWaveform.h new file mode 100644 index 000000000..0554c4a46 --- /dev/null +++ b/src/examples/tutorial_with_lightweight_datamodel/01_datamodel/ADCWaveform.h @@ -0,0 +1,37 @@ + +// Copyright 2020, Jefferson Science Associates, LLC. +// Subject to the terms in the LICENSE file found in the top-level directory. + +#pragma once +#include +#include + +struct ADCWaveform: public JObject { + + JOBJECT_PUBLIC(ADCWaveform) + + uint32_t crate; + uint32_t slot; + uint32_t channel; + + uint32_t pedestal; + uint32_t timestamp; + std::vector samples; + + void Summarize(JObjectSummary& summary) const override { + + std::ostringstream oss; + for (auto sample: samples) { + oss << sample << ", "; + } + + summary.add(crate, NAME_OF(crate), "%d"); + summary.add(slot, NAME_OF(slot), "%d"); + summary.add(channel, NAME_OF(channel), "%d"); + summary.add(pedestal, NAME_OF(pedestal), "%d"); + summary.add(timestamp, NAME_OF(timestamp), "%d"); + summary.add(oss.str().c_str(), NAME_OF(samples), "%s"); + } +}; + + diff --git a/src/examples/tutorial_with_lightweight_datamodel/05_csv_file_writer/CsvWriter.h b/src/examples/tutorial_with_lightweight_datamodel/05_csv_file_writer/CsvWriter.h index 0f608fa5c..23209c0ef 100644 --- a/src/examples/tutorial_with_lightweight_datamodel/05_csv_file_writer/CsvWriter.h +++ b/src/examples/tutorial_with_lightweight_datamodel/05_csv_file_writer/CsvWriter.h @@ -6,7 +6,7 @@ #include "CalorimeterHit.h" #include "CalorimeterCluster.h" #include "SimParticle.h" -#include "ADCHit.h" +#include "ADCPulse.h" #include From e630a38039091ed61b069ccf96a68039b5e26050 Mon Sep 17 00:00:00 2001 From: Nathan Brei Date: Tue, 9 Sep 2025 00:06:32 -0400 Subject: [PATCH 3/6] Implement RecHit_factory --- .../11_calibrations/RecHit_factory.cc | 55 +++++++++++++++++++ .../11_calibrations/RecHit_factory.h | 30 ++++++++++ .../Protocluster_factory.h | 3 +- src/libraries/JANA/Components/JHasOutputs.h | 8 +++ .../JANA/Components/JLightweightOutput.h | 2 + 5 files changed, 97 insertions(+), 1 deletion(-) diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.cc b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.cc index e69de29bb..dd496dab1 100644 --- a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.cc +++ b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.cc @@ -0,0 +1,55 @@ + +#include "RecHit_factory.h" +#include "ADCPulse.h" +#include +#include "CalorimeterHit.h" + +RecHit_factory::RecHit_factory() { + m_adc_pulses_in.SetRequestedDatabundleNames({"raw"}); + m_calo_hits_out.SetShortNames({"rechits"}); +} + +void RecHit_factory::Process(const JEvent& event) { + + auto name = m_adc_pulses_in.GetRealizedDatabundleNames(); + + + // This time we iterate over EACH input databundle we've been provided + for (size_t pulse_databundle_index = 0; pulse_databundle_indexsize(); ++pulse_databundle_index) { + + + // Unlike ouptuts and regular inputs, variadic inputs distinguish between 'requested' and 'realized' databundles. + // This is because variadic inputs may be flexible, i.e. they may be optional, or may use EmptyInputPolicy::IncludeEverything. + + auto name = m_adc_pulses_in.GetRealizedDatabundleNames().at(pulse_databundle_index); + + // In this simple example, we assume a one-to-one correspondence between input ADCPulses and output CalorimeterHits. + if (m_adc_pulses_in->size() != m_calo_hits_out->size()) { + throw JException("Found wrong number of ADCPulse inputs!"); + } + + LOG_DEBUG(GetLogger()) << "Reconstructing hits. " + << m_adc_pulses_in.GetRealizedDatabundleNames().at(pulse_databundle_index) + << " -> " + << m_calo_hits_out.GetUniqueNames().at(pulse_databundle_index); + + // Process each pulse in this databundle + for (const auto* pulse: m_adc_pulses_in->at(pulse_databundle_index)) { + + // Translate from DAQ coordinates to detector coordinates + auto& detector_coords = m_translation_table->TranslateDAQCoordinates(event.GetRunNumber(), {pulse->crate, pulse->slot, pulse->channel}); + + auto hit = new CalorimeterHit(detector_coords.cell_id, + detector_coords.indices.at(0), detector_coords.indices.at(1), + detector_coords.x, detector_coords.y, detector_coords.z, + 0, 0); + + // TODO: Apply energy calibrations + hit->energy = pulse->amplitude; + hit->time = pulse->timestamp; + + m_calo_hits_out->at(pulse_databundle_index).push_back(hit); + } + } +} + diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.h b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.h index e69de29bb..5bbffd3c8 100644 --- a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.h +++ b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.h @@ -0,0 +1,30 @@ + +#pragma once +#include +#include "TranslationTable_service.h" +#include +#include + + +class RecHit_factory : public JFactory { + +private: + + VariadicInput m_adc_pulses_in {this}; + + + VariadicOutput m_calo_hits_out {this}; + + + Service m_translation_table{this}; + +public: + + RecHit_factory(); + + void Process(const JEvent& event) override; + +}; + + + diff --git a/src/examples/tutorial_with_podio_datamodel/03_protocluster_factory/Protocluster_factory.h b/src/examples/tutorial_with_podio_datamodel/03_protocluster_factory/Protocluster_factory.h index 3a8f53227..119283e14 100644 --- a/src/examples/tutorial_with_podio_datamodel/03_protocluster_factory/Protocluster_factory.h +++ b/src/examples/tutorial_with_podio_datamodel/03_protocluster_factory/Protocluster_factory.h @@ -1,6 +1,7 @@ #pragma once -#include +#include +#include #include #include diff --git a/src/libraries/JANA/Components/JHasOutputs.h b/src/libraries/JANA/Components/JHasOutputs.h index 8bda27058..1d02a3d35 100644 --- a/src/libraries/JANA/Components/JHasOutputs.h +++ b/src/libraries/JANA/Components/JHasOutputs.h @@ -48,6 +48,14 @@ class JHasOutputs { virtual void SetShortNames(std::vector) {} virtual void SetUniqueNames(std::vector) {} + std::vector GetUniqueNames() { + std::vector results; + for (auto* databundle: m_databundles) { + results.push_back(databundle->GetUniqueName()); + } + return results; + }; + virtual void LagrangianStore(JFactorySet&, JDatabundle::Status) {} virtual void EulerianStore(JFactorySet&) {} diff --git a/src/libraries/JANA/Components/JLightweightOutput.h b/src/libraries/JANA/Components/JLightweightOutput.h index cdd51edaa..feb321e30 100644 --- a/src/libraries/JANA/Components/JLightweightOutput.h +++ b/src/libraries/JANA/Components/JLightweightOutput.h @@ -105,6 +105,7 @@ class VariadicOutput : public JHasOutputs::VariadicOutputBase { } std::vector>& operator()() { return m_transient_datas; } + std::vector>* operator->() { return &m_transient_datas; } void SetShortNames(std::vector short_names) override { GetDatabundles().clear(); // TODO: Tiny memory leak @@ -180,3 +181,4 @@ class VariadicOutput : public JHasOutputs::VariadicOutputBase { } // jana::components template using Output = jana::components::Output; +template using VariadicOutput = jana::components::VariadicOutput; From 18214f24d9a8ca4b5d752dc9ae2c4131f19740d2 Mon Sep 17 00:00:00 2001 From: Nathan Brei Date: Tue, 9 Sep 2025 14:12:23 -0400 Subject: [PATCH 4/6] TranslationTable_service handles gain/offset calibrations --- .../11_calibrations/RecHit_factory.cc | 32 +++++-- .../11_calibrations/RecHit_factory.h | 6 +- .../11_calibrations/RecHit_factory_tests.cc | 21 ++-- .../TranslationTable_service.cc | 95 +++++++++++++------ .../TranslationTable_service.h | 48 +++++++--- 5 files changed, 136 insertions(+), 66 deletions(-) diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.cc b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.cc index dd496dab1..24b65a491 100644 --- a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.cc +++ b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.cc @@ -9,12 +9,19 @@ RecHit_factory::RecHit_factory() { m_calo_hits_out.SetShortNames({"rechits"}); } -void RecHit_factory::Process(const JEvent& event) { +void RecHit_factory::ChangeRun(const JEvent& event) { - auto name = m_adc_pulses_in.GetRealizedDatabundleNames(); + // We use ChangeRun to obtain any run-level data we need. This is ONLY called when the run number has changed. + // We cache the run-encoded data on the factory directly (Remember that there are many factories in memory at any given time!) + // If the data is large, we use shared_ptrs (under the hood here) to ensure that there is only one copy in memory, and that + // it gets deleted once the run number changes. + m_lookup_table = m_translation_table_svc->GetDAQLookupTable(event.GetRunNumber()); +} + +void RecHit_factory::Process(const JEvent&) { - // This time we iterate over EACH input databundle we've been provided + // We iterate over EACH input databundle we've been provided for (size_t pulse_databundle_index = 0; pulse_databundle_indexsize(); ++pulse_databundle_index) { @@ -28,25 +35,32 @@ void RecHit_factory::Process(const JEvent& event) { throw JException("Found wrong number of ADCPulse inputs!"); } - LOG_DEBUG(GetLogger()) << "Reconstructing hits. " - << m_adc_pulses_in.GetRealizedDatabundleNames().at(pulse_databundle_index) + LOG_DEBUG(GetLogger()) << "Reconstructing hits. " + << m_adc_pulses_in.GetRealizedDatabundleNames().at(pulse_databundle_index) << " -> " << m_calo_hits_out.GetUniqueNames().at(pulse_databundle_index); // Process each pulse in this databundle + for (const auto* pulse: m_adc_pulses_in->at(pulse_databundle_index)) { // Translate from DAQ coordinates to detector coordinates - auto& detector_coords = m_translation_table->TranslateDAQCoordinates(event.GetRunNumber(), {pulse->crate, pulse->slot, pulse->channel}); + + auto& row = m_lookup_table->at({pulse->crate, pulse->slot, pulse->channel}); + auto& detector_coords = std::get<0>(row); + auto& calib = std::get<1>(row); auto hit = new CalorimeterHit(detector_coords.cell_id, detector_coords.indices.at(0), detector_coords.indices.at(1), detector_coords.x, detector_coords.y, detector_coords.z, 0, 0); - // TODO: Apply energy calibrations - hit->energy = pulse->amplitude; - hit->time = pulse->timestamp; + // Apply gains and offsets + + hit->energy = ((pulse->integral - pulse->pedestal) * calib.gain) - calib.pedestal; + hit->time = (pulse->timestamp * calib.tick_period) - calib.time_offset; + + // Add hit to corresponding output databundle m_calo_hits_out->at(pulse_databundle_index).push_back(hit); } diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.h b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.h index 5bbffd3c8..129b2d112 100644 --- a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.h +++ b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.h @@ -12,16 +12,18 @@ class RecHit_factory : public JFactory { VariadicInput m_adc_pulses_in {this}; - VariadicOutput m_calo_hits_out {this}; + Service m_translation_table_svc {this}; - Service m_translation_table{this}; + TranslationTable_service::DAQLookupTable m_lookup_table; public: RecHit_factory(); + void ChangeRun(const JEvent& event) override; + void Process(const JEvent& event) override; }; diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory_tests.cc b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory_tests.cc index 2caf97710..d7d2c4255 100644 --- a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory_tests.cc +++ b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory_tests.cc @@ -8,25 +8,26 @@ TEST_CASE("TranslationTable_service_BasicTests") { JApplication app; auto sut = std::make_shared(); - sut->AddRow(22, + sut->AddHardcodedRow(22, {.crate=1, .slot=1, .channel=1}, - {.detector_id = 7, .cell_id = 9, .indices = {1,2,3}, .x = 0.4, .y = 0.8, .z = 0.0}); + {.detector_id = 7, .cell_id = 9, .indices = {1,2,3}, .x = 0.4, .y = 0.8, .z = 0.0}, {}); - sut->AddRow(22, + sut->AddHardcodedRow(22, {.crate=1, .slot=1, .channel=2}, - {.detector_id = 7, .cell_id = 10, .indices = {1,2,4}, .x = 0.5, .y = 0.8, .z = 0.0}); + {.detector_id = 7, .cell_id = 10, .indices = {1,2,4}, .x = 0.5, .y = 0.8, .z = 0.0}, {}); - sut->AddRow(22, + sut->AddHardcodedRow(22, {.crate=1, .slot=2, .channel=1}, - {.detector_id = 7, .cell_id = 11, .indices = {1,2,5}, .x = 0.6, .y = 0.8, .z = 0.0}); + {.detector_id = 7, .cell_id = 11, .indices = {1,2,5}, .x = 0.6, .y = 0.8, .z = 0.0}, {}); app.ProvideService(sut); app.Initialize(); auto tt = app.template GetService(); - auto row = tt->TranslateDAQCoordinates(22, {1, 1, 2}); - REQUIRE(row.detector_id == 7); - REQUIRE(row.cell_id== 10); - REQUIRE(row.indices.at(2) == 4); + auto lookup_table = tt->GetDAQLookupTable(22); + const auto& row = lookup_table->at({1, 1, 2}); + REQUIRE(std::get<0>(row).detector_id == 7); + REQUIRE(std::get<0>(row).cell_id== 10); + REQUIRE(std::get<0>(row).indices.at(2) == 4); } TEST_CASE("RecHit_factory_BasicTests") { diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/TranslationTable_service.cc b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/TranslationTable_service.cc index 345821795..e7f3627ee 100644 --- a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/TranslationTable_service.cc +++ b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/TranslationTable_service.cc @@ -1,41 +1,74 @@ #include "TranslationTable_service.h" -void TranslationTable_service::Init() { -} -void TranslationTable_service::AddRow(int run_number, + +// It is helpful to think of a JService as being a JANA adapter for a third-party data service, meant +// to be used for ANY data that is not inserted into the event by a component ("side-loading"). +// In practice this means calibrations, geometry, magnetic field maps, etc. + +// We recommend: +// 1. Having JServices be as standalone as possible. In particular, they should not depend on the +// project's data model, because they are likely to be reused by different projects in the future. +// For example, if we do a beam test on an ePIC detector in HallD, we would want to reuse the +// GlueX translation tables with the ePIC data model and reconstruction factories. +// +// 2. Don't include logic that could belong on a factory or algorithm. This is because JServices are effectively +// singletons, i.e. there's only one instance in the project at any time. This means that you can't do A/B testing, +// and can't swap out different implementations like you would with a Factory. +// + +void TranslationTable_service::AddHardcodedRow(int run_number, TranslationTable_service::DAQCoordinates daq_coords, - TranslationTable_service::DetectorCoordinates det_coords) { - - std::map* table = nullptr; - auto table_it = m_daq_to_det_lookup.find(run_number); - if (table_it == m_daq_to_det_lookup.end()) { - auto owned_table = std::make_unique>(); - table = owned_table.get(); - m_daq_to_det_lookup[run_number] = std::move(owned_table); - } - else { - table = table_it->second.get(); - } - (*table)[daq_coords] = det_coords; + TranslationTable_service::DetectorCoordinates det_coords, + TranslationTable_service::ChannelCalibration calibs) { + + m_hardcoded_data.push_back({run_number, daq_coords, det_coords, calibs}); } -const TranslationTable_service::DetectorCoordinates& TranslationTable_service::TranslateDAQCoordinates(int run_number, - const DAQCoordinates& daq_coords) { - std::map* table = nullptr; - auto table_it = m_daq_to_det_lookup.find(run_number); - if (table_it == m_daq_to_det_lookup.end()) { - auto owned_table = FetchTable(run_number); - table = owned_table.get(); - m_daq_to_det_lookup[run_number] = std::move(owned_table); - } - else { - table = table_it->second.get(); + +TranslationTable_service::DAQLookupTable TranslationTable_service::GetDAQLookupTable(int run_number) { + + auto table_it = m_daq_lookups.find(run_number); + if (table_it != m_daq_lookups.end()) { + auto locked = table_it->second.lock(); + if (locked) { + // We have the lookup table still, hand out another copy + return locked; + } } - return table->at(daq_coords); + + // At this point we either never had the lookup table, or it was already destroyed, so we need to re-fetch it + + auto shared_table = FetchDAQLookupTable(run_number); + m_daq_lookups[run_number] = shared_table; + // m_daq_lookups holds on to a weak pointer, which goes away automatically when the last factory lets go + return shared_table; } -std::unique_ptr> TranslationTable_service::FetchTable(int /*run_number*/) { - throw JException("FetchTable() not implemented yet! Use hardcoded table instead. (Hint: Are you using a different run number from what you hardcoded?)"); + +TranslationTable_service::DAQLookupTable TranslationTable_service::FetchDAQLookupTable(int run_number) { + + // In principle, you could load the table from anywhere you like, including a text file. + // However, managing external resources is tricky, so we recommend starting with the existing machinery instead of implementing something incrementally. + // JANA's calibration manager supports both small calibration data (e.g. gain values) and larger resources (e.g. magnetic field maps) with + // swappable backends including XML, SQLite, and MySQL. If you are working on ePIC, you'll probably be loading these things + // through dd4hep instead of JANA2's own calibration manager. It doesn't matter, just be consistent. + + auto shared_table = std::make_shared>>(); + + for (auto& hardcoded_row : m_hardcoded_data) { + + auto& row_run_number = std::get<0>(hardcoded_row); + const DAQCoordinates& daq_coords = std::get<1>(hardcoded_row); + const DetectorCoordinates& det_coords = std::get<2>(hardcoded_row); + const ChannelCalibration& calibs = std::get<3>(hardcoded_row); + + if (row_run_number == run_number) { + shared_table->insert({daq_coords, std::make_tuple(det_coords, calibs)}); + } + } + return shared_table; } + diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/TranslationTable_service.h b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/TranslationTable_service.h index d2dfca467..b36f170c3 100644 --- a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/TranslationTable_service.h +++ b/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/TranslationTable_service.h @@ -6,35 +6,55 @@ class TranslationTable_service : public JService { public: struct DAQCoordinates { - int crate; - int slot; - int channel; + uint32_t crate; + uint32_t slot; + uint32_t channel; }; struct DetectorCoordinates { - int detector_id; - int cell_id; + uint32_t detector_id; + uint32_t cell_id; std::vector indices; double x; double y; double z; }; + struct ChannelCalibration { + double gain; // MeV per ADC count + double pedestal; // MeV + double tick_period; // ns per clock tick + double time_offset; // ns + }; + + using DAQLookupTable = std::shared_ptr>>; private: - std::map>> m_daq_to_det_lookup; - // std::map m_cell_id_to_daq_lookup; - // std::map m_cell_id_to_det_lookup; + std::map>>> m_daq_lookups; + + // std::map>>> m_cell_lookups; + + std::vector> m_hardcoded_data; public: - void Init() override; - const DetectorCoordinates& TranslateDAQCoordinates(int run_number, const DAQCoordinates&); - // const DetectorCoordinates& Translate(int run_number, const DAQCoordinates&) const; - void AddRow(int run_number, DAQCoordinates daq_coords, DetectorCoordinates det_coords); - std::unique_ptr> FetchTable(int run_number); - + // For hit reconstruction + DAQLookupTable GetDAQLookupTable(int run_number); + + // For digitization of sim data, as well as two-phase hit reconstruction ("digihits") + // std::shared_ptr>> GetCellLookupTable(int run_number); + + // For testing + void AddHardcodedRow(int run_number, DAQCoordinates daq_coords, DetectorCoordinates det_coords, ChannelCalibration calibs); + +private: + + DAQLookupTable FetchDAQLookupTable(int run_number); + + // std::shared_ptr>> FetchCellLookupTable(int run_number); }; inline bool operator<(const TranslationTable_service::DAQCoordinates& lhs, const TranslationTable_service::DAQCoordinates& rhs) { return (lhs.crate < rhs.crate || lhs.slot < rhs.slot || lhs.channel < rhs.channel); } + + From c087bd4eb21721fca8cda4eb742f80d35f3c8176 Mon Sep 17 00:00:00 2001 From: Nathan Brei Date: Tue, 9 Sep 2025 15:09:55 -0400 Subject: [PATCH 5/6] Rename hit reconstruction example --- .../{11_calibrations => 11_hit_reco}/CMakeLists.txt | 6 +++--- .../HitReconstruction_factory.cc} | 8 ++++---- .../HitReconstruction_factory.h} | 4 ++-- .../HitReconstruction_factory_tests.cc} | 0 .../TranslationTable_service.cc | 0 .../TranslationTable_service.h | 0 .../{11_calibrations => 11_hit_reco}/hit_reco_plugin.cc | 0 .../tutorial_with_lightweight_datamodel/CMakeLists.txt | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) rename src/examples/tutorial_with_lightweight_datamodel/{11_calibrations => 11_hit_reco}/CMakeLists.txt (85%) rename src/examples/tutorial_with_lightweight_datamodel/{11_calibrations/RecHit_factory.cc => 11_hit_reco/HitReconstruction_factory.cc} (93%) rename src/examples/tutorial_with_lightweight_datamodel/{11_calibrations/RecHit_factory.h => 11_hit_reco/HitReconstruction_factory.h} (85%) rename src/examples/tutorial_with_lightweight_datamodel/{11_calibrations/RecHit_factory_tests.cc => 11_hit_reco/HitReconstruction_factory_tests.cc} (100%) rename src/examples/tutorial_with_lightweight_datamodel/{11_calibrations => 11_hit_reco}/TranslationTable_service.cc (100%) rename src/examples/tutorial_with_lightweight_datamodel/{11_calibrations => 11_hit_reco}/TranslationTable_service.h (100%) rename src/examples/tutorial_with_lightweight_datamodel/{11_calibrations => 11_hit_reco}/hit_reco_plugin.cc (100%) diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/CMakeLists.txt b/src/examples/tutorial_with_lightweight_datamodel/11_hit_reco/CMakeLists.txt similarity index 85% rename from src/examples/tutorial_with_lightweight_datamodel/11_calibrations/CMakeLists.txt rename to src/examples/tutorial_with_lightweight_datamodel/11_hit_reco/CMakeLists.txt index 53937d432..23964d472 100644 --- a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/CMakeLists.txt +++ b/src/examples/tutorial_with_lightweight_datamodel/11_hit_reco/CMakeLists.txt @@ -5,13 +5,13 @@ add_jana_library(lw_hit_reco_common SOURCES - RecHit_factory.cc + HitReconstruction_factory.cc TranslationTable_service.cc PUBLIC_HEADER - RecHit_factory.h + HitReconstruction_factory.h TranslationTable_service.h TESTS - RecHit_factory_tests.cc + HitReconstruction_factory_tests.cc ) # `add_jana_library` will create a target for us, but we still need to link it against diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.cc b/src/examples/tutorial_with_lightweight_datamodel/11_hit_reco/HitReconstruction_factory.cc similarity index 93% rename from src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.cc rename to src/examples/tutorial_with_lightweight_datamodel/11_hit_reco/HitReconstruction_factory.cc index 24b65a491..70b84b89b 100644 --- a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.cc +++ b/src/examples/tutorial_with_lightweight_datamodel/11_hit_reco/HitReconstruction_factory.cc @@ -1,15 +1,15 @@ -#include "RecHit_factory.h" +#include "HitReconstruction_factory.h" #include "ADCPulse.h" #include #include "CalorimeterHit.h" -RecHit_factory::RecHit_factory() { +HitReconstruction_factory::HitReconstruction_factory() { m_adc_pulses_in.SetRequestedDatabundleNames({"raw"}); m_calo_hits_out.SetShortNames({"rechits"}); } -void RecHit_factory::ChangeRun(const JEvent& event) { +void HitReconstruction_factory::ChangeRun(const JEvent& event) { // We use ChangeRun to obtain any run-level data we need. This is ONLY called when the run number has changed. // We cache the run-encoded data on the factory directly (Remember that there are many factories in memory at any given time!) @@ -19,7 +19,7 @@ void RecHit_factory::ChangeRun(const JEvent& event) { m_lookup_table = m_translation_table_svc->GetDAQLookupTable(event.GetRunNumber()); } -void RecHit_factory::Process(const JEvent&) { +void HitReconstruction_factory::Process(const JEvent&) { // We iterate over EACH input databundle we've been provided for (size_t pulse_databundle_index = 0; pulse_databundle_indexsize(); ++pulse_databundle_index) { diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.h b/src/examples/tutorial_with_lightweight_datamodel/11_hit_reco/HitReconstruction_factory.h similarity index 85% rename from src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.h rename to src/examples/tutorial_with_lightweight_datamodel/11_hit_reco/HitReconstruction_factory.h index 129b2d112..c927bd033 100644 --- a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory.h +++ b/src/examples/tutorial_with_lightweight_datamodel/11_hit_reco/HitReconstruction_factory.h @@ -6,7 +6,7 @@ #include -class RecHit_factory : public JFactory { +class HitReconstruction_factory : public JFactory { private: @@ -20,7 +20,7 @@ class RecHit_factory : public JFactory { public: - RecHit_factory(); + HitReconstruction_factory(); void ChangeRun(const JEvent& event) override; diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory_tests.cc b/src/examples/tutorial_with_lightweight_datamodel/11_hit_reco/HitReconstruction_factory_tests.cc similarity index 100% rename from src/examples/tutorial_with_lightweight_datamodel/11_calibrations/RecHit_factory_tests.cc rename to src/examples/tutorial_with_lightweight_datamodel/11_hit_reco/HitReconstruction_factory_tests.cc diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/TranslationTable_service.cc b/src/examples/tutorial_with_lightweight_datamodel/11_hit_reco/TranslationTable_service.cc similarity index 100% rename from src/examples/tutorial_with_lightweight_datamodel/11_calibrations/TranslationTable_service.cc rename to src/examples/tutorial_with_lightweight_datamodel/11_hit_reco/TranslationTable_service.cc diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/TranslationTable_service.h b/src/examples/tutorial_with_lightweight_datamodel/11_hit_reco/TranslationTable_service.h similarity index 100% rename from src/examples/tutorial_with_lightweight_datamodel/11_calibrations/TranslationTable_service.h rename to src/examples/tutorial_with_lightweight_datamodel/11_hit_reco/TranslationTable_service.h diff --git a/src/examples/tutorial_with_lightweight_datamodel/11_calibrations/hit_reco_plugin.cc b/src/examples/tutorial_with_lightweight_datamodel/11_hit_reco/hit_reco_plugin.cc similarity index 100% rename from src/examples/tutorial_with_lightweight_datamodel/11_calibrations/hit_reco_plugin.cc rename to src/examples/tutorial_with_lightweight_datamodel/11_hit_reco/hit_reco_plugin.cc diff --git a/src/examples/tutorial_with_lightweight_datamodel/CMakeLists.txt b/src/examples/tutorial_with_lightweight_datamodel/CMakeLists.txt index 90fa35799..b841ad125 100644 --- a/src/examples/tutorial_with_lightweight_datamodel/CMakeLists.txt +++ b/src/examples/tutorial_with_lightweight_datamodel/CMakeLists.txt @@ -9,7 +9,7 @@ add_subdirectory(07_root_histogram_writer) add_subdirectory(08_root_file_writer) add_subdirectory(09_root_file_reader) add_subdirectory(10_root_event_viewer) -add_subdirectory(11_calibrations) +add_subdirectory(11_hit_reco) add_subdirectory(12_geometry) add_subdirectory(13_digitization) add_subdirectory(14_simulation_source) From 2b470c23f59f32f76d5e58d563aa2b87410645ee Mon Sep 17 00:00:00 2001 From: Nathan Brei Date: Tue, 9 Sep 2025 15:18:46 -0400 Subject: [PATCH 6/6] Add TDCPulse, TDCHit to lightweight datamodel --- .../01_datamodel/TDCHit.h | 37 +++++++++++++++++++ .../01_datamodel/TDCPulse.h | 31 ++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 src/examples/tutorial_with_lightweight_datamodel/01_datamodel/TDCHit.h create mode 100644 src/examples/tutorial_with_lightweight_datamodel/01_datamodel/TDCPulse.h diff --git a/src/examples/tutorial_with_lightweight_datamodel/01_datamodel/TDCHit.h b/src/examples/tutorial_with_lightweight_datamodel/01_datamodel/TDCHit.h new file mode 100644 index 000000000..5f1a08ec5 --- /dev/null +++ b/src/examples/tutorial_with_lightweight_datamodel/01_datamodel/TDCHit.h @@ -0,0 +1,37 @@ + +// Copyright 2020-2025, Jefferson Science Associates, LLC. +// Subject to the terms in the LICENSE file found in the top-level directory. + +#pragma once +#include +#include + + +struct TDCHit: public JObject { + + JOBJECT_PUBLIC(TDCHit) + + uint32_t cell_id; // Cell's id + + double x; // x location of cell's center [cm] + double y; // y location of cell's center [cm] + double z; // z location of cell's center [cm] + + double time; // Time [ns] + + + // Define a constructor that forces all fields to be initialized + TDCHit(uint32_t cell_id, double x, double y, double z, uint64_t time) + : cell_id(cell_id), x(x), y(y), z(z), time(time) {} + + + void Summarize(JObjectSummary& summary) const override { + summary.add(cell_id, NAME_OF(cell_id), "%d", "Cell ID"); + summary.add(x, NAME_OF(x), "%f", "x location of cell center [cm]"); + summary.add(y, NAME_OF(y), "%f", "y location of cell center [cm]"); + summary.add(z, NAME_OF(z), "%f", "z location of cell center [cm]"); + summary.add(time, NAME_OF(time), "%d", "Time [ns]"); + } +}; + + diff --git a/src/examples/tutorial_with_lightweight_datamodel/01_datamodel/TDCPulse.h b/src/examples/tutorial_with_lightweight_datamodel/01_datamodel/TDCPulse.h new file mode 100644 index 000000000..6a9723f3b --- /dev/null +++ b/src/examples/tutorial_with_lightweight_datamodel/01_datamodel/TDCPulse.h @@ -0,0 +1,31 @@ + +// Copyright 2020, Jefferson Science Associates, LLC. +// Subject to the terms in the LICENSE file found in the top-level directory. + +#pragma once +#include +#include + +struct TDCPulse: public JObject { + + JOBJECT_PUBLIC(TDCPulse) + + uint32_t crate; + uint32_t slot; + uint32_t channel; + + uint32_t coarse_time; + uint32_t fine_time; + bool is_leading; + + void Summarize(JObjectSummary& summary) const override { + summary.add(crate, NAME_OF(crate), "%d"); + summary.add(slot, NAME_OF(slot), "%d"); + summary.add(channel, NAME_OF(channel), "%d"); + summary.add(coarse_time, NAME_OF(coarse_time), "%d"); + summary.add(fine_time, NAME_OF(fine_time), "%d"); + summary.add(is_leading, NAME_OF(is_leading), "%d"); + } +}; + +