Project
Loading...
Searching...
No Matches
CalibdEdx.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
13
14#include <algorithm>
15#include <array>
16#include <string>
17#include <vector>
18#include <cmath>
19#include <cstddef>
20#include <gsl/span>
21#include <numeric>
22#include <string_view>
23#include <utility>
24#include <chrono>
25
26// o2 includes
29#include "DataFormatsTPC/Defs.h"
32#include "Framework/Logger.h"
34#include "TPCBase/Utils.h"
36
37// root includes
38#include "TFile.h"
39#include "THn.h"
40#include "TTree.h"
41#include "TLinearFitter.h"
42
43// boost includes
44#include <boost/histogram.hpp>
45
46using namespace o2::tpc;
47namespace bh = boost::histogram;
48
49CalibdEdx::CalibdEdx(int dEdxBins, float mindEdx, float maxdEdx, int angularBins, bool fitSnp)
50 : mFitSnp(fitSnp)
51{
52 const int snpBins = fitSnp ? angularBins : 1;
53 mHist = bh::make_histogram(
54 FloatAxis(dEdxBins, mindEdx * MipScale, maxdEdx * MipScale, "dEdx"),
55 FloatAxis(angularBins, 0, 1, "Tgl"),
56 FloatAxis(snpBins, -1, 1, "Snp"),
57 IntAxis(0, SECTORSPERSIDE * SIDES, "sector"),
58 IntAxis(0, GEMSTACKSPERSECTOR, "stackType"),
59 IntAxis(0, CHARGETYPES, "charge"));
60}
61
63{
64 mFitSnp = other.mFitSnp;
65 mApplyCuts = other.mApplyCuts;
66 mCuts = other.mCuts;
67 mSectorThreshold = other.mSectorThreshold;
68 m1DThreshold = other.m1DThreshold;
69 m2DThreshold = other.m2DThreshold;
70 mFitCut = other.mFitCut;
71 mFitLowCutFactor = other.mFitLowCutFactor;
72 mFitPasses = other.mFitPasses;
73 mTFID = other.mTFID;
74
75 mHist = other.mHist;
76 mCalib = other.mCalib;
77 mCalibIn = other.mCalibIn;
78
79 mMatType = other.mMatType;
80
81 // debug streamer not copied on purpose
82}
83
84void CalibdEdx::fill(const TrackTPC& track)
85{
86 // applying cuts
87 if (track.hasBothSidesClusters() || (mApplyCuts && !mCuts.goodTrack(track))) {
88 return;
89 }
90
91 const auto& dEdx = track.getdEdx();
92 const auto sideOffset = track.hasASideClustersOnly() ? 0 : SECTORSPERSIDE;
93 const std::vector<float> dEdxMax{dEdx.dEdxMaxIROC, dEdx.dEdxMaxOROC1, dEdx.dEdxMaxOROC2, dEdx.dEdxMaxOROC3};
94 const std::vector<float> dEdxTot{dEdx.dEdxTotIROC, dEdx.dEdxTotOROC1, dEdx.dEdxTotOROC2, dEdx.dEdxTotOROC3};
95 std::vector<float> dEdxMaxCorr(4); // for deugging
96 std::vector<float> dEdxTotCorr(4); // for deugging
97
98 // We need a copy of the track to perform propagations
99 o2::track::TrackPar cpTrack = track;
100
101 // Beth-Bloch correction for non MIP tracks
102 const auto& gasParam = ParameterGas::Instance();
103 const float betaGamma = track.getP() / o2::constants::physics::MassPionCharged;
104 const float dEdxScale = MipScale / BetheBlochAleph(betaGamma, gasParam.BetheBlochParam[0],
105 gasParam.BetheBlochParam[1], gasParam.BetheBlochParam[2],
106 gasParam.BetheBlochParam[3], gasParam.BetheBlochParam[4]);
107
108 for (const GEMstack roc : {IROCgem, OROC1gem, OROC2gem, OROC3gem}) {
109 // Local x value of the center pad row of each roc type in cm (IROC, OROC1, ...).
110 constexpr std::array<float, 4> xks{108.475f, 151.7f, 188.8f, 227.65f};
111
112 // propagate track
113 const bool okProp = o2::base::Propagator::Instance()->PropagateToXBxByBz(cpTrack, xks[roc], 0.9f, 2., mMatType);
114 if (!okProp) {
115 continue;
116 }
117
118 // If the track was propagated to a different sector we need to rotate the local frame to get the correct Snp value
119 float sector = std::floor(18.f * cpTrack.getPhiPos() / o2::constants::math::TwoPI);
120 if (mFitSnp) {
121 float localFrame = std::floor(18.f * o2::math_utils::to02PiGen(cpTrack.getAlpha()) / o2::constants::math::TwoPI);
122 if (std::abs(sector - localFrame) > 0.1) {
123 const float alpha = SECPHIWIDTH * (0.5 + sector);
124 cpTrack.rotateParam(alpha);
125 }
126 }
127 const float snp = cpTrack.getSnp();
128 const float tgl = cpTrack.getTgl();
129 const float scaledTgl = scaleTgl(std::abs(tgl), roc);
130 if (track.hasCSideClusters()) {
131 sector += SECTORSPERSIDE;
132 }
133
134 // undo previously done corrections, to allow for residual corrections
135 // output will still be the full correction
136 const float corrMax = mCalibIn.getCorrection(StackID{static_cast<int>(sector), roc}, ChargeType::Max, tgl, snp);
137 const float corrTot = mCalibIn.getCorrection(StackID{static_cast<int>(sector), roc}, ChargeType::Tot, tgl, snp);
138 dEdxMaxCorr[roc] = corrMax;
139 dEdxTotCorr[roc] = corrTot;
140
141 static bool reported = false;
142 if (!reported && mCalibIn.getDims() >= 0) {
143 const auto meanParamTot = mCalibIn.getMeanParams(ChargeType::Tot);
144 LOGP(info, "Undoing previously applied corrections with mean qTot Params {}", utils::elementsToString(meanParamTot));
145 reported = true;
146 }
147
148 mHist(dEdxMax[roc] * dEdxScale * corrMax, scaledTgl, snp, sector, roc, ChargeType::Max);
149 mHist(dEdxTot[roc] * dEdxScale * corrTot, scaledTgl, snp, sector, roc, ChargeType::Tot);
150 }
151
152 if (mDebugOutputStreamer) {
153 const float tgl = track.getTgl();
154 const float p = track.getP();
155
156 (*mDebugOutputStreamer) << "dedx"
157 << "dEdxMax=" << dEdxMax
158 << "dEdxMaxCorr=" << dEdxMaxCorr
159 << "dEdxTot=" << dEdxTot
160 << "dEdxTotCorr=" << dEdxTotCorr
161 << "tgl=" << tgl
162 << "p=" << p
163 << "tfid=" << mTFID
164 << "\n";
165 }
166}
167
168void CalibdEdx::fill(const gsl::span<const TrackTPC> tracks)
169{
170 for (const auto& track : tracks) {
171 fill(track);
172 }
173}
174
176{
177 if (other != nullptr) {
178 mHist += other->getHist();
179 }
180}
181
182template <typename Hist>
183void fitHist(const Hist& hist, CalibdEdxCorrection& corr, TLinearFitter& fitter,
184 const float dEdxCut, const float dEdxLowCutFactor, const int passes, const CalibdEdxCorrection* stackMean = nullptr, o2::utils::TreeStreamRedirector* streamer = nullptr)
185{
186 using timer = std::chrono::high_resolution_clock;
187 using ax = CalibdEdx::Axis;
188 auto start = timer::now();
189
190 // number of bins per stack
191 int stackBins = 1;
192 for (int i = 0; i < ax::Sector; ++i) {
193 stackBins *= hist.axis(i).size();
194 }
195 const bool projSectors = stackMean != nullptr;
196
197 constexpr int sectors = SECTORSPERSIDE * SIDES;
198 constexpr int stackCount = 144;
199 // number of fits to perform
200 const int fitCount = projSectors ? GEMSTACKSPERSECTOR * CHARGETYPES : stackCount * CHARGETYPES;
201 // number of GEM stacks per fit
202 const int fitStacks = projSectors ? sectors : 1;
203
204 for (int fitPass = 0; fitPass < passes; ++fitPass) {
205
206 auto entry = bh::indexed(hist).begin();
207 for (int fit = 0; fit < fitCount; ++fit) {
208 int entries = 0;
209 int outliers = 0;
210 StackID id{};
211 id.type = static_cast<GEMstack>(entry->bin(ax::Stack).center());
212 const auto charge = static_cast<ChargeType>(entry->bin(ax::Charge).center());
213 fitter.ClearPoints();
214
215 for (int stack = 0; stack < fitStacks; ++stack) {
216 id.sector = static_cast<int>(entry->bin(ax::Sector).center());
217
218 for (int bin = 0; bin < stackBins; ++bin, ++entry) {
219 const int counts = *entry;
220 // skip empty bin
221 if (counts == 0) {
222 continue;
223 }
224 entries += counts;
225
226 double dEdx = entry->bin(ax::dEdx).center();
227 double inputs[] = {
228 CalibdEdx::recoverTgl(entry->bin(ax::Tgl).center(), id.type),
229 entry->bin(ax::Snp).center()};
230
231 // ignore tracks with dEdx above a threshold defined by previous fit
232 if (fitPass > 0) {
233 float oldCorr = corr.getCorrection(id, charge, inputs[0], inputs[1]);
234 float lowerCut = (1.f - dEdxLowCutFactor * dEdxCut) * oldCorr;
235 float upperCut = (1.f + dEdxCut) * oldCorr;
236 if (dEdx < lowerCut || dEdx > upperCut) {
237 outliers += counts;
238 continue;
239 }
240 // LOGP(info, "sector: {}, gemType: {}, bin: {}, fitPass: {}, oldCorr: {}, lowerCut: {}, upperCut: {}, dEdx: {}, counts: {}", id.sector, id.type, bin, fitPass, oldCorr, lowerCut, upperCut, dEdx, counts);
241 }
242
243 // scale fitted dEdx using the stacks mean
244 if (stackMean != nullptr) {
245 dEdx /= stackMean->getCorrection(id, charge);
246 }
247 const double error = 1. / sqrt(counts);
248 fitter.AddPoint(inputs, dEdx, error);
249
250 if (streamer) {
251 float oldCorr = corr.getCorrection(id, charge, inputs[0], inputs[1]);
252 float lowerCut = (1.f - dEdxLowCutFactor * dEdxCut) * oldCorr;
253 float upperCut = (1.f + dEdxCut) * oldCorr;
254
255 (*streamer) << "fit_standard"
256 << "dedx=" << dEdx
257 << "itgl=" << hist.axis(ax::Tgl).index(entry->bin(ax::Tgl).center())
258 << "snp=" << inputs[1]
259 << "iter=" << fitPass
260 << "ifit=" << fit
261 << "bin=" << bin
262 << "isector=" << int(id.sector)
263 << "istack=" << int(id.type)
264 << "icharge=" << int(charge)
265 << "counts=" << counts
266 << "oldCorr=" << oldCorr
267 << "lowerCut=" << lowerCut
268 << "upperCut=" << upperCut
269 << "\n";
270 }
271 }
272 }
273 fitter.Eval();
274
275 const auto paramSize = CalibdEdxCorrection::ParamSize;
276 float params[paramSize] = {0};
277 for (int param = 0; param < fitter.GetNumberFreeParameters(); ++param) {
278 params[param] = fitter.GetParameter(param);
279 }
280
281 // with a projected hist, copy the fit to every sector
282 if (projSectors) {
283 for (int i = 0; i < sectors; ++i) {
284 id.sector = i;
285 const float mean = stackMean->getCorrection(id, charge);
286
287 // rescale the params to get the true correction
288 float scaledParams[paramSize];
289 for (int i = 0; i < paramSize; ++i) {
290 scaledParams[i] = params[i] * mean;
291 }
292 corr.setParams(id, charge, scaledParams);
293 corr.setChi2(id, charge, fitter.GetChisquare());
294 corr.setEntries(id, charge, entries);
295 }
296 } else {
297 corr.setParams(id, charge, params);
298 corr.setChi2(id, charge, fitter.GetChisquare());
299 corr.setEntries(id, charge, entries);
300 }
301 LOGP(debug, "Sector: {}, gemType: {}, charge: {}, Fit pass: {} with {} % outliers in {} entries. Fitter Points: {}, mean fit: {}",
302 id.sector, int(id.type), int(charge), fitPass, (float)outliers / (float)entries * 100, entries, fitter.GetNpoints(), params[0]);
303 }
304 }
305 auto stop = timer::now();
306 std::chrono::duration<float> time = stop - start;
307 LOGP(info, "Calibration fits took: {}", time.count());
308}
309
310template <typename Hist>
311auto ProjectBoostHistoXFast(const Hist& hist, std::vector<int>& bin_indices, int axis)
312{
313 const unsigned int nbins = hist.axis(axis).size();
314 const double binStartX = hist.axis(axis).bin(0).lower();
315 const double binEndX = hist.axis(axis).bin(nbins - 1).upper();
316 auto histoProj = boost::histogram::make_histogram(CalibdEdx::FloatAxis(nbins, binStartX, binEndX));
317
318 // loop over all bins of the requested axis
319 for (int i = 0; i < nbins; ++i) {
320 // setting bin of the requested axis
321 bin_indices[axis] = i;
322
323 // access the bin content specified by bin_indices
324 histoProj.at(i) = hist.at(bin_indices);
325 }
326
327 return histoProj;
328}
329
330template <typename Hist>
331auto ProjectBoostHistoXFastAllSectors(const Hist& hist, std::vector<int>& bin_indices, StackID id, const CalibdEdxCorrection* stackMean)
332{
333 // get an average histogram over all stacks of the same type
334 using ax = CalibdEdx::Axis;
335 const unsigned int nbinsdedx = hist.axis(ax::dEdx).size();
336 const double binStartX = hist.axis(ax::dEdx).bin(0).lower();
337 const double binEndX = hist.axis(ax::dEdx).bin(nbinsdedx - 1).upper();
338
339 // make fine histogram to be able to correctly store normalized dEdx values
340 auto histoProj = boost::histogram::make_histogram(CalibdEdx::FloatAxis(nbinsdedx, binStartX, binEndX));
341
342 // loop over sectors for merging the histograms
343 for (int iSec = 0; iSec < hist.axis(ax::Sector).size(); ++iSec) {
344 bin_indices[ax::Sector] = iSec;
345 id.sector = static_cast<int>(hist.axis(ax::Sector).bin(iSec).center());
346
347 // loop over all bins of the requested axis
348 for (int i = 0; i < nbinsdedx; ++i) {
349 // setting bin of the requested axis
350 bin_indices[ax::dEdx] = i;
351
352 // access the bin content specified by bin_indices
353 const float counts = hist.at(bin_indices);
354 float dEdx = hist.axis(ax::dEdx).bin(i).center();
355
356 // scale the dedx to the mean
357 if (stackMean != nullptr) {
358 const auto charge = static_cast<ChargeType>(bin_indices[ax::Charge]);
359 dEdx /= stackMean->getCorrection(id, charge);
360 }
361
362 // fill the histogram with dedx as the bin center and the counts as the weight
363 histoProj(dEdx, bh::weight(counts));
364 }
365 }
366 return histoProj;
367}
368
369void CalibdEdx::fitHistGaus(TLinearFitter& fitter, CalibdEdxCorrection& corr, const CalibdEdxCorrection* stackMean)
370{
371 using timer = std::chrono::high_resolution_clock;
372 using ax = CalibdEdx::Axis;
373 auto start = timer::now();
374 const bool projSectors = stackMean != nullptr;
375 constexpr int sectors = SECTORSPERSIDE * SIDES;
376 for (int iSnp = 0; iSnp < mHist.axis(ax::Snp).size(); ++iSnp) {
377 const int iSecEnd = projSectors ? 1 : mHist.axis(ax::Sector).size();
378 for (int iSec = 0; iSec < iSecEnd; ++iSec) {
379 for (int iStack = 0; iStack < mHist.axis(ax::Stack).size(); ++iStack) {
380 for (int iCharge = 0; iCharge < mHist.axis(ax::Charge).size(); ++iCharge) {
381
382 fitter.ClearPoints();
383 StackID id{};
384 id.type = static_cast<GEMstack>(mHist.axis(ax::Stack).bin(iStack).center());
385 id.sector = static_cast<int>(mHist.axis(ax::Sector).bin(iSec).center());
386 const auto charge = static_cast<ChargeType>(mHist.axis(ax::Charge).bin(iCharge).center());
387 int entries = 0;
388
389 for (int iTgl = 0; iTgl < mHist.axis(ax::Tgl).size(); ++iTgl) {
390 // calculate sigma vs tgl in first iteration
391 // apply cut in n sigma in second iteration
392 float sigma_vs_tgl = 0;
393 float mean_vs_tgl = 0;
394 std::vector<int> bin_indices(ax::Size);
395 bin_indices[ax::Tgl] = iTgl;
396 bin_indices[ax::Snp] = iSnp;
397 bin_indices[ax::Sector] = iSec;
398 bin_indices[ax::Stack] = iStack;
399 bin_indices[ax::Charge] = iCharge;
400
401 for (int iter = 0; iter < 2; ++iter) {
402 auto boostHist1d = projSectors ? ProjectBoostHistoXFastAllSectors(mHist, bin_indices, id, stackMean) : ProjectBoostHistoXFast(mHist, bin_indices, ax::dEdx);
403
404 float lowerCut = 0;
405 float upperCut = 0;
406
407 // make gaussian fit
408 if (iter == 0) {
409 int maxElementIndex = std::max_element(boostHist1d.begin(), boostHist1d.end()) - boostHist1d.begin() - 1;
410 if (maxElementIndex < 0) {
411 maxElementIndex = 0;
412 }
413 float maxElementCenter = 0.5 * (boostHist1d.axis(0).bin(maxElementIndex).upper() + boostHist1d.axis(0).bin(maxElementIndex).lower());
414
415 lowerCut = (1.f - mFitLowCutFactor * mFitCut) * maxElementCenter;
416 upperCut = (1.f + mFitCut) * maxElementCenter;
417 } else {
418 lowerCut = mean_vs_tgl - sigma_vs_tgl * mSigmaLower;
419 upperCut = mean_vs_tgl + sigma_vs_tgl * mSigmaUpper;
420 }
421
422 // Restrict fit range to maximum +- restrictFitRangeToMax
423 double max_range = mHist.axis(ax::dEdx).bin(mHist.axis(ax::dEdx).size() - 1).lower();
424 double min_range = mHist.axis(ax::dEdx).bin(0).lower();
425 if ((upperCut <= lowerCut) || (lowerCut > max_range) || (upperCut < min_range)) {
426 break;
427 }
428
429 // remove up and low bins
430 boostHist1d = boost::histogram::algorithm::reduce(boostHist1d, boost::histogram::algorithm::shrink(lowerCut, upperCut));
431
432 try {
433 auto fitValues = o2::utils::fitGaus<float>(boostHist1d.begin(), boostHist1d.end(), o2::utils::BinCenterView(boostHist1d.axis(0).begin()), false);
434
435 // add the mean from gaus fit to the fitter
436 double dEdx = fitValues[1];
437 double inputs[] = {
438 CalibdEdx::recoverTgl(mHist.axis(ax::Tgl).bin(iTgl).center(), id.type),
439 mHist.axis(ax::Snp).bin(iSnp).center()};
440
441 const auto fitNPoints = fitValues[3];
442 const float sigma = fitValues[2];
443 const double fitMeanErr = (fitNPoints > 0) ? (sigma / std::sqrt(fitNPoints)) : -1;
444 if (iter == 0) {
445 sigma_vs_tgl = sigma;
446 mean_vs_tgl = dEdx;
447 } else {
448 entries += fitNPoints;
449 if (fitMeanErr > 0) {
450 fitter.AddPoint(inputs, dEdx, fitMeanErr);
451 }
452 }
453
454 if (mDebugOutputStreamer) {
455 const int nbinsx = boostHist1d.axis(0).size();
456 std::vector<float> binCenter(nbinsx);
457 std::vector<float> dedx(nbinsx);
458 for (int ix = 0; ix < nbinsx; ix++) {
459 binCenter[ix] = boostHist1d.axis(0).bin(ix).center();
460 dedx[ix] = boostHist1d.at(ix);
461 }
462
463 (*mDebugOutputStreamer) << "fit_gaus"
464 << "fitConstant=" << fitValues[0]
465 << "fitMean=" << dEdx
466 << "fitMeanErr=" << fitMeanErr
467 << "fitSigma=" << sigma_vs_tgl
468 << "fitSum=" << fitNPoints
469 << "dedx=" << binCenter
470 << "counts=" << dedx
471 << "itgl=" << bin_indices[1]
472 << "isnp=" << bin_indices[2]
473 << "isector=" << bin_indices[3]
474 << "istack=" << bin_indices[4]
475 << "icharge=" << bin_indices[5]
476 << "upperCut=" << upperCut
477 << "lowerCut=" << lowerCut
478 << "mFitCut=" << mFitCut
479 << "mFitLowCutFactor=" << mFitLowCutFactor
480 << "iter=" << iter
481 << "mSigmaLower=" << mSigmaLower
482 << "mSigmaUpper=" << mSigmaUpper
483 << "projSectors=" << projSectors
484 << "\n";
485 }
486 } catch (o2::utils::FitGausError_t err) {
487 LOGP(warning, "Skipping bin: iTgl {} iSnp {} iSec {} iStack {} iCharge {} iter {}", iTgl, iSnp, iSec, iStack, iCharge, iter);
488 LOG(warning) << createErrorMessageFitGaus(err);
489 break;
490 }
491 }
492 }
493
494 const int fitStatus = fitter.Eval();
495 if (fitStatus > 0) {
496 LOGP(warning, "Fit failed");
497 continue;
498 }
499
500 const auto paramSize = CalibdEdxCorrection::ParamSize;
501 float params[paramSize] = {0};
502 for (int param = 0; param < fitter.GetNumberFreeParameters(); ++param) {
503 params[param] = fitter.GetParameter(param);
504 }
505
506 // with a projected hist, copy the fit to every sector
507 if (projSectors) {
508 for (int i = 0; i < sectors; ++i) {
509 id.sector = i;
510 const float mean = stackMean->getCorrection(id, charge);
511
512 // rescale the params to get the true correction
513 float scaledParams[paramSize];
514 for (int i = 0; i < paramSize; ++i) {
515 scaledParams[i] = params[i] * mean;
516 }
517 corr.setParams(id, charge, scaledParams);
518 corr.setChi2(id, charge, fitter.GetChisquare());
519 corr.setEntries(id, charge, entries);
520 }
521 } else {
522 corr.setParams(id, charge, params);
523 corr.setChi2(id, charge, fitter.GetChisquare());
524 corr.setEntries(id, charge, entries);
525 }
526 }
527 }
528 }
529 }
530 auto stop = timer::now();
531 std::chrono::duration<float> time = stop - start;
532 LOGP(info, "Calibration fits took: {}", time.count());
533}
534
535void CalibdEdx::finalize(const bool useGausFits, const bool averageSectors)
536{
537 const float entries = minStackEntries();
538 mCalib.clear();
539
540 TLinearFitter fitter(2);
541 // Choose the fit dimension based on the available statistics
542 if (mFitSnp && entries >= m2DThreshold) {
543 fitter.SetFormula("1 ++ x ++ x*x ++ x*x*x ++ x*x*x*x ++ y ++ y*y ++ x*y");
544 mCalib.setDims(2);
545 } else if (entries >= m1DThreshold) {
546 fitter.SetFormula("1 ++ x ++ x*x ++ x*x*x ++ x*x*x*x");
547 mCalib.setDims(1);
548 } else {
549 fitter.SetFormula("1");
550 mCalib.setDims(0);
551 }
552 LOGP(info, "Fitting {}D dE/dx correction for GEM stacks with gaussian fits {}, minStackEntries {}, m2DThreshold {}, m1DThreshold {}, mFitSnp {}",
553 mCalib.getDims(), useGausFits, entries, m2DThreshold, m1DThreshold, mFitSnp);
554
555 // if entries below minimum sector threshold, integrate all sectors
556 if (mCalib.getDims() == 0 || entries >= mSectorThreshold) {
557 if (!useGausFits) {
558 fitHist(mHist, mCalib, fitter, mFitCut, mFitLowCutFactor, mFitPasses, nullptr, mDebugOutputStreamer.get());
559 } else {
560 fitHistGaus(fitter, mCalib, nullptr);
561 }
562 } else {
563 LOGP(info, "Integrating GEM stacks sectors in dE/dx correction due to low statistics");
564
565 // get mean of each GEM stack
566 CalibdEdxCorrection meanCorr{};
567 meanCorr.setDims(0);
568 if (averageSectors) {
569 // set mean dEdx per stack to unity
570 meanCorr.setUnity();
571 } else {
572 // get the mean dEdx for each stack
573 TLinearFitter meanFitter(0);
574 meanFitter.SetFormula("1");
575 fitHist(mHist, meanCorr, meanFitter, mFitCut, mFitLowCutFactor, mFitPasses, nullptr, mDebugOutputStreamer.get());
576 }
577 if (!useGausFits) {
578 // get higher dimension corrections with projected sectors
579 fitHist(mHist, mCalib, fitter, mFitCut, mFitLowCutFactor, mFitPasses, &meanCorr, mDebugOutputStreamer.get());
580 } else {
581 // get higher dimension corrections with projected sectors
582 fitHistGaus(fitter, mCalib, &meanCorr);
583 }
584 }
585}
586
588{
589 // sum over the dEdx and track-param bins to get the number of entries per stack and charge
590 auto projection = bh::algorithm::project(mHist, std::vector<int>{Axis::Sector, Axis::Stack, Axis::Charge});
591 auto dEdxCounts = bh::indexed(projection);
592 // find the stack with the least number of entries
593 // use explicit int comparator to avoid ambiguous operator< between accessor and unlimited_storage::reference (boost/clang issue)
594 auto min_it = std::min_element(dEdxCounts.begin(), dEdxCounts.end(),
595 [](const auto& a, const auto& b) { return static_cast<int>(*a) < static_cast<int>(*b); });
596 return *min_it;
597}
598
599bool CalibdEdx::hasEnoughData(float minEntries) const
600{
601 return minStackEntries() >= minEntries;
602}
603
604THnF* CalibdEdx::getTHnF() const
605{
606 std::vector<int> bins{};
607 std::vector<double> axisMin{};
608 std::vector<double> axisMax{};
609
610 const size_t histRank = mHist.rank();
611
612 for (size_t i = 0; i < histRank; ++i) {
613 const auto& ax = mHist.axis(i);
614 bins.push_back(ax.size());
615 axisMin.push_back(*ax.begin());
616 axisMax.push_back(*ax.end());
617 }
618
619 auto hn = new THnF("hdEdxMIP", "MIP dEdx per GEM stack", histRank, bins.data(), axisMin.data(), axisMax.data());
620 return hn;
621}
622
624{
625 auto hn = getTHnF();
626 const size_t histRank = mHist.rank();
627 std::vector<double> xs(histRank);
628 for (auto&& entry : bh::indexed(mHist)) {
629 if (*entry == 0) {
630 continue;
631 }
632 for (int i = 0; i < histRank; ++i) {
633 xs[i] = entry.bin(i).center();
634 }
635
636 hn->Fill(xs.data(), *entry);
637 }
638 return hn;
639}
640
641void CalibdEdx::setFromRootHist(const THnF* hist)
642{
643 // Get the number of dimensions
644 int n_dim = hist->GetNdimensions();
645
646 // Vectors to store axis ranges and bin counts
647 std::vector<std::pair<double, double>> axis_ranges(n_dim); // Min and max of each axis
648 std::vector<int> bin_counts(n_dim); // Number of bins in each dimension
649
650 // Loop over each dimension to extract the bin edges and ranges
651 for (int dim = 0; dim < n_dim; ++dim) {
652 TAxis* axis = hist->GetAxis(dim);
653 int bins = axis->GetNbins();
654 double min = axis->GetXmin();
655 double max = axis->GetXmax();
656 bin_counts[dim] = bins;
657 axis_ranges[dim] = {min, max}; // Store the min and max range for the axis
658 }
659
660 // Define a Boost histogram using the bin edges
661 mHist = bh::make_histogram(
662 FloatAxis(bin_counts[0], axis_ranges[0].first, axis_ranges[0].second, "dEdx"), // dE/dx
663 FloatAxis(bin_counts[1], axis_ranges[1].first, axis_ranges[1].second, "Tgl"), // Tgl
664 FloatAxis(bin_counts[2], axis_ranges[2].first, axis_ranges[2].second, "Snp"), // snp
665 IntAxis(0, bin_counts[3], "sector"), // sector
666 IntAxis(0, bin_counts[4], "stackType"), // stack type
667 IntAxis(0, bin_counts[5], "charge") // charge type
668 );
669
670 // Fill the Boost histogram with the bin contents from the ROOT histogram
671 int total_bins = hist->GetNbins();
672 for (int bin = 0; bin < total_bins; ++bin) {
673 std::vector<int> bin_indices(n_dim);
674 double content = hist->GetBinContent(bin, bin_indices.data()); // Get bin coordinates
675
676 // Check if any coordinate is in the underflow (0) or overflow (nbins+1) bins
677 bool is_underflow_or_overflow = false;
678 for (int dim = 0; dim < n_dim; ++dim) {
679 if ((bin_indices[dim] == 0) || (bin_indices[dim] == bin_counts[dim] + 1)) {
680 is_underflow_or_overflow = true;
681 break;
682 }
683 }
684
685 // Skip underflow/overflow bins
686 if (is_underflow_or_overflow) {
687 continue;
688 }
689
690 // Assign the content to the corresponding bin in the Boost histogram
691 mHist.at(bin_indices[0] - 1, bin_indices[1] - 1, bin_indices[2] - 1, bin_indices[3] - 1, bin_indices[4] - 1, bin_indices[5] - 1) = content;
692 }
693}
694
696{
697 const int uniqueEntries = std::accumulate(mHist.begin(), mHist.end(), 0.0) / GEMSTACKSPERSECTOR / 2;
698 LOGP(info, "Total number of track entries: {}. Min. entries per GEM stack: {}", uniqueEntries, minStackEntries());
699}
700
701void CalibdEdx::writeTTree(std::string_view fileName) const
702{
703 TFile f(fileName.data(), "recreate");
704
705 TTree tree("hist", "Saving boost histogram to TTree");
706
707 // FIXME: infer axis type and remove the hardcoded float
708 std::vector<float> row(mHist.rank());
709 for (int i = 0; i < mHist.rank(); ++i) {
710 tree.Branch(mHist.axis(i).metadata().c_str(), &row[i]);
711 }
712 float count = 0;
713 tree.Branch("counts", &count);
714
715 for (auto&& entry : bh::indexed(mHist)) {
716 if (*entry == 0) {
717 continue;
718 }
719 for (int i = 0; i < mHist.rank(); ++i) {
720 // Rescale Tgl
721 if (Axis::Tgl == i) {
722 row[i] = recoverTgl(entry.bin(i).center(), static_cast<GEMstack>(entry.bin(Axis::Stack).center()));
723 } else {
724 row[i] = entry.bin(i).center();
725 }
726 }
727 count = *entry;
728 tree.Fill();
729 }
730
731 f.Write();
732 f.Close();
733}
734
735void CalibdEdx::enableDebugOutput(std::string_view fileName)
736{
737 mDebugOutputStreamer = std::make_unique<o2::utils::TreeStreamRedirector>(fileName.data(), "recreate");
738}
739
741{
742 // This will call the TreeStream destructor and write any stored data.
743 mDebugOutputStreamer.reset();
744}
745
747{
748 if (mDebugOutputStreamer) {
749 LOGP(info, "Closing dump file");
750 mDebugOutputStreamer->Close();
751 }
752}
753
754void CalibdEdx::dumpToFile(const char* outFile)
755{
756 TFile f(outFile, "RECREATE");
757 f.WriteObject(this, "calib");
758 const auto* thn = getRootHist();
759 f.WriteObject(thn, "histogram_data");
760}
761
763{
764 std::unique_ptr<TFile> f(TFile::Open(inFile));
765 if (!f || f->IsZombie()) {
766 LOGP(error, "Could not open file: {}", inFile);
767 CalibdEdx calTmp;
768 return calTmp;
769 }
770
771 auto obj = f->Get<CalibdEdx>("calib");
772 if (!obj) {
773 LOGP(error, "Could not read CalibdEdx object from file: {}", inFile);
774 CalibdEdx calTmp;
775 return calTmp;
776 }
777
778 THnF* hTmp = f->Get<THnF>("histogram_data");
779
780 CalibdEdx cal(*obj);
781 delete obj;
782
783 if (!hTmp) {
784 LOGP(warning, "Could not read histogram from file: {}. Returning empty histogram", inFile);
785 return cal;
786 }
787 cal.setFromRootHist(hTmp);
788 return cal;
789}
790
791void CalibdEdx::setSigmaFitRange(const float lowerSigma, const float upperSigma)
792{
793 mSigmaUpper = upperSigma;
794 mSigmaLower = lowerSigma;
795}
auto ProjectBoostHistoXFast(const Hist &hist, std::vector< int > &bin_indices, int axis)
auto ProjectBoostHistoXFastAllSectors(const Hist &hist, std::vector< int > &bin_indices, StackID id, const CalibdEdxCorrection *stackMean)
void fitHist(const Hist &hist, CalibdEdxCorrection &corr, TLinearFitter &fitter, const float dEdxCut, const float dEdxLowCutFactor, const int passes, const CalibdEdxCorrection *stackMean=nullptr, o2::utils::TreeStreamRedirector *streamer=nullptr)
This file provides the container used for time based residual dE/dx calibration.
constexpr std::array< float, 5 > xks
Definition PID.cxx:39
std::ostringstream debug
int16_t charge
Definition RawEventData.h:5
int16_t time
Definition RawEventData.h:4
std::vector< long > entries
int32_t i
configStandalone Light OpenGL display display_light Apply smoothing to points Enable Z buffer Highlight links Highlight seeds Highlight sector tracks Highlight final tracks Show individual Show related Highlight grid Show only individual collision Color clusters belonging to track objects Set line Enable drawing TPC data Enable drawing TOF data Enable drawing magnetic field Set field line step count Invert colors configStandalone Heavy OpenGL display display_heavy Show tracks with ITS contribution Show tracks with TOF contribution Enable propagation of loopers Show clusters at nominal x of pad row for early transformed data Split CE tracks when they cross the central Mark clusters Mark adjacent clusters Hide rejected tracks Apply filter on tracks to be displayed Separate sectors
configStandalone Light OpenGL display display_light Apply smoothing to points Enable Z buffer Highlight links Highlight seeds Highlight sector tracks Highlight final tracks Show individual sector
char error[CURL_ERROR_SIZE]
std::vector< o2::its::TrackITS > tracks
bounded_vector< float > bins
double lower[3]
Definition of the parameter class for the detector gas.
Header to collect physics constants.
uint32_t roc
Definition RawData.h:3
uint32_t stack
Definition RawData.h:1
GPUd() value_type estimateLTFast(o2 static GPUd() float estimateLTIncrement(const o2 PropagatorImpl * Instance(bool uninitialized=false)
Definition Propagator.h:180
const std::array< float, ParamSize > getMeanParams(ChargeType charge) const
Parameters averaged over all stacks.
void setUnity()
set all corrections to 1, used for default initialization and to reset corrections
Class that creates dE/dx histograms from a sequence of tracks objects.
Definition CalibdEdx.h:43
void disableDebugOutput()
Disable debug output to file. Also writes and closes stored time slots.
static constexpr float recoverTgl(float scaledTgl, GEMstack rocType)
Definition CalibdEdx.h:178
static constexpr float scaleTgl(float tgl, GEMstack rocType)
Definition CalibdEdx.h:177
void print() const
Print statistics info.
void finalize(const bool useGausFits=true, const bool averageSectors=false)
boost::histogram::axis::regular< float, boost::histogram::use_default, boost::histogram::use_default, boost::histogram::axis::option::none_t > FloatAxis
Definition CalibdEdx.h:58
boost::histogram::axis::integer< int, boost::histogram::use_default, boost::histogram::axis::option::none_t > IntAxis
Definition CalibdEdx.h:56
void merge(const CalibdEdx *other)
Add counts from another container.
THnF * getRootHist() const
Return calib data as a THn.
int minStackEntries() const
Return the number of hist entries of the gem stack with less statistics.
void dumpToFile(const char *outFile)
dump this object to a file - the boost histogram is converted to a ROOT histogram -
void finalizeDebugOutput() const
Write debug output to file.
void writeTTree(std::string_view fileName) const
Save the histograms to a TTree.
void fill(const TrackTPC &tracks)
Fill histograms using tracks data.
Definition CalibdEdx.cxx:84
void setSigmaFitRange(const float lowerSigma, const float upperSigma)
void enableDebugOutput(std::string_view fileName)
Enable debug output to file of the time slots calibrations outputs and dE/dx histograms.
bool hasEnoughData(float minEntries) const
Check if there are enough data to compute the calibration.
static CalibdEdx readFromFile(const char *inFile)
read the object from a file
static constexpr float MipScale
Inverse of target dE/dx value for MIPs.
Definition CalibdEdx.h:175
CalibdEdx(const CalibdEdx &other)
copy ctor
Definition CalibdEdx.cxx:62
bool goodTrack(o2::tpc::TrackTPC const &track)
Definition TrackCuts.cxx:31
Axis iterator over bin centers.
GLfloat GLfloat GLfloat alpha
Definition glcorearb.h:279
GLint GLsizei count
Definition glcorearb.h:399
GLuint entry
Definition glcorearb.h:5735
GLsizeiptr size
Definition glcorearb.h:659
GLint first
Definition glcorearb.h:399
GLdouble f
Definition glcorearb.h:310
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLint GLint GLsizei GLint GLenum GLenum type
Definition glcorearb.h:275
GLenum const GLfloat * params
Definition glcorearb.h:272
GLuint start
Definition glcorearb.h:469
GLenum GLfloat param
Definition glcorearb.h:271
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
double mean(std::vector< double >::const_iterator first, std::vector< double >::const_iterator last)
float to02PiGen(float phi)
Definition Utils.h:70
std::string elementsToString(Iterator begin, Iterator end, const std::string separator=", ")
Definition Utils.h:74
Global TPC definitions and constants.
GEMstack
TPC GEM stack types.
Definition Defs.h:55
@ IROCgem
Definition Defs.h:55
@ OROC1gem
Definition Defs.h:56
@ OROC3gem
Definition Defs.h:58
@ OROC2gem
Definition Defs.h:57
ChargeType
Definition Defs.h:72
@ Tot
Definition Defs.h:74
@ Max
Definition Defs.h:73
constexpr int ix
Definition TrackUtils.h:69
FitGausError_t
Error code for invalid result in the fitGaus process.
std::string createErrorMessageFitGaus(o2::utils::FitGausError_t errorcode)
Printing an error message when then fit returns an invalid result.
GEM stack identification.
Definition Defs.h:79
GEMstack type
Definition Defs.h:81
constexpr size_t min
constexpr size_t max
VectorOfTObjectPtrs other
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
std::unique_ptr< TTree > tree((TTree *) flIn.Get(std::string(o2::base::NameConf::CTFTREENAME).c_str()))
std::vector< int > row