Belle II Software development
PXDDataMCGainCalibrationAlgorithm.cc
1/**************************************************************************
2 * basf2 (Belle II Analysis Software Framework) *
3 * Author: The Belle II Collaboration *
4 * *
5 * See git log for contributors and copyright holders. *
6 * This file is licensed under LGPL-3.0, see LICENSE.md. *
7 **************************************************************************/
8
9#include <pxd/calibration/PXDDataMCGainCalibrationAlgorithm.h>
10#include <pxd/dbobjects/PXDClusterChargeMapPar.h>
11#include <pxd/dbobjects/PXDGainMapPar.h>
12
13#include <string>
14#include <algorithm>
15#include <map>
16#include <set>
17
18#include <sstream>
19#include <iostream>
20
21#include <boost/format.hpp>
22#include <cmath>
23
24
25//ROOT
26#include <TRandom.h>
27#include <TF1.h>
28#include <TH2I.h>
29#include <TCanvas.h>
30
31using namespace std;
32using boost::format;
33using namespace Belle2;
34
35
36// Anonymous namespace for data objects used by PXDDataMCGainCalibrationAlgorithm class
37namespace {
38
40 int m_signal;
42 int m_run;
44 int m_exp;
45
47 void getNumberOfBins(const std::shared_ptr<TH1I>& histo_ptr, unsigned short& nBinsU, unsigned short& nBinsV)
48 {
49 set<unsigned short> uBinSet;
50 set<unsigned short> vBinSet;
51
52 // Loop over all bins of input histo
53 for (auto histoBin = 1; histoBin <= histo_ptr->GetNbinsX(); histoBin++) {
54 // The bin label contains the vxdid, uBin and vBin
55 string label = histo_ptr->GetXaxis()->GetBinLabel(histoBin);
56
57 // Parse label string format to read sensorID, uBin and vBin
58 istringstream stream(label);
59 string token;
60 getline(stream, token, '_');
61 getline(stream, token, '_');
62 unsigned short uBin = std::stoi(token);
63
64 getline(stream, token, '_');
65 unsigned short vBin = std::stoi(token);
66
67 uBinSet.insert(uBin);
68 vBinSet.insert(vBin);
69 }
70
71 if (uBinSet.empty() || vBinSet.empty()) {
72 B2FATAL("Not able to determine the grid size. Something is wrong with collected data.");
73 } else {
74 nBinsU = *uBinSet.rbegin() + 1;
75 nBinsV = *vBinSet.rbegin() + 1;
76 }
77 }
78
80 unsigned short getNumberOfSensors(const std::shared_ptr<TH1I>& histo_ptr)
81 {
82 set<unsigned short> sensorSet;
83
84 // Loop over all bins of input histo
85 for (auto histoBin = 1; histoBin <= histo_ptr->GetNbinsX(); histoBin++) {
86 // The bin label contains the vxdid, uBin and vBin
87 string label = histo_ptr->GetXaxis()->GetBinLabel(histoBin);
88
89 // Parse label string format to read sensorID, uBin and vBin
90 istringstream stream(label);
91 string token;
92 getline(stream, token, '_');
93 VxdID sensorID(token);
94 sensorSet.insert(sensorID.getID());
95 }
96
97 return sensorSet.size();
98 }
99
100}
101
102
104 CalibrationAlgorithm("PXDClusterChargeCollector"),
105 minClusters(5000), noiseSigma(0.6), safetyFactor(2.0), forceContinue(false), strategy(0),
106 doCalibration(false), useChargeHistogram(false), chargePayloadName("PXDClusterChargeMapPar")
107{
109 " ------------------------------ PXDDataMCGainCalibrationAlgorithm -----------------------------------\n"
110 " \n"
111 " Algorithm for estimating pxd median/MPV cluster charges for different position on sensor in ADU, and optionally calculate gain \n"
112 " ------------------------------------------------------------------------------------------------\n"
113 );
114
115}
116
117
119{
120
121 // Get counter histogram
122 auto cluster_counter = getObjectPtr<TH1I>("PXDClusterCounter");
123 if (!cluster_counter) {
124 B2INFO("Not enough Data: cluster counter does not exist ");
125 return c_NotEnoughData;
126 }
127
128 // Extract number of sensors from counter histograms
129 auto nSensors = getNumberOfSensors(cluster_counter);
130
131 // Extract the number of grid bins from counter histograms
132 unsigned short nBinsU = 0;
133 unsigned short nBinsV = 0;
134 getNumberOfBins(cluster_counter, nBinsU, nBinsV);
135
136 // Check that we have collected enough Data
137 if (cluster_counter->GetEntries() < int(safetyFactor * minClusters * nSensors * nBinsU * nBinsV)) {
138 if (not forceContinue) {
139 B2INFO("Not enough Data: Only " << cluster_counter->GetEntries() << " hits were collected but " << int(safetyFactor * minClusters*
140 nSensors * nBinsU * nBinsV) << " needed!");
141 return c_NotEnoughData;
142 } else {
143 B2INFO("Continue despite low statistics: Only " << cluster_counter->GetEntries() << " hits were collected but" << int(
145 nSensors * nBinsU * nBinsV) << " would be desirable!");
146 }
147 }
148
149 B2INFO("Start calibration using a " << nBinsU << "x" << nBinsV << " grid per sensor.");
150
151 // This is the PXD charge calibration payload for conditions DB
152 PXDClusterChargeMapPar* chargeMapPar = new PXDClusterChargeMapPar(nBinsU, nBinsV);
153 // This is the PXD gain correction payload for conditions DB
154 PXDGainMapPar* gainMapPar = new PXDGainMapPar(nBinsU, nBinsV);
155 set<VxdID> pxdSensors;
156
157 // Read back existing DB payloads
158 PXDClusterChargeMapPar* chargeMapPtr = nullptr;
159 PXDGainMapPar* gainMapPtr = nullptr;
160 auto dbtree = getObjectPtr<TTree>("dbtree");
161 dbtree->SetBranchAddress("run", &m_run);
162 dbtree->SetBranchAddress("exp", &m_exp);
163 dbtree->SetBranchAddress("chargeMap", &chargeMapPtr);
164 dbtree->SetBranchAddress("gainMap", &gainMapPtr);
165
166 // Loop over all bins of input histo
167 for (auto histoBin = 1; histoBin <= cluster_counter->GetNbinsX(); histoBin++) {
168
169 // The bin label contains the vxdid, uBin and vBin
170 string label = cluster_counter->GetXaxis()->GetBinLabel(histoBin);
171
172 // Parse label string format to read sensorID, uBin and vBin
173 istringstream stream(label);
174 string token;
175 getline(stream, token, '_');
176 VxdID sensorID(token);
177
178 getline(stream, token, '_');
179 unsigned short uBin = std::stoi(token);
180
181 getline(stream, token, '_');
182 unsigned short vBin = std::stoi(token);
183
184 // Read back the counters for number of collected clusters
185 int numberOfDataHits = cluster_counter->GetBinContent(histoBin);
186
187 // Only perform fitting, when enough data is available
188 if (numberOfDataHits >= minClusters) {
189
190 B2INFO("start EstimateCharge");
191 // Compute the cluster charge or gain for the part of PXD
192 auto Charge = EstimateCharge(sensorID, uBin, vBin, histoBin);
193
194 // Store the charge or gain
195 if (!doCalibration) {
196
197 B2INFO("EstimateCharge: sensor " << sensorID.getID() << " U " << uBin << " V " << vBin << " Charge " << Charge);
198 chargeMapPar->setContent(sensorID.getID(), uBin, vBin, Charge);
199
200 } else {
201
202 auto Gain = 0.0;
203
204 if (Charge <= 0.0) {
205 B2WARNING("Retrieved negative charge for data for sensor=" << sensorID << " uBin=" << uBin << " vBin=" << vBin <<
206 ". Set gain to default value (=1.0).");
207 Gain = 1.0;
208 } else {
209 dbtree->GetEntry(0);
210 double mcCharge = chargeMapPtr->getContent(sensorID.getID(), uBin, vBin);
211 //GetChargeFromDB(sensorID, uBin, vBin, dbtree);
212 if (mcCharge <= 0.0) {
213 B2WARNING("Retrieved negative charge for MC from DB for sensor=" << sensorID << " uBin=" << uBin << " vBin=" << vBin <<
214 ". Set gain to default value (=1.0).");
215 Gain = 1.0;
216 } else {
217 Gain = Charge / mcCharge;
218 B2INFO("Estimated Gain: sensor " << sensorID.getID() << " U " << uBin << " V " << vBin << " Gain " << Gain << " = " << Charge <<
219 " / " << mcCharge);
220 }
221
222 }
223
224 gainMapPar->setContent(sensorID.getID(), uBin, vBin, Gain);
225
226 }
227
228 } else {
229
230 B2WARNING(label << ": Number of data hits too small for fitting (" << numberOfDataHits << " < " << minClusters <<
231 "). Use default value of 0 for charge, 1 for gain.");
232 if (!doCalibration) chargeMapPar->setContent(sensorID.getID(), uBin, vBin, 0.0);
233 else gainMapPar->setContent(sensorID.getID(), uBin, vBin, 1.0);
234
235 }
236
237 pxdSensors.insert(sensorID);
238
239 }
240
241 chargeMapPtr = nullptr;
242 gainMapPtr = nullptr;
243
244 // Save the charge map to database. Note that this will set the database object name to the same as the collector but you
245 // are free to change it.
246 if (!doCalibration) {
247 saveCalibration(chargeMapPar, chargePayloadName);
248
249 B2INFO("PXD Cluster Charge Calibration Successful");
250 return c_OK;
251
252 } else {
253
254 // Post processing of gain map. It is possible that the gain
255 // computation failed on some parts. Here, we replace default
256 // values (1.0) by local averages of neighboring sensor parts.
257
258 for (const auto& sensorID : pxdSensors) {
259 for (unsigned short vBin = 0; vBin < nBinsV; ++vBin) {
260 float meanGain = 0;
261 unsigned short nGood = 0;
262 unsigned short nBad = 0;
263 for (unsigned short uBin = 0; uBin < nBinsU; ++uBin) {
264 auto gain = gainMapPar->getContent(sensorID.getID(), uBin, vBin);
265 // Filter default gains
266 if (gain != 1.0) {
267 nGood += 1;
268 meanGain += gain;
269 } else {
270 nBad += 1;
271 }
272 }
273 B2RESULT("Gain calibration on sensor=" << sensorID << " and vBin=" << vBin << " was successful on " << nGood << "/" << nBinsU <<
274 " uBins.");
275
276 // Check if we can repair bad calibrations with a local average
277 if (nGood > 0 && nBad > 0) {
278 meanGain /= nGood;
279 for (unsigned short uBin = 0; uBin < nBinsU; ++uBin) {
280 auto gain = gainMapPar->getContent(sensorID.getID(), uBin, vBin);
281 if (gain == 1.0) {
282 gainMapPar->setContent(sensorID.getID(), uBin, vBin, meanGain);
283 B2RESULT("Gain calibration on sensor=" << sensorID << ", vBin=" << vBin << " uBin " << uBin <<
284 ": Replace default gain with average "
285 << meanGain);
286 }
287 }
288 }
289 }
290 }
291
292 // Save the gain map to database. Note that this will set the database object name to the same as the collector but you
293 // are free to change it.
294 saveCalibration(gainMapPar, "PXDGainMapPar");
295
296 B2INFO("PXD Gain Calibration Successful");
297 return c_OK;
298
299 }
300
301}
302
303
304double PXDDataMCGainCalibrationAlgorithm::EstimateCharge(VxdID sensorID, unsigned short uBin, unsigned short vBin,
305 unsigned short histoBin)
306{
307
308 double charge = -1.;
309
311 B2FATAL("strategy unavailable, use 0 for medians, 1 for landau fit and 2 for mean!");
312 }
313
314 // Construct a tree name for requested part of PXD
315 auto layerNumber = sensorID.getLayerNumber();
316 auto ladderNumber = sensorID.getLadderNumber();
317 auto sensorNumber = sensorID.getSensorNumber();
318
319 if (!useChargeHistogram) {
320 const string treename = str(format("tree_%1%_%2%_%3%_%4%_%5%") % layerNumber % ladderNumber % sensorNumber % uBin % vBin);
321 // Vector with cluster signals from collected data
322 vector<double> signals;
323 // Fill data_signal vector from input data
324 auto tree = getObjectPtr<TTree>(treename);
325 tree->SetBranchAddress("signal", &m_signal);
326
327 // Loop over tree
328 const auto nEntries = tree->GetEntries();
329 int incr(1);
330 if (int(nEntries / minClusters) > 2) incr = int(2 * nEntries / minClusters);
331 for (int i = 0; i < nEntries; i += incr) {
332 tree->GetEntry(i);
333 double noise = gRandom->Gaus(0.0, noiseSigma);
334 signals.push_back(m_signal + noise); //qyliu: why we introduce noise simulation here?
335 }
336
337 if (strategy == 0) {
338 double median = CalculateMedian(signals);
339 B2INFO("EstimateCharge: sensor " << sensorID.getID() << "(" << layerNumber << "," << ladderNumber << "," << sensorNumber
340 << ") U " << uBin << " V " << vBin
341 << " Charge " << median);
342 charge = median;
343 } else if (strategy == 1) {
344 double median = CalculateMedian(signals);
345 double landaumpv = FitLandau(signals);
346 double diff = (landaumpv - median);
347 double difff = 0.;
348 if (landaumpv > 0.) difff = (landaumpv - median) / landaumpv;
349 B2INFO("EstimateCharge: sensor " << sensorID.getID() << "(" << layerNumber << "," << ladderNumber << "," << sensorNumber
350 << ") U " << uBin << " V " << vBin
351 << " Charge " << landaumpv << " Median " << median << " diff = " << diff << "/" << difff);
352 charge = landaumpv; //FitLandau(signals);
353 // the preceding branches cover the other configured strategies
354 // cppcheck-suppress knownConditionTrueFalse
355 } else if (strategy == 2) {
356 double mean = 0;
357 for (const auto& each : signals)
358 mean += each;
359 if (signals.size() > 0) mean = mean / signals.size();
360 charge = mean;
361 }
362
363 } else {
364
365 auto cluster_counter = getObjectPtr<TH2I>("PXDClusterCharge");
366 TH1D* hist_signals = cluster_counter->ProjectionY("proj", histoBin, histoBin);
367
368 if (strategy == 0) {
369 double median = CalculateMedian(hist_signals);
370 B2INFO("EstimateCharge: sensor " << sensorID.getID() << "(" << layerNumber << "," << ladderNumber << "," << sensorNumber
371 << ") U " << uBin << " V " << vBin
372 << " Charge " << median);
373 charge = median;
374 } else if (strategy == 1) {
375 double median = CalculateMedian(hist_signals);
376 double landaumpv = FitLandau(hist_signals);
377 double diff = (landaumpv - median);
378 double difff = 0.;
379 if (landaumpv > 0.) difff = (landaumpv - median) / landaumpv;
380 B2INFO("EstimateCharge: sensor " << sensorID.getID() << "(" << layerNumber << "," << ladderNumber << "," << sensorNumber
381 << ") U " << uBin << " V " << vBin
382 << " Charge " << landaumpv << " Median " << median << " diff = " << diff << "/" << difff);
383
384 //return landaumpv; //FitLandau(signals);
385 charge = landaumpv;
386 // the preceding branches cover the other configured strategies
387 // cppcheck-suppress knownConditionTrueFalse
388 } else if (strategy == 2) {
389 charge = hist_signals->GetMean();
390 }
391
392 delete hist_signals;
393 }
394
395 return charge;
396
397}
398
400{
401 auto size = signals.size();
402
403 if (size == 0) {
404 return 0.0; // Undefined, really.
405 } else {
406 sort(signals.begin(), signals.end());
407 if (size % 2 == 0) {
408 return (signals[size / 2 - 1] + signals[size / 2]) / 2;
409 } else {
410 return signals[size / 2];
411 }
412 }
413}
414
416{
417 auto size = hist_signals->GetEntries();
418
419 if (size == 0) return 0.0; // Undefined.
420
421 int sum = 0;
422 for (int ibin = 0; ibin < hist_signals->GetNbinsX(); ++ibin) {
423 sum += hist_signals->GetBinContent(ibin + 1);
424 if (sum > size / 2) {
425 return hist_signals->GetBinLowEdge(ibin + 1);
426 }
427 }
428
429 B2WARNING("Could not find median! using default value 0.0!");
430 return 0.0;
431}
432
433double PXDDataMCGainCalibrationAlgorithm::FitLandau(vector<double>& signals)
434{
435 auto size = signals.size();
436 if (size == 0) return 0.0; // Undefined, really.
437
438 // get max and min values of vector
439 int max = *max_element(signals.begin(), signals.end());
440 int min = *min_element(signals.begin(), signals.end());
441 // make even bins
442 if ((max - min) % 2) max--;
443 int nbin = max - min;
444
445 // create histogram to hold signals and fill it
446 TH1D* hist_signals = new TH1D("", "", nbin, min, max);
447 for (auto it = signals.begin(); it != signals.end(); ++it) {
448 hist_signals->Fill(*it);
449 }
450
451 // create fit function
452 TF1* landau = new TF1("landau", "TMath::Landau(x,[0],[1])*[2]", min, max);
453 landau->SetParNames("MPV", "sigma", "scale");
454 landau->SetParameters(35, 8, 1000);
455 landau->SetParLimits(0, 0., 80.);
456
457 //do fit and get results, fit range restricted to exclude low charge peak
458 float fitmin(min);
459 float fitmax(350);
460 Int_t status = hist_signals->Fit("landau", "Lq", "", fitmin, fitmax);
461 double MPV = landau->GetParameter("MPV");
462
463 B2INFO("Fit result: " << status << " MPV " << MPV << " sigma " << landau->GetParameter("sigma")
464 << " scale " << landau->GetParameter("scale") << " chi2 " << landau->GetChisquare());
465
466 // clean up
467 delete hist_signals;
468 delete landau;
469
470 // check fit status
471 if (status == 0) return MPV;
472 else {
473 B2WARNING("Fit failed! using default value 0.0!");
474 return 0.0;
475 }
476}
477
479{
480 auto size = hist_signals->GetEntries();
481 if (size == 0) return 0.0; // Undefined.
482
483 int max = hist_signals->GetBinLowEdge(hist_signals->GetNbinsX() + 1);
484 int min = hist_signals->GetBinLowEdge(1);
485
486 // create fit function
487 TF1* landau = new TF1("landau", "TMath::Landau(x,[0],[1])*[2]", min, max);
488 landau->SetParNames("MPV", "sigma", "scale");
489 landau->SetParameters(35, 8, 1000);
490 landau->SetParLimits(0, 0., 80.);
491
492 // do fit and get results, fit range restricted to exclude low charge peak
493 float fitmin(min);
494 float fitmax(250);
495 Int_t status = hist_signals->Fit("landau", "Lq", "", fitmin, fitmax);
496 double MPV = landau->GetParameter("MPV");
497
498 B2INFO("Fit result: " << status << " MPV " << MPV << " sigma " << landau->GetParameter("sigma")
499 << " scale " << landau->GetParameter("scale") << " chi2 " << landau->GetChisquare());
500
501 // clean up
502 delete landau;
503
504 // check fit status
505 if (status == 0) return MPV;
506 else {
507 B2WARNING("Fit failed! using default value 0.0!");
508 return 0.0;
509 }
510}
511
void saveCalibration(TClonesArray *data, const std::string &name)
Store DBArray payload with given name with default IOV.
void setDescription(const std::string &description)
Set algorithm description (in constructor)
EResult
The result of calibration.
@ c_OK
Finished successfully =0 in Python.
@ c_NotEnoughData
Needs more data =2 in Python.
CalibrationAlgorithm(const std::string &collectorModuleName)
Constructor - sets the prefix for collected objects (won't be accesses until execute(....
The payload class for PXD cluster charge calibrations.
float getContent(unsigned short sensorID, unsigned short globalID) const
Get content.
void setContent(unsigned short sensorID, unsigned short globalID, float value)
Set map content.
bool doCalibration
flag to perform full calibration or only estimate charge: False: only estimate charge,...
static double FitLandau(std::vector< double > &signals)
calculate MPV of unsorted signal vector using a Landau fit
double EstimateCharge(VxdID sensorID, unsigned short uBin, unsigned short vBin, unsigned short histoBin)
Estimate median charge form collected clusters on part of PXD.
int minClusters
Minimum number of collected clusters for estimating median charge.
std::string chargePayloadName
Payload name for Cluster Charge.
float noiseSigma
Artificial noise sigma for smearing cluster charge.
bool useChargeHistogram
Flag to use histogram as charge input.
virtual EResult calibrate() override
Run algo on data.
int strategy
strategy to used for gain calibration, 0 for medians, 1 for landau fit
float safetyFactor
Safety factor for determining whether the collected number of clusters is enough.
static double CalculateMedian(std::vector< double > &signals)
Calculate a median from unsorted signal vector. The input vector gets sorted.
PXDDataMCGainCalibrationAlgorithm()
Constructor set the prefix to PXDDataMCGainCalibrationAlgorithm.
bool forceContinue
Force continue in low statistics runs instead of returning c_NotEnoughData.
The payload class for PXD gain corrections.
float getContent(unsigned short sensorID, unsigned short globalID) const
Get content.
void setContent(unsigned short sensorID, unsigned short globalID, float value)
Set map content.
Class to uniquely identify a any structure of the PXD and SVD.
Definition VxdID.h:32
baseType getID() const
Get the unique id.
Definition VxdID.h:93
baseType getSensorNumber() const
Get the sensor id.
Definition VxdID.h:99
baseType getLadderNumber() const
Get the ladder id.
Definition VxdID.h:97
baseType getLayerNumber() const
Get the layer id.
Definition VxdID.h:95
std::shared_ptr< T > getObjectPtr(const std::string &name, const std::vector< Calibration::ExpRun > &requestedRuns)
Get calibration data object by name and list of runs, the Merge function will be called to generate t...
unsigned short getNumberOfSensors(const std::shared_ptr< TH1I > &histo_ptr)
Helper function to extract number of sensors from counter histogram labels.
void getNumberOfBins(const std::shared_ptr< TH1I > &histo_ptr, unsigned short &nBinsU, unsigned short &nBinsV)
Helper function to extract number of bins along u side and v side from counter histogram labels.
Abstract base class for different kinds of events.
STL namespace.