Project
Loading...
Searching...
No Matches
ClusterFinderOriginal.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
19
21
22#include <algorithm>
23#include <cstring>
24#include <iterator>
25#include <limits>
26#include <numeric>
27#include <set>
28#include <stdexcept>
29#include <string>
30
31#include <TH2I.h>
32#include <TAxis.h>
33#include <TMath.h>
34#include <TRandom.h>
35
36#include <fairlogger/Logger.h>
37
38#include "MCHBase/Error.h"
42#include "PadOriginal.h"
43#include "ClusterOriginal.h"
44
45namespace o2::mch
46{
47
48//_________________________________________________________________________________________________
50 : mMathiesons(std::make_unique<MathiesonOriginal[]>(2)),
51 mPreCluster(std::make_unique<ClusterOriginal>())
52{
54}
55
56//_________________________________________________________________________________________________
58
59//_________________________________________________________________________________________________
60void ClusterFinderOriginal::init(bool run2Config)
61{
63
64 mPreClusterFinder.init();
65
66 if (run2Config) {
67
68 // function to reinterpret digit ADC as calibrated charge
69 mADCToCharge = [](uint32_t adc) {
70 float charge(0.);
71 std::memcpy(&charge, &adc, sizeof(adc));
72 return static_cast<double>(charge);
73 };
74
75 // minimum charge of pad, pixel and cluster
76 mLowestPadCharge = 4.f * 0.22875f;
77 mLowestPixelCharge = mLowestPadCharge / 12.;
78 mLowestClusterCharge = 2. * mLowestPadCharge;
79
80 // Mathieson function for station 1
81 mMathiesons[0].setPitch(0.21);
82 mMathiesons[0].setSqrtKx3AndDeriveKx2Kx4(0.7000);
83 mMathiesons[0].setSqrtKy3AndDeriveKy2Ky4(0.7550);
84
85 // Mathieson function for other stations
86 mMathiesons[1].setPitch(0.25);
87 mMathiesons[1].setSqrtKx3AndDeriveKx2Kx4(0.7131);
88 mMathiesons[1].setSqrtKy3AndDeriveKy2Ky4(0.7642);
89
90 } else {
91
92 // minimum charge of pad, pixel and cluster
93 mLowestPadCharge = ClusterizerParam::Instance().lowestPadCharge;
94 mLowestPixelCharge = mLowestPadCharge / 12.;
95 mLowestClusterCharge = 2. * mLowestPadCharge;
96
97 // Mathieson function for station 1
98 mMathiesons[0].setPitch(ResponseParam::Instance().pitchSt1);
99 mMathiesons[0].setSqrtKx3AndDeriveKx2Kx4(ResponseParam::Instance().mathiesonSqrtKx3St1);
100 mMathiesons[0].setSqrtKy3AndDeriveKy2Ky4(ResponseParam::Instance().mathiesonSqrtKy3St1);
101
102 // Mathieson function for other stations
103 mMathiesons[1].setPitch(ResponseParam::Instance().pitchSt2345);
104 mMathiesons[1].setSqrtKx3AndDeriveKx2Kx4(ResponseParam::Instance().mathiesonSqrtKx3St2345);
105 mMathiesons[1].setSqrtKy3AndDeriveKy2Ky4(ResponseParam::Instance().mathiesonSqrtKy3St2345);
106 }
107}
108
109//_________________________________________________________________________________________________
111{
113 mPreClusterFinder.deinit();
114}
115
116//_________________________________________________________________________________________________
118{
120 mClusters.clear();
121 mUsedDigits.clear();
122}
123
124//_________________________________________________________________________________________________
125void ClusterFinderOriginal::findClusters(gsl::span<const Digit> digits)
126{
129
130 // skip preclusters with only 1 digit
131 if (digits.size() < 2) {
132 return;
133 }
134
135 // set the Mathieson function to be used
136 mMathieson = (digits[0].getDetID() < 300) ? &mMathiesons[0] : &mMathiesons[1];
137
138 // reset the current precluster being processed
139 resetPreCluster(digits);
140
141 // try to simplify the precluster by removing pads if possible (sent back to preclustering)
142 std::vector<int> removedDigits{};
143 simplifyPreCluster(removedDigits);
144
145 if (mPreCluster->multiplicity() > 1) {
146
147 // extract clusters from the precluster
148 int iNewCluster = mClusters.size();
149 processPreCluster();
150
151 if (mClusters.size() > iNewCluster) {
152
153 // copy the digits associated to the new clusters (if any) in the list of used digits
154 int iFirstNewDigit = mUsedDigits.size();
155 for (const auto& pad : *mPreCluster) {
156 if (pad.isReal()) {
157 mUsedDigits.emplace_back(digits[pad.digitIndex()]);
158 }
159 }
160 int nNewDigits = mUsedDigits.size() - iFirstNewDigit;
161
162 // give the new clusters a unique ID, make them point to these digits then set their resolution
163 for (; iNewCluster < mClusters.size(); ++iNewCluster) {
164 mClusters[iNewCluster].uid = Cluster::buildUniqueId(digits[0].getDetID() / 100 - 1, digits[0].getDetID(), iNewCluster);
165 mClusters[iNewCluster].firstDigit = iFirstNewDigit;
166 mClusters[iNewCluster].nDigits = nNewDigits;
167 setClusterResolution(mClusters[iNewCluster]);
168 }
169 }
170 }
171
172 if (!removedDigits.empty()) {
173
174 // load the released digits (if any) in the preclusterizer
175 mPreClusterFinder.reset();
176 for (auto iDigit : removedDigits) {
177 mPreClusterFinder.loadDigit(digits[iDigit]);
178 }
179
180 // preclusterize and get the new preclusters and associated digits
181 std::vector<PreCluster> preClusters{};
182 std::vector<Digit> usedDigits{};
183 int nPreClusters = mPreClusterFinder.run();
184 preClusters.reserve(nPreClusters);
185 usedDigits.reserve(removedDigits.size());
186 mPreClusterFinder.getPreClusters(preClusters, usedDigits);
187
188 // clusterize every new preclusters
189 for (const auto& preCluster : preClusters) {
190 findClusters({&usedDigits[preCluster.firstDigit], preCluster.nDigits});
191 }
192 }
193}
194
195//_________________________________________________________________________________________________
196void ClusterFinderOriginal::resetPreCluster(gsl::span<const Digit>& digits)
197{
199
200 mPreCluster->clear();
201
202 mSegmentation = &mapping::segmentation(digits[0].getDetID());
203
204 for (int iDigit = 0; iDigit < digits.size(); ++iDigit) {
205
206 const auto& digit = digits[iDigit];
207 int padID = digit.getPadID();
208
209 double x = mSegmentation->padPositionX(padID);
210 double y = mSegmentation->padPositionY(padID);
211 double dx = mSegmentation->padSizeX(padID) / 2.;
212 double dy = mSegmentation->padSizeY(padID) / 2.;
213 double charge = mADCToCharge(digit.getADC());
214 bool isSaturated = digit.isSaturated();
215 int plane = mSegmentation->isBendingPad(padID) ? 0 : 1;
216
217 if (charge <= 0.) {
218 throw std::runtime_error("The precluster contains a digit with charge <= 0");
219 }
220
221 mPreCluster->addPad(x, y, dx, dy, charge, isSaturated, plane, iDigit, PadOriginal::kZero);
222 }
223}
224
225//_________________________________________________________________________________________________
226void ClusterFinderOriginal::simplifyPreCluster(std::vector<int>& removedDigits)
227{
232
233 // discard small clusters (leftovers from splitting or noise)
234 if (mPreCluster->multiplicity() < 3 && mPreCluster->charge() < mLowestClusterCharge) {
235 mPreCluster->clear();
236 return;
237 }
238
239 // the following is only for two-cathode preclusters
240 if (mPreCluster->multiplicity(0) == 0 || mPreCluster->multiplicity(1) == 0) {
241 return;
242 }
243
244 // tag every pad that overlap with another on the other plane
245 std::vector<bool> overlap(mPreCluster->multiplicity(), false);
246 for (int i = 0; i < mPreCluster->multiplicity(); ++i) {
247 const auto& padi = mPreCluster->pad(i);
248 for (int j = i + 1; j < mPreCluster->multiplicity(); ++j) {
249 const auto& padj = mPreCluster->pad(j);
250 if (padi.plane() == padj.plane() || (overlap[i] && overlap[j])) {
251 continue;
252 }
253 if (areOverlapping(padi, padj, SDistancePrecision)) {
254 overlap[i] = true;
255 overlap[j] = true;
256 }
257 }
258 }
259
260 // count the number of pads that do not overlap
261 int nNotOverlapping(0);
262 for (int i = 0; i < mPreCluster->multiplicity(); ++i) {
263 if (!overlap[i]) {
264 ++nNotOverlapping;
265 }
266 }
267
268 // discard pads with no overlap (unless it is at the edge or there is only one with low charge)
269 // loop over pads in decreasing index order to do not shift the indices while removing
270 if (nNotOverlapping > 0) {
271 const mapping::CathodeSegmentation* cathSeg[2] = {&mSegmentation->bending(), &mSegmentation->nonBending()};
272 for (int i = mPreCluster->multiplicity() - 1; i >= 0; --i) {
273 if (overlap[i]) {
274 continue;
275 }
276 const auto& pad = mPreCluster->pad(i);
277 if (nNotOverlapping == 1 && pad.charge() < mLowestPadCharge) {
278 break; // there is only one
279 }
280 int cathPadIdx = cathSeg[1 - pad.plane()]->findPadByPosition(pad.x(), pad.y());
281 if (!cathSeg[1 - pad.plane()]->isValid(cathPadIdx)) {
282 continue;
283 }
284 removedDigits.push_back(pad.digitIndex());
285 mPreCluster->removePad(i);
286 }
287 }
288
289 // now adresses the case of large charge asymmetry between the two planes
290 if (!mPreCluster->isSaturated() && mPreCluster->chargeAsymmetry() > 0.5) {
291
292 // get the pads with minimum and maximum charge on the plane with maximum integrated charge
293 int plane = mPreCluster->maxChargePlane();
294 double chargeMin(std::numeric_limits<double>::max()), chargeMax(-1.);
295 int iPadMin(-1), iPadMax(-1);
296 for (int i = 0; i < mPreCluster->multiplicity(); ++i) {
297 const auto& pad = mPreCluster->pad(i);
298 if (pad.plane() == plane) {
299 if (pad.charge() < chargeMin) {
300 chargeMin = pad.charge();
301 iPadMin = i;
302 }
303 if (pad.charge() > chargeMax) {
304 chargeMax = pad.charge();
305 iPadMax = i;
306 }
307 }
308 }
309 if (iPadMin < 0 || iPadMax < 0) {
310 throw std::runtime_error("This plane should contain at least 1 pad!?");
311 }
312
313 // distance of the pad with minimum charge to the pad with maximum charge
314 const auto& padMin = mPreCluster->pad(iPadMin);
315 const auto& padMax = mPreCluster->pad(iPadMax);
316 double dxOfMin = (padMin.x() - padMax.x()) / padMax.dx() / 2.;
317 double dyOfMin = (padMin.y() - padMax.y()) / padMax.dy() / 2.;
318 double distOfMin = TMath::Sqrt(dxOfMin * dxOfMin + dyOfMin * dyOfMin);
319
320 // arrange pads of this plane according to their distance to the pad with maximum charge normalized to its size
321 double precision = SDistancePrecision / 2. * TMath ::Sqrt((1. / padMax.dx() / padMax.dx() + 1. / padMax.dy() / padMax.dy()) / 2.);
322 auto cmp = [precision](double dist1, double dist2) { return dist1 < dist2 - precision; };
323 std::multimap<double, int, decltype(cmp)> distIndices(cmp);
324 for (int i = 0; i < mPreCluster->multiplicity(); ++i) {
325 if (i == iPadMax) {
326 distIndices.emplace(0., i);
327 } else {
328 const auto& pad = mPreCluster->pad(i);
329 if (pad.plane() == plane) {
330 double dx = (pad.x() - padMax.x()) / padMax.dx() / 2.;
331 double dy = (pad.y() - padMax.y()) / padMax.dy() / 2.;
332 distIndices.emplace(TMath::Sqrt(dx * dx + dy * dy), i);
333 }
334 }
335 }
336
337 // try to extract from this plane the cluster centered around the pad with maximum charge
338 double previousDist(std::numeric_limits<float>::max());
339 double previousChargeMax(-1.);
340 std::set<int, std::greater<>> padsToRemove{};
341 for (const auto& distIndex : distIndices) {
342 const auto& pad = mPreCluster->pad(distIndex.second);
343
344 // try not to overstep the pad with minimum charge
345 if (distIndex.first > distOfMin + precision) {
346 double ddx = (pad.x() - padMax.x()) / padMax.dx() / 2. * dxOfMin;
347 double ddy = (pad.y() - padMax.y()) / padMax.dy() / 2. * dyOfMin;
348 if ((ddx > -precision && ddy > -precision) ||
349 (TMath::Abs(ddx) > TMath::Abs(ddy) + precision && ddx > -precision) ||
350 (TMath::Abs(ddy) > TMath::Abs(ddx) + precision && ddy > -precision)) {
351 continue;
352 }
353 }
354
355 // stop if their is a gap between this pad and the last one tagged for removal
356 if (distIndex.first > previousDist + 1. + precision) {
357 break;
358 }
359
360 // update the maximum charge if we reach another ring
361 if (TMath::Abs(distIndex.first - previousDist) >= precision) {
362 previousChargeMax = chargeMax;
363 }
364
365 // tag all pads at this distance to be removed if the maximum charge decreases
366 if (pad.charge() <= previousChargeMax) {
367 if (TMath::Abs(distIndex.first - previousDist) < precision) {
368 chargeMax = TMath::Max(pad.charge(), chargeMax);
369 } else {
370 chargeMax = pad.charge();
371 }
372 previousDist = distIndex.first;
373 padsToRemove.insert(distIndex.second);
374 }
375 }
376
377 // remove the tagged pads (in decreasing index order to do not invalidate them while removing)
378 for (auto iPad : padsToRemove) {
379 removedDigits.push_back(mPreCluster->pad(iPad).digitIndex());
380 mPreCluster->removePad(iPad);
381 }
382 }
383}
384
385//_________________________________________________________________________________________________
386void ClusterFinderOriginal::processPreCluster()
387{
389
390 buildPixArray();
391 if (mPixels.empty()) {
392 return;
393 }
394
395 // switch between different clustering methods depending on the complexity of the precluster
396 std::pair<int, int> nPadsXY = mPreCluster->sizeInPads(PadOriginal::kZero);
397 if (nPadsXY.first < 4 && nPadsXY.second < 4) {
398
399 // simple precluster
400 processSimple();
401
402 } else {
403
404 // find the local maxima in the pixel array
405 std::unique_ptr<TH2D> histAnode(nullptr);
406 std::multimap<double, std::pair<int, int>, std::greater<>> localMaxima{};
407 findLocalMaxima(histAnode, localMaxima);
408 if (localMaxima.empty()) {
409 return;
410 }
411
412 if (localMaxima.size() == 1 || mPreCluster->multiplicity() <= 50) {
413
414 // precluster of reasonable size --> treat it in one piece
415 process();
416
417 } else {
418
419 // too large precluster --> split it and treat every pieces separately
420 for (const auto& localMaximum : localMaxima) {
421
422 // select the part of the precluster that is around the local maximum
423 restrictPreCluster(*histAnode, localMaximum.second.first, localMaximum.second.second);
424
425 // treat it
426 process();
427 }
428 }
429 }
430}
431
432//_________________________________________________________________________________________________
433void ClusterFinderOriginal::buildPixArray()
434{
436
437 mPixels.clear();
438
439 // pixel half size is the minimum pad half size on both plane
440 std::pair<double, double> dim = mPreCluster->minPadDimensions(-1, false);
441 double width[2] = {dim.first, dim.second};
442
443 // to make sure the grid is aligned with the smallest pad in both direction
444 double xy0[2] = {0., 0.};
445 bool found[2] = {false, false};
446 for (const auto& pad : *mPreCluster) {
447 for (int ixy = 0; ixy < 2; ++ixy) {
448 if (!found[ixy] && TMath::Abs(pad.dxy(ixy) - width[ixy]) < SDistancePrecision) {
449 xy0[ixy] = pad.xy(ixy);
450 found[ixy] = true;
451 }
452 }
453 if (found[0] && found[1]) {
454 break;
455 }
456 }
457
458 // to deal with mono-cathod clusters
459 int plane0 = 0, plane1 = 1;
460 if (mPreCluster->multiplicity(0) == 0) {
461 plane0 = 1;
462 } else if (mPreCluster->multiplicity(1) == 0) {
463 plane1 = 0;
464 }
465
466 // grid size is the intersect of the cluster areas on both planes
467 double area[2][2] = {0.};
468 mPreCluster->area(plane0, area);
469 if (plane1 != plane0) {
470 double area2[2][2] = {0.};
471 mPreCluster->area(plane1, area2);
472 area[0][0] = TMath::Max(area[0][0], area2[0][0]);
473 area[0][1] = TMath::Min(area[0][1], area2[0][1]);
474 area[1][0] = TMath::Max(area[1][0], area2[1][0]);
475 area[1][1] = TMath::Min(area[1][1], area2[1][1]);
476 }
477
478 // abort if the areas do not intersect
479 if (area[0][1] - area[0][0] < SDistancePrecision || area[1][1] - area[1][0] < SDistancePrecision) {
480 return;
481 }
482
483 // adjust limits
484 int nbins[2] = {0, 0};
485 for (int ixy = 0; ixy < 2; ++ixy) {
486 double precision = SDistancePrecision / width[ixy] / 2.;
487 double dist = (area[ixy][0] - xy0[ixy]) / width[ixy] / 2.;
488 area[ixy][0] = xy0[ixy] + (TMath::Nint(dist + precision) - 0.5) * width[ixy] * 2.;
489 nbins[ixy] = TMath::Ceil((area[ixy][1] - area[ixy][0]) / width[ixy] / 2. - precision);
490 area[ixy][1] = area[ixy][0] + nbins[ixy] * width[ixy] * 2.;
491 }
492
493 // book pixel histograms and fill them
494 TH2D hCharges("Charges", "", nbins[0], area[0][0], area[0][1], nbins[1], area[1][0], area[1][1]);
495 TH2I hEntries("Entries", "", nbins[0], area[0][0], area[0][1], nbins[1], area[1][0], area[1][1]);
496 for (const auto& pad : *mPreCluster) {
497 ProjectPadOverPixels(pad, hCharges, hEntries);
498 }
499
500 // store fired pixels with an entry from both planes if both planes are fired
501 for (int i = 1; i <= nbins[0]; ++i) {
502 double x = hCharges.GetXaxis()->GetBinCenter(i);
503 for (int j = 1; j <= nbins[1]; ++j) {
504 int entries = hEntries.GetBinContent(i, j);
505 if (entries == 0 || (plane0 != plane1 && (entries < 1000 || entries % 1000 < 1))) {
506 continue;
507 }
508 double y = hCharges.GetYaxis()->GetBinCenter(j);
509 double charge = hCharges.GetBinContent(i, j);
510 mPixels.emplace_back(x, y, width[0], width[1], charge);
511 }
512 }
513
514 // split the pixel into 2 if there is only one
515 if (mPixels.size() == 1) {
516 auto& pixel = mPixels.front();
517 pixel.setdx(width[0] / 2.);
518 pixel.setx(pixel.x() - width[0] / 2.);
519 mPixels.emplace_back(pixel.x() + width[0], pixel.y(), width[0] / 2., width[1], pixel.charge());
520 }
521
522 // sort and remove pixels with the lowest signal if there are too many
523 size_t nPads = mPreCluster->multiplicity();
524 if (mPixels.size() > nPads) {
525 std::stable_sort(mPixels.begin(), mPixels.end(), [](const PadOriginal& pixel1, const PadOriginal& pixel2) {
526 return pixel1.charge() > pixel2.charge();
527 });
528 mPixels.erase(mPixels.begin() + nPads, mPixels.end());
529 }
530}
531
532//_________________________________________________________________________________________________
533void ClusterFinderOriginal::ProjectPadOverPixels(const PadOriginal& pad, TH2D& hCharges, TH2I& hEntries) const
534{
536
537 const TAxis* xaxis = hCharges.GetXaxis();
538 const TAxis* yaxis = hCharges.GetYaxis();
539
540 int iMin = TMath::Max(1, xaxis->FindBin(pad.x() - pad.dx() + SDistancePrecision));
541 int iMax = TMath::Min(hCharges.GetNbinsX(), xaxis->FindBin(pad.x() + pad.dx() - SDistancePrecision));
542 int jMin = TMath::Max(1, yaxis->FindBin(pad.y() - pad.dy() + SDistancePrecision));
543 int jMax = TMath::Min(hCharges.GetNbinsY(), yaxis->FindBin(pad.y() + pad.dy() - SDistancePrecision));
544
545 double charge = pad.charge();
546 int entry = 1 + pad.plane() * 999;
547
548 for (int i = iMin; i <= iMax; ++i) {
549 for (int j = jMin; j <= jMax; ++j) {
550 int entries = hEntries.GetBinContent(i, j);
551 hCharges.SetBinContent(i, j, (entries > 0) ? TMath::Min(hCharges.GetBinContent(i, j), charge) : charge);
552 hEntries.SetBinContent(i, j, entries + entry);
553 }
554 }
555}
556
557//_________________________________________________________________________________________________
558void ClusterFinderOriginal::findLocalMaxima(std::unique_ptr<TH2D>& histAnode,
559 std::multimap<double, std::pair<int, int>, std::greater<>>& localMaxima)
560{
564
565 // create a 2D histogram from the pixel array
566 double xMin(std::numeric_limits<double>::max()), xMax(-std::numeric_limits<double>::max());
567 double yMin(std::numeric_limits<double>::max()), yMax(-std::numeric_limits<double>::max());
568 double dx(mPixels.front().dx()), dy(mPixels.front().dy());
569 for (const auto& pixel : mPixels) {
570 xMin = TMath::Min(xMin, pixel.x());
571 xMax = TMath::Max(xMax, pixel.x());
572 yMin = TMath::Min(yMin, pixel.y());
573 yMax = TMath::Max(yMax, pixel.y());
574 }
575 int nBinsX = TMath::Nint((xMax - xMin) / dx / 2.) + 1;
576 int nBinsY = TMath::Nint((yMax - yMin) / dy / 2.) + 1;
577 histAnode = std::make_unique<TH2D>("anode", "anode", nBinsX, xMin - dx, xMax + dx, nBinsY, yMin - dy, yMax + dy);
578 for (const auto& pixel : mPixels) {
579 histAnode->Fill(pixel.x(), pixel.y(), pixel.charge());
580 }
581
582 // find the local maxima
583 std::vector<std::vector<int>> isLocalMax(nBinsX, std::vector<int>(nBinsY, 0));
584 for (int j = 1; j <= nBinsY; ++j) {
585 for (int i = 1; i <= nBinsX; ++i) {
586 if (isLocalMax[i - 1][j - 1] == 0 && histAnode->GetBinContent(i, j) >= mLowestPixelCharge) {
587 flagLocalMaxima(*histAnode, i, j, isLocalMax);
588 }
589 }
590 }
591
592 // store local maxima and tag corresponding pixels
593 TAxis* xAxis = histAnode->GetXaxis();
594 TAxis* yAxis = histAnode->GetYaxis();
595 for (int j = 1; j <= nBinsY; ++j) {
596 for (int i = 1; i <= nBinsX; ++i) {
597 if (isLocalMax[i - 1][j - 1] > 0) {
598 localMaxima.emplace(histAnode->GetBinContent(i, j), std::make_pair(i, j));
599 auto itPixel = findPad(mPixels, xAxis->GetBinCenter(i), yAxis->GetBinCenter(j), mLowestPixelCharge);
600 itPixel->setStatus(PadOriginal::kMustKeep);
601 if (localMaxima.size() > 99) {
602 break;
603 }
604 }
605 }
606 if (localMaxima.size() > 99) {
607 mErrorMap.add(ErrorType::Clustering_TooManyLocalMaxima, mSegmentation->detElemId(), 0);
608 LOG(warning) << "Too many local maxima !!!";
609 break;
610 }
611 }
612}
613
614//_________________________________________________________________________________________________
615void ClusterFinderOriginal::flagLocalMaxima(const TH2D& histAnode, int i0, int j0, std::vector<std::vector<int>>& isLocalMax) const
616{
619
620 int idxi0 = i0 - 1;
621 int idxj0 = j0 - 1;
622 int charge0 = TMath::Nint(histAnode.GetBinContent(i0, j0));
623 int iMin = TMath::Max(1, i0 - 1);
624 int iMax = TMath::Min(histAnode.GetNbinsX(), i0 + 1);
625 int jMin = TMath::Max(1, j0 - 1);
626 int jMax = TMath::Min(histAnode.GetNbinsY(), j0 + 1);
627
628 for (int j = jMin; j <= jMax; ++j) {
629 int idxj = j - 1;
630 for (int i = iMin; i <= iMax; ++i) {
631 if (i == i0 && j == j0) {
632 continue;
633 }
634 int idxi = i - 1;
635 int charge = TMath::Nint(histAnode.GetBinContent(i, j));
636 if (charge0 < charge) {
637 isLocalMax[idxi0][idxj0] = -1;
638 return;
639 } else if (charge0 > charge) {
640 isLocalMax[idxi][idxj] = -1;
641 } else if (isLocalMax[idxi][idxj] == -1) {
642 isLocalMax[idxi0][idxj0] = -1;
643 return;
644 } else if (isLocalMax[idxi][idxj] == 0) {
645 isLocalMax[idxi0][idxj0] = 1;
646 flagLocalMaxima(histAnode, i, j, isLocalMax);
647 if (isLocalMax[idxi][idxj] == -1) {
648 isLocalMax[idxi0][idxj0] = -1;
649 return;
650 } else {
651 isLocalMax[idxi][idxj] = -2;
652 }
653 }
654 }
655 }
656 isLocalMax[idxi0][idxj0] = 1;
657}
658
659//_________________________________________________________________________________________________
660void ClusterFinderOriginal::restrictPreCluster(const TH2D& histAnode, int i0, int j0)
661{
664
665 // drop all pixels from the array and put back the ones around the local maximum
666 mPixels.clear();
667 const TAxis* xAxis = histAnode.GetXaxis();
668 const TAxis* yAxis = histAnode.GetYaxis();
669 double dx = xAxis->GetBinWidth(1) / 2.;
670 double dy = yAxis->GetBinWidth(1) / 2.;
671 double charge0 = histAnode.GetBinContent(i0, j0);
672 int iMin = TMath::Max(1, i0 - 1);
673 int iMax = TMath::Min(histAnode.GetNbinsX(), i0 + 1);
674 int jMin = TMath::Max(1, j0 - 1);
675 int jMax = TMath::Min(histAnode.GetNbinsY(), j0 + 1);
676 for (int j = jMin; j <= jMax; ++j) {
677 for (int i = iMin; i <= iMax; ++i) {
678 double charge = histAnode.GetBinContent(i, j);
679 if (charge >= mLowestPixelCharge && charge <= charge0) {
680 mPixels.emplace_back(xAxis->GetBinCenter(i), yAxis->GetBinCenter(j), dx, dy, charge);
681 }
682 }
683 }
684
685 // discard all pads of the clusters except the ones overlapping with selected pixels
686 for (auto& pad : *mPreCluster) {
687 pad.setStatus(PadOriginal::kOver);
688 for (const auto& pixel : mPixels) {
689 if (areOverlapping(pad, pixel, SDistancePrecision)) {
690 pad.setStatus(PadOriginal::kZero);
691 break;
692 }
693 }
694 }
695}
696
697//_________________________________________________________________________________________________
698void ClusterFinderOriginal::processSimple()
699{
703
704 // add virtual pads if necessary
705 addVirtualPad();
706
707 // calculate pad-pixel coupling coefficients and pixel visibilities
708 std::vector<double> coef(0);
709 std::vector<double> prob(0);
710 computeCoefficients(coef, prob);
711
712 // discard "invisible" pixels
713 for (int ipix = 0; ipix < mPixels.size(); ++ipix) {
714 if (prob[ipix] < 0.01) {
715 mPixels[ipix].setCharge(0);
716 }
717 }
718
719 // run the MLEM algorithm with 15 iterations
720 double qTot = mlem(coef, prob, 15);
721
722 // abort if the total charge of the pixels is too low
723 if (qTot < 1.e-4 || (mPreCluster->multiplicity() < 3 && qTot < mLowestClusterCharge)) {
724 return;
725 }
726
727 // fit all pads but the ones saturated
728 for (auto& pad : *mPreCluster) {
729 if (!pad.isSaturated()) {
730 pad.setStatus(PadOriginal::kUseForFit);
731 }
732 }
733
734 // use all the pixels as a single cluster seed
735 std::vector<int> pixels(mPixels.size());
736 std::iota(pixels.begin(), pixels.end(), 0);
737
738 // set the fit range based on the limits of the pixel array
739 double fitRange[2][2] = {{std::numeric_limits<double>::max(), -std::numeric_limits<double>::max()},
740 {std::numeric_limits<double>::max(), -std::numeric_limits<double>::max()}};
741 for (const auto& pixel : mPixels) {
742 fitRange[0][0] = TMath::Min(fitRange[0][0], pixel.x() - 3. * pixel.dx());
743 fitRange[0][1] = TMath::Max(fitRange[0][1], pixel.x() + 3. * pixel.dx());
744 fitRange[1][0] = TMath::Min(fitRange[1][0], pixel.y() - 3. * pixel.dy());
745 fitRange[1][1] = TMath::Max(fitRange[1][1], pixel.y() + 3. * pixel.dy());
746 }
747
748 // do the fit
749 double fitParam[SNFitParamMax + 1] = {0.};
750 fit({&pixels}, fitRange, fitParam);
751}
752
753//_________________________________________________________________________________________________
754void ClusterFinderOriginal::process()
755{
760
761 // add virtual pads if necessary
762 addVirtualPad();
763
764 // number of pads to be considered in the precluster
765 size_t npadOK(0);
766 for (const auto& pad : *mPreCluster) {
767 if (pad.status() == PadOriginal::kZero) {
768 ++npadOK;
769 }
770 }
771
772 // compute the limits of the histogram based on the current pixel array
773 double xMin(std::numeric_limits<double>::max()), xMax(-std::numeric_limits<double>::max());
774 double yMin(std::numeric_limits<double>::max()), yMax(-std::numeric_limits<double>::max());
775 for (const auto& pixel : mPixels) {
776 xMin = TMath::Min(xMin, pixel.x());
777 xMax = TMath::Max(xMax, pixel.x());
778 yMin = TMath::Min(yMin, pixel.y());
779 yMax = TMath::Max(yMax, pixel.y());
780 }
781
782 std::vector<double> coef(0);
783 std::vector<double> prob(0);
784 std::unique_ptr<TH2D> histMLEM(nullptr);
785 while (true) {
786
787 // calculate pad-pixel coupling coefficients and pixel visibilities
788 computeCoefficients(coef, prob);
789
790 // discard "invisible" pixels
791 for (int ipix = 0; ipix < mPixels.size(); ++ipix) {
792 if (prob[ipix] < 0.01) {
793 mPixels[ipix].setCharge(0);
794 }
795 }
796
797 // run the MLEM algorithm with 15 iterations
798 double qTot = mlem(coef, prob, 15);
799
800 // abort if the total charge of the pixels is too low
801 if (qTot < 1.e-4 || (npadOK < 3 && qTot < mLowestClusterCharge)) {
802 return;
803 }
804
805 // create a 2D histogram from the pixel array
806 double dx(mPixels.front().dx()), dy(mPixels.front().dy());
807 int nBinsX = TMath::Nint((xMax - xMin) / dx / 2.) + 1;
808 int nBinsY = TMath::Nint((yMax - yMin) / dy / 2.) + 1;
809 histMLEM.reset(nullptr); // delete first to avoid "Replacing existing TH1: mlem (Potential memory leak)"
810 histMLEM = std::make_unique<TH2D>("mlem", "mlem", nBinsX, xMin - dx, xMax + dx, nBinsY, yMin - dy, yMax + dy);
811 for (const auto& pixel : mPixels) {
812 histMLEM->Fill(pixel.x(), pixel.y(), pixel.charge());
813 }
814
815 // stop here if the pixel size is small enough
816 if ((dx < 0.07 || dy < 0.07) && dy < dx) {
817 break;
818 }
819
820 // calculate the position of the center-of-gravity around the pixel with maximum charge
821 double xyCOG[2] = {0., 0.};
822 findCOG(*histMLEM, xyCOG);
823
824 // decrease the pixel size and align the array with the position of the center-of-gravity
825 refinePixelArray(xyCOG, npadOK, xMin, xMax, yMin, yMax);
826 }
827
828 // discard pixels with low visibility by moving their charge to their nearest neighbour (cuts are empirical !!!)
829 double threshold = TMath::Min(TMath::Max(histMLEM->GetMaximum() / 100., 2.0 * mLowestPixelCharge), 100.0 * mLowestPixelCharge);
830 cleanPixelArray(threshold, prob);
831
832 // re-run the MLEM algorithm with 2 iterations
833 double qTot = mlem(coef, prob, 2);
834
835 // abort if the total charge of the pixels is too low
836 if (qTot < 2. * mLowestPixelCharge) {
837 return;
838 }
839
840 // update the histogram
841 for (const auto& pixel : mPixels) {
842 histMLEM->SetBinContent(histMLEM->GetXaxis()->FindBin(pixel.x()), histMLEM->GetYaxis()->FindBin(pixel.y()), pixel.charge());
843 }
844
845 // split the precluster into clusters
846 split(*histMLEM, coef);
847}
848
849//_________________________________________________________________________________________________
850void ClusterFinderOriginal::addVirtualPad()
851{
854
855 const mapping::CathodeSegmentation* cathSeg[2] = {&mSegmentation->bending(), &mSegmentation->nonBending()};
856
857 // decide what plane to consider for x and y directions
858 int iPlaneXY[2] = {1, 0}; // 0 = bending plane and 1 = non-bending plane as defined in resetPreCluster(...)
859
860 // check if virtual pads are needed in each direction
861 // if the minimum pad size in y is the same on both planes, check also the other plane
862 std::pair<double, double> dim0 = mPreCluster->minPadDimensions(0, PadOriginal::kZero, true);
863 std::pair<double, double> dim1 = mPreCluster->minPadDimensions(1, PadOriginal::kZero, true);
864 bool sameSizeY = TMath::Abs(dim0.second - dim1.second) < SDistancePrecision;
865 bool addVirtualPad[2] = {false, false};
866 std::pair<int, int> nPadsXY0 = mPreCluster->sizeInPads(iPlaneXY[0], PadOriginal::kZero);
867 std::pair<int, int> nPadsXY1 = mPreCluster->sizeInPads(iPlaneXY[1], PadOriginal::kZero);
868 if (nPadsXY0.first == 2 && (!sameSizeY || nPadsXY1.first <= 2)) {
869 addVirtualPad[0] = true;
870 }
871 if (nPadsXY1.second == 2 && (!sameSizeY || nPadsXY0.second <= 2)) {
872 addVirtualPad[1] = true;
873 }
874
875 double chargeReduction[2] = {100., 15.};
876 for (int ixy = 0; ixy < 2; ++ixy) {
877
878 // no need to add virtual pads in this direction
879 if (!addVirtualPad[ixy]) {
880 continue;
881 }
882
883 // find pads with maximum and next-to-maximum charges on the plane of interest
884 // find min and max dimensions of the precluster in this direction on that plane
885 long iPadMax[2] = {-1, -1};
886 double chargeMax[2] = {0., 0.};
887 double limits[2] = {std::numeric_limits<double>::max(), -std::numeric_limits<double>::max()};
888 for (int i = 0; i < mPreCluster->multiplicity(); ++i) {
889 const auto& pad = mPreCluster->pad(i);
890 if (pad.plane() != iPlaneXY[ixy]) {
891 continue;
892 }
893 if (pad.charge() > chargeMax[0]) {
894 chargeMax[1] = chargeMax[0];
895 chargeMax[0] = pad.charge();
896 iPadMax[1] = iPadMax[0];
897 iPadMax[0] = i;
898 } else if (pad.charge() > chargeMax[1]) {
899 chargeMax[1] = pad.charge();
900 iPadMax[1] = i;
901 }
902 double xy = pad.xy(ixy);
903 if (xy < limits[0]) {
904 limits[0] = xy;
905 }
906 if (xy > limits[1]) {
907 limits[1] = xy;
908 }
909 }
910
911 // try to add a virtual pad next to the max pad then, if done, next to the next-to-max pads
912 // do not try to add a second virtual pad if the next-to-max charge is too low
913 int n = (chargeMax[1] / chargeMax[0] < 0.5) ? 1 : 2;
914 int sideDone(0);
915 for (int i = 0; i < n; ++i) {
916
917 if (iPadMax[i] < 0) {
918 throw std::runtime_error("This plane should contain at least 2 pads!?");
919 }
920
921 // check if the pad is at the cluster limit and on which side to add a virtual pad
922 const auto& pad = mPreCluster->pad(iPadMax[i]);
923 int side(0);
924 double xy = pad.xy(ixy);
925 if (TMath::Abs(xy - limits[0]) < SDistancePrecision) {
926 side = -1;
927 } else if (TMath::Abs(xy - limits[1]) < SDistancePrecision) {
928 side = 1;
929 } else {
930 break;
931 }
932
933 // do not add 2 virtual pads on the same side
934 if (side == sideDone) {
935 break;
936 }
937
938 // find the pad to add in the mapping
939 double pos[2] = {pad.x(), pad.y()};
940 pos[ixy] += side * (pad.dxy(ixy) + SDistancePrecision);
941 pos[1 - ixy] += SDistancePrecision; // pickup always the same in case we fall at the border between 2 pads
942 int cathPadIdx = cathSeg[iPlaneXY[ixy]]->findPadByPosition(pos[0], pos[1]);
943 if (!cathSeg[iPlaneXY[ixy]]->isValid(cathPadIdx)) {
944 break;
945 }
946
947 // add the virtual pad
948 double charge = TMath::Max(TMath::Min(chargeMax[i] / chargeReduction[ixy], mLowestPadCharge), 2. * mLowestPixelCharge);
949 mPreCluster->addPad(cathSeg[iPlaneXY[ixy]]->padPositionX(cathPadIdx), cathSeg[iPlaneXY[ixy]]->padPositionY(cathPadIdx),
950 cathSeg[iPlaneXY[ixy]]->padSizeX(cathPadIdx) / 2., cathSeg[iPlaneXY[ixy]]->padSizeY(cathPadIdx) / 2.,
951 charge, false, iPlaneXY[ixy], -1, pad.status());
952
953 sideDone = side;
954 }
955 }
956}
957
958//_________________________________________________________________________________________________
959void ClusterFinderOriginal::computeCoefficients(std::vector<double>& coef, std::vector<double>& prob) const
960{
962
963 coef.assign(mPreCluster->multiplicity() * mPixels.size(), 0.);
964 prob.assign(mPixels.size(), 0.);
965
966 int iCoef(0);
967 for (const auto& pad : *mPreCluster) {
968
969 // ignore the pads that must not be considered
970 if (pad.status() != PadOriginal::kZero) {
971 iCoef += mPixels.size();
972 continue;
973 }
974
975 for (int i = 0; i < mPixels.size(); ++i) {
976
977 // charge (given by Mathieson integral) on pad, assuming the Mathieson is center at pixel.
978 coef[iCoef] = chargeIntegration(mPixels[i].x(), mPixels[i].y(), pad);
979
980 // update the pixel visibility
981 prob[i] += coef[iCoef];
982
983 ++iCoef;
984 }
985 }
986}
987
988//_________________________________________________________________________________________________
989double ClusterFinderOriginal::mlem(const std::vector<double>& coef, const std::vector<double>& prob, int nIter)
990{
993
994 double qTot(0.);
995 double maxProb = *std::max_element(prob.begin(), prob.end());
996 std::vector<double> padSum(mPreCluster->multiplicity(), 0.);
997
998 for (int iter = 0; iter < nIter; ++iter) {
999
1000 // calculate expectations, ignoring the pads that must not be considered
1001 int iCoef(0);
1002 for (int iPad = 0; iPad < mPreCluster->multiplicity(); ++iPad) {
1003 const auto& pad = mPreCluster->pad(iPad);
1004 if (pad.status() != PadOriginal::kZero) {
1005 iCoef += mPixels.size();
1006 continue;
1007 }
1008 padSum[iPad] = 0.;
1009 for (const auto& pixel : mPixels) {
1010 padSum[iPad] += pixel.charge() * coef[iCoef++];
1011 }
1012 }
1013
1014 qTot = 0.;
1015 for (int iPix = 0; iPix < mPixels.size(); ++iPix) {
1016
1017 // skip "invisible" pixel
1018 if (prob[iPix] < 0.01) {
1019 mPixels[iPix].setCharge(0.);
1020 continue;
1021 }
1022
1023 double pixelSum(0.);
1024 double pixelNorm(maxProb);
1025 for (int iPad = 0; iPad < mPreCluster->multiplicity(); ++iPad) {
1026
1027 // ignore the pads that must not be considered
1028 const auto& pad = mPreCluster->pad(iPad);
1029 if (pad.status() != PadOriginal::kZero) {
1030 continue;
1031 }
1032
1033 // correct for pad charge overflows
1034 int iCoef = iPad * mPixels.size() + iPix;
1035 if (pad.isSaturated() && padSum[iPad] > pad.charge()) {
1036 pixelNorm -= coef[iCoef];
1037 continue;
1038 }
1039
1040 if (padSum[iPad] > 1.e-6) {
1041 pixelSum += pad.charge() * coef[iCoef] / padSum[iPad];
1042 }
1043 }
1044
1045 // correct the pixel charge
1046 if (pixelNorm > 1.e-6) {
1047 mPixels[iPix].setCharge(mPixels[iPix].charge() * pixelSum / pixelNorm);
1048 qTot += mPixels[iPix].charge();
1049 } else {
1050 mPixels[iPix].setCharge(0.);
1051 }
1052 }
1053
1054 // can happen in clusters with large number of overflows - speeding up
1055 if (qTot < 1.e-6) {
1056 return qTot;
1057 }
1058 }
1059
1060 return qTot;
1061}
1062
1063//_________________________________________________________________________________________________
1064void ClusterFinderOriginal::findCOG(const TH2D& histMLEM, double xy[2]) const
1065{
1067
1068 // define the range of pixels and the minimum charge to consider
1069 int ix0(0), iy0(0), iz0(0);
1070 double chargeThreshold = histMLEM.GetBinContent(histMLEM.GetMaximumBin(ix0, iy0, iz0)) / 10.;
1071 int ixMin = TMath::Max(1, ix0 - 1);
1072 int ixMax = TMath::Min(histMLEM.GetNbinsX(), ix0 + 1);
1073 int iyMin = TMath::Max(1, iy0 - 1);
1074 int iyMax = TMath::Min(histMLEM.GetNbinsY(), iy0 + 1);
1075
1076 // first only consider pixels above threshold
1077 const TAxis* xAxis = histMLEM.GetXaxis();
1078 const TAxis* yAxis = histMLEM.GetYaxis();
1079 double xq(0.), yq(0.), q(0.);
1080 bool onePixelWidthX(true), onePixelWidthY(true);
1081 for (int iy = iyMin; iy <= iyMax; ++iy) {
1082 for (int ix = ixMin; ix <= ixMax; ++ix) {
1083 double charge = histMLEM.GetBinContent(ix, iy);
1084 if (charge >= chargeThreshold) {
1085 xq += xAxis->GetBinCenter(ix) * charge;
1086 yq += yAxis->GetBinCenter(iy) * charge;
1087 q += charge;
1088 if (ix != ix0) {
1089 onePixelWidthX = false;
1090 }
1091 if (iy != iy0) {
1092 onePixelWidthY = false;
1093 }
1094 }
1095 }
1096 }
1097
1098 // if all pixels used so far are aligned with iy0, add one more, with the highest charge, in y direction
1099 if (onePixelWidthY) {
1100 double xPixel(0.), yPixel(0.), chargePixel(0.);
1101 int ixPixel(ix0);
1102 for (int iy = iyMin; iy <= iyMax; ++iy) {
1103 if (iy != iy0) {
1104 for (int ix = ixMin; ix <= ixMax; ++ix) {
1105 double charge = histMLEM.GetBinContent(ix, iy);
1106 if (charge > chargePixel) {
1107 xPixel = xAxis->GetBinCenter(ix);
1108 yPixel = yAxis->GetBinCenter(iy);
1109 chargePixel = charge;
1110 ixPixel = ix;
1111 }
1112 }
1113 }
1114 }
1115 xq += xPixel * chargePixel;
1116 yq += yPixel * chargePixel;
1117 q += chargePixel;
1118 if (ixPixel != ix0) {
1119 onePixelWidthX = false;
1120 }
1121 }
1122
1123 // if all pixels used so far are aligned with ix0, add one more, with the highest charge, in x direction
1124 if (onePixelWidthX) {
1125 double xPixel(0.), yPixel(0.), chargePixel(0.);
1126 for (int ix = ixMin; ix <= ixMax; ++ix) {
1127 if (ix != ix0) {
1128 for (int iy = iyMin; iy <= iyMax; ++iy) {
1129 double charge = histMLEM.GetBinContent(ix, iy);
1130 if (charge > chargePixel) {
1131 xPixel = xAxis->GetBinCenter(ix);
1132 yPixel = yAxis->GetBinCenter(iy);
1133 chargePixel = charge;
1134 }
1135 }
1136 }
1137 }
1138 xq += xPixel * chargePixel;
1139 yq += yPixel * chargePixel;
1140 q += chargePixel;
1141 }
1142
1143 // compute the position of the centrer-of-gravity
1144 xy[0] = xq / q;
1145 xy[1] = yq / q;
1146}
1147
1148//_________________________________________________________________________________________________
1149void ClusterFinderOriginal::refinePixelArray(const double xyCOG[2], size_t nPixMax, double& xMin, double& xMax, double& yMin, double& yMax)
1150{
1156
1157 xMin = std::numeric_limits<double>::max();
1158 xMax = -std::numeric_limits<double>::max();
1159 yMin = std::numeric_limits<double>::max();
1160 yMax = -std::numeric_limits<double>::max();
1161
1162 // sort pixels according to the charge and move all pixels that must be kept at the beginning
1163 std::stable_sort(mPixels.begin(), mPixels.end(), [](const PadOriginal& pixel1, const PadOriginal& pixel2) {
1164 return (pixel1.status() == PadOriginal::kMustKeep && pixel2.status() != PadOriginal::kMustKeep) ||
1165 (pixel1.status() == pixel2.status() && pixel1.charge() > pixel2.charge());
1166 });
1167 double pixMinCharge = TMath::Min(0.01 * mPixels.front().charge(), 100. * mLowestPixelCharge);
1168
1169 // define the half-size and shift of the new pixels depending on the direction of splitting
1170 double width[2] = {mPixels.front().dx(), mPixels.front().dy()};
1171 double shift[2] = {0., 0.};
1172 if (width[0] > width[1]) {
1173 width[0] /= 2.;
1174 shift[0] = -width[0];
1175 } else {
1176 width[1] /= 2.;
1177 shift[1] = -width[1];
1178 }
1179
1180 // define additional shift to align pixels with the center of gravity (no more than new pixel size)
1181 double shiftToCOG[2] = {0., 0.};
1182 for (int ixy = 0; ixy < 2; ++ixy) {
1183 shiftToCOG[ixy] = mPixels.front().xy(ixy) + shift[ixy] - xyCOG[ixy];
1184 shiftToCOG[ixy] -= int(shiftToCOG[ixy] / width[ixy] / 2.) * width[ixy] * 2.;
1185 }
1186
1187 int nPixels = mPixels.size();
1188 int nNewPixels(0);
1189 for (int i = 0; i < nPixels; ++i) {
1190
1191 // do not exceed the maximum number of pixels expected and
1192 // skip pixels with charge too low unless they must be kept (this onward thanks to the ordering)
1193 auto& pixel = mPixels[i];
1194 if (nNewPixels == nPixMax || (pixel.charge() < pixMinCharge && pixel.status() != PadOriginal::kMustKeep)) {
1195 mPixels.erase(mPixels.begin() + i, mPixels.begin() + nPixels);
1196 break;
1197 }
1198
1199 // shift half the pixel left(bottom) and toward COG and update limits
1200 pixel.setx(pixel.x() + shift[0] - shiftToCOG[0]);
1201 pixel.sety(pixel.y() + shift[1] - shiftToCOG[1]);
1202 pixel.setdx(width[0]);
1203 pixel.setdy(width[1]);
1204 pixel.setCharge(mLowestPadCharge);
1205 xMin = TMath::Min(xMin, pixel.x());
1206 yMin = TMath::Min(yMin, pixel.y());
1207 ++nNewPixels;
1208
1209 // stop if the maximum number of pixels is reached: cannot add the second half-pixel
1210 if (nNewPixels == nPixMax) {
1211 xMax = TMath::Max(xMax, pixel.x());
1212 yMax = TMath::Max(yMax, pixel.y());
1213 continue;
1214 }
1215
1216 // add the second half-pixel on the right(top) and update the limits
1217 mPixels.emplace_back(pixel);
1218 auto& pixel2 = mPixels.back();
1219 pixel2.setx(pixel2.x() - 2. * shift[0]);
1220 pixel2.sety(pixel2.y() - 2. * shift[1]);
1221 pixel2.setStatus(PadOriginal::kZero);
1222 xMax = TMath::Max(xMax, pixel2.x());
1223 yMax = TMath::Max(yMax, pixel2.y());
1224 ++nNewPixels;
1225 }
1226
1227 // add pixels if the center of gravity is at the limit of the pixel array and update limits
1228 if (mPixels.size() < nPixMax && xyCOG[1] + width[1] > yMax) {
1229 yMax = xyCOG[1] + 2. * width[1];
1230 mPixels.emplace_back(mPixels.front());
1231 mPixels.back().setx(xyCOG[0]);
1232 mPixels.back().sety(yMax);
1233 }
1234 if (mPixels.size() < nPixMax && xyCOG[1] - width[1] < yMin) {
1235 yMin = xyCOG[1] - 2. * width[1];
1236 mPixels.emplace_back(mPixels.front());
1237 mPixels.back().setx(xyCOG[0]);
1238 mPixels.back().sety(yMin);
1239 }
1240 if (mPixels.size() < nPixMax && xyCOG[0] + width[0] > xMax) {
1241 xMax = xyCOG[0] + 2. * width[0];
1242 mPixels.emplace_back(mPixels.front());
1243 mPixels.back().setx(xMax);
1244 mPixels.back().sety(xyCOG[1]);
1245 }
1246 if (mPixels.size() < nPixMax && xyCOG[0] - width[0] < xMin) {
1247 xMin = xyCOG[0] - 2. * width[0];
1248 mPixels.emplace_back(mPixels.front());
1249 mPixels.back().setx(xMin);
1250 mPixels.back().sety(xyCOG[1]);
1251 }
1252}
1253
1254//_________________________________________________________________________________________________
1255void ClusterFinderOriginal::cleanPixelArray(double threshold, std::vector<double>& prob)
1256{
1260
1261 for (int i = 0; i < mPixels.size(); ++i) {
1262
1263 auto& pixel = mPixels[i];
1264 if (pixel.charge() >= threshold) {
1265 continue;
1266 }
1267
1268 // make it "invisible"
1269 prob[i] = 0.;
1270
1271 // no charge to move
1272 if (pixel.charge() <= 0.) {
1273 continue;
1274 }
1275
1276 // find the nearest neighbour above the threshold
1277 int iNeighbour(-1);
1278 double distMin(std::numeric_limits<double>::max());
1279 for (int j = 0; j < mPixels.size(); ++j) {
1280 auto& pixel2 = mPixels[j];
1281 if (j != i && pixel2.charge() >= threshold) {
1282 double distX = (pixel.x() - pixel2.x()) / pixel2.dx();
1283 double distY = (pixel.y() - pixel2.y()) / pixel2.dy();
1284 double dist = distX * distX + distY * distY;
1285 if (dist < distMin) {
1286 distMin = dist;
1287 iNeighbour = j;
1288 }
1289 }
1290 }
1291 if (iNeighbour < 0) {
1292 LOG(info) << "There is no pixel above the threshold!?";
1293 continue;
1294 }
1295
1296 // move the charge
1297 mPixels[iNeighbour].setCharge(mPixels[iNeighbour].charge() + pixel.charge());
1298 pixel.setCharge(0.);
1299 }
1300}
1301
1302//_________________________________________________________________________________________________
1303int ClusterFinderOriginal::fit(const std::vector<const std::vector<int>*>& clustersOfPixels,
1304 const double fitRange[2][2], double fitParam[SNFitParamMax + 1])
1305{
1314
1315 // there must be at most SNFitClustersMax clusters fitted at a time
1316 if (clustersOfPixels.empty() || clustersOfPixels.size() > SNFitClustersMax) {
1317 throw std::runtime_error(std::string("Cannot fit ") + clustersOfPixels.size() + " clusters at a time");
1318 }
1319
1320 // number of pads to use, number of virtual pads and average pad charge
1321 int nRealPadsToFit(0), nVirtualPadsToFit(0);
1322 double averagePadCharge(0.);
1323 for (const auto& pad : *mPreCluster) {
1324 if (pad.status() == PadOriginal::kUseForFit) {
1325 averagePadCharge += pad.charge();
1326 if (pad.isReal()) {
1327 ++nRealPadsToFit;
1328 } else {
1329 ++nVirtualPadsToFit;
1330 }
1331 }
1332 }
1333 // need at least 2 real pads to fit
1334 if (nRealPadsToFit < 2) {
1335 fitParam[SNFitParamMax] = 0.;
1336 return 0;
1337 }
1338 averagePadCharge /= nRealPadsToFit;
1339
1340 // determine the clusters' position seeds ordered per decreasing charge and the overall mean position
1341 // as well as the total charge of all the pixels associated to the part of the precluster being fitted
1342 double xMean(0.), yMean(0.);
1343 fitParam[SNFitParamMax] = 0.;
1344 std::multimap<double, std::pair<double, double>, std::greater<>> xySeed{};
1345 for (const auto iPixels : clustersOfPixels) {
1346 double chargeMax(0.), xSeed(0.), ySeed(0.);
1347 for (auto iPixel : *iPixels) {
1348 const auto& pixel = mPixels[iPixel];
1349 double charge = pixel.charge();
1350 fitParam[SNFitParamMax] += charge;
1351 xMean += pixel.x() * charge;
1352 yMean += pixel.y() * charge;
1353 if (charge > chargeMax) {
1354 chargeMax = charge;
1355 xSeed = pixel.x();
1356 ySeed = pixel.y();
1357 }
1358 }
1359 xySeed.emplace(chargeMax, std::make_pair(xSeed, ySeed));
1360 }
1361 xMean /= fitParam[SNFitParamMax];
1362 yMean /= fitParam[SNFitParamMax];
1363
1364 // reduce the number of clusters to fit if there are not enough pads in each direction
1365 auto nPadsXY = mPreCluster->sizeInPads(PadOriginal::kUseForFit);
1366 if (xySeed.size() > 1) {
1367 int max = TMath::Min(SNFitClustersMax, (nRealPadsToFit + 1) / 3);
1368 if (max > 1) {
1369 if ((nPadsXY.first < 3 && nPadsXY.second < 3) ||
1370 (nPadsXY.first == 3 && nPadsXY.second < 3) ||
1371 (nPadsXY.first < 3 && nPadsXY.second == 3)) {
1372 max = 1;
1373 }
1374 }
1375 if (xySeed.size() > max) {
1376 xySeed.erase(std::next(xySeed.begin(), max), xySeed.end());
1377 }
1378 }
1379
1380 // prepare the initial fit parameters and limits (use clusters' position seeds if several clusters are used, mean position otherwise)
1381 // the last 2 parameters are fixed to the total pixel charge and the average pad charge for the part of the precluster being fitted
1382 double param[SNFitParamMax + 2] = {xMean, yMean, 0.6, xMean, yMean, 0.6, xMean, yMean, fitParam[SNFitParamMax], averagePadCharge};
1383 double parmin[SNFitParamMax] = {fitRange[0][0], fitRange[1][0], 1.e-9, fitRange[0][0], fitRange[1][0], 1.e-9, fitRange[0][0], fitRange[1][0]};
1384 double parmax[SNFitParamMax] = {fitRange[0][1], fitRange[1][1], 1., fitRange[0][1], fitRange[1][1], 1., fitRange[0][1], fitRange[1][1]};
1385 if (xySeed.size() > 1) {
1386 int iParam(0);
1387 for (const auto& seed : xySeed) {
1388 param[iParam++] = seed.second.first;
1389 param[iParam++] = seed.second.second;
1390 ++iParam;
1391 }
1392 }
1393
1394 // try to fit with only 1 cluster, then 2 (if any), then 3 (if any) and stop if the fit gets worse
1395 // the fitted parameters are used as initial parameters of the corresponding cluster(s) for the next fit
1396 double chi2n0(std::numeric_limits<float>::max());
1397 int nTrials(0);
1398 int nParamUsed(0);
1399 for (int nFitClusters = 1; nFitClusters <= xySeed.size(); ++nFitClusters) {
1400
1401 // number of parameters to use
1402 nParamUsed = 3 * nFitClusters - 1;
1403
1404 // do the fit
1405 double chi2 = fit(param, parmin, parmax, nParamUsed, nTrials);
1406
1407 // stop here if the normalized chi2 is not (significantly) smaller than in the previous fit
1408 int dof = TMath::Max(nRealPadsToFit + nVirtualPadsToFit - nParamUsed, 1);
1409 double chi2n = chi2 / dof;
1410 if (nParamUsed > 2 &&
1411 (chi2n > chi2n0 || (nFitClusters == xySeed.size() && chi2n * (1 + TMath::Min(1 - param[nParamUsed - 3], 0.25)) > chi2n0))) {
1412 nParamUsed -= 3;
1413 break;
1414 }
1415 chi2n0 = chi2n;
1416
1417 // reset the clusters position to the center of the pad if there is only one in this direction
1418 if (nPadsXY.first == 1) {
1419 for (int i = 0; i < nParamUsed; ++i) {
1420 if (i == 0 || i == 3 || i == 6) {
1421 param[i] = xMean;
1422 }
1423 }
1424 }
1425 if (nPadsXY.second == 1) {
1426 for (int i = 0; i < nParamUsed; ++i) {
1427 if (i == 1 || i == 4 || i == 7) {
1428 param[i] = yMean;
1429 }
1430 }
1431 }
1432
1433 // make sure the parameters are within limits and save them
1434 for (int i = 0; i < nParamUsed; ++i) {
1435 param[i] = TMath::Max(param[i], parmin[i]);
1436 param[i] = TMath::Min(param[i], parmax[i]);
1437 fitParam[i] = param[i];
1438 }
1439
1440 // stop here if the current chi2 is too low
1441 if (chi2 < 0.1) {
1442 break;
1443 }
1444 }
1445
1446 // store the reconstructed clusters if their charge is high enough
1447 double chargeFraction[SNFitClustersMax] = {0.};
1448 param2ChargeFraction(fitParam, nParamUsed, chargeFraction);
1449 for (int iParam = 0; iParam < nParamUsed; iParam += 3) {
1450 if (chargeFraction[iParam / 3] * fitParam[SNFitParamMax] >= mLowestClusterCharge) {
1451 mClusters.push_back({static_cast<float>(fitParam[iParam]), static_cast<float>(fitParam[iParam + 1]), 0., 0., 0., 0, 0, 0});
1452 }
1453 }
1454
1455 return nParamUsed;
1456}
1457
1458//_________________________________________________________________________________________________
1459double ClusterFinderOriginal::fit(double currentParam[SNFitParamMax + 2],
1460 const double parmin[SNFitParamMax], const double parmax[SNFitParamMax],
1461 int nParamUsed, int& nTrials) const
1462{
1465
1466 // default step size in x, y and charge fraction
1467 static const double defaultShift[SNFitParamMax] = {0.01, 0.002, 0.02, 0.01, 0.002, 0.02, 0.01, 0.002};
1468
1469 double shift[SNFitParamMax] = {0.};
1470 for (int i = 0; i < nParamUsed; ++i) {
1471 shift[i] = defaultShift[i];
1472 }
1473
1474 // copy of current and best parameters and associated first derivatives and chi2
1475 double param[2][SNFitParamMax] = {{0.}, {0.}};
1476 double deriv[2][SNFitParamMax] = {{0.}, {0.}};
1477 double chi2[2] = {0., std::numeric_limits<float>::max()};
1478 int iBestParam(1);
1479
1480 double shiftSave(0.);
1481 int nSimilarSteps[SNFitParamMax] = {0};
1482 int nLoop(0), nFail(0);
1483
1484 while (true) {
1485 ++nLoop;
1486
1487 // keep the best results from the previous step and save the new ones in the other slot
1488 int iCurrentParam = 1 - iBestParam;
1489
1490 // get the chi2 of the fit with the current parameters
1491 chi2[iCurrentParam] = computeChi2(currentParam, nParamUsed);
1492 ++nTrials;
1493
1494 // compute first and second chi2 derivatives w.r.t. each parameter
1495 double deriv2nd[SNFitParamMax] = {0.};
1496 for (int i = 0; i < nParamUsed; ++i) {
1497 param[iCurrentParam][i] = currentParam[i];
1498 currentParam[i] += defaultShift[i] / 10.;
1499 double chi2Shift = computeChi2(currentParam, nParamUsed);
1500 ++nTrials;
1501 deriv[iCurrentParam][i] = (chi2Shift - chi2[iCurrentParam]) / defaultShift[i] * 10;
1502 deriv2nd[i] = param[0][i] != param[1][i] ? (deriv[0][i] - deriv[1][i]) / (param[0][i] - param[1][i]) : 0;
1503 currentParam[i] -= defaultShift[i] / 10.;
1504 }
1505
1506 // abort if we exceed the maximum number of trials (integrated over the fits with 1, 2 and 3 clusters)
1507 if (nTrials > 2000) {
1508 break;
1509 }
1510
1511 // choose the best parameters between the current ones and the best ones from the previous step
1512 iBestParam = chi2[0] < chi2[1] ? 0 : 1;
1513 nFail = iBestParam == iCurrentParam ? 0 : nFail + 1;
1514
1515 // stop here if we reached the maximum number of iterations
1516 if (nLoop > 150) {
1517 break;
1518 }
1519
1520 double stepMax(0.), derivMax(0.);
1521 int iDerivMax(0);
1522 for (int i = 0; i < nParamUsed; ++i) {
1523
1524 // estimate the shift to perform of this parameter to reach the minimum chi2
1525 double previousShift = shift[i];
1526 if (nLoop == 1) {
1527 // start with the default step size
1528 shift[i] = TMath::Sign(defaultShift[i], -deriv[iCurrentParam][i]);
1529 } else if (TMath::Abs(deriv[0][i]) < 1.e-3 && TMath::Abs(deriv[1][i]) < 1.e-3) {
1530 // stay there if the minimum is reached w.r.t. to this parameter
1531 shift[i] = 0;
1532 } else if ((deriv[iBestParam][i] * deriv[1 - iBestParam][i] > 0. &&
1533 TMath::Abs(deriv[iBestParam][i]) > TMath::Abs(deriv[1 - iBestParam][i])) ||
1534 TMath::Abs(deriv[0][i] - deriv[1][i]) < 1.e-3 ||
1535 TMath::Abs(deriv2nd[i]) < 1.e-6) {
1536 // same size of shift if the first derivative increases or remain constant
1537 shift[i] = -TMath::Sign(shift[i], (chi2[0] - chi2[1]) * (param[0][i] - param[1][i]));
1538 // move faster if we already did >= 2 steps like this and the chi2 improved
1539 if (iBestParam == iCurrentParam) {
1540 if (nSimilarSteps[i] > 1) {
1541 shift[i] *= 2.;
1542 }
1543 ++nSimilarSteps[i];
1544 }
1545 } else {
1546 // adjust the shift otherwise
1547 shift[i] = deriv2nd[i] != 0. ? -deriv[iBestParam][i] / deriv2nd[i] : 0.;
1548 nSimilarSteps[i] = 0;
1549 }
1550
1551 // maximum shift normalized to the default step size and maximum first derivative
1552 stepMax = TMath::Max(stepMax, TMath::Abs(shift[i]) / defaultShift[i]);
1553 if (TMath::Abs(deriv[iBestParam][i]) > derivMax) {
1554 iDerivMax = i;
1555 derivMax = TMath::Abs(deriv[iBestParam][i]);
1556 }
1557
1558 // limit the shift to 10 times the default step size
1559 if (TMath::Abs(shift[i]) / defaultShift[i] > 10.) {
1560 shift[i] = TMath::Sign(10., shift[i]) * defaultShift[i];
1561 }
1562
1563 // reset the current parameter and adjust the shift if the chi2 did not improve
1564 if (iBestParam != iCurrentParam) {
1565 nSimilarSteps[i] = 0;
1566 currentParam[i] = param[iBestParam][i];
1567 if (TMath::Abs(shift[i] + previousShift) > 0.1 * defaultShift[i]) {
1568 shift[i] = (shift[i] + previousShift) / 2.;
1569 } else {
1570 shift[i] /= -2.;
1571 }
1572 }
1573
1574 // reduce the shift if the step is too big
1575 if (TMath::Abs(shift[i] * deriv[iBestParam][i]) > chi2[iBestParam]) {
1576 shift[i] = TMath::Sign(chi2[iBestParam] / deriv[iBestParam][i], shift[i]);
1577 }
1578
1579 // introduce step relaxation factor
1580 if (nSimilarSteps[i] < 3) {
1581 double scMax = 1. + 4. / TMath::Max(nLoop / 2., 1.);
1582 if (TMath::Abs(previousShift) > 0. && TMath::Abs(shift[i] / previousShift) > scMax) {
1583 shift[i] = TMath::Sign(previousShift * scMax, shift[i]);
1584 }
1585 }
1586
1587 // shift the current parameter and make sure we do not overstep its limits
1588 currentParam[i] += shift[i];
1589 if (currentParam[i] < parmin[i]) {
1590 shift[i] = parmin[i] - currentParam[i];
1591 currentParam[i] = parmin[i];
1592 } else if (currentParam[i] > parmax[i]) {
1593 shift[i] = parmax[i] - currentParam[i];
1594 currentParam[i] = parmax[i];
1595 }
1596 }
1597
1598 // stop here if the minimum was found
1599 if (stepMax < 1. && derivMax < 2.) {
1600 break;
1601 }
1602
1603 // check for small step
1604 if (shift[iDerivMax] == 0.) {
1605 shift[iDerivMax] = defaultShift[iDerivMax] / 10.;
1606 currentParam[iDerivMax] += shift[iDerivMax];
1607 continue;
1608 }
1609
1610 // further adjustment...
1611 if (nSimilarSteps[iDerivMax] == 0 && derivMax > 0.5 && nLoop > 9) {
1612 if (deriv2nd[iDerivMax] != 0. && TMath::Abs(deriv[iBestParam][iDerivMax] / deriv2nd[iDerivMax] / shift[iDerivMax]) > 10.) {
1613 if (iBestParam == iCurrentParam) {
1614 deriv2nd[iDerivMax] = -deriv2nd[iDerivMax];
1615 }
1616 shift[iDerivMax] = -deriv[iBestParam][iDerivMax] / deriv2nd[iDerivMax] / 10.;
1617 currentParam[iDerivMax] += shift[iDerivMax];
1618 if (iBestParam == iCurrentParam) {
1619 shiftSave = shift[iDerivMax];
1620 }
1621 }
1622 if (nFail > 10) {
1623 currentParam[iDerivMax] -= shift[iDerivMax];
1624 shift[iDerivMax] = 4. * shiftSave * (gRandom->Rndm(0) - 0.5);
1625 currentParam[iDerivMax] += shift[iDerivMax];
1626 }
1627 }
1628 }
1629
1630 // return the best parameters and associated chi2
1631 for (int i = 0; i < nParamUsed; ++i) {
1632 currentParam[i] = param[iBestParam][i];
1633 }
1634 return chi2[iBestParam];
1635}
1636
1637//_________________________________________________________________________________________________
1638double ClusterFinderOriginal::computeChi2(const double param[SNFitParamMax + 2], int nParamUsed) const
1639{
1645
1646 // get the fraction of charge carried by each cluster
1647 double chargeFraction[SNFitClustersMax] = {0.};
1648 param2ChargeFraction(param, nParamUsed, chargeFraction);
1649
1650 double chi2(0.);
1651 for (const auto& pad : *mPreCluster) {
1652
1653 // skip pads not to be used for this fit
1654 if (pad.status() != PadOriginal::kUseForFit) {
1655 continue;
1656 }
1657
1658 // compute the expected pad charge with these cluster parameters
1659 double padChargeFit(0.);
1660 for (int iParam = 0; iParam < nParamUsed; iParam += 3) {
1661 padChargeFit += chargeIntegration(param[iParam], param[iParam + 1], pad) * chargeFraction[iParam / 3];
1662 }
1663 padChargeFit *= param[SNFitParamMax];
1664
1665 // compute the chi2
1666 double delta = padChargeFit - pad.charge();
1667 chi2 += delta * delta / pad.charge();
1668 }
1669
1670 return chi2 / param[SNFitParamMax + 1];
1671}
1672
1673//_________________________________________________________________________________________________
1674void ClusterFinderOriginal::param2ChargeFraction(const double param[SNFitParamMax], int nParamUsed,
1675 double fraction[SNFitClustersMax]) const
1676{
1678 if (nParamUsed == 2) {
1679 fraction[0] = 1.;
1680 } else if (nParamUsed == 5) {
1681 fraction[0] = param[2];
1682 fraction[1] = TMath::Max(1. - fraction[0], 0.);
1683 } else {
1684 fraction[0] = param[2];
1685 fraction[1] = TMath::Max((1. - fraction[0]) * param[5], 0.);
1686 fraction[2] = TMath::Max(1. - fraction[0] - fraction[1], 0.);
1687 }
1688}
1689
1690//_________________________________________________________________________________________________
1691float ClusterFinderOriginal::chargeIntegration(double x, double y, const PadOriginal& pad) const
1692{
1694 double xPad = pad.x() - x;
1695 double yPad = pad.y() - y;
1696 return mMathieson->integrate(xPad - pad.dx(), yPad - pad.dy(), xPad + pad.dx(), yPad + pad.dy());
1697}
1698
1699//_________________________________________________________________________________________________
1700void ClusterFinderOriginal::split(const TH2D& histMLEM, const std::vector<double>& coef)
1701{
1705
1706 // save the pad charges as they can be modified during the splitting
1707 std::vector<double> padCharges(0);
1708 padCharges.reserve(mPreCluster->multiplicity());
1709 for (const auto& pad : *mPreCluster) {
1710 padCharges.push_back(pad.charge());
1711 }
1712
1713 // find clusters of pixels
1714 int nBinsX = histMLEM.GetNbinsX();
1715 int nBinsY = histMLEM.GetNbinsY();
1716 std::vector<std::vector<int>> clustersOfPixels{};
1717 std::vector<std::vector<bool>> isUsed(nBinsX, std::vector<bool>(nBinsY, false));
1718 for (int j = 1; j <= nBinsY; ++j) {
1719 for (int i = 1; i <= nBinsX; ++i) {
1720 if (!isUsed[i - 1][j - 1] && histMLEM.GetBinContent(i, j) >= mLowestPixelCharge) {
1721 // add a new cluster of pixels and the associated pixels recursively
1722 clustersOfPixels.emplace_back();
1723 addPixel(histMLEM, i, j, clustersOfPixels.back(), isUsed);
1724 }
1725 }
1726 }
1727 if (clustersOfPixels.size() > 200) {
1728 throw std::runtime_error("Too many clusters of pixels!");
1729 }
1730
1731 // compute the coupling between clusters of pixels and pads (including overflows)
1732 std::vector<std::vector<double>> couplingClPad(clustersOfPixels.size(), std::vector<double>(mPreCluster->multiplicity(), 0.));
1733 for (int iPad = 0; iPad < mPreCluster->multiplicity(); ++iPad) {
1734 int idx0 = iPad * mPixels.size();
1735 for (int iCluster = 0; iCluster < clustersOfPixels.size(); ++iCluster) {
1736 for (auto ipixel : clustersOfPixels[iCluster]) {
1737 if (coef[idx0 + ipixel] >= SLowestCoupling) {
1738 couplingClPad[iCluster][iPad] += coef[idx0 + ipixel];
1739 }
1740 }
1741 }
1742 }
1743
1744 // compute the coupling between clusters of pixels (excluding coupling via pads in overflow)
1745 std::vector<std::vector<double>> couplingClCl(clustersOfPixels.size(), std::vector<double>(clustersOfPixels.size(), 0.));
1746 for (int iPad = 0; iPad < mPreCluster->multiplicity(); ++iPad) {
1747 if (!mPreCluster->pad(iPad).isSaturated()) {
1748 for (int iCluster1 = 0; iCluster1 < clustersOfPixels.size(); ++iCluster1) {
1749 if (couplingClPad[iCluster1][iPad] >= SLowestCoupling) {
1750 for (int iCluster2 = iCluster1 + 1; iCluster2 < clustersOfPixels.size(); ++iCluster2) {
1751 if (couplingClPad[iCluster2][iPad] >= SLowestCoupling) {
1752 couplingClCl[iCluster1][iCluster2] += TMath::Sqrt(couplingClPad[iCluster1][iPad] * couplingClPad[iCluster2][iPad]);
1753 }
1754 }
1755 }
1756 }
1757 }
1758 }
1759 for (int iCluster1 = 0; iCluster1 < clustersOfPixels.size(); ++iCluster1) {
1760 for (int iCluster2 = iCluster1 + 1; iCluster2 < clustersOfPixels.size(); ++iCluster2) {
1761 couplingClCl[iCluster2][iCluster1] = couplingClCl[iCluster1][iCluster2];
1762 }
1763 }
1764
1765 // define the fit range
1766 const TAxis* xAxis = histMLEM.GetXaxis();
1767 const TAxis* yAxis = histMLEM.GetYaxis();
1768 double fitRange[2][2] = {{xAxis->GetXmin() - xAxis->GetBinWidth(1), xAxis->GetXmax() + xAxis->GetBinWidth(1)},
1769 {yAxis->GetXmin() - yAxis->GetBinWidth(1), yAxis->GetXmax() + yAxis->GetBinWidth(1)}};
1770
1771 std::vector<bool> isClUsed(clustersOfPixels.size(), false);
1772 std::vector<int> coupledClusters{};
1773 std::vector<int> clustersForFit{};
1774 std::vector<const std::vector<int>*> clustersOfPixelsForFit{};
1775 for (int iCluster = 0; iCluster < clustersOfPixels.size(); ++iCluster) {
1776
1777 // skip clusters of pixels already used
1778 if (isClUsed[iCluster]) {
1779 continue;
1780 }
1781
1782 // fill the list of coupled clusters of pixels recursively starting from this one
1783 coupledClusters.clear();
1784 addCluster(iCluster, coupledClusters, isClUsed, couplingClCl);
1785
1786 while (coupledClusters.size() > 0) {
1787
1788 // select the clusters of pixels for the fit: all of them if <= SNFitClustersMax
1789 // or the group of maximum 3 the least coupled with the others
1790 clustersForFit.clear();
1791 if (coupledClusters.size() <= SNFitClustersMax) {
1792 clustersForFit.swap(coupledClusters);
1793 } else {
1794 extractLeastCoupledClusters(coupledClusters, clustersForFit, couplingClCl);
1795 }
1796
1797 // select the associated pads for the fit
1798 int nSelectedPads = selectPads(coupledClusters, clustersForFit, couplingClPad);
1799
1800 // abort if there are not enough pads selected, deselect pads and
1801 // merge the clusters of pixels selected for the fit into the others, if any
1802 if (nSelectedPads < 3 && coupledClusters.size() + clustersForFit.size() > 1) {
1803 for (auto& pad : *mPreCluster) {
1804 if (pad.status() == PadOriginal::kUseForFit || pad.status() == PadOriginal::kCoupled) {
1805 pad.setStatus(PadOriginal::kZero);
1806 }
1807 }
1808 if (coupledClusters.size() > 0) {
1809 merge(clustersForFit, coupledClusters, clustersOfPixels, couplingClCl, couplingClPad);
1810 }
1811 continue;
1812 }
1813
1814 // do the fit
1815 clustersOfPixelsForFit.clear();
1816 for (auto iCluster : clustersForFit) {
1817 clustersOfPixelsForFit.push_back(&clustersOfPixels[iCluster]);
1818 }
1819 double fitParam[SNFitParamMax + 1] = {0.};
1820 int nParamUsed = fit(clustersOfPixelsForFit, fitRange, fitParam);
1821
1822 // update the status (and possibly the charge) of selected pads
1823 updatePads(fitParam, nParamUsed);
1824 }
1825 }
1826
1827 // restore the pad charges in case they were modified during the splitting
1828 int iPad(0);
1829 for (auto& pad : *mPreCluster) {
1830 pad.setCharge(padCharges[iPad++]);
1831 }
1832}
1833
1834//_________________________________________________________________________________________________
1835void ClusterFinderOriginal::addPixel(const TH2D& histMLEM, int i0, int j0, std::vector<int>& pixels, std::vector<std::vector<bool>>& isUsed)
1836{
1839
1840 auto itPixel = findPad(mPixels, histMLEM.GetXaxis()->GetBinCenter(i0), histMLEM.GetYaxis()->GetBinCenter(j0), mLowestPixelCharge);
1841 pixels.push_back(std::distance(mPixels.begin(), itPixel));
1842 isUsed[i0 - 1][j0 - 1] = true;
1843
1844 int iMin = TMath::Max(1, i0 - 1);
1845 int iMax = TMath::Min(histMLEM.GetNbinsX(), i0 + 1);
1846 int jMin = TMath::Max(1, j0 - 1);
1847 int jMax = TMath::Min(histMLEM.GetNbinsY(), j0 + 1);
1848 for (int j = jMin; j <= jMax; ++j) {
1849 for (int i = iMin; i <= iMax; ++i) {
1850 if (!isUsed[i - 1][j - 1] && (i == i0 || j == j0) && histMLEM.GetBinContent(i, j) >= mLowestPixelCharge) {
1851 addPixel(histMLEM, i, j, pixels, isUsed);
1852 }
1853 }
1854 }
1855}
1856
1857//_________________________________________________________________________________________________
1858void ClusterFinderOriginal::addCluster(int iCluster, std::vector<int>& coupledClusters, std::vector<bool>& isClUsed,
1859 const std::vector<std::vector<double>>& couplingClCl) const
1860{
1863
1864 coupledClusters.push_back(iCluster);
1865 isClUsed[iCluster] = true;
1866
1867 for (int iCluster2 = 0; iCluster2 < couplingClCl.size(); ++iCluster2) {
1868 if (!isClUsed[iCluster2] && couplingClCl[iCluster][iCluster2] >= SLowestCoupling) {
1869 addCluster(iCluster2, coupledClusters, isClUsed, couplingClCl);
1870 }
1871 }
1872}
1873
1874//_________________________________________________________________________________________________
1875void ClusterFinderOriginal::extractLeastCoupledClusters(std::vector<int>& coupledClusters, std::vector<int>& clustersForFit,
1876 const std::vector<std::vector<double>>& couplingClCl) const
1877{
1880
1881 double minCoupling(DBL_MAX);
1882 int leastCoupledClusters[3] = {-1, -1, -1};
1883
1884 // compute the coupling of each cluster with all the others and find the least coupled
1885 std::vector<double> coupling1(coupledClusters.size(), 0.);
1886 for (int i = 0; i < coupledClusters.size(); ++i) {
1887 for (int j = i + 1; j < coupledClusters.size(); ++j) {
1888 coupling1[i] += couplingClCl[coupledClusters[i]][coupledClusters[j]];
1889 coupling1[j] += couplingClCl[coupledClusters[i]][coupledClusters[j]];
1890 }
1891 if (coupling1[i] < minCoupling) {
1892 leastCoupledClusters[0] = i;
1893 minCoupling = coupling1[i];
1894 }
1895 }
1896
1897 if (SNFitClustersMax > 1) {
1898
1899 bool tryTierce = SNFitClustersMax > 2 && coupledClusters.size() > 5;
1900
1901 for (int i = 0; i < coupledClusters.size(); ++i) {
1902 for (int j = i + 1; j < coupledClusters.size(); ++j) {
1903
1904 // look for a lower coupling with the others by grouping clusters by pair
1905 double coupling2 = coupling1[i] + coupling1[j] - 2. * couplingClCl[coupledClusters[i]][coupledClusters[j]];
1906 if (coupling2 < minCoupling) {
1907 leastCoupledClusters[0] = i;
1908 leastCoupledClusters[1] = j;
1909 leastCoupledClusters[2] = -1;
1910 minCoupling = coupling2;
1911 }
1912
1913 // look for a lower coupling with the others by grouping clusters by tierce
1914 if (tryTierce) {
1915 for (int k = j + 1; k < coupledClusters.size(); ++k) {
1916 double coupling3 = coupling2 + coupling1[k] -
1917 2. * (couplingClCl[coupledClusters[i]][coupledClusters[k]] +
1918 couplingClCl[coupledClusters[j]][coupledClusters[k]]);
1919 if (coupling3 < minCoupling) {
1920 leastCoupledClusters[0] = i;
1921 leastCoupledClusters[1] = j;
1922 leastCoupledClusters[2] = k;
1923 minCoupling = coupling3;
1924 }
1925 }
1926 }
1927 }
1928 }
1929 }
1930
1931 // transfert the least coupled group of clusters to the list to be used for the fit
1932 // take into account the shift of indices each time a cluster is remove
1933 int idxShift(0);
1934 for (int i = 0; i < 3 && leastCoupledClusters[i] >= 0; ++i) {
1935 clustersForFit.push_back(coupledClusters[leastCoupledClusters[i] - idxShift]);
1936 coupledClusters.erase(coupledClusters.begin() + leastCoupledClusters[i] - idxShift);
1937 ++idxShift;
1938 }
1939}
1940
1941//_________________________________________________________________________________________________
1942int ClusterFinderOriginal::selectPads(const std::vector<int>& coupledClusters, const std::vector<int>& clustersForFit,
1943 const std::vector<std::vector<double>>& couplingClPad)
1944{
1946
1947 int nSelectedPads(0);
1948
1949 for (int iPad = 0; iPad < mPreCluster->multiplicity(); ++iPad) {
1950
1951 // exclude pads already used or saturated
1952 auto& pad = mPreCluster->pad(iPad);
1953 if (pad.status() != PadOriginal::kZero || pad.isSaturated()) {
1954 continue;
1955 }
1956
1957 for (int iCluster1 : clustersForFit) {
1958
1959 // select pads coupled with a cluster of pixels used in the fit
1960 if (couplingClPad[iCluster1][iPad] >= SLowestCoupling) {
1961 pad.setStatus(PadOriginal::kUseForFit);
1962 ++nSelectedPads;
1963
1964 // excluding those also coupled with another cluster of pixels not used in the fit
1965 for (int iCluster2 : coupledClusters) {
1966 if (couplingClPad[iCluster2][iPad] >= SLowestCoupling) {
1967 pad.setStatus(PadOriginal::kCoupled);
1968 --nSelectedPads;
1969 break;
1970 }
1971 }
1972
1973 break;
1974 }
1975 }
1976 }
1977
1978 return nSelectedPads;
1979}
1980
1981//_________________________________________________________________________________________________
1982void ClusterFinderOriginal::merge(const std::vector<int>& clustersForFit, const std::vector<int>& coupledClusters,
1983 std::vector<std::vector<int>>& clustersOfPixels,
1984 std::vector<std::vector<double>>& couplingClCl,
1985 std::vector<std::vector<double>>& couplingClPad) const
1986{
1988
1989 for (int iCluster1 : clustersForFit) {
1990
1991 // find the cluster among the others the most coupled with this one
1992 double maxCoupling(-1.);
1993 int iMostCoupled(0);
1994 for (int iCluster2 : coupledClusters) {
1995 if (couplingClCl[iCluster1][iCluster2] > maxCoupling) {
1996 maxCoupling = couplingClCl[iCluster1][iCluster2];
1997 iMostCoupled = iCluster2;
1998 }
1999 }
2000
2001 // copy the pixels of this cluster into the most coupled one
2002 clustersOfPixels[iMostCoupled].insert(clustersOfPixels[iMostCoupled].end(),
2003 clustersOfPixels[iCluster1].begin(), clustersOfPixels[iCluster1].end());
2004
2005 // update the coupling with the other clusters
2006 for (int iCluster2 : coupledClusters) {
2007 if (iCluster2 != iMostCoupled) {
2008 couplingClCl[iMostCoupled][iCluster2] += couplingClCl[iCluster1][iCluster2];
2009 couplingClCl[iCluster2][iMostCoupled] = couplingClCl[iMostCoupled][iCluster2];
2010 }
2011 }
2012
2013 // update the coupling between clusters and pads
2014 for (int iPad = 0; iPad < mPreCluster->multiplicity(); ++iPad) {
2015 if (mPreCluster->pad(iPad).status() == PadOriginal::kZero) {
2016 couplingClPad[iMostCoupled][iPad] += couplingClPad[iCluster1][iPad];
2017 }
2018 }
2019 }
2020}
2021
2022//_________________________________________________________________________________________________
2023void ClusterFinderOriginal::updatePads(const double fitParam[SNFitParamMax + 1], int nParamUsed)
2024{
2026
2027 // get the fraction of charge carried by each fitted cluster
2028 double chargeFraction[SNFitClustersMax] = {0.};
2029 if (nParamUsed > 0) {
2030 param2ChargeFraction(fitParam, nParamUsed, chargeFraction);
2031 }
2032
2033 for (auto& pad : *mPreCluster) {
2034
2035 if (pad.status() == PadOriginal::kUseForFit) {
2036
2037 // discard the pads already used in a fit
2038 pad.setStatus(PadOriginal::kOver);
2039
2040 } else if (pad.status() == PadOriginal::kCoupled) {
2041
2042 // subtract the charge from the fitted clusters
2043 if (nParamUsed > 0) {
2044 double padChargeFit(0.);
2045 for (int iParam = 0; iParam < nParamUsed; iParam += 3) {
2046 padChargeFit += chargeIntegration(fitParam[iParam], fitParam[iParam + 1], pad) * chargeFraction[iParam / 3];
2047 }
2048 padChargeFit *= fitParam[SNFitParamMax];
2049 pad.setCharge(pad.charge() - padChargeFit);
2050 }
2051
2052 // reset the pad status to further use it if its charge is high enough
2053 pad.setStatus((pad.charge() > mLowestPadCharge) ? PadOriginal::kZero : PadOriginal::kOver);
2054 }
2055 }
2056}
2057
2058//_________________________________________________________________________________________________
2059void ClusterFinderOriginal::setClusterResolution(Cluster& cluster) const
2060{
2063
2064 if (cluster.getChamberId() < 4) {
2065
2066 // do not consider mono-cathode clusters in stations 1 and 2
2067 cluster.ex = ClusterizerParam::Instance().defaultClusterResolutionX;
2068 cluster.ey = ClusterizerParam::Instance().defaultClusterResolutionY;
2069
2070 } else {
2071
2072 // find pads below the cluster
2073 int padIDNB(-1), padIDB(-1);
2074 bool padsFound = mSegmentation->findPadPairByPosition(cluster.x, cluster.y, padIDB, padIDNB);
2075
2076 // look for these pads (if any) in the list of digits associated to this cluster
2077 auto itPadNB = mUsedDigits.end();
2078 if (padsFound || mSegmentation->isValid(padIDNB)) {
2079 itPadNB = std::find_if(mUsedDigits.begin() + cluster.firstDigit, mUsedDigits.end(),
2080 [padIDNB](const Digit& digit) { return digit.getPadID() == padIDNB; });
2081 }
2082 auto itPadB = mUsedDigits.end();
2083 if (padsFound || mSegmentation->isValid(padIDB)) {
2084 itPadB = std::find_if(mUsedDigits.begin() + cluster.firstDigit, mUsedDigits.end(),
2085 [padIDB](const Digit& digit) { return digit.getPadID() == padIDB; });
2086 }
2087
2088 // set the cluster resolution accordingly
2089 cluster.ex = (itPadNB == mUsedDigits.end()) ? ClusterizerParam::Instance().badClusterResolutionX
2090 : ClusterizerParam::Instance().defaultClusterResolutionX;
2091 cluster.ey = (itPadB == mUsedDigits.end()) ? ClusterizerParam::Instance().badClusterResolutionY
2092 : ClusterizerParam::Instance().defaultClusterResolutionY;
2093 }
2094}
2095
2096} // namespace o2::mch
Definition of a class to reconstruct clusters with the original MLEM algorithm.
Definition of the cluster used by the original cluster finder algorithm.
Configurable parameters for MCH clustering.
definition of the MCH processing errors
int16_t charge
Definition RawEventData.h:5
int32_t i
float & yMax
Original definition of the Mathieson function.
Definition of the pad used by the original cluster finder algorithm.
uint16_t pos
Definition RawData.h:3
uint32_t j
Definition RawData.h:0
uint32_t side
Definition RawData.h:0
Configurable parameters for MCH charge induction and signal generation.
HMPID cluster implementation.
Definition Cluster.h:27
void findClusters(gsl::span< const Digit > digits)
cluster for internal use
void add(ErrorType errorType, uint32_t id0, uint32_t id1, uint64_t n=1)
Definition ErrorMap.cxx:33
Original Mathieson function.
float integrate(float xMin, float yMin, float xMax, float yMax) const
@ kUseForFit
should be used for fit
Definition PadOriginal.h:37
@ kOver
processing is over
Definition PadOriginal.h:39
@ kMustKeep
do not kill (for pixels)
Definition PadOriginal.h:40
@ kCoupled
coupled to another cluster of pixels
Definition PadOriginal.h:38
@ kZero
pad "basic" state
Definition PadOriginal.h:36
void loadDigit(const Digit &digit)
void getPreClusters(std::vector< o2::mch::PreCluster > &preClusters, std::vector< Digit > &digits)
double padPositionX(int dePadIndex) const
double padSizeY(int dePadIndex) const
const CathodeSegmentation & nonBending() const
const CathodeSegmentation & bending() const
bool findPadPairByPosition(double x, double y, int &bpad, int &nbpad) const
bool isBendingPad(int dePadIndex) const
double padPositionY(int dePadIndex) const
double padSizeX(int dePadIndex) const
bool isValid(int dePadIndex) const
GLdouble n
Definition glcorearb.h:1982
GLint GLenum GLint x
Definition glcorearb.h:403
GLuint entry
Definition glcorearb.h:5735
GLuint GLuint end
Definition glcorearb.h:469
GLint GLsizei width
Definition glcorearb.h:270
GLint y
Definition glcorearb.h:270
GLint GLint GLsizei GLint GLenum GLenum const void * pixels
Definition glcorearb.h:275
GLenum GLfloat param
Definition glcorearb.h:271
GLenum GLint GLint * precision
Definition glcorearb.h:1899
bool isValid(std::string_view dcsAlias)
double padSizeY(int i)
double padSizeX(int i)
O2MCHMAPPINGIMPL3_EXPORT const Segmentation & segmentation(int detElemId)
bool areOverlapping(const PadOriginal &pad1, const PadOriginal &pad2, double precision)
auto findPad(std::vector< PadOriginal > &pads, double x, double y, double minCharge)
std::uniform_real_distribution< float > distX(-127.5, 127.5)
std::uniform_real_distribution< float > distY(-68., 68.)
Enum< T >::Iterator begin(Enum< T >)
Definition Defs.h:173
const int iy
Definition TrackUtils.h:69
const int ix
Definition TrackUtils.h:69
Defining DataPointCompositeObject explicitly as copiable.
constexpr size_t max
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
std::vector< Digit > digits
ArrayADC adc
char const *restrict const cmp
Definition x9.h:96