Project
Loading...
Searching...
No Matches
KrClusterFinder.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
15
17#include "Math/IntegratorOptions.h"
18#include <fairlogger/Logger.h>
19
20#include <limits>
21
22using namespace o2::trd;
23using namespace o2::trd::constants;
24
26{
27 // provides chi2 estimate comparing y[] to par[0] * TMath::Landau(x[], par[1], par[2])
28 // par[0] : amplitude
29 // par[1] : location parameter (approximately most probable value)
30 // par[2] : sigma
31 double retVal = 0;
32 for (int i = xLowerBound; i <= xUpperBound; ++i) {
33 if (fabs(y[i]) < 1e-3f) {
34 // exclude bins with zero errors like in TH1::Fit
35 // the standard bin error is the square root of its content
36 continue;
37 }
38 retVal += TMath::Power(y[i] - par[0] * TMath::Landau(x[i], par[1], par[2]), 2) / y[i];
39 }
40 return retVal;
41}
42
43double KrClusterFinder::getRms(const std::vector<uint64_t>& adcIndices, int itTrunc, double nRmsTrunc, int minAdc, double& rmsTime, uint32_t& sumAdc) const
44{
45 double rmsAdc = 1e7;
46 double meanAdc = -10.;
47 rmsTime = 1e7;
48 double meanTime = -10.f;
49
50 for (int it = 0; it < itTrunc; ++it) {
51 // iterations for truncated mean
52 sumAdc = 0;
53 uint32_t sumAdc2 = 0;
54 uint32_t sumTime = 0;
55 uint32_t sumTime2 = 0;
56 uint32_t sumWeights = 0;
57 for (const auto& adcIdx : adcIndices) {
58 int iTb = adcIdx % TIMEBINS;
59 int iDigit = adcIdx / TIMEBINS;
60 int adc = mDigits[iDigit].getADC()[iTb] - mBaselineAdc;
61 if (adc < minAdc) {
62 continue;
63 }
64 if (fabs(adc - meanAdc) < nRmsTrunc * rmsAdc) {
65 sumAdc += adc;
66 sumAdc2 += adc * adc;
67 sumWeights += 1;
68 sumTime += iTb;
69 sumTime2 += iTb * iTb;
70 }
71 }
72 if (sumWeights > 0) {
73 auto sumWeightsInv = 1. / sumWeights;
74 meanAdc = sumAdc * sumWeightsInv;
75 rmsAdc = TMath::Sqrt(sumWeightsInv * (sumAdc2 - 2. * meanAdc * sumAdc + sumWeights * meanAdc * meanAdc));
76 meanTime = sumTime * sumWeightsInv;
77 rmsTime = TMath::Sqrt(sumWeightsInv * (sumTime2 - 2. * meanTime * sumTime + sumWeights * meanTime * meanTime));
78 }
79 }
80 return rmsAdc;
81}
82
84{
85 mFitter.SetFCN<LandauChi2Functor>(3, mLandauChi2Functor, mInitialFitParams.data());
86 mFitter.Config().ParSettings(0).SetLimits(0., 1.e5);
87 mFitter.Config().ParSettings(1).SetLimits(0., 30.);
88 mFitter.Config().ParSettings(2).SetLimits(1.e-3, 20.);
89 mFuncLandauFit = std::make_unique<TF1>(
90 "fLandauFit", [&](double* x, double* par) { return par[0] * TMath::Landau(x[0], par[1], par[2]); }, 0., static_cast<double>(TIMEBINS), 3);
91}
92
94{
95 mKrClusters.clear();
96 mTrigRecs.clear();
97}
98
99void KrClusterFinder::setInput(const gsl::span<const Digit>& digitsIn, const gsl::span<const TriggerRecord>& trigRecIn)
100{
101 mDigits = digitsIn;
102 mTriggerRecords = trigRecIn;
103}
104
106{
107 if (mDigits.size() == 0 || mTriggerRecords.size() == 0) {
108 return;
109 }
110 int nClsTotal = 0;
111 int nClsDropped = 0;
112 int nClsInvalidFit = 0;
113 /*
114 The input digits and trigger records are provided on a per time frame basis.
115 The cluster finding is performed for each trigger. In order to not copy the
116 input data we sort the digits for each trigger by their detector ID and keep
117 the sorted indices in the digitIdxArray.
118 */
119 std::vector<uint64_t> digitIdxArray(mDigits.size());
120 std::iota(digitIdxArray.begin(), digitIdxArray.end(), 0);
121 for (const auto& trig : mTriggerRecords) {
122 // sort the digits of given trigger record by detector ID
123 const auto& digits = mDigits; // this reference is only needed to be able to pass it to the lambda for sorting
124 std::stable_sort(std::begin(digitIdxArray) + trig.getFirstDigit(), std::begin(digitIdxArray) + trig.getNumberOfDigits() + trig.getFirstDigit(),
125 [&digits](uint64_t i, uint64_t j) { return digits[i].getDetector() < digits[j].getDetector(); });
126
127 /*
128 Count number of digits per detector:
129 For each detector we need to know the number of digits.
130 We fill the array idxFirstDigitInDet with the total number of digits
131 up to given detector. So we start at 0 for detector 0 and end up with
132 the total number of digits in entry 540. Thus the number of digits in
133 detector iDet can be calculated with:
134 idxFirstDigitInDet[iDet+1] - idxFirstDigitInDet[iDet]
135 Note, that for a global index one needs to add trig.getFirstDigit()
136 */
137 int currDet = 0;
138 int nextDet = 0;
139 int digitCounter = 0;
140 std::array<unsigned int, MAXCHAMBER + 1> idxFirstDigitInDet{};
141 auto idxHelperPtr = &(idxFirstDigitInDet.data()[1]);
142 idxHelperPtr[-1] = 0;
143 for (int iDigit = trig.getFirstDigit(); iDigit < trig.getFirstDigit() + trig.getNumberOfDigits(); ++iDigit) {
144 if (mDigits[digitIdxArray[iDigit]].getDetector() > currDet) {
145 nextDet = mDigits[digitIdxArray[iDigit]].getDetector();
146 for (int iDet = currDet; iDet < nextDet; ++iDet) {
147 idxHelperPtr[iDet] = digitCounter;
148 }
149 currDet = nextDet;
150 }
151 ++digitCounter;
152 }
153 for (int iDet = currDet; iDet <= MAXCHAMBER; ++iDet) {
154 idxHelperPtr[iDet] = digitCounter;
155 }
156
157 // the cluster search is done on a per detector basis
158 for (int iDet = 0; iDet < MAXCHAMBER; ++iDet) {
159 unsigned int nDigitsInDet = idxFirstDigitInDet[iDet + 1] - idxFirstDigitInDet[iDet];
160 std::vector<bool> isAdcUsed(nDigitsInDet * TIMEBINS); // keep track of the ADC values which have already been checked or added to a cluster
161 std::vector<bool> isDigitUsed(nDigitsInDet); // keep track of the digits which have already been processed
162 bool continueClusterSearch = true;
163 // the following loop searches for one cluster at a time in iDet
164 while (continueClusterSearch) {
165
166 // start by finding the max ADC value in all digits of iDet
167 uint16_t adcMax = 0;
168 int tbMax = 0;
169 int rowMax = 0;
170 int colMax = 0;
171 for (unsigned int iDigit = 0; iDigit < nDigitsInDet; ++iDigit) {
172 uint64_t digitIdx = digitIdxArray[trig.getFirstDigit() + idxFirstDigitInDet[iDet] + iDigit]; // global index for array of all digits (mDigits)
173 if (mDigits[digitIdx].isSharedDigit()) {
174 // we need to skip the shared digits which are duplicates contained in the global digits array
175 continue;
176 }
177 if (isDigitUsed[iDigit]) {
178 // if a maximum has been found for this digit then all ADCs above threshold are already flagged as used
179 continue;
180 }
181 int tbMaxADC = -1;
182 auto maxAdcInDigit = mDigits[digitIdx].getADCmax(tbMaxADC);
183 if (maxAdcInDigit > adcMax) {
184 // potentially found a maximum
185 tbMax = tbMaxADC;
186 rowMax = mDigits[digitIdx].getPadRow();
187 colMax = mDigits[digitIdx].getPadCol();
188 adcMax = maxAdcInDigit;
189 }
190 }
191 if (adcMax < mMinAdcForMax) {
192 // the maximum ADC value is below the threshold, go to next chamber
193 break;
194 }
195
196 // cluster around max ADC value
197 int lowerTb = TIMEBINS;
198 int upperTb = 0;
199 int lowerCol = NCOLUMN;
200 int upperCol = 0;
201 int lowerRow = NROWC1;
202 int upperRow = 0;
203 int nUsedADCsInCl = 0;
204 std::vector<uint64_t> constituentAdcIndices;
205 for (unsigned int iDigit = 0; iDigit < nDigitsInDet; ++iDigit) {
206 uint64_t digitIdx = digitIdxArray[trig.getFirstDigit() + idxFirstDigitInDet[iDet] + iDigit]; // global index for array of all digits (mDigits)
207 if (mDigits[digitIdx].isSharedDigit()) {
208 // we need to skip the shared digits which are duplicates contained in the global digits array
209 continue;
210 }
211 int row = mDigits[digitIdx].getPadRow();
212 if (std::abs(row - rowMax) > 1) {
213 continue;
214 }
215 int col = mDigits[digitIdx].getPadCol();
216 if (std::abs(col - colMax) > 2) {
217 continue;
218 }
219 bool addedAdc = false;
220 for (int iTb = 0; iTb < TIMEBINS; ++iTb) {
221 if (isAdcUsed[iDigit * TIMEBINS + iTb]) {
222 continue;
223 }
224 // flag this ADC value as used, regardless of whether or not it is added to the cluster
225 // (if it is below the threshold it won't be used for another cluster anyway)
226 isAdcUsed[iDigit * TIMEBINS + iTb] = true;
227 if (mDigits[digitIdx].getADC()[iTb] > mMinAdcClContrib) {
228 addedAdc = true;
229 if (iTb < lowerTb) {
230 lowerTb = iTb;
231 }
232 if (iTb > upperTb) {
233 upperTb = iTb;
234 }
235 ++nUsedADCsInCl;
236 }
237 constituentAdcIndices.push_back(digitIdx * TIMEBINS + iTb); // also add ADC values below threshold here
238 }
239 if (addedAdc) {
240 isDigitUsed[iDigit] = true;
241 if (row < lowerRow) {
242 lowerRow = row;
243 }
244 if (row > upperRow) {
245 upperRow = row;
246 }
247 if (col < lowerCol) {
248 lowerCol = col;
249 }
250 if (col > upperCol) {
251 upperCol = col;
252 }
253 }
254 }
255
256 // determine cluster size
257 int clSizeTime = upperTb - lowerTb;
258 int clSizeRow = upperRow - lowerRow;
259 int clSizeCol = upperCol - lowerCol;
260
261 if (nUsedADCsInCl > 0) {
262 // after this cluster is processed, continue looking for another one in iDet
263 continueClusterSearch = true;
264 }
265
266 // sum up ADC values (total sum, sum per time bin, total sum for ADCs above energy threshold)
267 std::array<int, TIMEBINS> sumPerTb{};
268 int sumOfAllTimeBins = 0;
269 int sumOfAllTimeBinsAboveThreshold = 0;
270 for (const auto idx : constituentAdcIndices) {
271 auto iTb = idx % TIMEBINS;
272 auto iDigit = idx / TIMEBINS;
273 sumOfAllTimeBins += mDigits[iDigit].getADC()[iTb] - mBaselineAdc;
274 sumPerTb[iTb] += mDigits[iDigit].getADC()[iTb] - mBaselineAdc;
275 if (mDigits[iDigit].getADC()[iTb] > mMinAdcClEoverT) {
276 sumOfAllTimeBinsAboveThreshold += mDigits[iDigit].getADC()[iTb] - mBaselineAdc;
277 }
278 }
279
280 uint32_t sumOfAdcTrunc;
281 double rmsTimeTrunc;
282 auto rmsAdcClusterTrunc = getRms(constituentAdcIndices, 2, 3., static_cast<uint32_t>(mMinAdcClEoverT * .95), rmsTimeTrunc, sumOfAdcTrunc);
283 (void)rmsAdcClusterTrunc; // return value not used, so silence compiler warning about unused variable
284
285 // ADC value and time bin of first maximum
286 int maxAdcA = -1;
287 int maxTbA = -1;
288 mLandauChi2Functor.x.clear();
289 mLandauChi2Functor.y.clear();
290 for (int iTb = 0; iTb < TIMEBINS; ++iTb) {
291 mLandauChi2Functor.x.push_back((float)iTb);
292 mLandauChi2Functor.y.push_back(sumPerTb[iTb]);
293 if (sumPerTb[iTb] < mMinAdcForMax) {
294 continue;
295 }
296 if (sumPerTb[iTb] > maxAdcA) {
297 maxAdcA = sumPerTb[iTb];
298 maxTbA = iTb;
299 }
300 }
301 mLandauChi2Functor.xLowerBound = maxAdcA - 1;
302 mLandauChi2Functor.xUpperBound = maxAdcA + 2;
303
304 // ADC value and time bin of second maximum (if there is one)
305 int maxAdcB = -1;
306 int maxTbB = -1;
307 for (int iTb = 2; iTb < TIMEBINS - 2; ++iTb) { // we need to check the neighbouring two bins, so ignore the outermost two bins in the search for a second maximum
308 if (std::abs(maxTbA - iTb) < 3 || sumPerTb[iTb] < mMinAdcForSecondMax) {
309 // we are too close to the first maximum (or the ADC value is not too small)
310 continue;
311 }
312 if (sumPerTb[iTb] > maxAdcB) {
313 // check neighbours
314 if (sumPerTb[iTb - 1] < sumPerTb[iTb] &&
315 sumPerTb[iTb - 2] < sumPerTb[iTb] &&
316 sumPerTb[iTb + 1] < sumPerTb[iTb] &&
317 sumPerTb[iTb + 2] < sumPerTb[iTb]) {
318 maxAdcB = sumPerTb[iTb];
319 maxTbB = iTb;
320 }
321 }
322 }
323
324 // quality checks for maxima
325 bool isBadA = false;
326 bool isBadB = false;
327 if (maxAdcA < 0 || maxTbA <= 1 || maxTbA >= (TIMEBINS - 2)) {
328 isBadA = true;
329 }
330 if (maxAdcB < 0) {
331 // second maximum must be inside the time acceptance by construction
332 isBadB = true;
333 }
334 if (!isBadA) {
335 // we have a good first maximum, let's check its shape
336 if ((sumPerTb[maxTbA - 1] / static_cast<double>(maxAdcA)) < 0.1 || (sumPerTb[maxTbA + 1] / static_cast<double>(maxAdcA)) < 0.25) {
337 isBadA = true;
338 }
339 }
340 if (!isBadB) {
341 // we have a good second maximum, let's check its shape
342 if ((sumPerTb[maxTbB - 1] / static_cast<double>(maxAdcB)) < 0.1 || (sumPerTb[maxTbB + 1] / static_cast<double>(maxAdcB)) < 0.25) {
343 isBadB = true;
344 }
345 }
346 if (!isBadA && !isBadB) {
347 // we have two maxima, check order and size
348 if (maxTbA > maxTbB || maxAdcA <= maxAdcB) {
349 isBadA = true;
350 isBadB = true;
351 }
352 }
353
354 if (clSizeCol > 0 && clSizeTime > 3 && clSizeTime < TIMEBINS) {
355 mFitter.Config().ParSettings(0).SetValue(maxAdcA);
356 mFitter.Config().ParSettings(1).SetValue(maxTbA);
357 mFitter.Config().ParSettings(2).SetValue(.5);
358 bool fitOK = mFitter.FitFCN();
359 if (!fitOK) {
360 ++nClsInvalidFit;
361 }
362 mFuncLandauFit->SetParameters(mFitResult->GetParams()[0], mFitResult->GetParams()[1], mFitResult->GetParams()[2]);
363 double integralLandauFit = fitOK ? mFuncLandauFit->Integral(0., static_cast<double>(TIMEBINS), 1.e-9) : 0.;
364
365 double rmsTime;
366 uint32_t rmsSumAdc;
367 auto rmsAdc = getRms(constituentAdcIndices, 1, 2.5, mMinAdcClContrib, rmsTime, rmsSumAdc);
368
369 double sumAdcA = 0.;
370 double sumAdcB = 0.;
371 if (!isBadA && !isBadB) {
372 // use the Landau fit to the first peak to extrapolate the energy of larger time bins (close time bins are evaluated from the array directly)
373 // for the second peak the Landau fit is used to subtract the energy from the first peak
374 for (int iTb = 0; iTb < TIMEBINS; ++iTb) {
375 if (iTb < (maxTbB - 2)) {
376 sumAdcA += mLandauChi2Functor.y[iTb];
377 } else {
378 sumAdcA += mFuncLandauFit->Eval(mLandauChi2Functor.x[iTb]);
379 sumAdcB += mLandauChi2Functor.y[iTb] - mFuncLandauFit->Eval(mLandauChi2Functor.x[iTb]);
380 }
381 }
382 }
383 // create Kr cluster
384 if (sumOfAllTimeBins <= std::numeric_limits<uint16_t>::max() &&
385 sumOfAllTimeBinsAboveThreshold <= std::numeric_limits<uint16_t>::max() &&
386 integralLandauFit <= std::numeric_limits<uint16_t>::max() &&
387 sumAdcA <= std::numeric_limits<uint16_t>::max() &&
388 sumAdcB <= std::numeric_limits<uint16_t>::max() &&
389 rmsAdc <= std::numeric_limits<uint16_t>::max() &&
390 rmsTime <= std::numeric_limits<uint8_t>::max() &&
391 nUsedADCsInCl <= std::numeric_limits<uint8_t>::max() &&
392 sumOfAdcTrunc <= std::numeric_limits<uint16_t>::max()) {
393 if (maxTbA < 0) {
394 maxTbA = tbMax;
395 }
396 if (maxTbB < 0) {
397 maxTbB = 2 * TIMEBINS + 5; // larger than any maximum time bin we can have
398 }
399 KrCluster cluster;
400 cluster.setGlobalPadID(iDet, rowMax, colMax);
401 cluster.setAdcData(sumOfAllTimeBins, (int)rmsAdc, (int)sumAdcA, (int)sumAdcB, sumOfAllTimeBinsAboveThreshold, (int)integralLandauFit, sumOfAdcTrunc);
402 cluster.setTimeData(maxTbA, maxTbB, (int)rmsTime);
403 cluster.setClusterSizeData(clSizeRow, clSizeCol, clSizeTime, nUsedADCsInCl);
404 mKrClusters.push_back(cluster);
405 ++nClsTotal;
406 } else {
407 //mFitResult->Print(std::cout);
408 ++nClsDropped;
409 LOG(debug) << "Kr cluster cannot be added because values are out of range";
410 LOGF(debug, "sumOfAllTimeBins(%i), sumAdcA(%f), sumAdcB(%f), clSizeRow(%i), clSizeCol(%i), clSizeTime(%i), maxTbA(%i), maxTbB(%i)", sumOfAllTimeBins, sumAdcA, sumAdcB, clSizeRow, clSizeCol, clSizeTime, maxTbA, maxTbB);
411 LOGF(debug, "rmsAdc(%f), rmsTime(%f), nUsedADCsInCl(%i), sumOfAllTimeBinsAboveThreshold(%i), integralLandauFit(%f), sumOfAdcTrunc(%u)", rmsAdc, rmsTime, nUsedADCsInCl, sumOfAllTimeBinsAboveThreshold, integralLandauFit, sumOfAdcTrunc);
412 }
413 }
414 } // end cluster search
415 } // end detector loop
416 } // end trigger loop
417
418 // we don't need the exact BC time, just use first interaction record within this TF
419 mTrigRecs.emplace_back(mTriggerRecords[0].getBCData(), nClsTotal);
420 LOGF(info, "Number of Kr clusters with a) invalid fit (%i) b) out-of-range values which were dropped (%i)", nClsInvalidFit, nClsDropped);
421}
int32_t i
int32_t retVal
The TRD Krypton cluster finder from digits.
uint32_t j
Definition RawData.h:0
uint32_t col
Definition RawData.h:4
std::ostringstream debug
void setInput(const gsl::span< const Digit > &digitsIn, const gsl::span< const TriggerRecord > &trigRecIn)
Provide digits and trigger records as input.
double getRms(const std::vector< uint64_t > &adcIndices, int itTrunc, double nRmsTrunc, int minAdc, double &rmsTime, uint32_t &sumAdc) const
Calculate some statistics for the given cluster constituent ADC values.
void init()
Initialization.
void setTimeData(int timeMaxA, int timeMaxB, int timeRms)
Definition KrCluster.cxx:37
void setGlobalPadID(int det, int row, int col)
Definition KrCluster.cxx:19
void setClusterSizeData(int rowSize, int colSize, int timeSize, int nAdcs)
Definition KrCluster.cxx:44
void setAdcData(int adcSum, int adcRms, int adcMaxA, int adcMaxB, int adcEoT, int adcIntegral, int adcSumTrunc)
Definition KrCluster.cxx:26
GLint GLenum GLint x
Definition glcorearb.h:403
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
constexpr int TIMEBINS
the number of time bins
Definition Constants.h:74
constexpr int NCOLUMN
the number of pad columns for each chamber
Definition Constants.h:41
constexpr int MAXCHAMBER
the maximum number of installed chambers
Definition Constants.h:30
constexpr int NROWC1
the number of pad rows for chambers of type C1 (installed in stacks 0, 1, 3 and 4)
Definition Constants.h:43
std::vector< float > y
ADC sum per time bin.
std::vector< float > x
the number of the time bin
double operator()(const double *par) const
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
std::vector< Digit > digits
std::vector< int > row
ArrayADC adc