diff --git a/sbnobj/Common/PMT/Data/CMakeLists.txt b/sbnobj/Common/PMT/Data/CMakeLists.txt index 6aa848ff..c62bc7ea 100644 --- a/sbnobj/Common/PMT/Data/CMakeLists.txt +++ b/sbnobj/Common/PMT/Data/CMakeLists.txt @@ -1,10 +1,12 @@ cet_make_library( SOURCE + PMTBeamSignal.cxx PMTconfiguration.cxx V1730Configuration.cxx V1730channelConfiguration.cxx LIBRARIES - lardataobj::RawData + messagefacility::MF_MessageLogger + lardataobj::RawData ) art_dictionary(DICTIONARY_LIBRARIES sbnobj::Common_PMT_Data) diff --git a/sbnobj/Common/PMT/Data/PMTBeamSignal.cxx b/sbnobj/Common/PMT/Data/PMTBeamSignal.cxx new file mode 100644 index 00000000..20f7e819 --- /dev/null +++ b/sbnobj/Common/PMT/Data/PMTBeamSignal.cxx @@ -0,0 +1,122 @@ +/** + * @file sbnobj/Common/PMT/Data/PMTBeamSignal.cxx + * @brief Holds the event-by-event RWM or EW times + * @author Anna Heggestuen (aheggest@colostate.edu), adapted from M. Vicenzi in https://github.com/SBNSoftware/icaruscode/pull/751 + * @date May 19, 2025 + * @see sbnobj/Common/PMT/Data/PMTBeamSignal.h + */ + +//library header +#include "sbnobj/Common/PMT/Data/PMTBeamSignal.hh" + +// framework libraries +#include "messagefacility/MessageLogger/MessageLogger.h" + + +// ----------------------------------------------------------------------------- +void sbn::timing::SelectFirstOpHitByTime(const recob::OpHit* const hit, + std::map &risemap) +{ + const int ch = hit->OpChannel(); + double ts = hit->StartTime(); + double tr = hit->RiseTime(); + // select the first ophit (by time) in each channel + if (risemap.find(ch) != risemap.end()) + { + if (tr < risemap[ch]) + { + risemap[ch] = ts + tr; + } + } + else + { + risemap[ch] = ts + tr; + } +} +// ----------------------------------------------------------------------------- +int sbn::timing::getSideByChannel(const int channel) +{ + /* + Channels are numbered from east to west, from North (cryo side) to South (beam side) + We look in the opposide direction wrt to the beam direction South->North: + - Left is the east wall of each cryostat; + - Right is the west side of each cryostat; + - [ 0:89 ] and [180:269] are on the left, + the return value of the function is 0; + - [ 90-179 ] and [ 270:359 ] are on the right, + the return value of the function is 1; + */ + + int side = channel / 90; // always round down + return side % 2; +} + +// ----------------------------------------------------------------------------- +double sbn::timing::getFlashBunchTime(std::map risemap, + std::vector RWMTimes) +{ + // if no RWM info available, all pmt_start_time_rwm are invalid + // return icarus::timing::NoTime as well for the flash + if (RWMTimes.empty()) + return sbn::timing::NoTime; + + std::vector channels; + std::vector hit_rise_time_rwm; + for (auto it = risemap.begin(); it != risemap.end(); it++){ + int ch = it->first; + channels.push_back(ch); + auto rwm = RWMTimes.at(ch); + if (!rwm.isValid()){ + mf::LogTrace("PMTBeamSignal getFlashBunchTime") << "No RWM signal for channel " << ch << " " + << "(Crate " << rwm.crate << ", Board " << rwm.digitizerLabel + << ", SpecialChannel " << rwm.specialChannel << ")\n"; + } + float rwm_trigger = rwm.startTime; // rwm time w.r.t. trigger time [us] + float firstHitRisetime_trigger = it->second; // first opHit rise time w.r.t. trigger time [us] + hit_rise_time_rwm.push_back(firstHitRisetime_trigger - rwm_trigger); + } + + double tfirst_left = std::numeric_limits::max(); + double tfirst_right = std::numeric_limits::max(); + + int nleft = 0; + int nright = 0; + for(std::size_t i = 0; i < hit_rise_time_rwm.size(); i++){ + int ch = channels[i]; + int side = sbn::timing::getSideByChannel(ch); + double t = hit_rise_time_rwm[i]; // rise time w.r.t. rwm + + // if any RWM copy is missing (therefore missing for an entire PMT crate), + // it might not be possible to use the first hits (they might not have a RMW time) + // so return icarus::timing::NoTime as in other bad cases + if (!RWMTimes[i].isValid()) + return sbn::timing::NoTime; + + // count hits separetely on the two walls + if (side == 0) + { + nleft++; + if (t < tfirst_left) + tfirst_left = t; + } + else if (side == 1) + { + nright++; + if (t < tfirst_right) + tfirst_right = t; + } + } //end loop over m_hit_rise_time_rwm vector + + // if there are no hits in one of the walls... very rare? + if (nleft < 1 || nright < 1) + { + mf::LogWarning("PMTBeamSignal getFlashBunchTime") << "Flash doesn't have hits on both walls!" + << "Left: " << nleft << " t " << tfirst_left << " " + << "Right: " << nright << " t " << tfirst_right; + // return what we have... + return (tfirst_left < tfirst_right) ? tfirst_left : tfirst_right; + } + + return (tfirst_left + tfirst_right) / 2.; + +} diff --git a/sbnobj/Common/PMT/Data/PMTBeamSignal.hh b/sbnobj/Common/PMT/Data/PMTBeamSignal.hh new file mode 100644 index 00000000..cfe471db --- /dev/null +++ b/sbnobj/Common/PMT/Data/PMTBeamSignal.hh @@ -0,0 +1,82 @@ +/** + * @file sbnobj/Common/PMT/Data/PMTBeamSignal.hh (moved from icaruscode/IcarusObj/Legacy/PMTBeamSignal.h) + * @brief Holds the event-by-event RWM or EW times + * @author Matteo Vicenzi (mvicenzi@bnl.gov), Anna Heggestuen (aheggest@colostate.edu) + * @date March 14 2024, updated May 2025 + */ + +#ifndef SBNOBJ_COMMON_PMT_DATA_PMTBEAMSIGNAL_HH +#define SBNOBJ_COMMON_PMT_DATA_PMTBEAMSIGNAL_HH + +// LArSoft libraries +#include "lardataobj/RecoBase/OpHit.h" + +// C/C++ standard libraries +#include +#include +#include +#include +#include + +namespace sbn::timing +{ + + /// Special value to denote no special channel information. + static constexpr auto NoChannel = std::numeric_limits::max(); + /// Special value to denote no time channel information. + static constexpr double NoTime = std::numeric_limits::max(); + // Special value to denote no sample information. + static constexpr std::size_t NoSample = 0; + + void SelectFirstOpHitByTime(const recob::OpHit* const hit, + std::map &risemap); + + int getSideByChannel(const int channel); + + /** + * @brief Beam time as seen by a PMT readout board. + * + * This could either be an early warning (EW) or a resistive wall monitor (RWM) time. + * These signals are delivered via fibers and digitized in special PMT channels. + * + * Both the time in @ref DetectorClocksElectronicsTime "electronics time scale" + * and time time relative to the hardware trigger are included. + * + * The information in this object may be missing: its validity should + * always be checked in advance with `isValid()`. + */ + + struct PMTBeamSignal + { + + /// Special channel this time was extracted from. + /// These are defined in `CAEN_V1730_setup_icarus.fcl`. + unsigned int specialChannel = NoChannel; + + /// Board on which the special channel is on (e.g: WW-TOP-A). + /// Should match the same format as `icarusDB::PMTChannelInfo_t::digitizerLabel`. + std::string digitizerLabel = ""; + + /// Crate this time applies to (e.g.: WW-TOP). + /// Corresponds to the first part of `digitizerLabel`. + std::string crate = ""; + + /// Sample within the waveform where the reference signal is found. + std::size_t sample = NoSample; + + /// Start time in electronics time [us]. + double startTimeAbs = NoTime; + + /// Start time relative to trigger time [us]. + double startTime = NoTime; + + /// Returns whether the time is valid. + bool isValid() const { return (sample != NoSample); } + }; + + double getFlashBunchTime(std::map risemap, + std::vector RWMTimes); + +} // namespace sbn::timing + +#endif // SBNOBJ_COMMON_PMT_DATA_PMTBEAMSIGNAL_HH \ No newline at end of file diff --git a/sbnobj/Common/PMT/Data/classes.h b/sbnobj/Common/PMT/Data/classes.h index 183a3c78..8a8e6bb0 100644 --- a/sbnobj/Common/PMT/Data/classes.h +++ b/sbnobj/Common/PMT/Data/classes.h @@ -12,6 +12,7 @@ // SBN libraries #include "sbnobj/Common/PMT/Data/PMTconfiguration.h" +#include "sbnobj/Common/PMT/Data/PMTBeamSignal.hh" // framework libraries #include "canvas/Persistency/Common/Ptr.h" diff --git a/sbnobj/Common/PMT/Data/classes_def.xml b/sbnobj/Common/PMT/Data/classes_def.xml index 2afad393..c6f4a535 100644 --- a/sbnobj/Common/PMT/Data/classes_def.xml +++ b/sbnobj/Common/PMT/Data/classes_def.xml @@ -41,7 +41,10 @@ - + + + +