ATLAS Offline Software
Loading...
Searching...
No Matches
LArOFFCRawChannelBuilder.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
6
7#include "GaudiKernel/SystemOfUnits.h"
12#include "LArSimEvent/LArHit.h"
16
17#include <cmath>
18#include <fstream>
19#include <map>
20#include <vector>
21#include <algorithm>
22#include <ranges>
23
25 const ILArShape::ShapeRef_t& shape, const ILArOFC::OFCRef_t& ofc) const {
26 // Convolve pulse shape with time-reversed OFC to build forward-correction
27 // template
28
29 const int shapeSize = shape.size();
30 const int ofcSize = ofc.size();
31
32 std::vector<double> ofcRev(ofcSize);
33 for (int i = 0; i < ofcSize; ++i)
34 ofcRev[i] = ofc[ofcSize - 1 - i];
35
36 const int fullLen = shapeSize + ofcSize - 1;
37 std::vector<double> fullConv(fullLen, 0.0);
38
39 for (int i = 0; i < shapeSize; ++i)
40 for (int j = 0; j < ofcSize; ++j)
41 fullConv[i + j] += shape[i] * ofcRev[j];
42
43 // Drop early samples to align correction with trigger window
44 const int startIndex = 2;
45 if (startIndex >= fullLen)
46 return {};
47
48 return std::vector<double>(fullConv.begin() + startIndex, fullConv.end());
49}
50
51double LArOFFCRawChannelBuilder::computeOFFC(const std::vector<short>& samples,
52 int firstSample,
53 const ILArOFC::OFCRef_t& ofc,
54 const ILArShape::ShapeRef_t& shape,
55 double pedestal) const {
56 // OFFC parameters (configured via job options)
57 // belowThreshold : ADC threshold to detect quiet regions
58 // belowTillReset : consecutive quiet samples before cache reset
59 // npulse : max number of overlapping pulses tracked
60 // Q3cut : shape-consistency cut for pulse acceptance
61 // filterThreshold : minimum filtered amplitude to seed a pulse
62
63 const int nSamples = samples.size();
64 const int ofcLen = ofc.size();
65
66 // Pedestal subtraction
67 std::vector<double> samp_no_ped(nSamples);
68 for (int i = 0; i < nSamples; ++i)
69 samp_no_ped[i] = samples[i] - pedestal;
70
71 std::vector<double> filtered(nSamples, 0.0);
72 std::vector<double> reco(nSamples, 0.0);
73
74 // Precompute convolved pulse
75 std::vector<double> convPulse = convolvePulse(shape, ofc);
76 const int convSize = convPulse.size();
77 std::vector<double> cache(convSize, 0.0);
78
79 // Index of pulse maximum in reference shape
80 auto it = std::ranges::max_element(shape);
81 int shapemax = std::distance(shape.begin(), it);
82
83 // Determine how many pulses stored
84 std::vector<int> context(m_nPulse, 0);
85 int belowCounter = 0;
86
87 const int loopEnd = std::max(0, nSamples - ofcLen);
88
89 for (int i = 0; i < loopEnd; ++i) {
90
91 // Reset correction cache after extended quiet region
92 if (std::abs(samp_no_ped[i]) < m_belowThreshold) {
93 if (++belowCounter == m_belowTillReset) {
94 belowCounter = 0;
95 std::fill(cache.begin(), cache.end(), 0.0);
96 }
97 } else {
98 belowCounter = 0;
99 }
100
101 // Standard OF filtering
102 for (int j = 0; j < ofcLen; ++j)
103 filtered[i] += samp_no_ped[i + j] * ofc[j];
104
105 if (i > 4) {
106 // Apply forward correction
107 reco[i - 1] = filtered[i - 1] + cache[2];
108 const double A = reco[i - 1];
109
110 // Local maximum + amplitude cut
111 if (A > m_filterThreshold && filtered[i - 1] > filtered[i - 2] &&
112 filtered[i - 1] > filtered[i]) {
113
114 // Shape-consistency (Q3) test
115 auto shapeVal = [&](int k) {
116 return (k >= 0 && k < (int)shape.size()) ? shape[k] : 0.0;
117 };
118
119 double Q3 = 0.0;
120 Q3 += std::abs(filtered[i - 2] - A * shapeVal(shapemax - 1));
121 Q3 += std::abs(filtered[i - 1] - A * shapeVal(shapemax));
122 Q3 += std::abs(filtered[i] - A * shapeVal(shapemax + 1));
123 Q3 += std::abs(filtered[i - 3] - A * shapeVal(shapemax - 2));
124
125 // Accept pulse and subtract its forward correction
126 if (Q3 < m_Q3cut) {
127 for (int& c : context) {
128 if (c == 0) {
129 for (int k = 0; k < convSize; ++k)
130 cache[k] -= convPulse[k] * A;
131 c = convSize;
132 break;
133 }
134 }
135 }
136 }
137 }
138
139 // Advance correction cache in time
140 std::rotate(cache.begin(), cache.begin() + 1, cache.end());
141 cache.back() = 0.0;
142
143 for (int& c : context)
144 if (c > 0)
145 --c;
146 }
147
148 // Return reconstructed amplitude at requested sample
149 return reco[firstSample + 1];
150}
151
153 ATH_CHECK(m_digitKey.initialize());
154 ATH_CHECK(m_rawChannelKey.initialize());
155 ATH_CHECK(m_pedestalKey.initialize());
156 ATH_CHECK(m_adc2MeVKey.initialize());
157 ATH_CHECK(m_ofcKey.initialize());
158 ATH_CHECK(m_shapeKey.initialize());
159 ATH_CHECK(m_cablingKey.initialize());
162
163 if (m_useDBFortQ) {
164 if (m_run1DSPThresholdsKey.empty() && m_run2DSPThresholdsKey.empty()) {
166 "useDB requested but neither Run1... nor Run2... initialized.");
167 return StatusCode::FAILURE;
168 }
169 }
170
171 ATH_CHECK(detStore()->retrieve(m_onlineId, "LArOnlineID"));
172
173 if (m_firstSample < 5) {
174 ATH_MSG_ERROR("firstSample must be >= 5 to allow for OFFC processing");
175 return StatusCode::FAILURE;
176 }
177
178 const std::string cutmsg = m_absECutFortQ.value() ? " fabs(E) < " : " E < ";
179 ATH_MSG_INFO("Energy cut for time and quality computation: "
180 << cutmsg << " taken from COOL folder "
181 << m_run1DSPThresholdsKey.key() << " (run1) "
182 << m_run2DSPThresholdsKey.key() << " (run2) ");
183
184 return StatusCode::SUCCESS;
185}
186
187StatusCode LArOFFCRawChannelBuilder::execute(const EventContext& ctx) const {
188
189 ATH_MSG_VERBOSE("Executing LArOFFCRawChannelBuilder::execute");
190
191 // Get event inputs from read handles:
192 const LArDigitContainer* inputContainer{};
193 ATH_CHECK(SG::get(inputContainer, m_digitKey, ctx));
194
195 // Write output via write handle
196 auto outputContainer = std::make_unique<LArRawChannelContainer>();
197
198 // Get Conditions input
199 const ILArPedestal* peds{};
200 ATH_CHECK(SG::get(peds, m_pedestalKey, ctx));
201
202 const LArADC2MeV* adc2MeVs{};
203 ATH_CHECK(SG::get(adc2MeVs, m_adc2MeVKey, ctx));
204
205 const ILArOFC* ofcs{nullptr};
206 ATH_CHECK(SG::get(ofcs, m_ofcKey, ctx));
207
208 const ILArShape* shapes{};
209 ATH_CHECK(SG::get(shapes, m_shapeKey, ctx));
210
211 const LArOnOffIdMapping* cabling{};
212 ATH_CHECK(SG::get(cabling, m_cablingKey, ctx));
213
214 std::unique_ptr<LArDSPThresholdsFlat> run2DSPThresh;
215 const LArDSPThresholdsComplete* run1DSPThresh = nullptr;
216 ATH_CHECK(SG::get(run1DSPThresh, m_run1DSPThresholdsKey, ctx));
217 if (m_useDBFortQ) {
218 if (!m_run2DSPThresholdsKey.empty()) {
221 run2DSPThresh = std::make_unique<LArDSPThresholdsFlat>(*dspThrshAttr);
222 if (ATH_UNLIKELY(!run2DSPThresh->good())) {
224 "Failed to initialize LArDSPThresholdFlat from attribute list "
225 "loaded from "
226 << m_run2DSPThresholdsKey.key() << ". Aborting.");
227 return StatusCode::FAILURE;
228 }
229 } else if (!m_run1DSPThresholdsKey.empty()) {
232 run1DSPThresh = dspThresh.cptr();
233 } else {
234 ATH_MSG_ERROR("No DSP threshold configured.");
235 return StatusCode::FAILURE;
236 }
237 }
238
239 // Loop over digits:
240 for (const LArDigit* digit : *inputContainer) {
241
242 size_t firstSample = m_firstSample;
243
244 const HWIdentifier id = digit->hardwareID();
245
246 const bool connected = cabling->isOnlineConnected(id);
247
248 const std::vector<short>& samples = digit->samples();
249 const int gain = digit->gain();
250 const float p = peds->pedestal(id, gain);
251
252 // The following autos will resolve either into vectors or vector-proxies
253 const auto& ofca = ofcs->OFC_a(id, gain);
254 const auto& adc2mev = adc2MeVs->ADC2MEV(id, gain);
255 const size_t nOFC = ofca.size();
256
257 // Sanity check on input conditions data:
258 // ensure that the size of the samples vector is compatible with ofc_a size
259 // when preceeding samples are saved
260 const size_t nSamples = samples.size() - firstSample;
261 if (nSamples < nOFC) {
262 ATH_MSG_ERROR("effective sample size: "
263 << nSamples << ", must be >= OFC_a size: " << ofca.size());
264 return StatusCode::FAILURE;
265 }
266
268 if (!connected)
269 continue; // No conditions for disconencted channel, who cares?
270 ATH_MSG_ERROR("No valid pedestal for connected channel "
271 << m_onlineId->channel_name(id) << " gain " << gain);
272 return StatusCode::FAILURE;
273 }
274
275 if (ATH_UNLIKELY(adc2mev.size() < 2)) {
276 if (!connected)
277 continue; // No conditions for disconencted channel, who cares?
278 ATH_MSG_ERROR("No valid ADC2MeV for connected channel "
279 << m_onlineId->channel_name(id) << " gain " << gain);
280 return StatusCode::FAILURE;
281 }
282
283 // Apply OFFC to get amplitude
284 // Evaluate sums in double-precision to get consistent results
285 // across platforms.
286
287 bool saturated = false;
288 // Check saturation AND discount pedestal
289 std::vector<double> samp_no_ped(nOFC, 0.0);
290 for (size_t i = 0; i < nOFC; ++i) {
291 if (samples[i + firstSample] == 4096 || samples[i + firstSample] == 0)
292 saturated = true;
293 samp_no_ped[i] = samples[i + firstSample] - p;
294 }
295
296 uint16_t iquaShort = 0;
297 float tau = 0;
298
299 uint16_t prov = 0xa5; // Means all constants from DB
300 if (saturated)
301 prov |= 0x0400;
302
303 float ecut(0.);
304 if (m_useDBFortQ) {
305 if (run2DSPThresh) {
306 ecut = run2DSPThresh->tQThr(id);
307 } else if (run1DSPThresh) {
308 ecut = run1DSPThresh->tQThr(id);
309 } else {
310 ATH_MSG_ERROR("DSP threshold problem");
311 return StatusCode::FAILURE;
312 }
313 } else {
314 ecut = m_eCutFortQ;
315 }
316
317 const auto& fullShape = shapes->Shape(id, gain);
318
319 double A = computeOFFC(samples, firstSample, ofca, fullShape, p);
320
321 const float E = adc2mev[0] + A * adc2mev[1];
322
323 const float E1 = m_absECutFortQ.value() ? std::fabs(E) : E;
324
325 if (E1 > ecut) {
326 ATH_MSG_VERBOSE("Channel " << m_onlineId->channel_name(id) << " gain "
327 << gain
328 << " above threshold for tQ computation");
329 prov |= 0x2000; // fill bit in provenance that time+quality information
330 // are available
331
332 // Get time by applying OFC-b coefficients:
333 const auto& ofcb = ofcs->OFC_b(id, gain);
334 double At = 0;
335 for (size_t i = 0; i < nOFC; ++i) {
336 At += static_cast<double>(samp_no_ped[i]) * ofcb[i];
337 }
338
339 // At = m_offcTool->compute(samples, firstSample, ofcb, fullShape,
340 // ecutadc, p);
341 // Divide A*t/A to get time
342 tau = (std::fabs(A) > 0.1) ? At / A : 0.0;
343
344 // Get Q-factor
345 // fixing HEC to move +1 in case of 4 samples and firstSample 0 (copied
346 // from old LArRawChannelBuilder)
347 const size_t nSamples = samples.size();
348 if (fullShape.size() > nSamples && nSamples == 4 && m_firstSample == 0) {
349 if (m_onlineId->isHECchannel(id)) {
350 firstSample = 1;
351 }
352 }
353
354 if (ATH_UNLIKELY(fullShape.size() < nOFC + firstSample)) {
355 if (!connected)
356 continue; // No conditions for disconnected channel, who cares?
357 ATH_MSG_ERROR("No valid shape for channel "
358 << m_onlineId->channel_name(id) << " gain " << gain);
359 ATH_MSG_ERROR("Got size " << fullShape.size() << ", expected at least "
360 << nSamples + firstSample);
361 return StatusCode::FAILURE;
362 }
363
364 std::span<const float> shape(fullShape.data() + firstSample, fullShape.size() - firstSample);
365
366 double q = 0;
367 if (m_useShapeDer) {
368 const auto& fullshapeDer = shapes->ShapeDer(id, gain);
369 if (ATH_UNLIKELY(fullshapeDer.size() < nOFC + firstSample)) {
370 ATH_MSG_ERROR("No valid shape derivative for channel "
371 << m_onlineId->channel_name(id) << " gain " << gain);
372 ATH_MSG_ERROR("Got size " << fullshapeDer.size()
373 << ", expected at least "
374 << nOFC + firstSample);
375 return StatusCode::FAILURE;
376 }
377
378 std::span<const float> shapeDer(fullshapeDer.data() + firstSample, fullshapeDer.size() - firstSample);
379
380
381 for (size_t i = 0; i < nOFC; ++i) {
382 q += std::pow((A * (shape[i] - tau * shapeDer[i]) - (samp_no_ped[i])),
383 2);
384 }
385 } // end if useShapeDer
386 else {
387 // Q-factor w/o shape derivative
388 for (size_t i = 0; i < nOFC; ++i) {
389 q += std::pow((A * shape[i] - (samp_no_ped[i])), 2);
390 }
391 }
392
393 int iqua = std::min(static_cast<int>(q), 0xFFFF);
394
395 iquaShort = static_cast<uint16_t>(iqua & 0xFFFF);
396
397 tau -= ofcs->timeOffset(id, gain);
398 tau *= (Gaudi::Units::nanosecond /
399 Gaudi::Units::picosecond); // Convert time to ps
400 } // end if above cut
401
402 outputContainer->emplace_back(id, static_cast<int>(std::floor(E + 0.5)),
403 static_cast<int>(std::floor(tau + 0.5)),
404 iquaShort, prov, (CaloGain::CaloGain)gain);
405 }
406
408 ATH_CHECK(outputHandle.record(std::move(outputContainer)));
409
410 return StatusCode::SUCCESS;
411}
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_ERROR(x)
#define ATH_MSG_INFO(x)
#define ATH_MSG_VERBOSE(x)
#define ATH_UNLIKELY(x)
Handle class for reading from StoreGate.
const ServiceHandle< StoreGateSvc > & detStore() const
virtual OFCRef_t OFC_b(const HWIdentifier &id, int gain, int tbin=0) const =0
virtual OFCRef_t OFC_a(const HWIdentifier &id, int gain, int tbin=0) const =0
access to OFCs by online ID, gain, and tbin (!=0 for testbeam)
LArVectorProxy OFCRef_t
This class defines the interface for accessing Optimal Filtering coefficients for each channel provid...
Definition ILArOFC.h:26
virtual float timeOffset(const HWIdentifier &CellID, int gain) const =0
virtual float pedestal(const HWIdentifier &id, int gain) const =0
LArVectorProxy ShapeRef_t
This class defines the interface for accessing Shape (Nsample variable, Dt = 25 ns fixed) @stereotype...
Definition ILArShape.h:26
virtual ShapeRef_t Shape(const HWIdentifier &id, int gain, int tbin=0, int mode=0) const =0
virtual ShapeRef_t ShapeDer(const HWIdentifier &id, int gain, int tbin=0, int mode=0) const =0
const LArVectorProxy ADC2MEV(const HWIdentifier &id, int gain) const
Definition LArADC2MeV.h:32
float tQThr(const HWIdentifier chid) const
Container class for LArDigit.
Liquid Argon digit base class.
Definition LArDigit.h:25
SG::WriteHandleKey< LArRawChannelContainer > m_rawChannelKey
Gaudi::Property< bool > m_useDBFortQ
double computeOFFC(const std::vector< short > &samples, int firstSample, const ILArOFC::OFCRef_t &ofc, const ILArShape::ShapeRef_t &shape, double pedestal) const
SG::ReadCondHandleKey< ILArShape > m_shapeKey
Gaudi::Property< double > m_belowThreshold
The OFFC algorithm extends the standard optimal filtering by identifying in-time pulses and subtracti...
Gaudi::Property< bool > m_absECutFortQ
Gaudi::Property< int > m_nPulse
Maximum number of overlapping pulses that can be tracked simultaneously.
SG::ReadCondHandleKey< LArADC2MeV > m_adc2MeVKey
Gaudi::Property< int > m_firstSample
SG::ReadCondHandleKey< LArOnOffIdMapping > m_cablingKey
std::vector< double > convolvePulse(const ILArShape::ShapeRef_t &shape, const ILArOFC::OFCRef_t &ofc) const
SG::ReadCondHandleKey< ILArPedestal > m_pedestalKey
Gaudi::Property< float > m_eCutFortQ
SG::ReadHandleKey< LArDigitContainer > m_digitKey
Gaudi::Property< double > m_filterThreshold
Minimum filtered amplitude required to consider a sample as a pulse peak.
SG::ReadCondHandleKey< ILArOFC > m_ofcKey
Gaudi::Property< double > m_Q3cut
Quality cut used for pulse acceptance.
Gaudi::Property< bool > m_useShapeDer
StatusCode execute(const EventContext &ctx) const override
SG::ReadCondHandleKey< AthenaAttributeList > m_run2DSPThresholdsKey
SG::ReadCondHandleKey< LArDSPThresholdsComplete > m_run1DSPThresholdsKey
Gaudi::Property< int > m_belowTillReset
Number of consecutive below-threshold samples required before the forward-subtraction cache is reset.
const_pointer_type cptr()
StatusCode record(std::unique_ptr< T > data)
Record a const object to the store.
const T * get(const ReadCondHandleKey< T > &key, const EventContext &ctx)
Convenience function to retrieve an object given a ReadCondHandleKey.
void rotate(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > mid, typename DataModel_detail::iterator< DVL > end)
Specialization of rotate for DataVector/List.
hold the test vectors and ease the comparison