From fac5bbfd59ec6b8e8b7f4d399b8bb1bbb014d964 Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Fri, 3 Apr 2026 19:30:23 -0500 Subject: [PATCH 01/13] Added art association utility header sbncode/Utilities/Assns.h Contains `sbn::RebindAssociatedProducts()` to make a copy of an association with the data product on one side replaced by another one. --- sbncode/CMakeLists.txt | 1 + sbncode/Utilities/AssnsUtils.h | 144 +++++++++++++++++++++++++++++++ sbncode/Utilities/CMakeLists.txt | 11 +++ 3 files changed, 156 insertions(+) create mode 100644 sbncode/Utilities/AssnsUtils.h create mode 100644 sbncode/Utilities/CMakeLists.txt diff --git a/sbncode/CMakeLists.txt b/sbncode/CMakeLists.txt index daed2590a..ab6eb0aab 100644 --- a/sbncode/CMakeLists.txt +++ b/sbncode/CMakeLists.txt @@ -21,6 +21,7 @@ add_subdirectory(CosmicID) add_subdirectory(DetSim) add_subdirectory(Cluster3D) add_subdirectory(HitFinder) +add_subdirectory(Utilities) # Supera # diff --git a/sbncode/Utilities/AssnsUtils.h b/sbncode/Utilities/AssnsUtils.h new file mode 100644 index 000000000..46be1e4c5 --- /dev/null +++ b/sbncode/Utilities/AssnsUtils.h @@ -0,0 +1,144 @@ +/** + * @file sbncode/Utilities/AssnsUtils.h + * @brief Framework-level utilities dealing with associations. + * @date April 2, 2026 + * @author Gianluca Petrillo (petrillo@slac.stanford.edu) + * + * This library is currently header-only. + */ + +#ifndef SBNCODE_UTILITIES_ASSNSUTILS_H +#define SBNCODE_UTILITIES_ASSNSUTILS_H + +// framework libraries +#include "art/Persistency/Common/PtrMaker.h" +#include "canvas/Persistency/Common/Ptr.h" +#include "canvas/Persistency/Common/Assns.h" + +// C/C++ libraries +#include // std::size_t +#include // std::get() +#include // std::is_same_v, ... + + +namespace sbn { + /** + * @brief Creates an association of a new product mirroring an existing one. + * @tparam DiscardData (default: `false`) whether to omit copying the data + * @tparam L type of left-side data in the association + * @tparam R type of right-side data in the association + * @tparam Data type of metadata in the association + * @tparam LorR type whose pointer is being rebound + * @param assns the existing association + * @param reboundPtrMaker tool to create a _art_ pointer to the new product + * @return the newly created association + * + * The existing association `assns` is replicated replacing all the pointers + * in the rebinding side (left or right) with their corresponding pointers + * from another collection of the same type. + * + * The application is for one-to-one processing of data product elements, + * where the element resulting from the processing is still associated to the + * same object as the processed element. + * For example, let's have a reprocessing that modifies the signal on a + * optical detector waveform (`raw::OpDetWaveform`), leaving the baseline + * unchanged. + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} + * using Assns_t = art::Assns; + * + * Assns_t const& oldAssns = event.getProduct(assnsTag); + * art::PtrMaker const makeWaveformPtr{ event }; + * + * auto newAssns + * = std::make_unique(sbn::RebindAssociatedProducts(oldAssns, makeWaveformPtr)); + * + * event.put(std::move(pWaveformBaselineAssns)); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * The alias `Assns_t` was used only to make the example more readable. + * + * As shown in the example, which side to rebind is determined by the type + * of the `art::PtrMaker` object. If its operand type is neither of the + * association types, a compilation error will occur. + * + * Note that the invalid pointers are also rebound, but their rebound pointer + * should be as invalid as the old one. + * + * If metadata is present in the associations, it is _copied_ element by + * element into the new one. It is possible to omit the metadata by calling + * with `DiscardData` set to `true` (`RebindAssociatedProducts(...)`), + * in which case the returned type will always be an association without data. + * + */ + template < + bool DiscardData = false, + typename L, typename R, typename Data, + typename LorR + > + art::Assns> + RebindAssociatedProducts( + art::Assns const& assns, + art::PtrMaker const& reboundPtrMaker + ); + +} // namespace sbn + + +// ----------------------------------------------------------------------------- +// --- Template implementation +// ----------------------------------------------------------------------------- +template < + bool DiscardData /* = false */, + typename L, typename R, typename Data, + typename LorR + > +art::Assns> +sbn::RebindAssociatedProducts( + art::Assns const& assns, + art::PtrMaker const& reboundPtrMaker +) { + static_assert(std::is_same_v || std::is_same_v, + "The PtrMaker tool must operate on either of the association data types."); + + constexpr bool RebindLeft = std::is_same_v; + constexpr bool hasMetadata = !std::is_void_v; + constexpr bool wantsMetadata = hasMetadata && !DiscardData; + + using DestData_t = std::conditional_t; + + /* + * How to deal with invalid pointers? (there should be none though!) + * a) preserve them (drawback: different product IDs are mixed in) + * b) replace them with default-constructed (which is also invalid) + * c) rebind it (drawback: rely on PtrMaker doing the right thing with them) + * Here I chose (c). + */ + auto rebind = [&reboundPtrMaker](auto& ptr) + { /* if (ptr) */ ptr = reboundPtrMaker(ptr.key()); }; + + art::Assns newAssns; + for (auto elements: assns) { + + art::Ptr leftPtr = std::move(std::get<0>(elements)); + art::Ptr rightPtr = std::move(std::get<1>(elements)); + + if constexpr(RebindLeft) { rebind(leftPtr); } + else { rebind(rightPtr); } + + if constexpr(wantsMetadata) { + newAssns.addSingle + (std::move(leftPtr), std::move(rightPtr), std::move(std::get<2>(elements))); + } + else { + newAssns.addSingle(std::move(leftPtr), std::move(rightPtr)); + } + + } // for assn pairs + + return newAssns; + +} // RebindAssociatedProducts() + + +// ----------------------------------------------------------------------------- + +#endif // SBNCODE_UTILITIES_ASSNSUTILS_H diff --git a/sbncode/Utilities/CMakeLists.txt b/sbncode/Utilities/CMakeLists.txt new file mode 100644 index 000000000..a94a8b369 --- /dev/null +++ b/sbncode/Utilities/CMakeLists.txt @@ -0,0 +1,11 @@ +cet_make_library( + INTERFACE + HEADERS_TARGET_ONLY + LIBRARIES + art::Persistency_Common + canvas::canvas + ) + +install_headers() +install_source() + From d9694c078a6a1940ee6ea21fff2ef975c7987f59 Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Fri, 3 Apr 2026 19:38:30 -0500 Subject: [PATCH 02/13] AdjustSimForTrigger_module.cc: code maintenance --- sbncode/DetSim/AdjustSimForTrigger_module.cc | 6 +++- sbncode/DetSim/CMakeLists.txt | 33 +++----------------- 2 files changed, 10 insertions(+), 29 deletions(-) diff --git a/sbncode/DetSim/AdjustSimForTrigger_module.cc b/sbncode/DetSim/AdjustSimForTrigger_module.cc index 5f6c5eee9..4e75a7ce3 100644 --- a/sbncode/DetSim/AdjustSimForTrigger_module.cc +++ b/sbncode/DetSim/AdjustSimForTrigger_module.cc @@ -24,9 +24,13 @@ #include "lardataobj/Simulation/SimEnergyDeposit.h" #include "lardataobj/Simulation/SimEnergyDepositLite.h" #include "lardataobj/Simulation/SimPhotons.h" +#include "lardata/DetectorInfoServices/DetectorClocksService.h" -#include +#include // std::boolalpha +#include #include +#include // std::move() +#include class AdjustSimForTrigger; diff --git a/sbncode/DetSim/CMakeLists.txt b/sbncode/DetSim/CMakeLists.txt index 113aa375a..813562b60 100644 --- a/sbncode/DetSim/CMakeLists.txt +++ b/sbncode/DetSim/CMakeLists.txt @@ -1,34 +1,11 @@ set( MODULE_LIBRARIES + lardata::DetectorClocksService + larcore::headers + sbncode::Utilities larcorealg::Geometry - larcore::Geometry_Geometry_service - larsim::Simulation - nug4::ParticleNavigation - lardataobj::Simulation - lardata::Utilities - lardataalg::DetectorInfo - larevt::Filters lardataobj::RawData - larevt::CalibrationDBI_Providers - nurandom::RandomUtils_NuRandomService_service - art::Framework_Core - art::Framework_Principal - art::Framework_Services_Registry - art_root_io::tfile_support ROOT::Core - art::Framework_Services_Optional_RandomNumberGenerator_service - art_root_io::TFileService_service - art::Persistency_Common - art::Persistency_Provenance - art::Utilities - canvas::canvas - messagefacility::MF_MessageLogger - messagefacility::headers - fhiclcpp::fhiclcpp - cetlib::cetlib - CLHEP::Random - ROOT::Geom - ROOT::XMLIO - ROOT::Gdml - FFTW3::FFTW3 + lardataobj::Simulation + sbnobj::ICARUS_PMT_Data ) cet_build_plugin(AdjustSimForTrigger art::module LIBRARIES ${MODULE_LIBRARIES}) From 91d41790c2e3e153e13e0e3193e91198a3174499 Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Fri, 3 Apr 2026 19:39:21 -0500 Subject: [PATCH 03/13] AdjustSimForTrigger module: produced a shifted raw::Trigger too --- sbncode/DetSim/AdjustSimForTrigger_module.cc | 28 +++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/sbncode/DetSim/AdjustSimForTrigger_module.cc b/sbncode/DetSim/AdjustSimForTrigger_module.cc index 4e75a7ce3..2052897bf 100644 --- a/sbncode/DetSim/AdjustSimForTrigger_module.cc +++ b/sbncode/DetSim/AdjustSimForTrigger_module.cc @@ -64,12 +64,13 @@ class AdjustSimForTrigger : public art::EDProducer { bool fShiftSimPhotons; bool fShiftWaveforms; double fAdditionalOffset; + bool fDropTriggerProduct; ///< Do not put the shifted trigger data product into the event. static constexpr auto& kModuleName = "AdjustSimForTrigger"; }; AdjustSimForTrigger::AdjustSimForTrigger(fhicl::ParameterSet const& p) : EDProducer{p} - , fInputTriggerLabel{p.get("InputTriggerLabel", "undefined")} + , fInputTriggerLabel{p.get("InputTriggerLabel")} , fInitAuxDetSimChannelLabel(p.get("InitAuxDetSimChannelLabel", "undefined")) , fInitBeamGateInfoLabel{p.get("InitBeamGateInfoLabel", "undefined")} , fInitSimEnergyDepositLabel{p.get("InitSimEnergyDepositLabel", "undefined")} @@ -83,6 +84,7 @@ AdjustSimForTrigger::AdjustSimForTrigger(fhicl::ParameterSet const& p) , fShiftSimPhotons{p.get("ShiftSimPhotons", false)} , fShiftWaveforms{p.get("ShiftWaveforms", false)} , fAdditionalOffset{p.get("AdditionalOffset", 0.)} + , fDropTriggerProduct{p.get("DropTriggerProduct", false)} { if (!(fShiftSimEnergyDeposits || fShiftSimPhotons || fShiftWaveforms || fShiftAuxDetIDEs || fShiftBeamGateInfo || fShiftSimEnergyDepositLites)) { @@ -96,6 +98,7 @@ AdjustSimForTrigger::AdjustSimForTrigger(fhicl::ParameterSet const& p) << "SHIFTING SIMPHOTONS? " << fShiftSimPhotons << '\n' << "SHIFTING OPDETWAVEFORMS? " << fShiftWaveforms; + if (!fDropTriggerProduct) produces>(); if (fShiftAuxDetIDEs) { produces>(); } if (fShiftBeamGateInfo) { produces>(); } if (fShiftSimEnergyDeposits) { produces>(); } @@ -127,13 +130,32 @@ void AdjustSimForTrigger::produce(art::Event& e) trigger.TriggerTime() < (std::numeric_limits::max() - std::numeric_limits::epsilon()); - const double timeShiftForTrigger_us = - hasValidTriggerTime ? clock_data.TriggerTime() - trigger.TriggerTime() + fAdditionalOffset : 0.; + const double newReferenceTime = + hasValidTriggerTime ? trigger.TriggerTime() - fAdditionalOffset : clock_data.TriggerTime(); + const double timeShiftForTrigger_us = clock_data.TriggerTime() - newReferenceTime; const double timeShiftForTrigger_ns = 1000. * timeShiftForTrigger_us; mf::LogInfo(kModuleName) << "FOR THIS EVENT THE TIME SHIFT BEING ASSUMED IS " << timeShiftForTrigger_ns << " ns ..."; + // Shifted trigger (beam gate info, optional, is later) + // sbn::ExtraTriggerInfo and raw::ExternalTrigger are not shifted (so far); + // it's debatable if they should be, since they only hold absolute timestamps + if (!fDropTriggerProduct) { + auto pShiftedTriggers = std::make_unique>(); + for (raw::Trigger const& unshiftedTrigger: triggers) { // ok, we required there is just one + pShiftedTriggers->emplace_back( + unshiftedTrigger.TriggerNumber(), + hasValidTriggerTime // trigger_time + ? (unshiftedTrigger.TriggerTime() + timeShiftForTrigger_us) + : clock_data.TriggerTime(), + unshiftedTrigger.BeamGateTime() + timeShiftForTrigger_us,// beamgate_time + unshiftedTrigger.TriggerBits() + ); + } // for all triggers + e.put(std::move(pShiftedTriggers)); + } // if produce shifted trigger + // Loop over the sim::AuxDetIDE and shift time BACK by the TRIGGER if (fShiftAuxDetIDEs) { auto const& simChannels = From a22d8bfb1bae59b21cbf6d58be96a8c0fd61f44b Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Fri, 3 Apr 2026 19:40:42 -0500 Subject: [PATCH 04/13] AdjustSimForTrigger module: produce an association between PMT waveforms and baselines --- sbncode/DetSim/AdjustSimForTrigger_module.cc | 33 ++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/sbncode/DetSim/AdjustSimForTrigger_module.cc b/sbncode/DetSim/AdjustSimForTrigger_module.cc index 2052897bf..95c02d31d 100644 --- a/sbncode/DetSim/AdjustSimForTrigger_module.cc +++ b/sbncode/DetSim/AdjustSimForTrigger_module.cc @@ -13,6 +13,8 @@ #include "art/Framework/Principal/Handle.h" #include "art/Framework/Principal/Run.h" #include "art/Framework/Principal/SubRun.h" +#include "art/Persistency/Common/PtrMaker.h" +#include "canvas/Persistency/Common/Assns.h" #include "canvas/Utilities/InputTag.h" #include "fhiclcpp/ParameterSet.h" #include "messagefacility/MessageLogger/MessageLogger.h" @@ -24,7 +26,10 @@ #include "lardataobj/Simulation/SimEnergyDeposit.h" #include "lardataobj/Simulation/SimEnergyDepositLite.h" #include "lardataobj/Simulation/SimPhotons.h" +#include "sbnobj/ICARUS/PMT/Data/WaveformBaseline.h" + #include "lardata/DetectorInfoServices/DetectorClocksService.h" +#include "sbncode/Utilities/AssnsUtils.h" // sbn::RebindAssociatedProducts() #include // std::boolalpha #include @@ -63,6 +68,7 @@ class AdjustSimForTrigger : public art::EDProducer { bool fShiftSimEnergyDepositLites; bool fShiftSimPhotons; bool fShiftWaveforms; + art::InputTag fBindWaveformBaselines; ///< Tag of OpDetWaveform-baseline associations to be rebound. double fAdditionalOffset; bool fDropTriggerProduct; ///< Do not put the shifted trigger data product into the event. static constexpr auto& kModuleName = "AdjustSimForTrigger"; @@ -83,6 +89,7 @@ AdjustSimForTrigger::AdjustSimForTrigger(fhicl::ParameterSet const& p) , fShiftSimEnergyDepositLites{p.get("ShiftSimEnergyDepositLites", false)} , fShiftSimPhotons{p.get("ShiftSimPhotons", false)} , fShiftWaveforms{p.get("ShiftWaveforms", false)} + , fBindWaveformBaselines{p.get("BindWaveformBaselines", "")} , fAdditionalOffset{p.get("AdditionalOffset", 0.)} , fDropTriggerProduct{p.get("DropTriggerProduct", false)} { @@ -91,12 +98,15 @@ AdjustSimForTrigger::AdjustSimForTrigger(fhicl::ParameterSet const& p) throw art::Exception(art::errors::EventProcessorFailure) << kModuleName << ": NO SHIFTS ENABLED!\n"; } + bool const doWaveformBaselines = fShiftWaveforms && !fBindWaveformBaselines.empty(); mf::LogInfo(kModuleName) << std::boolalpha << "SHIFTING AUXDETIDES? " << fShiftAuxDetIDEs << '\n' << "SHIFTING BEAMGATEINFO? " << fShiftBeamGateInfo << '\n' << "SHIFTING SIMENERGYDEPOSITS? " << fShiftSimEnergyDeposits << '\n' << "SHIFTING SIMENERGYDEPOSITLITES? " << fShiftSimEnergyDepositLites << '\n' << "SHIFTING SIMPHOTONS? " << fShiftSimPhotons << '\n' - << "SHIFTING OPDETWAVEFORMS? " << fShiftWaveforms; + << "SHIFTING OPDETWAVEFORMS? " << fShiftWaveforms << '\n' + << " ASSNS OPDETWAVEFORM-BASELINES? " << doWaveformBaselines + << (doWaveformBaselines? (" ('" + fBindWaveformBaselines.encode() + ")"): ""); if (!fDropTriggerProduct) produces>(); if (fShiftAuxDetIDEs) { produces>(); } @@ -104,7 +114,11 @@ AdjustSimForTrigger::AdjustSimForTrigger(fhicl::ParameterSet const& p) if (fShiftSimEnergyDeposits) { produces>(); } if (fShiftSimEnergyDepositLites) { produces>(); } if (fShiftSimPhotons) { produces>(); } - if (fShiftWaveforms) { produces>(); } + if (fShiftWaveforms) { + produces>(); + if (!fBindWaveformBaselines.empty()) + produces>(); + } } void AdjustSimForTrigger::produce(art::Event& e) @@ -279,6 +293,21 @@ void AdjustSimForTrigger::produce(art::Event& e) waveform.SetTimeStamp(waveform.TimeStamp() + timeShiftForTrigger_us); } e.put(std::move(pWaveforms)); + + if (!fBindWaveformBaselines.empty()) { + // given that the shifting is one-to-one, rebinding is just replacing + // each existing waveform pointer with one to the new waveform in the same position + auto const& waveformBaselineAssns + = e.getProduct>(fBindWaveformBaselines); + art::PtrMaker const makeWaveformPtr{ e }; + + auto pWaveformBaselineAssns + = std::make_unique> + (sbn::RebindAssociatedProducts(waveformBaselineAssns, makeWaveformPtr)); + + e.put(std::move(pWaveformBaselineAssns)); + } // if rebinding associations + } } From 770e49850aed490f1f50d512e04cc59375e73277 Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Thu, 16 Apr 2026 20:29:47 -0500 Subject: [PATCH 05/13] AdjustSimForTrigger: added documentation --- sbncode/DetSim/AdjustSimForTrigger_module.cc | 161 +++++++++++++++++-- 1 file changed, 152 insertions(+), 9 deletions(-) diff --git a/sbncode/DetSim/AdjustSimForTrigger_module.cc b/sbncode/DetSim/AdjustSimForTrigger_module.cc index 95c02d31d..624ba1c3e 100644 --- a/sbncode/DetSim/AdjustSimForTrigger_module.cc +++ b/sbncode/DetSim/AdjustSimForTrigger_module.cc @@ -1,11 +1,8 @@ -//////////////////////////////////////////////////////////////////////// -// Class: AdjustSimForTrigger -// Plugin Type: producer (Unknown Unknown) -// File: AdjustSimForTrigger_module.cc -// -// Generated at December 2023 by Bruce Howard (howard@fnal.gov) using -// cetskelgen. -//////////////////////////////////////////////////////////////////////// +/** + * @file sbncode/DetSim/AdjustSimForTrigger_module.cc + * @author Bruce Howard (howard@fnal.gov) + * @date December 2023 + */ #include "art/Framework/Core/EDProducer.h" #include "art/Framework/Core/ModuleMacros.h" @@ -37,8 +34,154 @@ #include // std::move() #include -class AdjustSimForTrigger; +/** + * @brief Applies a time shift to selected data products for simulation. + * + * This module produces a selection of simulation data products shifting their + * time reference to a new "event" (in the sense of something happening, not in + * the DAQ sense). + * + * The time point of the new reference event is specified in + * @ref DetectorClocksElectronicsTime "electronics time scale". + * Technically, this module applies a time shift so that the time of that + * reference event becomes the previous reference value in electronics time scale. + * + * More practically, we start from an electronics time scale where the reference + * time is set at `DetectorClocksData::TriggerTime()` (typically `1500.0` + * µs). + * That time reference may represent a hardware trigger, as the function name + * suggest, or anything else. At this point we introduce a new trigger time + * (from the simulation; e.g. `1502.0` µs) and we want that time to become + * the new reference (and "hardware trigger") of the electronics time scale. + * In this example, this module will add a shift of -2 µs to all the + * selected data products, so that the time of the new trigger is `1500.0` + * and all follows. + * + * @note The new trigger time is going to be whatever value is in + * `DetectorClocksData::TriggerTime()`, rather than an hard-coded value + * like `1500.0`. Therefore some care needs to be taken in the workflow + * to make sure that at the time of the execution of this module + * `DetectorClocksService` is yielding the desired new-reference value. + * Also, after this module is called `DetectorClocksService` itself may + * report obsolete values. Specifically, `DetectorClocksServiceStandard` + * will report the old `BeamGateTime()`, which was from ether a + * configuration parameter or from a trigger data product. + * + * This module reads the new reference time from a `raw::Trigger` object + * (assumed to hold a time on the current electronics scale), and, unless + * configured not to (`DropTriggerProduct`) it produces a new trigger data + * product with the shifted trigger information. This data product is suitable + * for configuring services like `DetectorClocksServiceStandard` for the + * following stages of the workflow. + * + * If the reference time is not valid, no shift is performed at all. + * A reference time is valid if it is neither the maximum nor the minimum value + * of a double (`std::numeric_limits::max()` and + * `std::numeric_limits::min()`). + * + * + * ### Optional shift + * + * It is possible to specify a fixed additional shift to the time reference. + * This may be useful for example if the simulation of the trigger yields + * exactly the time at which the triggering activity is detected, but the + * trigger hardware would take still some time in order to tag that time. + * Note, however, that the additional shift is also applied to the beam gate + * time. + * This additional time is specified via the `AdditionalOffset` configuration + * parameter. + * If the new reference time is invalid, no shift is applied and this offset is + * also ignored. + * + * + * Input + * ------ + * + * * `std::vector` (`InputTriggerLabel`): the first of the + * triggers in the collection will be used as a new reference. + * If the trigger time value is not valid, no shift at all will be performed. + * However, the beam gate time is still expected to be valid. + * An empty collection is not allowed, even when there is no valid trigger. + * + * * `art::Assns` + * (`BindWaveformBaselines`): the association of the original optical detector + * waveforms to their baselines. + * + * + * Output + * ------- + * + * For each enabled data product to be shifted, the corresponding shifted data + * product is produced, with elements in the same order as in the original + * collections. Normally, no associations are ported on. + * + * * `std::vector`: a collection of shifted triggers is produced; + * the first one is the shifted version of the reference trigger, which can + * then be used as new trigger data product e.g. for + * `DetectorClocksServiceStandard`. If the reference trigger time is not + * valid, the trigger time will be overwritten: the trigger time will be set + * to the value from `DetectorClocksService`, and the beam gate time will + * be the same as the input trigger object. This collection is produced by + * default, but it can be disabled via `DropTriggerProduct` configuration + * parameter. + * * `std::vector`: the beam gate used by the event + * generator, shifted. Generator times and particles from the detector + * simulation (GEANT4) are not shifted, but pretty much everything else is, + * including scintillation photons and energy depositions. Depending on which + * aspect of the simulation is being investigated, either the unshifted + * (input) or shifted (output of this module) gate needs to be used. + * * `art::Assns`: enabled only + * if the optical waveforms are being shifted _and_ an association data + * product name is specified (`BindWaveformBaselines`), it rebinds the + * original waveform baselines to the shifted waveforms. Note that neither the + * baseline nor the order nor the content of the waveforms change: only their + * start time (and implicitly the end time) does. + * + * + * Configuration parameters + * ------------------------- + * + * * `InputTriggerLabel` (input tag, mandatory): the tag of the trigger data + * product with the new reference time. It must be available and not empty. + * * `ShiftAuxDetIDE` (bool, default: `false`): enable the shifting of auxiliary + * detector simulation data product at `InitAuxDetSimChannelLabel`. + * * `InitAuxDetSimChannelLabel` (input tag): tag of the auxiliary detector + * simulation `sim::AuxDetSimChannel` product to be shifted. + * * `ShiftBeamGateInfo` (bool, default: `false`): enable the shifting of + * beam gate data product at `InitBeamGateInfoLabel`. + * * `InitBeamGateInfoLabel` (input tag): tag of the simulation beam gate data + * product to be shifted. This can be produced by a LArSoft generation module + * or by a trigger module. + * * `ShiftSimEnergyDeposits` (bool, default: `false`): enable the shifting of + * full energy deposit data product at `InitSimEnergyDepositLabel`. + * * `InitSimEnergyDepositLabel` (input tag): tag of the simulated energy + * deposition data product to be shifted. + * * `ShiftSimEnergyDepositLites` (bool, default: `false`): enable the shifting + * of lightweight energy deposit data product at + * `InitSimEnergyDepositLiteLabel`. + * * `InitSimEnergyDepositLiteLabel` (input tag): tag of the lightweight + * simulated energy deposition data product to be shifted. This reduced + * version is typically kept around for tracking back to truth information. + * * `ShiftSimPhotons` (bool, default: `false`): enable the shifting of + * scintillation photon data product at `InitSimPhotonsLabel`. + * * `InitSimPhotonsLabel` (input tag): tag of the simulated scintillation photon + * data product to be shifted. + * * `ShiftWaveforms` (bool, default: `false`): enable the shifting of optical + * detector waveform data product at `InitWaveformLabel`. + * * `InitWaveformLabel` (input tag): tag of the simulated optical detector + * waveform data product to be shifted. These waveforms may already be + * available if the worflow extracted the trigger time (new reference) out of + * them. + * * `BindWaveformBaselines` (input tag, default: `""`): tag of the association + * between the optical detector waveforms being shifted and their baselines. + * If empty (default), baseline associations will not be produced. + * * `AdditionalOffset` (real value, default: `0`): additional offset in + * microseconds to be added to the new reference trigger. + * * `DropTriggerProduct` (flag, default: `false`): if set, no shifted trigger + * data product will be produced. + * + */ class AdjustSimForTrigger : public art::EDProducer { public: explicit AdjustSimForTrigger(fhicl::ParameterSet const& p); From c43bec92700a59ecddee75f108552efbd78d707e Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Thu, 16 Apr 2026 20:30:20 -0500 Subject: [PATCH 06/13] RecoUtils: Added missing library --- sbncode/CAFMaker/RecoUtils/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/sbncode/CAFMaker/RecoUtils/CMakeLists.txt b/sbncode/CAFMaker/RecoUtils/CMakeLists.txt index a992d7c63..295847117 100644 --- a/sbncode/CAFMaker/RecoUtils/CMakeLists.txt +++ b/sbncode/CAFMaker/RecoUtils/CMakeLists.txt @@ -11,4 +11,5 @@ art_make_library( LIBRARY_NAME caf_RecoUtils larsim::MCCheater_BackTrackerService_service larsim::MCCheater_ParticleInventoryService_service larcorealg::Geometry + sbnanaobj::StandardRecord ) From 496585af76dfb6aed87328dd5ac5e59bf9eb6134 Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Wed, 6 May 2026 23:13:11 -0500 Subject: [PATCH 07/13] AdjustSimForTrigger module: updated documentation --- sbncode/DetSim/AdjustSimForTrigger_module.cc | 67 +++++++++++++++----- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/sbncode/DetSim/AdjustSimForTrigger_module.cc b/sbncode/DetSim/AdjustSimForTrigger_module.cc index 624ba1c3e..74c5587ae 100644 --- a/sbncode/DetSim/AdjustSimForTrigger_module.cc +++ b/sbncode/DetSim/AdjustSimForTrigger_module.cc @@ -75,11 +75,16 @@ * for configuring services like `DetectorClocksServiceStandard` for the * following stages of the workflow. * - * If the reference time is not valid, no shift is performed at all. + * If the reference time is not valid, the applied shift is `0`. * A reference time is valid if it is neither the maximum nor the minimum value * of a double (`std::numeric_limits::max()` and * `std::numeric_limits::min()`). * + * @note This module does not provide indication on whether a shift was + * performed or not. The only way to know is to check the reference time + * input used and verify whether it's considered valid by this module + * according to the logic described above. + * * * ### Optional shift * @@ -95,6 +100,22 @@ * also ignored. * * + * ### Multiple shifts on the same event + * + * In general, using as input a sample that has already undergone a previous + * shift is surprisingly robust: as long as the input is all consistently + * shifted, offsets and delays that were already applied are not re-applied + * (this was explicitly verified on `sim::SimPhotons`). + * However, shifting is not "undone": if an input event was shifted and now the + * new proposed time reference is invalid, the event will be applied no + * additional shift, leaving it to the time reference after the first shift. + * + * The chances of the old time reference being completely invalidated depend on + * how the changes intervened between the first and the second shift. + * The module _could_ be adapted to attempt a shifting reversal, if provided + * with the necessary additional information on the first shift. + * + * * Input * ------ * @@ -116,21 +137,35 @@ * product is produced, with elements in the same order as in the original * collections. Normally, no associations are ported on. * - * * `std::vector`: a collection of shifted triggers is produced; - * the first one is the shifted version of the reference trigger, which can - * then be used as new trigger data product e.g. for - * `DetectorClocksServiceStandard`. If the reference trigger time is not - * valid, the trigger time will be overwritten: the trigger time will be set - * to the value from `DetectorClocksService`, and the beam gate time will - * be the same as the input trigger object. This collection is produced by - * default, but it can be disabled via `DropTriggerProduct` configuration - * parameter. - * * `std::vector`: the beam gate used by the event - * generator, shifted. Generator times and particles from the detector - * simulation (GEANT4) are not shifted, but pretty much everything else is, - * including scintillation photons and energy depositions. Depending on which - * aspect of the simulation is being investigated, either the unshifted - * (input) or shifted (output of this module) gate needs to be used. + * * `std::vector` (if `ShiftAuxDetIDEs` is set): + * all `sim::AuxDetIDE` entry and exit times are shifted. Order of channels + * and IDE is preserved. + * * `std::vector` (if `ShiftSimEnergyDeposits` is set): + * all `sim::SimEnergyDeposit` entry and exit times are shifted. + * Order of deposits is preserved. + * * `std::vector` (if `ShiftSimEnergyDepositLites` + * is set): all `sim::SimEnergyDepositLite` entry and exit times are shifted. + * Order of deposits is preserved. + * * `std::vector` (if `ShiftSimPhotons` is set): + * all `sim::SimPhotons` times in all channels are shifted. + * Order of channels and photons is preserved. + * * `std::vector` (unless `DropTriggerProduct` is set): + * a collection of shifted triggers is produced; the first one is the shifted + * version of the reference trigger, which can then be used as new trigger + * data product e.g. for `DetectorClocksServiceStandard`. If the reference + * trigger time is not valid, the trigger time will be overwritten: the + * trigger time will be set to the value from `DetectorClocksService`, and the + * beam gate time will be the same as the input trigger object. + * * `std::vector` (if `ShiftBeamGateInfo` is set): + * the beam gate used by the event generator, shifted. Generator times and + * particles from the detector simulation (GEANT4) are not shifted, but pretty + * much everything else may be, including scintillation photons and energy + * depositions. Depending on which aspect of the simulation is being + * investigated, either the unshifted (input) or shifted (output of this + * module) gate needs to be used. + * * `std::vector` (if `ShiftWaveforms` is set): the input + * PMT waveforms, from which presumably the new trigger was extracted, are + * shifted by simply modifying their timestamps. * * `art::Assns`: enabled only * if the optical waveforms are being shifted _and_ an association data * product name is specified (`BindWaveformBaselines`), it rebinds the From 7e96f96e8396af5f6143952d69f7a4dd67256430 Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Wed, 6 May 2026 23:13:35 -0500 Subject: [PATCH 08/13] Some CAF time quentities not shifted when they are invalid. Prevents things like magic "invalid" values like -9999.0 from becoming -9997.8 after a 1.2 us shift. Introduced an utility `caf::SRdefaults` for fetching those magic values. --- sbncode/CAFMaker/CAFMaker_module.cc | 15 ++-- sbncode/CAFMaker/SRDefaults.cxx | 27 ++++++++ sbncode/CAFMaker/SRDefaults.h | 103 ++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 sbncode/CAFMaker/SRDefaults.cxx create mode 100644 sbncode/CAFMaker/SRDefaults.h diff --git a/sbncode/CAFMaker/CAFMaker_module.cc b/sbncode/CAFMaker/CAFMaker_module.cc index 985b8e127..fed2195c2 100644 --- a/sbncode/CAFMaker/CAFMaker_module.cc +++ b/sbncode/CAFMaker/CAFMaker_module.cc @@ -19,6 +19,7 @@ #include "sbncode/CAFMaker/FillReco.h" #include "sbncode/CAFMaker/FillExposure.h" #include "sbncode/CAFMaker/FillTrigger.h" +#include "sbncode/CAFMaker/SRDefaults.h" #include "sbncode/CAFMaker/Utils.h" // C/C++ includes @@ -554,19 +555,25 @@ void CAFMaker::SBNDShiftPMTReference(StandardRecord &rec, double SBNDFrame) cons } void CAFMaker::FixPMTReferenceTimes(StandardRecord &rec, double PMT_reference_time) { + // Fix the flashes for (SROpFlash &f: rec.opflashes) { f.time += PMT_reference_time; f.timemean += PMT_reference_time; f.firsttime += PMT_reference_time; } - + // Fix the flash matches for (SRSlice &s: rec.slc) { - s.fmatch.time += PMT_reference_time; + if (s.fmatch.time != SRDefaults::For(s.fmatch).time) + s.fmatch.time += PMT_reference_time; - s.barycenterFM.flashTime +=PMT_reference_time; - s.barycenterFM.flashFirstHit +=PMT_reference_time; + if (s.barycenterFM.flashTime != SRDefaults::For(s.barycenterFM).flashTime) + s.barycenterFM.flashTime += PMT_reference_time; + + if (s.barycenterFM.flashFirstHit != SRDefaults::For(s.barycenterFM).flashFirstHit) + s.barycenterFM.flashFirstHit += PMT_reference_time; + } // TODO: fix more? diff --git a/sbncode/CAFMaker/SRDefaults.cxx b/sbncode/CAFMaker/SRDefaults.cxx new file mode 100644 index 000000000..e97f2ebf5 --- /dev/null +++ b/sbncode/CAFMaker/SRDefaults.cxx @@ -0,0 +1,27 @@ +/** + * @file sbncode/CAFMaker/SRDefaults.cxx + * @brief Discovery of default values in Standard Record classes. + * @author Gianluca Petrillo (petrillo@slac.stanford.edu) + * @date May 6, 2026 + * @see sbncode/CAFMaker/SRDefaults.h + */ + +#include "sbncode/CAFMaker/SRDefaults.h" + + +// ----------------------------------------------------------------------------- +namespace { + + template + std::tuple staticInit + (std::tuple objs) + { (std::get(objs).setDefault(), ...); return objs; } + +} // local namespace + + +// ----------------------------------------------------------------------------- +caf::SRDefaults::SupportedTypes const caf::SRDefaults::defaultObjs + = staticInit(caf::SRDefaults::SupportedTypes{}); + +// ----------------------------------------------------------------------------- diff --git a/sbncode/CAFMaker/SRDefaults.h b/sbncode/CAFMaker/SRDefaults.h new file mode 100644 index 000000000..a198f01c5 --- /dev/null +++ b/sbncode/CAFMaker/SRDefaults.h @@ -0,0 +1,103 @@ +/** + * @file sbncode/CAFMaker/SRDefaults.h + * @brief Discovery of default values in Standard Record classes. + * @author Gianluca Petrillo (petrillo@slac.stanford.edu) + * @date May 6, 2026 + * @see sbncode/CAFMaker/SRDefaults.cxx + */ + + +#ifndef SBNCODE_CAFMAKER_SRDEFAULTS_H +#define SBNCODE_CAFMAKER_SRDEFAULTS_H + +// Standard Record objects +#include "sbnanaobj/StandardRecord/SRFlashMatch.h" +#include "sbnanaobj/StandardRecord/SRTPCPMTBarycenterMatch.h" + +// C/C++ standard library +#include +#include // std::decay_t + +// ----------------------------------------------------------------------------- +namespace caf { class SRDefaults; } +/** + * @brief Collection of Standard Record objects with defaulted values. + * + * Example of usage: to test if `caf::SRFlashMatch::time` is still the default + * value: + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} + * if (match.time == caf::SRDefaults::For().time) { ... } + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Note that this may be, and usually is, different from the + * default-constructed value (e.g. `caf::SRFlashMatch{}.time`). + * + * + * Extension directions + * --------------------- + * + * Only classes that offer `setDefault()` member function can be included in + * this class. To add such a class: + * 1. include the appropriate header above; + * 2. add the name of the class as a `tuple` type in the definition of + * `SupportedTypes`. + * + * In case a class is used that is not supported, a static error on `std::get()` + * is typically emitted. In GCC 12.1.0: + * ``` + * static assertion failed: the type T in std::get must occur exactly once in the tuple + * ``` + * + */ +class caf::SRDefaults { + + public: + + /// A tuple with all the types included in this object. + using SupportedTypes = std::tuple< + caf::SRFlashMatch, + caf::SRTPCPMTBarycenterMatch + >; + + /// Single copy of defaulted objects. + static SupportedTypes const defaultObjs; + + + /** + * @brief Returns an object of type `SRobj` with its default values set. + * @tparam SRobj type of the Standard Record object to obtain + * @return a static object of type `SRobj` with its default values set + * + * A call of this method must explicitly include the template type `SRobj`, + * e.g. `caf::SRDefaults::For()`. + */ + template + static SRobj const& For(); + + /** + * @brief Returns an object of type `SRobj` with its default values set. + * @tparam SRobj type of the Standard Record object to obtain + * @return a static object of type `SRobj` with its default values set + * + * A call to this method should have as argument an existing object of the + * `SRobj` type, that can be used to omit the explicit type in the call; + * e.g., `caf::SRDefaults::For(track)`. + */ + template + static SRobj const& For(SRobj const&); + +}; + + +// ----------------------------------------------------------------------------- +// --- template implementation +// ----------------------------------------------------------------------------- +template +SRobj const& caf::SRDefaults::For() { return std::get(defaultObjs); } + +template +SRobj const& caf::SRDefaults::For(SRobj const&) + { return For>(); } + +// ----------------------------------------------------------------------------- + +#endif // SBNCODE_CAFMAKER_SRDEFAULTS_H From a63f32253a543a84c2bc8864601622892f31f01c Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Wed, 13 May 2026 17:51:39 -0500 Subject: [PATCH 09/13] AdjustSimForTrigger module can now copy sbn::ExtraTriggerInfo product Must be explicitly disabled if not desired. The data is currently not shifted (not clear what a shift on timestamps would mean). --- sbncode/DetSim/AdjustSimForTrigger_module.cc | 21 +++++++++++++++++++- sbncode/DetSim/CMakeLists.txt | 1 + 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/sbncode/DetSim/AdjustSimForTrigger_module.cc b/sbncode/DetSim/AdjustSimForTrigger_module.cc index 74c5587ae..3b94846a7 100644 --- a/sbncode/DetSim/AdjustSimForTrigger_module.cc +++ b/sbncode/DetSim/AdjustSimForTrigger_module.cc @@ -24,6 +24,7 @@ #include "lardataobj/Simulation/SimEnergyDepositLite.h" #include "lardataobj/Simulation/SimPhotons.h" #include "sbnobj/ICARUS/PMT/Data/WaveformBaseline.h" +#include "sbnobj/Common/Trigger/ExtraTriggerInfo.h" #include "lardata/DetectorInfoServices/DetectorClocksService.h" #include "sbncode/Utilities/AssnsUtils.h" // sbn::RebindAssociatedProducts() @@ -156,6 +157,9 @@ * trigger time is not valid, the trigger time will be overwritten: the * trigger time will be set to the value from `DetectorClocksService`, and the * beam gate time will be the same as the input trigger object. + * * `sbn::ExtraTriggerInfo` (if `SkipExtraTriggerInfo` is not set): the + * input data product is duplicated in output. The only time information in + * the object, absolute timestamps, is currently not shifted. * * `std::vector` (if `ShiftBeamGateInfo` is set): * the beam gate used by the event generator, shifted. Generator times and * particles from the detector simulation (GEANT4) are not shifted, but pretty @@ -215,6 +219,9 @@ * microseconds to be added to the new reference trigger. * * `DropTriggerProduct` (flag, default: `false`): if set, no shifted trigger * data product will be produced. + * * `SkipExtraTriggerInfo` (flag: default: `false`): if set, the data product + * `sbn::ExtraTriggerInfo` (with the `InputTriggerLabel` tag) is not copied + * and put into the event. Otherwise, it is required to be present. * */ class AdjustSimForTrigger : public art::EDProducer { @@ -249,6 +256,7 @@ class AdjustSimForTrigger : public art::EDProducer { art::InputTag fBindWaveformBaselines; ///< Tag of OpDetWaveform-baseline associations to be rebound. double fAdditionalOffset; bool fDropTriggerProduct; ///< Do not put the shifted trigger data product into the event. + bool fSkipExtraTriggerInfo; ///< Copy input `sbn::ExtraTriggerInfo`. static constexpr auto& kModuleName = "AdjustSimForTrigger"; }; @@ -270,6 +278,7 @@ AdjustSimForTrigger::AdjustSimForTrigger(fhicl::ParameterSet const& p) , fBindWaveformBaselines{p.get("BindWaveformBaselines", "")} , fAdditionalOffset{p.get("AdditionalOffset", 0.)} , fDropTriggerProduct{p.get("DropTriggerProduct", false)} + , fSkipExtraTriggerInfo{p.get("SkipExtraTriggerInfo", false)} { if (!(fShiftSimEnergyDeposits || fShiftSimPhotons || fShiftWaveforms || fShiftAuxDetIDEs || fShiftBeamGateInfo || fShiftSimEnergyDepositLites)) { @@ -286,7 +295,10 @@ AdjustSimForTrigger::AdjustSimForTrigger(fhicl::ParameterSet const& p) << " ASSNS OPDETWAVEFORM-BASELINES? " << doWaveformBaselines << (doWaveformBaselines? (" ('" + fBindWaveformBaselines.encode() + ")"): ""); - if (!fDropTriggerProduct) produces>(); + if (!fDropTriggerProduct) { + produces>(); + produces(); + } if (fShiftAuxDetIDEs) { produces>(); } if (fShiftBeamGateInfo) { produces>(); } if (fShiftSimEnergyDeposits) { produces>(); } @@ -346,6 +358,13 @@ void AdjustSimForTrigger::produce(art::Event& e) ); } // for all triggers e.put(std::move(pShiftedTriggers)); + + if (!fSkipExtraTriggerInfo) { + auto extraTrigger = e.getProduct(fInputTriggerLabel); + // no shift performed at this time + e.put(std::make_unique(std::move(extraTrigger))); + } + } // if produce shifted trigger // Loop over the sim::AuxDetIDE and shift time BACK by the TRIGGER diff --git a/sbncode/DetSim/CMakeLists.txt b/sbncode/DetSim/CMakeLists.txt index 813562b60..e718bf218 100644 --- a/sbncode/DetSim/CMakeLists.txt +++ b/sbncode/DetSim/CMakeLists.txt @@ -6,6 +6,7 @@ set( MODULE_LIBRARIES lardataobj::RawData lardataobj::Simulation sbnobj::ICARUS_PMT_Data + sbnobj::Common_Trigger ) cet_build_plugin(AdjustSimForTrigger art::module LIBRARIES ${MODULE_LIBRARIES}) From d7150170d36db51d1d7bc5c44a3c022933704ffe Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Fri, 12 Jun 2026 18:03:57 -0500 Subject: [PATCH 10/13] Fixes from automated review --- sbncode/DetSim/AdjustSimForTrigger_module.cc | 10 +++++----- sbncode/Utilities/AssnsUtils.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sbncode/DetSim/AdjustSimForTrigger_module.cc b/sbncode/DetSim/AdjustSimForTrigger_module.cc index 3b94846a7..ca625de30 100644 --- a/sbncode/DetSim/AdjustSimForTrigger_module.cc +++ b/sbncode/DetSim/AdjustSimForTrigger_module.cc @@ -60,13 +60,13 @@ * and all follows. * * @note The new trigger time is going to be whatever value is in - * `DetectorClocksData::TriggerTime()`, rather than an hard-coded value + * `DetectorClocksData::TriggerTime()`, rather than a hard-coded value * like `1500.0`. Therefore some care needs to be taken in the workflow * to make sure that at the time of the execution of this module * `DetectorClocksService` is yielding the desired new-reference value. * Also, after this module is called `DetectorClocksService` itself may * report obsolete values. Specifically, `DetectorClocksServiceStandard` - * will report the old `BeamGateTime()`, which was from ether a + * will report the old `BeamGateTime()`, which was from either a * configuration parameter or from a trigger data product. * * This module reads the new reference time from a `raw::Trigger` object @@ -256,7 +256,7 @@ class AdjustSimForTrigger : public art::EDProducer { art::InputTag fBindWaveformBaselines; ///< Tag of OpDetWaveform-baseline associations to be rebound. double fAdditionalOffset; bool fDropTriggerProduct; ///< Do not put the shifted trigger data product into the event. - bool fSkipExtraTriggerInfo; ///< Copy input `sbn::ExtraTriggerInfo`. + bool fSkipExtraTriggerInfo; ///< Do not copy input `sbn::ExtraTriggerInfo`. static constexpr auto& kModuleName = "AdjustSimForTrigger"; }; @@ -293,11 +293,11 @@ AdjustSimForTrigger::AdjustSimForTrigger(fhicl::ParameterSet const& p) << "SHIFTING SIMPHOTONS? " << fShiftSimPhotons << '\n' << "SHIFTING OPDETWAVEFORMS? " << fShiftWaveforms << '\n' << " ASSNS OPDETWAVEFORM-BASELINES? " << doWaveformBaselines - << (doWaveformBaselines? (" ('" + fBindWaveformBaselines.encode() + ")"): ""); + << (doWaveformBaselines? (" ('" + fBindWaveformBaselines.encode() + "')"): ""); if (!fDropTriggerProduct) { produces>(); - produces(); + if (!fSkipExtraTriggerInfo) produces(); } if (fShiftAuxDetIDEs) { produces>(); } if (fShiftBeamGateInfo) { produces>(); } diff --git a/sbncode/Utilities/AssnsUtils.h b/sbncode/Utilities/AssnsUtils.h index 46be1e4c5..6eb3a1425 100644 --- a/sbncode/Utilities/AssnsUtils.h +++ b/sbncode/Utilities/AssnsUtils.h @@ -52,7 +52,7 @@ namespace sbn { * auto newAssns * = std::make_unique(sbn::RebindAssociatedProducts(oldAssns, makeWaveformPtr)); * - * event.put(std::move(pWaveformBaselineAssns)); + * event.put(std::move(newAssns)); * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * The alias `Assns_t` was used only to make the example more readable. * From ab3de066e51cd0cd047d4e2a12c4fc752730ae88 Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Wed, 22 Jul 2026 16:54:23 -0500 Subject: [PATCH 11/13] Minor maintenance to CAFMaker code Removed some unused or redundant code. --- sbncode/CAFMaker/FillFlashMatch.h | 9 --------- sbncode/CAFMaker/FillTrigger.cxx | 13 ------------- sbncode/CAFMaker/FillTrigger.h | 5 ----- 3 files changed, 27 deletions(-) diff --git a/sbncode/CAFMaker/FillFlashMatch.h b/sbncode/CAFMaker/FillFlashMatch.h index 071f5838f..bab40e044 100644 --- a/sbncode/CAFMaker/FillFlashMatch.h +++ b/sbncode/CAFMaker/FillFlashMatch.h @@ -2,17 +2,8 @@ #ifndef CAF_FILLFLASHMATCH_H #define CAF_FILLFLASHMATCH_H -#include - -#include "art/Framework/Services/Registry/ServiceHandle.h" - -// LArSoft includes -#include "larcore/Geometry/Geometry.h" -#include "larcorealg/Geometry/GeometryCore.h" - //Include new flash match class #include "sbnobj/Common/Reco/SimpleFlashMatchVars.h" -#include "sbnanaobj/StandardRecord/StandardRecord.h" #include "sbnanaobj/StandardRecord/SRFlashMatch.h" namespace caf diff --git a/sbncode/CAFMaker/FillTrigger.cxx b/sbncode/CAFMaker/FillTrigger.cxx index 8bf82c02c..cf720e1a3 100644 --- a/sbncode/CAFMaker/FillTrigger.cxx +++ b/sbncode/CAFMaker/FillTrigger.cxx @@ -25,23 +25,10 @@ namespace caf triggerInfo.gate_delta = addltrig_info.gateCountFromPreviousTrigger; } - void FillTriggerMC(double absolute_time, caf::SRTrigger& triggerInfo) { - triggerInfo.global_trigger_time = absolute_time; - triggerInfo.beam_gate_time_abs = absolute_time; - - // Set this to 0 since the "MC" trigger is (for now) always at the spill time - triggerInfo.trigger_within_gate = 0.; - - // TODO: fill others? - } - void FillTriggerSBND(caf::SRSBNDTimingInfo& timingInfo, caf::SRTrigger& triggerInfo){ triggerInfo.global_trigger_time = timingInfo.hltEtrig; triggerInfo.beam_gate_time_abs = timingInfo.hltBeamGate; - - double diff_ts = triggerInfo.global_trigger_det_time - triggerInfo.beam_gate_det_time; - triggerInfo.trigger_within_gate = diff_ts; } } diff --git a/sbncode/CAFMaker/FillTrigger.h b/sbncode/CAFMaker/FillTrigger.h index ea1aab7eb..949ab530a 100644 --- a/sbncode/CAFMaker/FillTrigger.h +++ b/sbncode/CAFMaker/FillTrigger.h @@ -2,13 +2,10 @@ #define CAF_FILLTRIGGER_H #include "sbnobj/Common/Trigger/ExtraTriggerInfo.h" -#include "sbnobj/Common/Trigger/BeamBits.h" #include "sbnanaobj/StandardRecord/SRTrigger.h" #include "sbnanaobj/StandardRecord/SRSBNDTimingInfo.h" #include "lardataobj/RawData/TriggerData.h" -#include - namespace caf { @@ -17,8 +14,6 @@ namespace caf caf::SRTrigger& triggerInfo, const double time_offset); - void FillTriggerMC(double absolute_time, caf::SRTrigger& triggerInfo); - void FillTriggerSBND(caf::SRSBNDTimingInfo& timingInfo, caf::SRTrigger& triggerInfo); } From 5349f83cc0507155fb165b24d64fa339fd03466b Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Wed, 22 Jul 2026 20:27:22 -0500 Subject: [PATCH 12/13] Updated CAFMaker module API * added `CAFMaker::GetHandleStrict()` that directly returns a handle * `GetByLabelStrict()` and similar now use `art::InputTag` as label type * minor implementation changes and documentation fixes --- sbncode/CAFMaker/CAFMaker_module.cc | 90 +++++++++++++++++------------ 1 file changed, 53 insertions(+), 37 deletions(-) diff --git a/sbncode/CAFMaker/CAFMaker_module.cc b/sbncode/CAFMaker/CAFMaker_module.cc index fed2195c2..43d865dce 100644 --- a/sbncode/CAFMaker/CAFMaker_module.cc +++ b/sbncode/CAFMaker/CAFMaker_module.cc @@ -68,6 +68,7 @@ #include "lardataalg/DetectorInfo/DetectorPropertiesData.h" #include "lardata/DetectorInfoServices/DetectorPropertiesService.h" #include "larcore/CoreUtils/ServiceUtil.h" +#include "larcorealg/CoreUtils/DebugUtils.h" // lar::debug #include "larevt/SpaceCharge/SpaceCharge.h" #include "larevt/SpaceChargeServices/SpaceChargeService.h" @@ -352,8 +353,10 @@ class CAFMaker : public art::EDProducer { const art::Event& evt, const art::InputTag& tag) const; - /// \brief Retrieve an object from an association, with error handling + /// \brief Makes a copy of an object from an association, with error handling /// + /// This function attempts to make a copy into `ret` of the first of the + /// objects in the association to `from`. /// This can go wrong in two ways: either the FindManyP itself is /// invalid, or the result for the requested index is empty. In most /// cases these have the same response, so conflating them here @@ -361,23 +364,28 @@ class CAFMaker : public art::EDProducer { /// /// \param fm The FindManyP object describing the association /// \param idx Which element of the FindManyP to look it - /// \param[out] ret The product retrieved + /// \param[out] ret Where to copy the product retrieved /// \return Whether \a ret was filled template bool GetAssociatedProduct(const art::FindManyP& fm, int idx, T& ret) const; - /// Equivalent of evt.getByLabel(label, handle) except failedToGet + /// Equivalent of evt.getByLabel(tag, handle) except failedToGet /// prints a message and aborts if StrictMode is true. template - void GetByLabelStrict(const EvtT& evt, const std::string& label, + void GetByLabelStrict(const EvtT& evt, const art::InputTag& tag, art::Handle& handle) const; - /// Equivalent of evt.getByLabel(label, handle) except failedToGet + /// Equivalent of evt.getByLabel(tag, handle) except failedToGet /// prints a message. - template - void GetByLabelIfExists(const art::Event& evt, const std::string& label, + template + void GetByLabelIfExists(const EvtT& evt, const art::InputTag& tag, art::Handle& handle) const; + /// Equivalent of evt.getHandle(tag) except that if `StrictMode` is set + /// when `failedToGet()` is `true` prints a message and aborts. + template + art::Handle GetHandleStrict(const EvtT& evt, const art::InputTag& tag) const; + /// \param pset The parameter set /// \param name Pass "foo.bar.baz" as {"foo", "bar", "baz"} /// \param[out] ret Value of the key, not set if we return false @@ -1236,9 +1244,9 @@ art::FindManyP CAFMaker::FindManyPStrict(const U& from, if (!tag.label().empty() && !ret.isValid() && fParams.StrictMode()) { std::cout << "CAFMaker: No Assn from '" - << cet::demangle_symbol(typeid(from).name()) << "' to '" - << cet::demangle_symbol(typeid(T).name()) - << "' found under label '" << tag << "'. " + << lar::debug::demangle() << "' to '" + << lar::debug::demangle() + << "' found under label '" << tag.encode() << "'. " << "Set 'StrictMode: false' to continue anyway." << std::endl; abort(); } @@ -1255,9 +1263,9 @@ art::FindManyP CAFMaker::FindManyPDStrict(const U& from, if (!tag.label().empty() && !ret.isValid() && fParams.StrictMode()) { std::cout << "CAFMaker: No Assn from '" - << cet::demangle_symbol(typeid(from).name()) << "' to '" - << cet::demangle_symbol(typeid(T).name()) - << "' found under label '" << tag << "'. " + << lar::debug::demangle() << "' to '" + << lar::debug::demangle() + << "' found under label '" << tag.encode() << "'. " << "Set 'StrictMode: false' to continue anyway." << std::endl; abort(); } @@ -1274,9 +1282,9 @@ art::FindOneP CAFMaker::FindOnePStrict(const U& from, if (!tag.label().empty() && !ret.isValid() && fParams.StrictMode()) { std::cout << "CAFMaker: No Assn from '" - << cet::demangle_symbol(typeid(from).name()) << "' to '" - << cet::demangle_symbol(typeid(T).name()) - << "' found under label '" << tag << "'. " + << lar::debug::demangle() << "' to '" + << lar::debug::demangle() + << "' found under label '" << tag.encode() << "'. " << "Set 'StrictMode: false' to continue anyway." << std::endl; abort(); } @@ -1293,9 +1301,9 @@ art::FindOneP CAFMaker::FindOnePDStrict(const U& from, if (!tag.label().empty() && !ret.isValid() && fParams.StrictMode()) { std::cout << "CAFMaker: No Assn from '" - << cet::demangle_symbol(typeid(from).name()) << "' to '" - << cet::demangle_symbol(typeid(T).name()) - << "' found under label '" << tag << "'. " + << lar::debug::demangle() << "' to '" + << lar::debug::demangle() + << "' found under label '" << tag.encode() << "'. " << "Set 'StrictMode: false' to continue anyway." << std::endl; abort(); } @@ -1309,7 +1317,7 @@ bool CAFMaker::GetAssociatedProduct(const art::FindManyP& fm, int idx, T& ret) const { if (!fm.isValid()) return false; - const std::vector> prods = fm.at(idx); + const std::vector>& prods = fm.at(idx); if (prods.empty()) return false; @@ -1320,32 +1328,40 @@ bool CAFMaker::GetAssociatedProduct(const art::FindManyP& fm, int idx, //...................................................................... template -void CAFMaker::GetByLabelStrict(const EvtT& evt, const std::string& label, +void CAFMaker::GetByLabelStrict(const EvtT& evt, const art::InputTag& tag, art::Handle& handle) const { - evt.getByLabel(label, handle); - if (!label.empty() && handle.failedToGet() && fParams.StrictMode()) { - std::cout << "CAFMaker: No product of type '" - << cet::demangle_symbol(typeid(*handle).name()) - << "' found under label '" << label << "'. " - << "Set 'StrictMode: false' to continue anyway." << std::endl; - abort(); - } + + handle = GetHandleStrict(evt, tag); } //...................................................................... -template -void CAFMaker::GetByLabelIfExists(const art::Event& evt, - const std::string& label, +template +void CAFMaker::GetByLabelIfExists(const EvtT& evt, + const art::InputTag& tag, art::Handle& handle) const { - evt.getByLabel(label, handle); - if (!label.empty() && handle.failedToGet() && fParams.StrictMode()) { - std::cout << "CAFMaker: No product of type '" - << cet::demangle_symbol(typeid(*handle).name()) - << "' found under label '" << label << "'. " + handle = evt.template getHandle(tag); + if (!tag.empty() && handle.failedToGet() && fParams.StrictMode()) { + std::cout << "CAFMaker: No product of type '" << lar::debug::demangle() + << "' found under label '" << tag.encode() << "'. " << "Continuing without it." << std::endl; } } +//...................................................................... +template +art::Handle CAFMaker::GetHandleStrict + (const EvtT& evt, const art::InputTag& tag) const +{ + art::Handle handle = evt.template getHandle(tag); + if (!handle.failedToGet() || !fParams.StrictMode() || tag.empty()) + return handle; + std::cout << "CAFMaker: No product of type '" << lar::debug::demangle() + << "' found under label '" << tag.encode() << "'. " + << "Set 'StrictMode: false' to continue anyway." << std::endl; + abort(); +} + + //...................................................................... template bool CAFMaker::GetPsetParameter(const fhicl::ParameterSet& pset, From 27cb8e64a36a846c9c66f8239b00c0fe87b6479c Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Wed, 22 Jul 2026 20:29:25 -0500 Subject: [PATCH 13/13] Rewritten CAFMaker time reference shift. BREAKING CHANGE! * removed the support for "unshifted trigger" as a reference * removed any support for different shifts in data and simulation * the only supported shift is from trigger time to beam gate time * both are learnt from `DetectorClocksService` * it is assumed that the current data is on trigger reference; we don't have a general way to check that * it is _possible_ to extend the system so that, given as parameter the value of the current reference (`1500`), then we can move to trigger time, beam time or any other time as reference, and also verify if we are already there. For once, I haven't gone all the way toward fancy and this is not implemented yet. * an additional global shift is also supported * an additional shift for CRT only is also supported * time reference shifting moved from central place (one place, all shifts) to object filling places (many places, one object each); the latter was already the case for ICARUS CRT (at least some of it) * filler functions are passed "shifter objects" which know which shift to apply * time shift applied to: * trigger information (shared; SBND should inherit the shift as before): `beam_gate_det_time`, `global_trigger_det_time` * CRT hit collection: `t0`, `t1`, `time` * CRT track collection: `time` (not sure if it is correct when using T0) * CRT/PMT match collection: `flashTime_us`, `firstOpHitPeakTime`, `firstOpHitStartTime`, `matchedCRTHits.time` * optical flashes * ICARUS: `time`, `firstTime` * SBND: `time`, `firstTime` * slice TPC/PMT matching (OpT0Finder): `opt0.time`, `opt0_sec.time` * slice TPC/PMT matching (barycentre): `barycenterFM.flashFirstHit`, `barycenterFM.flashTime` * slice TPC/PMT matching (simple): `time` * CRT hits for a track: same as for the event-wide collection * CRT track for a track: `crttrack.time` * removed configuration parameters (**breaking change**) * `UnshiftedTriggerLabel` * `CRTSimT0Offset` * `ReferencePMTFromTriggerToBeam` * `ReferenceCRTT0ToBeam` * `ReferenceCRTT1FromTriggerToBeam` * added configuration parameters: * `ShiftTimeFromTriggerToBeamGate` * `GlobalTimeReferenceOffset` * `CRTreferenceTimeOffset` --- sbncode/CAFMaker/CAFMakerParams.h | 44 ++--- sbncode/CAFMaker/CAFMaker_module.cc | 285 ++++++++++++++-------------- sbncode/CAFMaker/FillFlashMatch.cxx | 3 +- sbncode/CAFMaker/FillFlashMatch.h | 2 + sbncode/CAFMaker/FillReco.cxx | 64 ++++--- sbncode/CAFMaker/FillReco.h | 22 ++- sbncode/CAFMaker/FillTrigger.cxx | 12 +- sbncode/CAFMaker/FillTrigger.h | 3 +- sbncode/CAFMaker/TimeRefShifters.h | 161 ++++++++++++++++ 9 files changed, 380 insertions(+), 216 deletions(-) create mode 100644 sbncode/CAFMaker/TimeRefShifters.h diff --git a/sbncode/CAFMaker/CAFMakerParams.h b/sbncode/CAFMaker/CAFMakerParams.h index 544ec72f4..0b226d86f 100644 --- a/sbncode/CAFMaker/CAFMakerParams.h +++ b/sbncode/CAFMaker/CAFMakerParams.h @@ -392,24 +392,30 @@ namespace caf "OpFlash" }; - Atom CRTSimT0Offset { - Name("CRTSimT0Offset"), - Comment("start of beam gate/simulation time in the simulated CRT clock"), - 0, - }; - Atom TriggerLabel { Name("TriggerLabel"), Comment("Label of trigger."), "daqTrigger" }; - Atom UnshiftedTriggerLabel { - Name("UnshiftedTriggerLabel"), - Comment("Label of trigger emulation before applying trigger time shifts."), - "emuTriggerUnshifted" + Atom ShiftTimeFromTriggerToBeamGate { + Name("ShiftTimeFromTriggerToBeamGate"), + Comment("Shifts time in branches from trigger time to beam gate time."), + false + }; + + Atom GlobalTimeReferenceOffset { + Name("GlobalTimeReferenceOffset"), + Comment("Additional offset added to all times [us]"), + 0.0 }; + Atom CRTreferenceTimeOffset { + Name("CRTreferenceTimeOffset"), + Comment("Additional reference shift for relative CRT times (not timestamps) [us]"), + 0.0 + }; + Atom FlashTrigLabel { Name("FlashTrigLabel"), Comment("Label of bool of passing flash trigger."), @@ -470,24 +476,6 @@ namespace caf 25. }; - Atom ReferencePMTFromTriggerToBeam { - Name("ReferencePMTFromTriggerToBeam"), - Comment("Whether to switch the reference time of PMT reco from 'trigger' to 'beam spill' time."), - true - }; - - Atom ReferenceCRTT0ToBeam { - Name("ReferenceCRTT0ToBeam"), - Comment("Whether to switch the reference time of CRT T0 reco to the 'beam spill' time."), - true - }; - - Atom ReferenceCRTT1FromTriggerToBeam { - Name("ReferenceCRTT1FromTriggerToBeam"), - Comment("Whether to switch the reference time of CRT T1 reco from 'trigger' to the 'beam spill' time."), - true - }; - Atom CVNLabel { Name("CVNLabel"), Comment("Label of CVN scores."), diff --git a/sbncode/CAFMaker/CAFMaker_module.cc b/sbncode/CAFMaker/CAFMaker_module.cc index 43d865dce..a097856e1 100644 --- a/sbncode/CAFMaker/CAFMaker_module.cc +++ b/sbncode/CAFMaker/CAFMaker_module.cc @@ -1,15 +1,14 @@ ////////////////////////////////////////////////////////////////// -// \file CAFMaker_module.cc -/// \brief This module creates Common Analysis Files. -// Inspired by the NOvA CAFMaker package -// \author $Author: psihas@fnal.gov +/// \file CAFMaker_module.cc +/// \brief This module creates Common Analysis Files. +/// Inspired by the NOvA CAFMaker package +/// \author psihas@fnal.gov ////////////////////////////////////////////////////////////////// // ---------------- TO DO ---------------- // // - Add in cycle and batch to params // - Move this list some place useful -// - Add reco.CRT branch // --------------------------------------- @@ -19,6 +18,7 @@ #include "sbncode/CAFMaker/FillReco.h" #include "sbncode/CAFMaker/FillExposure.h" #include "sbncode/CAFMaker/FillTrigger.h" +#include "sbncode/CAFMaker/TimeRefShifters.h" #include "sbncode/CAFMaker/SRDefaults.h" #include "sbncode/CAFMaker/Utils.h" @@ -325,9 +325,25 @@ class CAFMaker : public art::EDProducer { void InitVolumes(); ///< Initialize volumes from Gemotry service void InitCRTMapping(); ///< Initialize CRT mapping - void FixPMTReferenceTimes(StandardRecord &rec, double PMT_reference_time); - void FixCRTReferenceTimes(StandardRecord &rec, double CRTT0_reference_time, double CRTT1_reference_time); - + /** + * @brief Returns the absolute timestamps of beam and trigger times + * @param clock_data LArSoft timing information + * @param trigger a trigger object + * @param extra extra information for the same `trigger` + * @return absolute timestamps of beam and trigger times [ns] + * + * The relation between absolute timestamps and relative times is extracted + * from the beam gate time in `trigger` and `extra`. That relation is then + * used to convert the beam and trigger times from the reference time system + * (`detinfo::DetectorClocksData::BeamGateTime()` and + * `detinfo::DetectorClocksData::TriggerTime()`). + */ + std::pair getReferenceTimestamps( + detinfo::DetectorClocksData const& clock_data, + raw::Trigger const& trigger, sbn::ExtraTriggerInfo const& extra + ) const; + + /// Applies a reference time shift (subtracting `refTimeOffset`) void SBNDShiftCRTReference(StandardRecord &rec, double SBNDFrame) const; void SBNDShiftPMTReference(StandardRecord &rec, double SBNDFrame) const; @@ -524,6 +540,34 @@ void CAFMaker::BlindEnergyParameters(StandardRecord* brec) { } } +std::pair CAFMaker::getReferenceTimestamps( + detinfo::DetectorClocksData const& clock_data, + raw::Trigger const& trigger, sbn::ExtraTriggerInfo const& extra +) const { + + double refTime; + std::int64_t refTimestamp = 0; + if (extra.isValidTimestamp(extra.beamGateTimestamp)) { + refTime = trigger.BeamGateTime(); + refTimestamp = extra.beamGateTimestamp; + } + else if (extra.isValidTimestamp(extra.triggerTimestamp)) { // backup + refTime = trigger.TriggerTime(); + refTimestamp = extra.triggerTimestamp; + } + else { // we assign the reference timestamp `0` to the trigger + refTime = trigger.TriggerTime(); + refTimestamp = 0; + } + + auto timeToTS = [refTime, refTimestamp](double time) + { return std::llround((time - refTime) * 1000.0) + refTimestamp; }; + + return + { timeToTS(clock_data.BeamGateTime()), timeToTS(clock_data.TriggerTime()) }; + +} + void CAFMaker::SBNDShiftCRTReference(StandardRecord &rec, double SBNDFrame) const { //CRT Space Point @@ -562,65 +606,6 @@ void CAFMaker::SBNDShiftPMTReference(StandardRecord &rec, double SBNDFrame) cons } } -void CAFMaker::FixPMTReferenceTimes(StandardRecord &rec, double PMT_reference_time) { - - // Fix the flashes - for (SROpFlash &f: rec.opflashes) { - f.time += PMT_reference_time; - f.timemean += PMT_reference_time; - f.firsttime += PMT_reference_time; - } - - // Fix the flash matches - for (SRSlice &s: rec.slc) { - if (s.fmatch.time != SRDefaults::For(s.fmatch).time) - s.fmatch.time += PMT_reference_time; - - if (s.barycenterFM.flashTime != SRDefaults::For(s.barycenterFM).flashTime) - s.barycenterFM.flashTime += PMT_reference_time; - - if (s.barycenterFM.flashFirstHit != SRDefaults::For(s.barycenterFM).flashFirstHit) - s.barycenterFM.flashFirstHit += PMT_reference_time; - - } - - // TODO: fix more? - -} - -void CAFMaker::FixCRTReferenceTimes(StandardRecord &rec, double CRTT0_reference_time, double CRTT1_reference_time) { - // Fix the hits - - double crttime_to_shift = fParams.CRTUseTS0() ? CRTT0_reference_time : CRTT1_reference_time; - - // As discussed/described in https://github.com/SBNSoftware/sbncode/pull/251, - // we added CRTHit::t0 and CRTHit::t1 in addition to CRTHit::time, - // not to break any existing studies that still use "CRTHit::time" - for (SRCRTHit &h: rec.crt_hits) { - h.t0 += CRTT0_reference_time; - h.t1 += CRTT1_reference_time; - h.time += crttime_to_shift; - } - - // Fix the hit matches - for (SRSlice &s: rec.slc) { - for (SRPFP &pfp: s.reco.pfp) { - pfp.trk.crthit.hit.t0 += CRTT0_reference_time; - pfp.trk.crthit.hit.t1 += CRTT1_reference_time; - pfp.trk.crthit.hit.time += crttime_to_shift; - } - } - for (SRPFP &pfp: rec.reco.pfp) { - pfp.trk.crthit.hit.t0 += CRTT0_reference_time; - pfp.trk.crthit.hit.t1 += CRTT1_reference_time; - pfp.trk.crthit.hit.time += crttime_to_shift; - } - - // TODO: fix more? - // Tracks? - -} - void CAFMaker::InitCRTMapping() { // ICARUS @@ -1662,34 +1647,87 @@ void CAFMaker::produce(art::Event& evt) noexcept { //####################################################### // Fill detector & reco //####################################################### - + + // this text will land into Doxygen `produce()` documentation + /** + * ## Time shifts + * + * Depending on the configuration, time shifts may be applied to some of the data. + * + * The following shifts are supported: + * * shift times from trigger to beam gate (`ShiftTimeFromTriggerToBeamGate` flag), + * which assumes the input is on trigger time (no safe way to verify it). + * Beam gate and trigger time are learnt from `DetectorClocksService`, + * which is the service that gives LArSoft modules the time scale. + * Absolute timestamps are not shifted. + * * additional time shift (`GlobalTimeReferenceOffset`); this is removed + * from all time values, since it's a forward shift of the reference time. + * This shift is always applied; when also enabling the shift from trigger + * to beam gate, it can be thought as an addition to the beam gate time. + * Absolute timestamps are not affected by this offset. + * * CRT-specific time reference offset (`CRTreferenceTimeOffset`); this is + * equivalent to `GlobalTimeReferenceOffset` and applied on top of it but + * only on CRT hits (and related). It also moves the (CRT) reference time, + * so it is effectively subtracted from the existing hit time. + * This offset applies with the same scale on all CRT hit times, including + * absolute timestamps. + * + * Currently there is no offset distinguishing between data and simulation: + * to apply specific shifts, two different configurations need to be used. + * + * @note Some time shifting is _always_ applied to CRT timestamps, even when + * `ShiftTimeFromTriggerToBeamGate` is disabled: in that case, the + * reference time is the trigger timestamp. + * + * SBND-specific shifts are applied on top of this reference change. + */ + + auto const trig_handle + = GetHandleStrict>(evt, fParams.TriggerLabel()); + auto const extratrig_handle + = GetHandleStrict(evt, fParams.TriggerLabel()); + bool const isValidTrigger + = trig_handle.isValid() && extratrig_handle.isValid() && !trig_handle->empty(); + + // subtract these offsets from existing times to move to the new reference time + double const triggerToBeamShift = fParams.ShiftTimeFromTriggerToBeamGate() + ? (clock_data.BeamGateTime() - clock_data.TriggerTime()): 0.0; // <= 0.0 + double const refTimeShift + = fParams.GlobalTimeReferenceOffset() + triggerToBeamShift; + + caf::TimeRefShifter const timeShifter{ refTimeShift }; + + // CRTrefTimeShift includes refTimeShift and is used in the same way (subtract) + double const CRTrefTimeShift = refTimeShift + fParams.CRTreferenceTimeOffset(); // [us] + // shift for the time stamps [ns] never includes the shift to beam gate reference + auto const [ beamGateTimestamp, triggerTimestamp ] = isValidTrigger + ? getReferenceTimestamps(clock_data, trig_handle->front(), *extratrig_handle) + : std::pair{}; + std::int64_t const refTimestamp = fParams.ShiftTimeFromTriggerToBeamGate() + ? beamGateTimestamp: triggerTimestamp; + std::int64_t const CRTrefTimeShiftTS = refTimestamp + static_cast( + std::llround(1000.0 + * (fParams.GlobalTimeReferenceOffset() + fParams.CRTreferenceTimeOffset()) + )); + caf::CRTtimeRefShifter const CRTtimeShifter + { fParams.CRTUseTS0(), CRTrefTimeShiftTS, CRTrefTimeShift }; + + if (refTimeShift || CRTrefTimeShift) { + mf::LogInfo("CAFMaker") << "Shifting time reference by " << refTimeShift << " us" + << "\n beam gate time: " << clock_data.BeamGateTime() << " us" + << "\n trigger time: " << clock_data.TriggerTime() << " us" + << "\n reference shift: trigger -> beam: " << triggerToBeamShift + << "\n reference shift: global change: " << fParams.GlobalTimeReferenceOffset() + << "\n CRT reference shift: times: " << CRTrefTimeShift << " us" + << "\n CRT reference shift: timestamps: " << CRTrefTimeShiftTS << " ns" + ; + } + //Beam gate and Trigger info - art::Handle extratrig_handle; - GetByLabelStrict(evt, fParams.TriggerLabel().encode(), extratrig_handle); - - art::Handle> trig_handle; - GetByLabelStrict(evt, fParams.TriggerLabel().encode(), trig_handle); - - art::Handle> unshifted_trig_handle; - if (!isRealData) - GetByLabelStrict(evt, fParams.UnshiftedTriggerLabel().encode(), unshifted_trig_handle); - - const bool isValidTrigger = extratrig_handle.isValid() && trig_handle.isValid() && trig_handle->size() == 1; - const bool isValidUnshiftedTrigger = unshifted_trig_handle.isValid() && unshifted_trig_handle->size() == 1; - - const double triggerShift = (isValidUnshiftedTrigger && isValidTrigger)? - unshifted_trig_handle->at(0).TriggerTime() - trig_handle->at(0).TriggerTime() : 0.; - caf::SRTrigger srtrigger; if (isValidTrigger) { - FillTrigger(*extratrig_handle, trig_handle->at(0), srtrigger, triggerShift); + FillTrigger(*extratrig_handle, trig_handle->at(0), srtrigger, timeShifter); } - // If not real data, fill in enough of the SRTrigger to make (e.g.) the CRT - // time referencing work. TODO: add more stuff to a "MC"-Trigger? - // No longer needed with incorporation of trigger emulation in the MC. - // else if(!isRealData) { - // FillTriggerMC(fParams.CRTSimT0Offset(), srtrigger); - // } // try to find the result of the Flash trigger if it was run bool pass_flash_trig = false; @@ -1700,9 +1738,6 @@ void CAFMaker::produce(art::Event& evt) noexcept { pass_flash_trig = *flashtrig_handle; } - int64_t CRT_T0_reference_time = isRealData ? -srtrigger.beam_gate_time_abs : -fParams.CRTSimT0Offset(); - double CRT_T1_reference_time = isRealData ? srtrigger.trigger_within_gate : -fParams.CRTSimT0Offset(); - // Fill various detector information associated with the event std::vector srcrthits; @@ -1741,7 +1776,7 @@ void CAFMaker::produce(art::Event& evt) noexcept { for (unsigned i = 0; i < crthits.size(); i++) { srcrthits.emplace_back(); - FillCRTHit(crthits[i], fParams.CRTUseTS0(), CRT_T0_reference_time, CRT_T1_reference_time, crtsimchanmap, srcrthits.back()); + FillCRTHit(crthits[i], CRTtimeShifter, crtsimchanmap, srcrthits.back()); } } @@ -1752,7 +1787,7 @@ void CAFMaker::produce(art::Event& evt) noexcept { const std::vector &crttracks = *crttracks_handle; for (unsigned i = 0; i < crttracks.size(); i++) { srcrttracks.emplace_back(); - FillCRTTrack(crttracks[i], fParams.CRTUseTS0(), srcrttracks.back()); + FillCRTTrack(crttracks[i], CRTtimeShifter, srcrttracks.back()); } } } @@ -1805,7 +1840,7 @@ void CAFMaker::produce(art::Event& evt) noexcept { const std::vector &crtpmtmatches = *crtpmtmatch_handle; for (unsigned i = 0; i < crtpmtmatches.size(); i++) { srcrtpmtmatches.emplace_back(); - FillCRTPMTMatch(crtpmtmatches[i], srcrtpmtmatches.back()); + FillCRTPMTMatch(crtpmtmatches[i], srcrtpmtmatches.back(), timeShifter, CRTtimeShifter); } } @@ -1830,7 +1865,7 @@ void CAFMaker::produce(art::Event& evt) noexcept { std::vector const& ophits = findManyHits.at(iflash); srflashes.emplace_back(); - FillICARUSOpFlash(flash, ophits, cryostat, srflashes.back()); + FillICARUSOpFlash(flash, ophits, cryostat, srflashes.back(), timeShifter); iflash++; } } @@ -1852,7 +1887,7 @@ void CAFMaker::produce(art::Event& evt) noexcept { for (const recob::OpFlash& flash : opflashes) { std::vector const& ophits = findManyHits.at(iflash); srflashes.emplace_back(); - FillSBNDOpFlash(flash, ophits, tpc, srflashes.back()); + FillSBNDOpFlash(flash, ophits, tpc, srflashes.back(), timeShifter); iflash++; } } @@ -2212,21 +2247,21 @@ void CAFMaker::produce(art::Event& evt) noexcept { //####################################################### FillSliceVars(*slice, primary, producer, recslc); FillSliceMetadata(primary_meta, recslc); - FillSliceFlashMatch(fmatch_map["fmatch"], recslc.fmatch); - FillSliceFlashMatch(fmatch_map["fmatchop"], recslc.fmatchop); + FillSliceFlashMatch(fmatch_map["fmatch"], recslc.fmatch, timeShifter); + FillSliceFlashMatch(fmatch_map["fmatchop"], recslc.fmatchop, timeShifter); auto sr_flash = fmatch_map.find("fmatchara"); if(sr_flash!=fmatch_map.end()) { - FillSliceFlashMatch(fmatch_map["fmatchara"], recslc.fmatchara); + FillSliceFlashMatch(fmatch_map["fmatchara"], recslc.fmatchara, timeShifter); } sr_flash = fmatch_map.find("fmatchopara"); if(sr_flash != fmatch_map.end()) { - FillSliceFlashMatch(fmatch_map["fmatchopara"], recslc.fmatchopara); + FillSliceFlashMatch(fmatch_map["fmatchopara"], recslc.fmatchopara, timeShifter); } FillSliceVertex(vertex, recslc); FillSliceCRUMBS(slcCRUMBS, recslc); - FillSliceOpT0Finder(slcOpT0, recslc); + FillSliceOpT0Finder(slcOpT0, recslc, timeShifter); FillSliceBarycenter(slcHits, slcSpacePoints, recslc); - FillTPCPMTBarycenterMatch(barycenterMatch, recslc); + FillTPCPMTBarycenterMatch(barycenterMatch, recslc, timeShifter); FillCVNScores(cvnResult, recslc); // select slice @@ -2401,11 +2436,11 @@ void CAFMaker::produce(art::Event& evt) noexcept { crthittagginginfo = fmCRTHitMatchInfo.at(iPart); } - FillTrackCRTHit(fmCRTHitMatch.at(iPart), crthitmatch, crthittagginginfo, fParams.CRTUseTS0(), CRT_T0_reference_time, CRT_T1_reference_time, crtsimchanmap, trk); + FillTrackCRTHit(fmCRTHitMatch.at(iPart), crthitmatch, crthittagginginfo, CRTtimeShifter, crtsimchanmap, trk); } // NOTE: SEE TODO AT fmCRTTrackMatch if (fmCRTTrackMatch.isValid() && fDet == kICARUS) { - FillTrackCRTTrack(fmCRTTrackMatch.at(iPart), trk); + FillTrackCRTTrack(fmCRTTrackMatch.at(iPart), trk, CRTtimeShifter); } if(foCRTSpacePointMatch.isValid() && fDet == kSBND) @@ -2511,47 +2546,13 @@ void CAFMaker::produce(art::Event& evt) noexcept { rec.crtpmt_matches = srcrtpmtmatches; rec.ncrtpmt_matches = srcrtpmtmatches.size(); - // Move the reference time of reconstructed objects from trigger time to beam spill/beam gate opening time. - // - // We want MC and Data to have the same reference time. - // In MC/LArSoft the "reference time" is canonically defined - // as the time when the start of the beam spill reaches the detector. - // - // In data it may be defined differently for different subsystems. In - // particular, some sub-systems define the reference time as the time - // of the trigger. We want to correct those to the universal reference - // time from MC. - // - // PMT's: - // - // TW (2024-03-29): In MC, when an event doesn't fire the trigger, the raw::Trigger will be - // filled with the default values, which are set to the numerical limits of double. - // In this case, we should set the PMT_reference_time to 0. - - // ICARUS: Fix the Reference time - const bool hasValidTriggerTime = - srtrigger.global_trigger_det_time > - (std::numeric_limits::min() + std::numeric_limits::epsilon()) && - srtrigger.global_trigger_det_time < - (std::numeric_limits::max() - std::numeric_limits::epsilon()); - - double PMT_reference_time = fParams.ReferencePMTFromTriggerToBeam() && hasValidTriggerTime ? triggerShift : 0.; - - mf::LogInfo("CAFMaker") << "Setting PMT reference time to " << PMT_reference_time << " us\n" - << " Trigger Time = " << srtrigger.global_trigger_det_time << " us\n" - << " Beam Gate Time = " << srtrigger.beam_gate_det_time << " us"; - - FixPMTReferenceTimes(rec, PMT_reference_time); - - // TODO: TPC? - // SBND: Fix the Reference time in data depending on the stream // For more information, see: // https://sbn-docdb.fnal.gov/cgi-bin/sso/RetrieveFile?docid=43090 if (isRealData && (fDet == kSBND)) { - // Fill trigger info + // Update trigger info (on top of the other shifts from the configuration) FillTriggerSBND(srsbndtiminginfo, srtrigger); // Shift timing reference frame diff --git a/sbncode/CAFMaker/FillFlashMatch.cxx b/sbncode/CAFMaker/FillFlashMatch.cxx index e6fee07d2..689d5e4f9 100644 --- a/sbncode/CAFMaker/FillFlashMatch.cxx +++ b/sbncode/CAFMaker/FillFlashMatch.cxx @@ -13,6 +13,7 @@ namespace caf //...................................................................... void FillSliceFlashMatch(const sbn::SimpleFlashMatch* fmatch /* can be nullptr */, caf::SRFlashMatch& srflash, + caf::TimeRefShifter<> const& shifter /* = {} */, bool allowEmpty) { if (fmatch == nullptr) { @@ -20,7 +21,7 @@ namespace caf return; } srflash.present = fmatch->present; - srflash.time = fmatch->time; + srflash.time = shifter.shiftedTime(fmatch->time); srflash.chargeQ = fmatch->charge.q; srflash.lightPE = fmatch->light.pe; srflash.score = fmatch->score.total; diff --git a/sbncode/CAFMaker/FillFlashMatch.h b/sbncode/CAFMaker/FillFlashMatch.h index bab40e044..d2d5de826 100644 --- a/sbncode/CAFMaker/FillFlashMatch.h +++ b/sbncode/CAFMaker/FillFlashMatch.h @@ -3,6 +3,7 @@ #define CAF_FILLFLASHMATCH_H //Include new flash match class +#include "sbncode/CAFMaker/TimeRefShifters.h" #include "sbnobj/Common/Reco/SimpleFlashMatchVars.h" #include "sbnanaobj/StandardRecord/SRFlashMatch.h" @@ -11,6 +12,7 @@ namespace caf void FillSliceFlashMatch(const sbn::SimpleFlashMatch* fmatch, caf::SRFlashMatch& srflash, + caf::TimeRefShifter<> const& shifter = {}, bool allowEmpty = false); } diff --git a/sbncode/CAFMaker/FillReco.cxx b/sbncode/CAFMaker/FillReco.cxx index fe79f99e1..f0d62c099 100644 --- a/sbncode/CAFMaker/FillReco.cxx +++ b/sbncode/CAFMaker/FillReco.cxx @@ -65,16 +65,13 @@ namespace caf } void FillCRTHit(const sbn::crt::CRTHit &hit, - bool use_ts0, - int64_t CRT_T0_reference_time, // ns, signed - double CRT_T1_reference_time, // us + caf::CRTtimeRefShifter const& shifter, const std::map, sim::AuxDetSimChannel> &crtsimchanmap, caf::SRCRTHit &srhit, bool allowEmpty) { - srhit.t0 = ( (long long)(hit.ts0()) /*u_int64_t to int64_t*/ + CRT_T0_reference_time )/1000.; - srhit.t1 = hit.ts1()/1000.+CRT_T1_reference_time; // ns -> us - srhit.time = use_ts0 ? srhit.t0 : srhit.t1; + // all times are in us + shifter.fillCRTtimes(hit.ts0(), hit.ts1(), &srhit.t0, &srhit.t1, &srhit.time); srhit.position.x = hit.x_pos; srhit.position.y = hit.y_pos; @@ -136,11 +133,11 @@ namespace caf } void FillCRTTrack(const sbn::crt::CRTTrack &track, - bool use_ts0, - caf::SRCRTTrack &srtrack, - bool allowEmpty) { + caf::CRTtimeRefShifter const& shifter, + caf::SRCRTTrack &srtrack, + bool allowEmpty) { - srtrack.time = (use_ts0 ? (float)track.ts0_ns : track.ts1_ns) / 1000.; + srtrack.time = shifter.shiftTime(shifter.usingTS0()? track.ts0_ns: track.ts1_ns); srtrack.hita.position.x = track.x1_pos; srtrack.hita.position.y = track.y1_pos; @@ -217,15 +214,17 @@ namespace caf void FillCRTPMTMatch(const sbn::crt::CRTPMTMatching &match, caf::SRCRTPMTMatch &srmatch, + caf::TimeRefShifter<> const& PMTshifter, + caf::CRTtimeRefShifter const& CRTshifter, bool allowEmpty){ // allowEmpty does not (yet) matter here (void) allowEmpty; //srmatch.setDefault(); srmatch.flashID = match.flashID; - srmatch.flashTime_us = match.flashTime; + srmatch.flashTime_us = PMTshifter.shiftedTime(match.flashTime); srmatch.flashGateTime = match.flashGateTime; - srmatch.firstOpHitPeakTime = match.firstOpHitPeakTime; - srmatch.firstOpHitStartTime = match.firstOpHitStartTime; + srmatch.firstOpHitPeakTime = PMTshifter.shiftedTime(match.firstOpHitPeakTime); + srmatch.firstOpHitStartTime = PMTshifter.shiftedTime(match.firstOpHitStartTime); srmatch.flashInGate = match.flashInGate; srmatch.flashInBeam = match.flashInBeam; srmatch.flashPE = match.flashPE; @@ -235,8 +234,8 @@ namespace caf unsigned int topen = 0, topex = 0, sideen = 0, sideex = 0; for(const auto& matchedCRTHit : match.matchedCRTHits){ caf::SRMatchedCRT matchedCRT; - matchedCRT.PMTTimeDiff = matchedCRTHit.PMTTimeDiff; - matchedCRT.time = matchedCRTHit.time; + matchedCRT.PMTTimeDiff = matchedCRTHit.PMTTimeDiff; + matchedCRT.time = CRTshifter.shiftTime(matchedCRTHit.time * 1000); matchedCRT.sys = matchedCRTHit.sys; matchedCRT.region = matchedCRTHit.region; matchedCRT.position = SRVector3D(matchedCRTHit.position.X(), matchedCRTHit.position.Y(), matchedCRTHit.position.Z()); @@ -258,11 +257,12 @@ namespace caf std::vector const& hits, int cryo, caf::SROpFlash &srflash, + caf::TimeRefShifter<> const& shifter, bool allowEmpty) { srflash.setDefault(); - srflash.time = flash.Time(); + srflash.time = shifter.shiftedTime(flash.Time()); srflash.timewidth = flash.TimeWidth(); double firstTime = std::numeric_limits::max(); @@ -271,7 +271,7 @@ namespace caf if (firstTime > hitTime) firstTime = hitTime; } - srflash.firsttime = firstTime; + srflash.firsttime = shifter.shiftedTimeIfNot(firstTime, std::numeric_limits::max()); srflash.cryo = cryo; // 0 in SBND, 0/1 for E/W in ICARUS @@ -306,11 +306,12 @@ namespace caf std::vector const& hits, int tpc, caf::SROpFlash &srflash, + caf::TimeRefShifter<> const& shifter, bool allowEmpty) { srflash.setDefault(); - srflash.time = flash.Time(); + srflash.time = shifter.shiftedTime(flash.Time()); srflash.timewidth = flash.TimeWidth(); double firstTime = std::numeric_limits::max(); @@ -319,7 +320,7 @@ namespace caf if (firstTime > hitTime) firstTime = hitTime; } - srflash.firsttime = firstTime; + srflash.firsttime = shifter.shiftedTimeIfNot(firstTime, std::numeric_limits::max()); srflash.tpc = tpc; srflash.totalpe = flash.TotalPE(); @@ -564,7 +565,7 @@ namespace caf } void FillSliceOpT0Finder(const std::vector> &opt0_v, - caf::SRSlice &slice) + caf::SRSlice &slice, caf::TimeRefShifter<> const& shifter) { if (opt0_v.empty()==false){ unsigned int nopt0 = opt0_v.size(); @@ -585,7 +586,7 @@ namespace caf const sbn::OpT0Finder &maxOpT0 = *opt0_v[max_idx]; slice.opt0.tpc = maxOpT0.tpc; - slice.opt0.time = maxOpT0.time; + slice.opt0.time = shifter.shiftedTime(maxOpT0.time); slice.opt0.score = maxOpT0.score; slice.opt0.measPE = maxOpT0.measPE; slice.opt0.hypoPE = maxOpT0.hypoPE; @@ -603,7 +604,7 @@ namespace caf } const sbn::OpT0Finder &secOpT0 = *opt0_v[sec_idx]; slice.opt0_sec.tpc = secOpT0.tpc; - slice.opt0_sec.time = secOpT0.time; + slice.opt0_sec.time = shifter.shiftedTime(secOpT0.time); slice.opt0_sec.score = secOpT0.score; slice.opt0_sec.measPE = secOpT0.measPE; slice.opt0_sec.hypoPE = secOpT0.hypoPE; @@ -679,9 +680,7 @@ namespace caf void FillTrackCRTHit(const std::vector> &t0match, const std::vector> &hitmatch, const std::vector> &hitmatchinfo, - bool use_ts0, - int64_t CRT_T0_reference_time, // ns, signed - double CRT_T1_reference_time, // us + caf::CRTtimeRefShifter const& shifter, const std::map, sim::AuxDetSimChannel> &crtsimchanmap, caf::SRTrack &srtrack, bool allowEmpty) @@ -709,18 +708,19 @@ namespace caf srtrack.crthit.hit.plane = t0match[0]->fID; } if (hitmatch.size()) { - FillCRTHit(*hitmatch[0], use_ts0, CRT_T0_reference_time, CRT_T1_reference_time, crtsimchanmap, srtrack.crthit.hit, allowEmpty); + FillCRTHit(*hitmatch[0], shifter, crtsimchanmap, srtrack.crthit.hit, allowEmpty); } } void FillTrackCRTTrack(const std::vector> &t0match, caf::SRTrack &srtrack, + caf::CRTtimeRefShifter const& timeShifter, bool allowEmpty) { if (t0match.size()) { assert(t0match.size() == 1); srtrack.crttrack.angle = t0match[0]->fTriggerConfidence; - srtrack.crttrack.time = t0match[0]->fTime / 1e3; /* ns -> us */ + srtrack.crttrack.time = timeShifter.shiftTime(t0match[0]->fTime); // ns -> us // TODO/FIXME: FILL MORE ONCE WE HAVE THE CRT HIT!!! @@ -1216,17 +1216,21 @@ namespace caf } void FillTPCPMTBarycenterMatch(const sbn::TPCPMTBarycenterMatch *matchInfo, - caf::SRSlice& slice) + caf::SRSlice& slice, + caf::TimeRefShifter<> const& shifter) { slice.barycenterFM.setDefault(); if ( matchInfo != nullptr ) { + bool const matched = (matchInfo->radius >= 0.0); slice.barycenterFM.chargeTotal = matchInfo->chargeTotal; slice.barycenterFM.chargeCenterXLocal = matchInfo->chargeCenterXLocal; slice.barycenterFM.chargeCenter = SRVector3D (matchInfo->chargeCenter.x(), matchInfo->chargeCenter.y(), matchInfo->chargeCenter.z()); slice.barycenterFM.chargeWidth = SRVector3D (matchInfo->chargeWidth.x(), matchInfo->chargeWidth.y(), matchInfo->chargeWidth.z()); - slice.barycenterFM.flashFirstHit = matchInfo->flashFirstHit; - slice.barycenterFM.flashTime = matchInfo->flashTime; + slice.barycenterFM.flashFirstHit + = shifter.shiftedTimeIf(matchInfo->flashFirstHit, matched); + slice.barycenterFM.flashTime + = shifter.shiftedTimeIf(matchInfo->flashTime, matched); slice.barycenterFM.flashPEs = matchInfo->flashPEs; slice.barycenterFM.flashCenter = SRVector3D (matchInfo->flashCenter.x(), matchInfo->flashCenter.y(), matchInfo->flashCenter.z()); slice.barycenterFM.flashWidth = SRVector3D (matchInfo->flashWidth.x(), matchInfo->flashWidth.y(), matchInfo->flashWidth.z()); diff --git a/sbncode/CAFMaker/FillReco.h b/sbncode/CAFMaker/FillReco.h index 39bfd2318..2487e0d8a 100644 --- a/sbncode/CAFMaker/FillReco.h +++ b/sbncode/CAFMaker/FillReco.h @@ -55,6 +55,8 @@ #include "sbnanaobj/StandardRecord/SRSlice.h" #include "sbnanaobj/StandardRecord/StandardRecord.h" +#include "sbncode/CAFMaker/TimeRefShifters.h" + namespace caf { @@ -107,7 +109,7 @@ namespace caf bool allowEmpty = false); void FillSliceOpT0Finder(const std::vector> &opt0_v, - caf::SRSlice &slice); + caf::SRSlice &slice, caf::TimeRefShifter<> const& shifter); void FillSliceBarycenter(const std::vector> &inputHits, const std::vector> &inputPoints, @@ -175,15 +177,14 @@ namespace caf void FillTrackCRTHit(const std::vector> &t0match, const std::vector> &hitmatch, const std::vector> &hitmatchinfo, - bool use_ts0, - int64_t CRT_T0_reference_time, // ns, signed - double CRT_T1_reference_time, // us + caf::CRTtimeRefShifter const& shifter, const std::map, sim::AuxDetSimChannel> &crtsimchanmap, caf::SRTrack &srtrack, bool allowEmpty = false); void FillTrackCRTTrack(const std::vector> &t0match, caf::SRTrack &srtrack, + caf::CRTtimeRefShifter const& timeShifter, bool allowEmpty = false); void FillTrackCRTSpacePoint(const anab::T0 &t0match, @@ -249,14 +250,12 @@ namespace caf unsigned truth_ind); void FillCRTHit(const sbn::crt::CRTHit &hit, - bool use_ts0, - int64_t CRT_T0_reference_time, // ns, signed - double CRT_T1_reference_time, // us + caf::CRTtimeRefShifter const& shifter, const std::map, sim::AuxDetSimChannel> &crtsimchanmap, caf::SRCRTHit &srhit, bool allowEmpty = false); void FillCRTTrack(const sbn::crt::CRTTrack &track, - bool use_ts0, + caf::CRTtimeRefShifter const& shifter, caf::SRCRTTrack &srtrack, bool allowEmpty = false); @@ -272,20 +271,25 @@ namespace caf std::vector const& hits, int cryo, caf::SROpFlash &srflash, + caf::TimeRefShifter<> const& shifter = {}, bool allowEmpty = false); void FillSBNDOpFlash(const recob::OpFlash &flash, std::vector const& hits, int tpc, caf::SROpFlash &srflash, + caf::TimeRefShifter<> const& shifter = {}, bool allowEmpty = false); void FillCRTPMTMatch(const sbn::crt::CRTPMTMatching &match, caf::SRCRTPMTMatch &srmatch, + caf::TimeRefShifter<> const& PMTshifter, + caf::CRTtimeRefShifter const& CRTshifter, bool allowEmpty = false); void FillTPCPMTBarycenterMatch(const sbn::TPCPMTBarycenterMatch *matchInfo, - caf::SRSlice& slice); + caf::SRSlice& slice, + caf::TimeRefShifter<> const& shifter); void FillCVNScores(const lcvn::Result *cvnResult, caf::SRSlice& slice); diff --git a/sbncode/CAFMaker/FillTrigger.cxx b/sbncode/CAFMaker/FillTrigger.cxx index cf720e1a3..5c6644c1e 100644 --- a/sbncode/CAFMaker/FillTrigger.cxx +++ b/sbncode/CAFMaker/FillTrigger.cxx @@ -1,20 +1,22 @@ #include "sbncode/CAFMaker/FillTrigger.h" +#include "sbnobj/Common/Trigger/BeamBits.h" namespace caf { void FillTrigger(const sbn::ExtraTriggerInfo& addltrig_info, const raw::Trigger& trig, caf::SRTrigger& triggerInfo, - const double time_offset = 0.0) + caf::TimeRefShifter<> const& shifter /* = {} */ + ) { triggerInfo.global_trigger_time = addltrig_info.triggerTimestamp; triggerInfo.beam_gate_time_abs = addltrig_info.beamGateTimestamp; - triggerInfo.beam_gate_det_time = trig.BeamGateTime() + time_offset; - triggerInfo.global_trigger_det_time = trig.TriggerTime() + time_offset; + triggerInfo.beam_gate_det_time = shifter.shiftedTime(trig.BeamGateTime()); + triggerInfo.global_trigger_det_time = shifter.shiftedTime(trig.TriggerTime()); double diff_ts = triggerInfo.global_trigger_det_time - triggerInfo.beam_gate_det_time; triggerInfo.trigger_within_gate = diff_ts; - + triggerInfo.prev_global_trigger_time = addltrig_info.previousTriggerTimestamp; triggerInfo.source_type = sbn::bits::value(addltrig_info.sourceType); triggerInfo.trigger_type = sbn::bits::value(addltrig_info.triggerType); @@ -26,7 +28,7 @@ namespace caf } void FillTriggerSBND(caf::SRSBNDTimingInfo& timingInfo, caf::SRTrigger& triggerInfo){ - + // updates the existing triggerInfo record triggerInfo.global_trigger_time = timingInfo.hltEtrig; triggerInfo.beam_gate_time_abs = timingInfo.hltBeamGate; } diff --git a/sbncode/CAFMaker/FillTrigger.h b/sbncode/CAFMaker/FillTrigger.h index 949ab530a..2ae879e96 100644 --- a/sbncode/CAFMaker/FillTrigger.h +++ b/sbncode/CAFMaker/FillTrigger.h @@ -1,6 +1,7 @@ #ifndef CAF_FILLTRIGGER_H #define CAF_FILLTRIGGER_H +#include "sbncode/CAFMaker/TimeRefShifters.h" #include "sbnobj/Common/Trigger/ExtraTriggerInfo.h" #include "sbnanaobj/StandardRecord/SRTrigger.h" #include "sbnanaobj/StandardRecord/SRSBNDTimingInfo.h" @@ -12,7 +13,7 @@ namespace caf void FillTrigger(const sbn::ExtraTriggerInfo& addltrig_info, const raw::Trigger& trig_info, caf::SRTrigger& triggerInfo, - const double time_offset); + caf::TimeRefShifter<> const& shifter = {}); void FillTriggerSBND(caf::SRSBNDTimingInfo& timingInfo, caf::SRTrigger& triggerInfo); } diff --git a/sbncode/CAFMaker/TimeRefShifters.h b/sbncode/CAFMaker/TimeRefShifters.h new file mode 100644 index 000000000..7317abf81 --- /dev/null +++ b/sbncode/CAFMaker/TimeRefShifters.h @@ -0,0 +1,161 @@ +/** + * @file sbncode/CAFMaker/TimeRefShifters.h + * @brief Simple utilities to apply a reference time shift. + * @author Gianluca Petrillo (petrillo@slac.stanford.edu) + * @date July 21, 2026 + * + * The purpose of these utilities is to ensure that the time reference shifts + * are applied in a consistent way. + */ + +#ifndef SBNCODE_CAFMAKER_TIMEREFSHIFTERS_H +#define SBNCODE_CAFMAKER_TIMEREFSHIFTERS_H + + +// C++ standard libraries +#include // std::int64_t + + +// ----------------------------------------------------------------------------- +namespace caf { + template struct TimeRefShifter; + struct CRTtimeRefShifter; +} +/** + * @brief Applies a standard time reference change to a time. + * @tparam RefTime type used to internally store the reference time shift + * + * The shifter object is constructed with a time shift value: the reference time + * is delayed in time by that value. As a consequence, all time points will + * acquire an "earlier" value. + * + * Example: + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} + * double T1 = 0.5, T2 = 1.0; // [us] + * int T3 = 50; // [ns] + * caf::TimeRefShifter const shifter{ 0.235 }; + * double shiftedT1 = shifter.shiftedTime(T1); + * shifter.shift(T2); + * int shiftedT3 = shifter.shiftedTimeNS(T3); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * will shift the time reference forward by 235 ns; the value of all shifted + * times will become 235 ns smaller (`0.265` for `shiftedT1`, `0.765` for `T2`, + * `-185` for `T3`; `T1` was not shifted). + * + */ +template +struct caf::TimeRefShifter { + + /// Forward shift of the reference time [us] + RefTime const refTimeShift = RefTime{ 0 }; + /// Returns a time with reference shifted compared to `t` [us] + + /// Returns `t` [us], shifted. + template + constexpr T shiftedTime(T t) const { return t - shiftAs(); } + + /// Returns `t` [us], shifted only if `doShift` is `true`. + template + constexpr T shiftedTimeIf(T t, bool doShift) const + { return doShift? shiftedTime(t): t; } + + /// Returns `t` [us], shifted only if it's not exactly equal to `defVal`. + template + constexpr T shiftedTimeIfNot(T t, T defVal) const + { return shiftedTimeIf(t, t != defVal); } + + /// Returns a time with reference shifted compared to `t` [ns] + template + constexpr T shiftedTimeNS(T t) const { return t - shiftNSas(); } + + /// Changes the value of `t` shifting its reference [us] + template + T& shift(T& t) const { return t -= shiftAs(); } + + /// Changes the value of `t` shifting its reference [ns] + template + T& shiftNS(T& t) const { return t -= shiftNSas(); } + + + private: + + template + constexpr T shiftAs() const { return static_cast(refTimeShift); } + template + constexpr T shiftNSas() const { return static_cast(refTimeShift * 1000); } + +}; // caf::TimeRefShifter + +// template deduction guide: if presented with an argument T... +namespace caf { template TimeRefShifter(T) -> TimeRefShifter; } + +// ----------------------------------------------------------------------------- +/** + * @brief Applies time reference shifts to CRT times and timestamps. + * + * This shifter works similarly to `caf::TimeRefShifter` but it handles relative + * times and absolute timestamps independent shifts. + * + * This object largely adopts the terminology from `sbn::crt::CRTHit`, calling + * time stamps with "TS0" and relative times as "TS1". + * In addition, it supports a "merged" time which is either from TS0 or TS1 + * depending on a fixes parameter. + * + * Note that the convention of `sbn::crt::CRTHit` are: + * * timestamps are expressed as signed 64-bit integers; + * * relative times are expressed as `double`. + * + * They are both expressed in nanoseconds. However, across the CRT-related data + * products conventions do change. + * + * The output follows CAF conventions instead, so it is always in `double` + * precision and in microseconds. That may not play well with actual + * 64-bit timestamps (TS0-style), which may lose precision. + */ +struct caf::CRTtimeRefShifter { + + bool const useTS0 = false; ///< Use TS0 for time? + std::int64_t const refTimeShiftTS = 0; ///< Reference shift for timestamps [ns] + double const refTimeShift = 0.0; ///< Reference shifts for relative times [us] + + + constexpr bool usingTS0() const { return useTS0; } + + /// Returns the timestamp `ts_ns` with shifted reference [us] + constexpr double shiftTimestamp(std::int64_t ts_ns) const + { return (ts_ns - refTimeShiftTS) / 1000.; } + + /// Returns the time `t` [ns] with shifted reference [us] + constexpr double shiftTime(double t_ns) const + { return t_ns / 1000. - refTimeShift; } + + /// Definitions here follow `sbn::crt::CRTHit` (i.e. TS1 is not really a timestamp) + template + void fillCRTtimes( + std::int64_t TS0, std::int64_t TS1, + T* t0 = nullptr, T* t1 = nullptr, T* time = nullptr + ) const + { + double const shT0 = shiftTimestamp(TS0); + double const shT1 = shiftTime(static_cast(TS1)); + if (t0) *t0 = shT0; + if (t1) *t1 = shT1; + if (time) *time = useTS0? shT0: shT1; + } + + + /// Creates a timestamp (of type `TS`) from a second and a nanosecond part. + template + static constexpr TS makeTS(S s, NS ns) + { + return static_cast + (static_cast(s) * 1'000'000'000 + static_cast(ns)); + } + +}; // caf::CRTtimeRefShifter + + +// ----------------------------------------------------------------------------- + + +#endif // SBNCODE_CAFMAKER_TIMEREFSHIFTERS_H