Belle II Software development
DQMHistAnalysisECLSummary.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//THIS MODULE
10#include <dqm/analysis/modules/DQMHistAnalysisECLSummary.h>
11
12//ECL
13#include <ecl/geometry/ECLGeometryPar.h>
14
15//ROOT
16#include <TStyle.h>
17#include <TLine.h>
18
19//boost
20#include "boost/format.hpp"
21
22using namespace Belle2;
23
24REG_MODULE(DQMHistAnalysisECLSummary);
25
28 m_neighbours_obj("F", 0.1)
29{
30
31 B2DEBUG(20, "DQMHistAnalysisECLSummary: Constructor done.");
32 addParam("pvPrefix", m_pvPrefix, "Prefix to use for PVs registered by this module",
33 std::string("ECL:channels_info:"));
34 addParam("useChannelMask", m_useChannelMask,
35 "Mask Cell IDs based on information from ECL PVs",
36 true);
37 addParam("maxDeviationForOccupancy", m_maxDeviationForOccupancy,
38 "The higher this parameter, the larger differences in occupancy are allowed for adjacent channels", 0.28);
39 addParam("maxDeviationForChi2", m_maxDeviationForChi2,
40 "The higher this parameter, the larger differences in the number of hits with bad chi2 are allowed for adjacent channels",
41 2.5);
42 addParam("onlyIfUpdated", m_onlyIfUpdated, "If true (default), update EPICS PVs only if histograms were updated.",
43 true);
44}
45
46
48{
49}
50
52{
54
55 //=== Set up ECL alarms and corresponding PVs
56
57 m_ecl_alarms = {
58 {"dead", "#splitline{dead}{channels}", 1, 1, 1e5},
59 {"cold", "#splitline{cold}{channels}", 1, 2, 1e5},
60 {"hot", "#splitline{hot}{channels}", 25, 50, 1e5},
61 {"bad_chi2", "#splitline{bad #chi^{2}}{channels}", 5, 10, 1e6},
62 {"bad_fit", "#splitline{fit incon-}{sistencies}", 5, 10, 0 },
63 };
64
65 // Prepare EPICS PVs
66 for (auto& alarm : m_ecl_alarms) {
67 // By crate
68 for (int crate_id = 1; crate_id <= ECL::ECL_CRATES; crate_id++) {
69 std::string pv_name = (boost::format("crate%02d:%s") % crate_id % alarm.name).str();
70 registerEpicsPV(m_pvPrefix + pv_name, pv_name);
71 }
72 // Totals
73 for (auto& ecl_part : {"All", "FWDEndcap", "Barrel", "BWDEndcap"}) {
74 std::string pv_name = (boost::format("%s:%s") % ecl_part % alarm.name).str();
75 registerEpicsPV(m_pvPrefix + pv_name, pv_name);
76 }
77 // Masked Cell IDs
78 std::string mask_pv_name = (boost::format("mask:%s") % alarm.name).str();
79 registerEpicsPV(m_pvPrefix + mask_pv_name, mask_pv_name);
80 }
81
83
84 //=== Set up the histogram to indicate alarm status
85
86 TString title = "#splitline{ECL errors monitoring}";
87 title += "{E - Error, W - Warning, L - Low statistics}";
88 title += ";ECLCollector ID (same as Crate ID)";
89 h_channels_summary = new TH2F("channels_summary", title,
90 ECL::ECL_CRATES, 1, ECL::ECL_CRATES + 1,
91 m_ecl_alarms.size(), 0, m_ecl_alarms.size());
92
93 // Do not show statistics box.
94 h_channels_summary->SetStats(0);
95 h_channels_summary->SetMinimum(0);
96 h_channels_summary->SetMaximum(1);
97
98 //=== Set X axis labels
99
100 for (int i = 1; i <= ECL::ECL_CRATES; i++) {
101 h_channels_summary->GetXaxis()->SetBinLabel(i, std::to_string(i).c_str());
102 }
103 h_channels_summary->LabelsOption("v", "X"); // Rotate X axis labels 90 degrees
104 h_channels_summary->SetTickLength(0, "XY");
105
106 //=== Customize offsets and margins
107
108 h_channels_summary->GetXaxis()->SetTitleOffset(0.95);
109 h_channels_summary->GetXaxis()->SetTitleSize(0.05);
110 h_channels_summary->GetXaxis()->SetLabelSize(0.04);
111 h_channels_summary->GetYaxis()->SetLabelSize(0.06);
112
113 c_channels_summary = new TCanvas("ECL/c_channels_summary_analysis");
114 c_channels_summary->SetTopMargin(0.10);
115 c_channels_summary->SetLeftMargin(0.20);
116 c_channels_summary->SetRightMargin(0.005);
117 c_channels_summary->SetBottomMargin(0.10);
118
119 //=== Additional canvases/histograms to display which channels have problems
120 c_occupancy = new TCanvas("ECL/c_cid_Thr5MeV_overlaid_analysis");
121 c_bad_chi2 = new TCanvas("ECL/c_bad_quality_overlaid_analysis");
122
123 h_bad_occ_overlay = new TH1F("bad_occ_overlay", "", ECL::ECL_TOTAL_CHANNELS,
124 1, ECL::ECL_TOTAL_CHANNELS + 1);
125 h_bad_occ_overlay->SetLineColor(kRed);
126 h_bad_occ_overlay->SetLineStyle(kDashed);
127 h_bad_occ_overlay->SetFillColor(kRed);
128 h_bad_occ_overlay->SetFillStyle(3007);
129
130 h_bad_chi2_overlay = new TH1F("bad_chi2_overlay", "", ECL::ECL_TOTAL_CHANNELS,
131 1, ECL::ECL_TOTAL_CHANNELS + 1);
132 h_bad_chi2_overlay->SetLineColor(kRed);
133 h_bad_chi2_overlay->SetLineStyle(kDashed);
134 h_bad_chi2_overlay->SetFillColor(kRed);
135 h_bad_chi2_overlay->SetFillStyle(3007);
136
137 h_bad_occ_overlay_green = new TH1F("bad_occ_overlay_green", "", ECL::ECL_TOTAL_CHANNELS,
138 1, ECL::ECL_TOTAL_CHANNELS + 1);
139 h_bad_occ_overlay_green->SetLineColor(kGreen);
140 h_bad_occ_overlay_green->SetLineStyle(kDotted);
141 h_bad_occ_overlay_green->SetFillColor(kGreen);
142 h_bad_occ_overlay_green->SetFillStyle(3013);
143
144 h_bad_chi2_overlay_green = new TH1F("bad_chi2_overlay_green", "", ECL::ECL_TOTAL_CHANNELS,
145 1, ECL::ECL_TOTAL_CHANNELS + 1);
146 h_bad_chi2_overlay_green->SetLineColor(kGreen);
147 h_bad_chi2_overlay_green->SetLineStyle(kDotted);
148 h_bad_chi2_overlay_green->SetFillColor(kGreen);
149 h_bad_chi2_overlay_green->SetFillStyle(3013);
150
151 B2DEBUG(20, "DQMHistAnalysisECLSummary: initialized.");
152}
153
155{
156 B2DEBUG(20, "DQMHistAnalysisECLSummary: beginRun called.");
157
158 //=== Update m_ecl_alarms based on PV limits
159
161
162 //=== Set Y axis labels from m_ecl_alarms
163
164 for (size_t i = 0; i < m_ecl_alarms.size(); i++) {
165 TString label = m_ecl_alarms[i].title;
166 label += " #geq ";
167 label += m_ecl_alarms[i].alarm_limit;
168 h_channels_summary->GetYaxis()->SetBinLabel(i + 1, label);
169 }
170}
171
173{
174 TH1* h_total_events = findHist("ECL/event", m_onlyIfUpdated);
175 if (!h_total_events) return;
176 m_total_events = h_total_events->GetEntries();
177
178 c_channels_summary->Clear();
179 h_channels_summary->Reset();
180
181 // [alarm_type][crate_id - 1] -> number of problematic channels in that crate
182 std::vector< std::vector<int> > alarm_counts = updateAlarmCounts();
183
184 //=== Set warning and error indicators on the histogram
185
186 const double HISTCOLOR_RED = 0.9;
187 const double HISTCOLOR_GREEN = 0.45;
188 const double HISTCOLOR_ORANGE = 0.65;
189 const double HISTCOLOR_GRAY = 0.00;
190
191 // TODO: Currently, I am explicitly removing
192 // TText labels from the histogram. However,
193 // normally I would assume that canvas->Clear()
194 // should have already removed them. So
195 // it might indicate a memory leak or some
196 // problem with ROOT->JSON->ROOT conversion
197 for (auto& text : m_labels) {
198 delete text;
199 }
200 m_labels.clear();
201
202 //== Set correct warning/error colors for each bin based on alarm thresholds
203
204 bool enough = false;
205 bool warn_flag = false;
206 bool error_flag = false;
207
208 for (size_t alarm_idx = 0; alarm_idx < alarm_counts.size(); alarm_idx++) {
209 for (size_t crate = 0; crate < alarm_counts[alarm_idx].size(); crate++) {
210 double color;
211 char label_text[2] = {0};
212 int alarm_limit = m_ecl_alarms[alarm_idx].alarm_limit;
213 int warning_limit = m_ecl_alarms[alarm_idx].warning_limit;
214
215 int alarms = alarm_counts[alarm_idx][crate];
216
217 if (m_total_events < m_ecl_alarms[alarm_idx].required_statistics || alarms < 0) {
218 // `alarms < 0` means that we could not get the relevant histogram.
219 // We assume that the histogram is not yet available due to low
220 // statistics but this is not a guarantee.
221 color = HISTCOLOR_GRAY;
222 label_text[0] = 'L';
223 } else if (alarms >= alarm_limit) {
224 color = HISTCOLOR_RED;
225 label_text[0] = 'E';
226 error_flag = true;
227 enough = true;
228 } else if (alarms >= warning_limit) {
229 color = HISTCOLOR_ORANGE;
230 label_text[0] = 'W';
231 warn_flag = true;
232 enough = true;
233 } else {
234 color = HISTCOLOR_GREEN;
235 enough = true;
236 }
237 if (label_text[0] == 'E' || label_text[0] == 'W') {
238 B2DEBUG(100, "Non-zero (" << alarm_counts[alarm_idx][crate]
239 << ") for alarm_idx, crate = " << alarm_idx << ", " << crate);
240 }
241 h_channels_summary->SetBinContent(crate + 1, alarm_idx + 1, color);
242 if (label_text[0] != 0) {
243 auto text = new TText((crate + 1.5), (alarm_idx + 0.5), label_text);
244 if (label_text[0] != 'L') text->SetTextColor(kWhite);
245 text->SetTextSize(0.03);
246 text->SetTextAlign(22); // centered
247 m_labels.push_back(text);
248 }
249 }
250 }
251
252 //=== Draw histogram, labels and grid
253
254 // Customize title
255 auto gstyle_title_h = gStyle->GetTitleH();
256 auto gstyle_title_x = gStyle->GetTitleX();
257 auto gstyle_title_y = gStyle->GetTitleY();
258 gStyle->SetTitleH(0.04);
259 gStyle->SetTitleX(0.60);
260 gStyle->SetTitleY(1.00);
261
262 //=== Set background color based on combined status.
263 c_channels_summary->cd();
264 colorizeCanvas(c_channels_summary, makeStatus(enough, warn_flag, error_flag));
266
267 //=== Prepare special style objects to use correct color palette
268 // and use it only for this histogram
269 // Based on https://root-forum.cern.ch/t/different-color-palettes-for-different-plots-with-texec/5250/3
270 // and https://root.cern/doc/master/multipalette_8C.html
271
272 if (!m_ecl_style) delete m_ecl_style;
273 if (!m_default_style) delete m_default_style;
274
275 m_ecl_style = new TExec("ecl_style",
276 "gStyle->SetPalette(kRainBow);"
277 "if (channels_summary) channels_summary->SetDrawOption(\"col\");");
278 h_channels_summary->GetListOfFunctions()->Add(m_ecl_style);
279 m_default_style = new TExec("default_style",
280 "gStyle->SetPalette(kBird);");
281
282 //=== Draw with special style
283 // https://root.cern.ch/js/latest/examples.htm#th2_colpal77
284 h_channels_summary->Draw("");
285 h_channels_summary->Draw("colpal55;same");
286 for (auto& text : m_labels) {
287 text->Draw();
288 }
290 m_default_style->Draw("same");
291
292 //
293 c_channels_summary->Modified();
294 c_channels_summary->Update();
295 c_channels_summary->Draw();
296
297 gStyle->SetTitleH(gstyle_title_h);
298 gStyle->SetTitleX(gstyle_title_x);
299 gStyle->SetTitleY(gstyle_title_y);
300}
301
303{
304 B2DEBUG(20, "DQMHistAnalysisECLSummary: endRun called");
305
306 updateAlarmCounts(true);
307}
308
309
311{
312 B2DEBUG(20, "terminate called");
313 delete c_channels_summary;
314 delete c_occupancy;
315 delete c_bad_chi2;
316 delete h_bad_occ_overlay;
317 delete h_bad_chi2_overlay;
318}
319
320std::pair<int, DQMHistAnalysisECLSummaryModule::ECLAlarmType> DQMHistAnalysisECLSummaryModule::getAlarmByName(std::string name)
321{
322 int index = 0;
323 for (auto& alarm_info : m_ecl_alarms) {
324 if (alarm_info.name == name) return {index, alarm_info};
325 index++;
326 }
327 B2FATAL("Could not get ECL alarm " + name);
328 return {-1, m_ecl_alarms[0]};
329}
330
332{
333 //===== Get alarm limits
334
335 for (auto& alarm : m_ecl_alarms) {
336 // In the current version, use only first crate PV to get alarm limits
337 int crate_id = 1;
338
339 std::string pv_name = (boost::format("crate%02d:%s") % crate_id % alarm.name).str();
340 double unused = 0;
341 double upper_warning_limit = 0;
342 double upper_alarm_limit = 0;
343 bool accessed = requestLimitsFromEpicsPVs(pv_name, unused, unused, upper_warning_limit, upper_alarm_limit);
344
345 if (!accessed || upper_alarm_limit <= 0 || upper_warning_limit <= 0) {
346 B2WARNING("Failed to get alarm limits");
347 return;
348 }
349
350 alarm.alarm_limit = upper_alarm_limit;
351 alarm.warning_limit = upper_warning_limit;
352 }
353
354 //===== Get masked channels
355
356 // alarm_type -> array_of_masked_channels
357 static std::map<std::string, dbr_sts_long_array> mask_info;
358 if (!getMaskedChannels(mask_info)) {
359 B2WARNING("Failed to get arrays of masked channels");
360 return;
361 }
362
363 for (auto& alarm : m_ecl_alarms) {
364 // Update the list of masked channels
365 m_mask[alarm.name].clear();
366 for (int i = 0; i < c_max_masked_channels; i++) {
367 int cell_id = mask_info[alarm.name].value[i];
368 // The array of masked channels goes like this: [15, 22, 716, 0, 0, 0, 0, 0]
369 // So if 0 (invalid channel ID) is encountered, stop reading further.
370 if (cell_id == 0) break;
371 m_mask[alarm.name].insert(cell_id);
372 }
373 }
374}
375
376bool DQMHistAnalysisECLSummaryModule::getMaskedChannels(std::map<std::string, dbr_sts_long_array>& mask_info)
377{
378 for (auto& alarm : m_ecl_alarms) {
379 std::string mask_pv_name = (boost::format("mask:%s") % alarm.name).str();
380 chid mask_chid = getEpicsPVChID(mask_pv_name);
381
382 if (mask_chid == nullptr) return false;
383 // ca_array_get data is NOT VALID here.
384 // It will only be valid after ca_pend_io returns ECA_NORMAL
385 auto r = ca_array_get(DBR_STS_LONG, c_max_masked_channels, mask_chid, &mask_info[alarm.name]);
386 if (r != ECA_NORMAL) return false;
387 }
388
389 // ca_pend_io has to be called here,
390 // see https://epics.anl.gov/base/R3-14/12-docs/CAref.html#ca_get
391
392 auto r = ca_pend_io(5.0);
393 if (r != ECA_NORMAL) return false;
394
395 return true;
396}
397
398std::vector< std::vector<int> > DQMHistAnalysisECLSummaryModule::updateAlarmCounts(bool update_mirabelle)
399{
400 std::vector< std::vector<int> > alarm_counts(
401 m_ecl_alarms.size(), std::vector<int>(ECL::ECL_CRATES));
402
403 //=== Get number of fit inconsistencies
404
405 TH1* h_fail_crateid = findHist("ECL/fail_crateid", false);
406
407 const int fit_alarm_index = getAlarmByName("bad_fit").first;
408 for (int crate_id = 1; crate_id <= ECL::ECL_CRATES; crate_id++) {
409 int errors_count = 0;
410 if (h_fail_crateid) {
411 errors_count = h_fail_crateid->GetBinContent(crate_id);
412 } else {
413 errors_count = -1;
414 }
415
416 alarm_counts[fit_alarm_index][crate_id - 1] = errors_count;
417 }
418
419 // [Cell ID] -> error_bitmask
420 std::map<int, int> error_bitmasks;
421
422 //=== Get number of dead/cold/hot channels
423 // cppcheck-suppress unassignedVariable
424 for (auto& [cell_id, error_bitmask] : getChannelsWithOccupancyProblems()) {
425 error_bitmasks[cell_id] |= error_bitmask;
426 }
427 //=== Get number of channels with bad_chi2
428 // cppcheck-suppress unassignedVariable
429 for (auto& [cell_id, error_bitmask] : getChannelsWithChi2Problems()) {
430 error_bitmasks[cell_id] |= error_bitmask;
431 }
432
433 //=== Combine the information
434
435 if (!update_mirabelle) {
436 h_bad_chi2_overlay->Reset();
437 h_bad_occ_overlay->Reset();
440 }
441
442 static std::vector<std::string> indices = {"dead", "cold", "hot", "bad_chi2"};
443 for (auto& index_name : indices) {
444 int alarm_index = getAlarmByName(index_name).first;
445 int alarm_bit = 1 << alarm_index;
446
447 for (auto& [cid, error_bitmask] : error_bitmasks) {
448 if ((error_bitmask & alarm_bit) == 0) continue;
449
450 bool masked = (m_mask[index_name].find(cid) != m_mask[index_name].end());
451
452 if (!masked) {
453 int crate_id = m_mapper.getCrateID(cid);
454 // If channel is not masked, increase the alarm counter.
455 alarm_counts[alarm_index][crate_id - 1] += 1;
456 }
457
458 if (update_mirabelle) continue;
459
460 // Unless it is the end of run, add problematic channels
461 // to the detailed 1D histogram.
462
463 if (index_name == "bad_chi2") {
464 if (!masked) h_bad_chi2_overlay->SetBinContent(cid, 1);
465 else h_bad_chi2_overlay_green->SetBinContent(cid, 1);
466 } else if (index_name == "dead" || index_name == "cold" || index_name == "hot") {
467 if (!masked) h_bad_occ_overlay->SetBinContent(cid, 1);
468 else h_bad_occ_overlay_green->SetBinContent(cid, 1);
469 }
470 }
471
472 if (update_mirabelle) continue;
473
474 //=== Prepare the histogram to display the list of all bad channels
475 if (index_name == "hot" || index_name == "bad_chi2") {
476 TCanvas* current_canvas;
477 TH1* main_hist;
478 TH1F* overlay_hist;
479 TH1F* overlay_hist_green;
480 if (index_name == "hot") {
481 main_hist = findHist("ECL/cid_Thr5MeV", m_onlyIfUpdated);
482 overlay_hist = h_bad_occ_overlay;
483 overlay_hist_green = h_bad_occ_overlay_green;
484 current_canvas = c_occupancy;
485 } else {
486 main_hist = findHist("ECL/bad_quality", m_onlyIfUpdated);
487 overlay_hist = h_bad_chi2_overlay;
488 overlay_hist_green = h_bad_chi2_overlay_green;
489 current_canvas = c_bad_chi2;
490 }
491
492 if (main_hist && main_hist->GetEntries() > 0) {
493 for (auto& overlay : {overlay_hist, overlay_hist_green}) {
494 for (int bin_id = 1; bin_id <= ECL::ECL_TOTAL_CHANNELS; bin_id++) {
495 if (overlay->GetBinContent(bin_id) == 0) continue;
496 // Do not adjust bin height for dead channels
497 if (main_hist->GetBinContent(bin_id) == 0) continue;
498 overlay->SetBinContent(bin_id, main_hist->GetBinContent(bin_id));
499 }
500 }
501
502 current_canvas->Clear();
503 current_canvas->cd();
504 main_hist->Draw("hist");
505 overlay_hist->Draw("hist;same");
506 overlay_hist_green->Draw("hist;same");
507 current_canvas->Modified();
508 current_canvas->Update();
509 current_canvas->Draw();
510 }
511 }
512 }
513
514 //== Update EPICS PVs or MiraBelle monObjs
515
516 for (size_t alarm_idx = 0; alarm_idx < alarm_counts.size(); alarm_idx++) {
517 auto& alarm = m_ecl_alarms[alarm_idx];
518 std::map<std::string, int> total;
519 // Convert values per crate to totals
520 for (size_t crate = 0; crate < alarm_counts[alarm_idx].size(); crate++) {
521 int crate_id = crate + 1;
522 int value = alarm_counts[alarm_idx][crate];
523
524 if (!update_mirabelle) {
525 std::string pv_name = (boost::format("crate%02d:%s") % crate_id % alarm.name).str();
526 setEpicsPV(pv_name, value);
527 }
528
529 total["All"] += value;
530 if (crate_id <= ECL::ECL_BARREL_CRATES) {
531 total["Barrel"] += value;
532 } else if (crate_id <= ECL::ECL_BARREL_CRATES + ECL::ECL_FWD_CRATES) {
533 total["FWDEndcap"] += value;
534 } else {
535 total["BWDEndcap"] += value;
536 }
537 }
538 // Export totals
539 for (auto& ecl_part : {"All", "FWDEndcap", "Barrel", "BWDEndcap"}) {
540 std::string pv_name = (boost::format("%s:%s") % ecl_part % alarm.name).str();
541 if (update_mirabelle) {
542 std::string var_name = pv_name;
543 std::replace(var_name.begin(), var_name.end(), ':', '_');
544 B2DEBUG(100, var_name << " = " << total[ecl_part]);
545 m_monObj->setVariable(var_name, total[ecl_part]);
546 } else {
547 setEpicsPV(pv_name, total[ecl_part]);
548 }
549 }
550 }
551
552 return alarm_counts;
553}
554
556{
557 static std::vector< std::vector<short> > neighbours(ECL::ECL_TOTAL_CHANNELS);
558 if (neighbours[0].size() == 0) {
559 for (int cid0 = 0; cid0 < ECL::ECL_TOTAL_CHANNELS; cid0++) {
560 // [0]First is the crystal itself.
561 // [1]Second is PHI neighbour, phi+1.
562 // [2]Third is PHI neighbour, phi-1.
563 // [3]Next one (sometimes two) are THETA neighbours in theta-1.
564 // [4]Next one (sometimes two) are THETA neighbours in theta+1.
565 neighbours[cid0] = m_neighbours_obj.getNeighbours(cid0 + 1);
566 // Remove first element (the crystal itself)
567 neighbours[cid0].erase(neighbours[cid0].begin());
568 }
569 }
570
571 TH1* h_occupancy = findHist("ECL/cid_Thr5MeV", m_onlyIfUpdated);
572 const double max_deviation = m_maxDeviationForOccupancy;
573 return getSuspiciousChannels(h_occupancy, m_total_events, neighbours,
574 max_deviation, true);
575}
576
578{
580
581 static std::vector< std::vector<short> > neighbours(ECL::ECL_TOTAL_CHANNELS);
582 if (neighbours[0].size() == 0) {
583 for (int cid_center = 1; cid_center <= ECL::ECL_TOTAL_CHANNELS; cid_center++) {
584 geom->Mapping(cid_center - 1);
585 const int theta_id_center = geom->GetThetaID();
586 int phi_id_center = geom->GetPhiID();
587 const int crystals_in_ring = m_neighbours_obj.getCrystalsPerRing(theta_id_center);
588 phi_id_center = phi_id_center * 144 / crystals_in_ring;
589 for (int cid0 = 0; cid0 < 8736; cid0++) {
590 if (cid0 == cid_center - 1) continue;
591 geom->Mapping(cid0);
592 int theta_id = geom->GetThetaID();
593 int phi_id = geom->GetPhiID();
594 phi_id = phi_id * 144 / m_neighbours_obj.getCrystalsPerRing(theta_id);
595 if (std::abs(theta_id - theta_id_center) <= 2 &&
596 std::abs(phi_id - phi_id_center) <= 2) {
597 neighbours[cid_center - 1].push_back(cid0);
598 }
599 }
600 }
601 }
602
603 TH1* h_bad_chi2 = findHist("ECL/bad_quality", m_onlyIfUpdated);
604 const double max_deviation = m_maxDeviationForChi2;
605 return getSuspiciousChannels(h_bad_chi2, m_total_events, neighbours,
606 max_deviation, false);
607}
608
610 TH1* hist, double total_events,
611 const std::vector< std::vector<short> >& neighbours,
612 double max_deviation, bool occupancy_histogram)
613{
614 std::map<int, int> retval;
615
616 if (!hist) return retval;
617 //== A bit hacky solution to skip incorrectly
618 // filled histograms
619 if (hist->Integral() <= 0) return retval;
620
621 //=== Extract alarm details
622 // cppcheck-suppress unassignedVariable
623 const auto& [dead_index, dead_alarm] = getAlarmByName("dead");
624 // cppcheck-suppress unassignedVariable
625 const auto& [cold_index, cold_alarm] = getAlarmByName("cold");
626 // cppcheck-suppress unassignedVariable
627 const auto& [hot_index, hot_alarm ] = getAlarmByName("hot");
628 // cppcheck-suppress unassignedVariable
629 const auto& [chi2_index, chi2_alarm] = getAlarmByName("bad_chi2");
630
631 double min_required_events;
632
633 if (occupancy_histogram) {
634 min_required_events = std::min({
635 dead_alarm.required_statistics,
636 cold_alarm.required_statistics,
637 hot_alarm.required_statistics,
638 });
639 } else {
640 min_required_events = chi2_alarm.required_statistics;
641 }
642
643 if (total_events < min_required_events) return retval;
644
645 int dead_bit = 1 << dead_index;
646 int cold_bit = 1 << cold_index;
647 int hot_bit = 1 << hot_index;
648 int chi2_bit = 1 << chi2_index;
649
650 // == Search for dead channels
651
652 if (occupancy_histogram) {
653 // This indicates that DQMHistAnalysisECL module is not included in the path
654 bool not_normalized = (findCanvas("ECL/c_cid_Thr5MeV_analysis") == nullptr);
655 if (total_events >= dead_alarm.required_statistics) {
656 double min_occupancy;
657 if (getRunType() == "null") {
658 // For null runs, occupancy should be higher than 0.0001%
659 min_occupancy = 1e-6;
660 } else {
661 // For cosmic runs, occupancy should be higher than 0.01%
662 min_occupancy = 1e-4;
663 // (for physics runs, as opposed to cosmics, this can actually be set to higher value)
664 }
665 if (not_normalized) {
666 // The histogram is not normalized, multiply the threshold by evt count
667 min_occupancy *= total_events;
668 }
669 for (int cid = 1; cid <= ECL::ECL_TOTAL_CHANNELS; cid++) {
670 if (hist->GetBinContent(cid) > min_occupancy) continue;
671 retval[cid] |= dead_bit;
672 }
673 }
674 if (total_events >= hot_alarm.required_statistics) {
675 // Number of hits with E > threshold should be less than 70%
676 double max_occupancy = 0.7;
677 if (not_normalized) {
678 // The histogram is not normalized, multiply the threshold by evt count
679 max_occupancy *= total_events;
680 }
681 for (int cid = 1; cid <= ECL::ECL_TOTAL_CHANNELS; cid++) {
682 if (hist->GetBinContent(cid) < max_occupancy) continue;
683 retval[cid] |= hot_bit;
684 }
685 }
686 }
687
688 // == Search for cold and hot channels (or channels with bad chi2)
689
690 for (int cid = 1; cid <= ECL::ECL_TOTAL_CHANNELS; cid++) {
691 double actual_value = hist->GetBinContent(cid);
692
693 std::vector<short> neighb = neighbours[cid - 1];
694 std::multiset<double> values_sorted;
695 for (auto& neighbour_cid : neighb) {
696 values_sorted.insert(hist->GetBinContent(neighbour_cid));
697 }
698
699 double expected_value;
700 if (occupancy_histogram) {
701 // Use "lower 25%" value:
702 expected_value = *std::next(values_sorted.begin(), 1 * neighb.size() / 4);
703 } else {
704 // Use "upper ~75%" value (48 is the expected number of neighbours)
705 expected_value = *std::next(values_sorted.begin(), 35 * neighb.size() / 48);
706 }
707 double deviation = std::abs((actual_value - expected_value) /
708 expected_value);
709
710 if (!occupancy_histogram) {
711 // Min occupancy for high-energy (> 1GeV) hits
712 double min_occupancy = 1.41e-5;
713 if (findCanvas("ECL/c_bad_quality_analysis") == nullptr) {
714 // The histogram is not normalized, multiply the threshold by evt count
715 min_occupancy *= total_events;
716 }
717
718 if (actual_value < min_occupancy) continue;
719 }
720
721 if (deviation < max_deviation) continue;
722
723 if (occupancy_histogram) {
724 if (actual_value < expected_value) retval[cid] |= cold_bit;
725 if (actual_value > 2 * expected_value) retval[cid] |= hot_bit;
726 } else {
727 if (actual_value > expected_value) retval[cid] |= chi2_bit;
728 }
729 }
730
731 return retval;
732}
733
735{
736 static std::map<std::string, std::vector<TLine*> > lines;
737 std::string name = hist->GetName();
738 if (lines[name].empty()) {
739 int x_min = hist->GetXaxis()->GetXmin();
740 int x_max = hist->GetXaxis()->GetXmax();
741 int y_min = hist->GetYaxis()->GetXmin();
742 int y_max = hist->GetYaxis()->GetXmax();
743 for (int x = x_min + 1; x < x_max; x++) {
744 auto l = new TLine(x, 0, x, 5);
745 l->SetLineStyle(kDashed);
746 lines[name].push_back(l);
747 }
748 for (int y = y_min + 1; y < y_max; y++) {
749 auto l = new TLine(1, y, ECL::ECL_CRATES + 1, y);
750 l->SetLineStyle(kDashed);
751 lines[name].push_back(l);
752 }
753 }
754 for (auto line : lines[name]) {
755 line->Draw();
756 }
757}
758
std::map< int, int > getSuspiciousChannels(TH1 *hist, double total_events, const std::vector< std::vector< short > > &neighbours, double max_deviation, bool occupancy_histogram)
Get outlier channels, ones with values (occupancy, errors, etc) that are much higher than the values ...
double m_total_events
Number of events with ECL data in the current run.
double m_maxDeviationForChi2
The higher this parameter, the larger differences in the number of hits with bad chi2 are allowed for...
TH1F * h_bad_occ_overlay
Overlay to indicate bad channels on occupancy histogram.
void initialize() override final
Initialize the module.
void updateAlarmConfig()
Set alarm limits in DQM based on EPICS PV limits.
std::vector< std::vector< int > > updateAlarmCounts(bool update_mirabelle=false)
Calculate and return number of problems per crate for all alarm categories.
std::vector< TText * > m_labels
Labels on the ECL alarms histogram.
ECL::ECLChannelMapper m_mapper
Object to map ECL crystal ID to ECL crate ID.
void drawGrid(TH2 *hist)
Draw grid with TLine primitives for the specified histogram.
TCanvas * c_channels_summary
TCanvas for ECL alarms regarding suspicious channels.
static const int c_max_masked_channels
Size of an array with masked channels.
std::pair< int, ECLAlarmType > getAlarmByName(std::string name)
Returns index of the specific alarm type and detailed information.
TH2F * h_channels_summary
Summarized information about ECL channels.
double m_maxDeviationForOccupancy
The higher this parameter, the larger differences in occupancy are allowed for adjacent channels.
ECL::ECLNeighbours m_neighbours_obj
Object to get ECL crystal neighbours.
TCanvas * c_occupancy
ECL occupancy histogram with highlighted suspicious channels.
TExec * m_ecl_style
Special object to specify style parameters.
std::string m_pvPrefix
Prefix to use for PVs registered by this module.
MonitoringObject * m_monObj
MiraBelle monitoring object.
void event() override final
Event processor.
TCanvas * c_bad_chi2
ECL bad chi2 histogram with highlighted suspicious channels.
TH1F * h_bad_chi2_overlay
Overlay to indicate bad channels on chi2 histogram.
void endRun() override final
Call when a run ends.
bool m_onlyIfUpdated
If true (default), update EPICS PVs only if there were changes in the histograms.
TH1F * h_bad_chi2_overlay_green
Overlay to indicate masked bad channels on chi2 histogram.
void beginRun() override final
Call when a run begins.
DQMHistAnalysisECLSummaryModule()
< derived from DQMHistAnalysisModule class.
TExec * m_default_style
Special object to revert changes done by ecl_style.
TH1F * h_bad_occ_overlay_green
Overlay to indicate masked bad channels on occupancy histogram.
std::map< std::string, std::set< int > > m_mask
List of masked Cell IDs for each alarm type.
bool getMaskedChannels(std::map< std::string, dbr_sts_long_array > &mask_info)
Get the array of masked channels for each type of alarm case monitored by this module.
bool m_useChannelMask
If true, mask Cell IDs based on DQM:ECL:channels_info:{alarm_type} PV info.
std::vector< ECLAlarmType > m_ecl_alarms
Different alarms monitored in h_channels_summary.
The base class for the histogram analysis module.
TCanvas * findCanvas(TString cname)
Find canvas by name.
static MonitoringObject * getMonitoringObject(const std::string &name)
Get MonitoringObject with given name (new object is created if non-existing)
void colorizeCanvas(TCanvas *canvas, EStatus status)
Helper function for Canvas colorization.
static TH1 * findHist(const std::string &histname, bool onlyIfUpdated=false)
Get histogram from list (no other search).
@ c_ColorTooFew
Not enough entries/event to judge.
static const std::string & getRunType(void)
Get the Run Type.
void setEpicsPV(std::string keyname, double value)
Write value to a EPICS PV.
EStatus makeStatus(bool enough, bool warn_flag, bool error_flag)
Helper function to judge the status for coloring and EPICS.
int registerEpicsPV(std::string pvname, std::string keyname="")
EPICS related Functions.
chid getEpicsPVChID(std::string keyname)
Get EPICS PV Channel Id.
bool requestLimitsFromEpicsPVs(chid id, double &lowerAlarm, double &lowerWarn, double &upperWarn, double &upperAlarm)
Get Alarm Limits from EPICS PV.
bool initFromFile()
Initialize channel mapper using data stored in default location.
int getCrateID(int iCOPPERNode, int iFINESSE, bool pcie40=false)
Get crate number by given COPPER node number and FINESSE number.
The Class for ECL Geometry Parameters.
static ECLGeometryPar * Instance()
Static method to get a reference to the ECLGeometryPar instance.
const std::vector< short int > & getNeighbours(short int cid) const
Return the neighbours for a given cell ID.
short int getCrystalsPerRing(const short int thetaid) const
return number of crystals in a given theta ring
Definition: ECLNeighbours.h:39
void setVariable(const std::string &var, float val, float upErr=-1., float dwErr=-1)
set value to float variable (new variable is made if not yet existing)
void addParam(const std::string &name, T &paramVariable, const std::string &description, const T &defaultValue)
Adds a new parameter to the module.
Definition: Module.h:560
#define REG_MODULE(moduleName)
Register the given module (without 'Module' suffix) with the framework.
Definition: Module.h:650
Abstract base class for different kinds of events.