Skip to content

Commit a36e260

Browse files
Benedikt Volkelchiarazampolli
authored andcommitted
[RelVal] Updates
* accept any number of histograms for overlay * make 1D overlay plots nicer to look at * automatic log10 scale if integrals differ by more than 2 orders of magnitude * unified relative y-ranges for nominal and ratio plot * choose different line colors * unify label and title sizes * re-position legends * choose x-range such that everything is seen (sometimes the x-axis range is huge while entries are only in some of the first bins and nothing could be seen) * more ratio plot directly underneath nominal plot and share x-axis * indicate in overlay plots if something is empty * always copy overlay plots with inspect command * copy those overlays that satisfy selection criteria * add brief QC section to README
1 parent e2b5dbc commit a36e260

3 files changed

Lines changed: 266 additions & 71 deletions

File tree

RelVal/PlotOverlays.C

Lines changed: 185 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,61 +2,195 @@
22
#include <vector>
33
#include <filesystem>
44

5+
6+
7+
void findRangeNotEmpty1D(TH1* h, double& min, double& max)
8+
{
9+
auto axis = h->GetXaxis();
10+
min = axis->GetBinLowEdge(1);
11+
max = axis->GetBinUpEdge(axis->GetNbins());
12+
for (int i = 1; i <= axis->GetNbins(); i++) {
13+
if (h->GetBinContent(i) != 0) {
14+
min = axis->GetBinLowEdge(i);
15+
break;
16+
}
17+
}
18+
for (int i = axis->GetNbins(); i >= 1 ; i--) {
19+
if (h->GetBinContent(i) != 0) {
20+
max = axis->GetBinUpEdge(i);
21+
break;
22+
}
23+
}
24+
}
25+
26+
27+
TH1* makeFrameFromHistograms(TPad& pad, std::vector<TH1*> const& histograms, bool& shouldBeLog)
28+
{
29+
// make a frame to fit all histograms
30+
// propose log10 scale in case some integrals differ by more than 2 orders of magnitude
31+
auto integralRef = histograms[0]->Integral();
32+
shouldBeLog = false;
33+
auto minY = histograms[0]->GetMinimum(0);
34+
double maxY = histograms[0]->GetMaximum();
35+
36+
double minX;
37+
double maxX;
38+
findRangeNotEmpty1D(histograms[0], minX, maxX);
39+
40+
// find minima and maxima
41+
for (int i = 1; i < histograms.size(); i++) {
42+
minY = std::min(histograms[i]->GetMinimum(0), minY);
43+
maxY = std::max(histograms[i]->GetMaximum(), maxY);
44+
45+
double minXNext;
46+
double maxXNext;
47+
findRangeNotEmpty1D(histograms[i], minXNext, maxXNext);
48+
minX = std::min(minX, minXNext);
49+
maxX = std::max(maxX, maxXNext);
50+
51+
auto integral = histograms[i]->Integral();
52+
if ((integralRef > 0 && integral / integralRef > 100) || (integral > 0 && integralRef / integral > 100)) {
53+
// decide whether to do a log plot
54+
shouldBeLog = true;
55+
}
56+
}
57+
58+
// finalise the y-axis limits
59+
if (shouldBeLog) {
60+
auto margin = std::log10(maxY / minY);
61+
minY = minY / std::pow(10, margin * 0.1);
62+
maxY = maxY * std::pow(10, margin * 0.3);;
63+
} else {
64+
auto margin = 0.1 * (maxY - minY);
65+
maxY += 3 * margin;
66+
minY -= std::max(0., margin);
67+
}
68+
69+
if (histograms[0]->GetXaxis()->IsAlphanumeric()) {
70+
auto alphanumericFrame = (TH1*)histograms[0]->Clone();
71+
alphanumericFrame->Reset("ICEMS");
72+
return alphanumericFrame;
73+
}
74+
75+
return pad.DrawFrame(minX, minY, maxX, maxY);
76+
}
77+
578
// overlay 1D histograms
6-
void overlay1D(std::vector<TH1*> hVec, std::vector<std::string> labelVec, TLegend* legend, std::string const& outputDir)
79+
void overlay1D(std::vector<TH1*> hVec, std::vector<std::string> labelVec, TLegend* additionalLegend, std::string const& outputDir)
780
{
881
TCanvas c("overlay", "", 800, 800);
982
c.cd();
1083

84+
TPad nominalPad("nominalPad", "nominalPad", 0, 0.3, 1., 1.);
85+
nominalPad.SetBottomMargin(0);
86+
TPad ratioPad("ratioPad", "ratioPad", 0, 0.05, 1. ,0.32);
87+
ratioPad.SetTopMargin(0);
88+
ratioPad.SetBottomMargin(0.2);
1189

12-
TPad lower_pad("lower_pad","lower_pad",0,0.05,1.,0.3);
13-
TPad upper_pad("upper_pad","upper_pad",0,0.25,1.,1.);
14-
upper_pad.Draw();
15-
lower_pad.Draw();
90+
nominalPad.Draw();
91+
ratioPad.Draw();
1692

17-
const int colors[6]={kRed,kBlue,kGreen,kMagenta,kCyan,kOrange}; //TODO what if more then 6 histograms
18-
const int linestyles[6]={1,10,2,9,8,7}; //TODO what if more then 6 histograms
93+
const int colors[7] = {kRed + 2, kBlue - 4, kGreen + 3, kMagenta + 1, kCyan + 2, kOrange + 5, kYellow - 6};
94+
const int linestyles[6] = {1, 10, 2, 9, 8, 7};
1995

20-
TLegend legendOverlay(0.65, 0.8, 0.9, 0.9);
96+
TLegend legendOverlay(0.65, 0.7, 0.9, 0.9);
2197
legendOverlay.SetFillStyle(0);
22-
int counter = 0;
23-
for (auto h : hVec){
24-
upper_pad.cd();
98+
legendOverlay.SetBorderSize(0);
99+
100+
bool logY{};
101+
nominalPad.cd();
102+
auto frame = makeFrameFromHistograms(nominalPad, hVec, logY);
103+
frame->SetTitle(hVec[0]->GetTitle());
104+
auto yAxis = frame->GetYaxis();
105+
yAxis->ChangeLabel(1, -1, -1, -1, -1, -1, " ");
106+
yAxis->SetTitleFont(43);
107+
yAxis->SetTitleSize(20);
108+
yAxis->SetLabelFont(43);
109+
yAxis->SetLabelSize(20);
110+
yAxis->SetTitle(hVec[0]->GetYaxis()->GetTitle());
111+
auto xAxis = frame->GetXaxis();
112+
xAxis->SetLabelFont(43);
113+
xAxis->SetLabelSize(0);
114+
115+
116+
std::vector<TH1*> ratios;
117+
118+
std::string emptyText;
119+
for (int i = 0; i < hVec.size(); i++) {
120+
auto& h = hVec[i];
121+
25122
h->SetStats(0);
26-
h->SetLineStyle(linestyles[counter]);
123+
h->SetLineStyle(linestyles[i % 6]);
27124
h->SetLineWidth(1);
28-
h->SetLineColor(colors[counter]);
29-
TH1F* hClone = (TH1F*)h->Clone();
125+
h->SetLineColor(colors[i % 7]);
30126

31-
h->GetXaxis()->SetLabelSize(0.);
32-
h->GetXaxis()->SetLabelOffset(999);
33-
h->GetYaxis()->SetLabelSize(0.05);
127+
if (i > 0) {
128+
// no ratio for the first histogram (which would simply be 1)
129+
TH1* hRatio = (TH1*)h->Clone();
130+
hRatio->Divide(h, hVec[0], 1.0, 1.0, "B"); // error option?
131+
ratios.push_back(hRatio);
132+
}
34133

35-
legendOverlay.AddEntry(h, labelVec[counter].c_str());
134+
legendOverlay.AddEntry(h, labelVec[i].c_str());
36135

37136
h->Draw("same E hist");
38-
39-
lower_pad.cd();
40-
hClone->Divide(h,hVec[0],1.0,1.0,"B"); // error option?
41-
hClone->SetTitle("");
42-
hClone->SetLineStyle(1);
43-
hClone->GetYaxis()->SetRangeUser(0.,10.);
44-
hClone->GetYaxis()->SetLabelSize(0.125);
45-
hClone->GetXaxis()->SetLabelSize(0.125);
46-
if (counter>0){
47-
hClone->Draw("same E1");
48-
}
49-
else {
50-
hClone->Draw("same");
137+
if (h->GetEntries() == 0) {
138+
emptyText += labelVec[i] + ", ";
51139
}
52-
counter++;
53140
}
54-
upper_pad.cd();
55-
if (legend){
56-
legend->Draw("same");
141+
142+
if (logY) {
143+
nominalPad.SetLogy();
144+
}
145+
146+
if (additionalLegend) {
147+
additionalLegend->SetBorderSize(0);
148+
additionalLegend->SetFillStyle(0);
149+
// To reposition the legend we need to: Draw, Update, set new coordinates, Modified
150+
additionalLegend->Draw("same");
151+
nominalPad.Update();
152+
additionalLegend->SetX1NDC(0.15);
153+
additionalLegend->SetY1NDC(0.7);
154+
additionalLegend->SetX2NDC(0.4);
155+
additionalLegend->SetY2NDC(0.9);
156+
nominalPad.Modified();
57157
}
58158
legendOverlay.Draw("same");
59159

160+
if (!emptyText.empty()) {
161+
emptyText.pop_back();
162+
emptyText.pop_back();
163+
emptyText = std::string("EMPTY: ") + emptyText;
164+
TText *t1 = new TText(0.2, 0.5, emptyText.c_str());
165+
t1->SetNDC();
166+
t1->Draw();
167+
}
168+
169+
ratioPad.cd();
170+
frame = makeFrameFromHistograms(ratioPad, ratios, logY);
171+
yAxis = frame->GetYaxis();
172+
yAxis->SetTitleFont(43);
173+
yAxis->SetTitleSize(20);
174+
yAxis->SetLabelFont(43);
175+
yAxis->SetLabelSize(20);
176+
yAxis->SetTitle("ratio");
177+
178+
xAxis = frame->GetXaxis();
179+
xAxis->SetTitleFont(43);
180+
xAxis->SetTitleSize(20);
181+
xAxis->SetLabelFont(43);
182+
xAxis->SetLabelSize(20);
183+
xAxis->SetTitle(hVec[0]->GetXaxis()->GetTitle());
184+
185+
for (int i = 0; i < ratios.size(); i++) {
186+
auto& h = ratios[i];
187+
h->Draw("same");
188+
}
189+
190+
if (logY) {
191+
ratioPad.SetLogy();
192+
}
193+
60194
auto savePath = outputDir + "/" + hVec[0]->GetName() + ".png";
61195
c.SaveAs(savePath.c_str());
62196
c.Close();
@@ -71,25 +205,36 @@ void overlay2D(std::vector<TH1*> hVec1, std::vector<std::string> labelVec, TLege
71205
}
72206
int nHistos = hVec.size();
73207

74-
TCanvas c("overlay", "", 2400, 800*(nHistos-1));
75-
c.Divide(3, nHistos-1);
208+
TCanvas c("overlay", "", 2400, 800 * (nHistos-1));
209+
c.Divide(3, nHistos - 1);
76210
c.cd(1);
77211
hVec[0]->SetTitle(hVec[0]->GetTitle() + TString("(" + labelVec[0] + ")"));
78212
hVec[0]->SetStats(0);
79213
hVec[0]->Draw("colz");
80214

81-
for (int i = 1; i<nHistos; i++){
215+
if (hVec[0]->GetEntries() == 0) {
216+
TText *t1 = new TText(0.5, 0.5, "EMPTY");
217+
t1->SetNDC();
218+
t1->Draw();
219+
}
220+
221+
for (int i = 1; i < nHistos; i++){
82222
auto hDiv = (TH2*)hVec[i]->Clone(Form("%s_ratio", hVec[i]->GetName()));
83223
hDiv->SetTitle(hVec[i]->GetTitle() + TString("(" + labelVec[i] + "/"+labelVec[0]+")"));
84224
hDiv->SetStats(0);
85225
hDiv->Divide(hVec[0]);
86226
hVec[i]->SetTitle(hVec[i]->GetTitle() + TString("(" + labelVec[i] + ")"));
87227
hVec[i]->SetStats(0);
88228

89-
c.cd(i*3-1);
229+
c.cd(i * 3 - 1);
90230
hVec[i]->Draw("colz");
231+
if (hVec[i]->GetEntries() == 0) {
232+
TText *t1 = new TText(0.5, 0.5, "EMPTY");
233+
t1->SetNDC();
234+
t1->Draw();
235+
}
91236

92-
c.cd(i*3);
237+
c.cd(i * 3);
93238
hDiv->Draw("colz");
94239
}
95240

RelVal/README.md

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ The [Python script](o2dpg_release_validation.py) is the entrypoint of the RelVal
4242

4343
The full help message of this script can be seen by typing
4444
```bash
45-
python o2dpg_release_validation.py [<sub-command>] --help
45+
${O2DPG_ROOT}/RelVal/o2dpg_release_validation.py [<sub-command>] --help
4646
```
4747
The wrapper includes 3 different sub-commands for now
4848
1. `rel-val` to steer the RelVal,
@@ -82,11 +82,14 @@ ${O2DPG_ROOT}/RelVal/o2dpg_release_validation.py inspect --path <path-to-outputd
8282
[--include-patterns <patterns>] [--exclude-patterns <patterns>] \
8383
[--enable-metric <metric_names>] [--disable-metric <metric_names>] \
8484
[--interpretations <interpretations_of_interest>] \
85-
[--critical <metric_names_considered_critical>]
85+
[--critical <metric_names_considered_critical>] \
86+
[--output|-o <target_directory>]
8687
```
8788
All of those options, except for `--include-patterns` and `--exclude-patterns` also work with the `rel-val` command.
8889
The output will by default be written to `rel_val_inspect`. All plots which are produced by the `rel-val` command are produced again for a potential given sub-set depending on the given options. Only the overlay plots are not produced again.
8990

91+
**NOTE** that with `inspect` the original overlay plots satisfying your selection criteria (e.g. `--include-patters` or `--interpretations`) are also copied over to the target directory.
92+
9093
**Other additional optional arguments**
9194
* `--use-values-as-thresholds [<list_of_other_Summary.json_files>]`: By passing a set of summaries that where produced from `rel-val`, the computed metric values can be used as **new** thresholds. To decide how to combine the values for multiple metrics referring to the same object, the option `--combine-thresholds mean|extreme` can be used. Also, an additional relative margin can be added for each metric with `--margin-threshold <metric> <percent>`; this argument must be repeated for if it should be used for multiple metrics.
9295
* `--regions [<list_of_other_Summary.json_files>]`: This computes means and standard deviations for each metric from previously computed values. The corresponding test is passed, if the value lies around the mean within the standard deviations. The deviation from the mean is also given as number-of-sigmas in the summary grid.
@@ -106,7 +109,7 @@ ${O2DPG_ROOT}/RelVal/o2dpg_release_validation.py print --metric-names
106109

107110
To convert the final output to something that can be digested by InfluxDB, use
108111
```bash
109-
python ${O2DPG_ROOT}/RelVal/o2dpg_release_validation.py influx --dir <rel-val-out-dir> [--tags k1=v1 k2=v2 ...] [--table-name <chosen-table-name>]
112+
${O2DPG_ROOT}/RelVal/o2dpg_release_validation.py influx --dir <rel-val-out-dir> [--tags k1=v1 k2=v2 ...] [--table-name <chosen-table-name>]
110113
```
111114
When the `--tags` argument is specified, these are injected as TAGS for InfluxDB in addition. The table name can also be specified explicitly; if not given, it defaults to `O2DPG_MC_ReleaseValidation`.
112115

@@ -118,8 +121,33 @@ There is an ongoing effort to unify the names of QC objects inside MC and data Q
118121

119122
MC QC objects are usually distributed over multiple files while those from data are all contained in one single file. It is possible to directly compare them with
120123
```bash
121-
python ${O2DPG_ROOT}/RelVal/o2dpg_release_validation.py rel-val -i ${MC_PRODUCTION}/QC/*.root -j ${DATA_PRODUCTION}/QC.root [--include-dirs <include-directories>]
124+
${O2DPG_ROOT}/RelVal/o2dpg_release_validation.py rel-val -i ${MC_PRODUCTION}/QC/*.root -j ${DATA_PRODUCTION}/QC.root [--include-dirs <include-directories>]
125+
```
126+
127+
## Run for QC
128+
This is a simple guide to run RelVal for QC.
129+
130+
### If you are interested in all QC plots
131+
To have everything and to use this as a starting point for deeper inspections, first run
132+
```bash
133+
${O2DPG_ROOT}/RelVal/o2dpg_release_validation.py rel-val -i QC_file_1.root -j QC_file_2.root -o rel_val_all [--labels meaningfulLabel1 meaningfulLabel2]
134+
```
135+
Now, there is of course a lot but from now on you are fully flexible.
136+
137+
In order to get some insight into a specific detector, say ITS, run
138+
```bash
139+
${O2DPG_ROOT}/RelVal/o2dpg_release_validation.py inspect --path rel_val_all --include-patterns "^ITS_" -o rel_val_ITS
140+
```
141+
This will only print pie charts and summaries for ITS and also copies all overlay plots related to ITS to your target directory `rel_val_ITS`.
142+
143+
The `inspect` command is much faster now since no new plots are generated and metrics do not have to be recomputed. It simply filters the results according to your criteria. However, what can be re-evaluated are the computed values against new thresholds.
144+
145+
### If you are only interested in some ROOT sub-directories to begin with
146+
If you only want to study for instance the ITS and CPV and there is no interest at this point to study any other detector, run
147+
```bash
148+
${O2DPG_ROOT}/RelVal/o2dpg_release_validation.py rel-val -i QC_file_1.root -j QC_file_2.root -o rel_val_all --include-dirs ITS CPV [--labels meaningfulLabel1 meaningfulLabel2]
122149
```
150+
From here on, you can use the `inspect` command as usual. But there will never be detectors other than ITS and CPV.
123151

124152
## Expert section
125153

0 commit comments

Comments
 (0)