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
310 if (strategy < 0 || strategy > 2) {
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 } else if (strategy == 2) {
354 double mean = 0;
355 for (auto& each : signals)
356 mean += each;
357 if (signals.size() > 0) mean = mean / signals.size();
358 charge = mean;
359 }
360
361 } else {
362
363 auto cluster_counter = getObjectPtr<TH2I>("PXDClusterCharge");
364 TH1D* hist_signals = cluster_counter->ProjectionY("proj", histoBin, histoBin);
365
366 if (strategy == 0) {
367 double median = CalculateMedian(hist_signals);
368 B2INFO("EstimateCharge: sensor " << sensorID.getID() << "(" << layerNumber << "," << ladderNumber << "," << sensorNumber
369 << ") U " << uBin << " V " << vBin
370 << " Charge " << median);
371 charge = median;
372 } else if (strategy == 1) {
373 double median = CalculateMedian(hist_signals);
374 double landaumpv = FitLandau(hist_signals);
375 double diff = (landaumpv - median);
376 double difff = 0.;
377 if (landaumpv > 0.) difff = (landaumpv - median) / landaumpv;
378 B2INFO("EstimateCharge: sensor " << sensorID.getID() << "(" << layerNumber << "," << ladderNumber << "," << sensorNumber
379 << ") U " << uBin << " V " << vBin
380 << " Charge " << landaumpv << " Median " << median << " diff = " << diff << "/" << difff);
381
382 //return landaumpv; //FitLandau(signals);
383 charge = landaumpv;
384 } else if (strategy == 2) {
385 charge = hist_signals->GetMean();
386 }
387
388 delete hist_signals;
389 }
390
391 return charge;
392
393}
394
396{
397 auto size = signals.size();
398
399 if (size == 0) {
400 return 0.0; // Undefined, really.
401 } else {
402 sort(signals.begin(), signals.end());
403 if (size % 2 == 0) {
404 return (signals[size / 2 - 1] + signals[size / 2]) / 2;
405 } else {
406 return signals[size / 2];
407 }
408 }
409}
410
412{
413 auto size = hist_signals->GetEntries();
414
415 if (size == 0) return 0.0; // Undefined.
416
417 int sum = 0;
418 for (int ibin = 0; ibin < hist_signals->GetNbinsX(); ++ibin) {
419 sum += hist_signals->GetBinContent(ibin + 1);
420 if (sum > size / 2) {
421 return hist_signals->GetBinLowEdge(ibin + 1);
422 }
423 }
424
425 B2WARNING("Could not find median! using default value 0.0!");
426 return 0.0;
427}
428
429double PXDDataMCGainCalibrationAlgorithm::FitLandau(vector<double>& signals)
430{
431 auto size = signals.size();
432 if (size == 0) return 0.0; // Undefined, really.
433
434 // get max and min values of vector
435 int max = *max_element(signals.begin(), signals.end());
436 int min = *min_element(signals.begin(), signals.end());
437 // make even bins
438 if ((max - min) % 2) max--;
439 int nbin = max - min;
440
441 // create histogram to hold signals and fill it
442 TH1D* hist_signals = new TH1D("", "", nbin, min, max);
443 for (auto it = signals.begin(); it != signals.end(); ++it) {
444 hist_signals->Fill(*it);
445 }
446
447 // create fit function
448 TF1* landau = new TF1("landau", "TMath::Landau(x,[0],[1])*[2]", min, max);
449 landau->SetParNames("MPV", "sigma", "scale");
450 landau->SetParameters(35, 8, 1000);
451 landau->SetParLimits(0, 0., 80.);
452
453 //do fit and get results, fit range restricted to exclude low charge peak
454 float fitmin(min);
455 float fitmax(350);
456 Int_t status = hist_signals->Fit("landau", "Lq", "", fitmin, fitmax);
457 double MPV = landau->GetParameter("MPV");
458
459 B2INFO("Fit result: " << status << " MPV " << MPV << " sigma " << landau->GetParameter("sigma")
460 << " scale " << landau->GetParameter("scale") << " chi2 " << landau->GetChisquare());
461
462 // clean up
463 delete hist_signals;
464 delete landau;
465
466 // check fit status
467 if (status == 0) return MPV;
468 else {
469 B2WARNING("Fit failed! using default value 0.0!");
470 return 0.0;
471 }
472}
473
475{
476 auto size = hist_signals->GetEntries();
477 if (size == 0) return 0.0; // Undefined.
478
479 int max = hist_signals->GetBinLowEdge(hist_signals->GetNbinsX() + 1);
480 int min = hist_signals->GetBinLowEdge(1);
481
482 // create fit function
483 TF1* landau = new TF1("landau", "TMath::Landau(x,[0],[1])*[2]", min, max);
484 landau->SetParNames("MPV", "sigma", "scale");
485 landau->SetParameters(35, 8, 1000);
486 landau->SetParLimits(0, 0., 80.);
487
488 // do fit and get results, fit range restricted to exclude low charge peak
489 float fitmin(min);
490 float fitmax(250);
491 Int_t status = hist_signals->Fit("landau", "Lq", "", fitmin, fitmax);
492 double MPV = landau->GetParameter("MPV");
493
494 B2INFO("Fit result: " << status << " MPV " << MPV << " sigma " << landau->GetParameter("sigma")
495 << " scale " << landau->GetParameter("scale") << " chi2 " << landau->GetChisquare());
496
497 // clean up
498 delete landau;
499
500 // check fit status
501 if (status == 0) return MPV;
502 else {
503 B2WARNING("Fit failed! using default value 0.0!");
504 return 0.0;
505 }
506}
507
Base class for calibration algorithms.
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.
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,...
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.
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.
Definition: PXDGainMapPar.h:43
float getContent(unsigned short sensorID, unsigned short globalID) const
Get content.
void setContent(unsigned short sensorID, unsigned short globalID, float value)
Set map content.
Definition: PXDGainMapPar.h:68
Class to uniquely identify a any structure of the PXD and SVD.
Definition: VxdID.h:33
baseType getID() const
Get the unique id.
Definition: VxdID.h:94
baseType getSensorNumber() const
Get the sensor id.
Definition: VxdID.h:100
baseType getLadderNumber() const
Get the ladder id.
Definition: VxdID.h:98
baseType getLayerNumber() const
Get the layer id.
Definition: VxdID.h:96
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.