Skip to content

Commit d493bba

Browse files
Benedikt Volkelchiarazampolli
authored andcommitted
Update README and small fixes/updates
* make README more readible and add further information * get a global summary format also when run only on 2 single files * update inspection interface * minor bug fixes
1 parent 47f82c2 commit d493bba

File tree

2 files changed

+132
-56
lines changed

2 files changed

+132
-56
lines changed

RelVal/README.md

Lines changed: 79 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,89 @@
1-
This macro ReleaseValidation.C permits to compare the QC.root output from different passes
1+
# O2DPG ReleaseValidation (RelVal)
22

3+
## The macro [ReleaseValidation.C](ReleaseValidation.C)
34

4-
## Usage
5-
The input variables which we need to give to the macro are:
5+
This macro `ReleaseValidation.C` allows to compare 2 ROOT files that contain objects of the types
6+
* ROOT histograms (deriving from `TH1`)
7+
* ROOT `TProfile`
8+
* ROOT `TEfficiency`
9+
* O2 `o2::quality_control::core::MonitorObjectCollection`
10+
* O2 `o2::quality_control::core::MonitorObject`
611

7-
- the two QC.root files, with corresponind path
12+
At the moment, 3 different comparisons are implemented:
13+
1. relative difference of bin contents,
14+
1. Chi2 test,
15+
1. simple comparison of number of entries
816

9-
- the Monitor object collection we want to focus on:
10-
QcTaskMIDDigits;
11-
DigitQcTaskFV0;
12-
TaskDigits;
13-
DigitQcTaskFT0;
14-
QcMFTAsync;
15-
ITSTrackTask;
16-
MatchedTracksITSTPC;
17-
MatchingTOF;
18-
ITSClusterTask;
19-
Clusters;
20-
PID;
21-
Tracks;
22-
Vertexing
17+
The first 2 tests are considered critical, hence if the threshold is exceeded, the comparison result is named `BAD`.
2318

24-
- which compatibility test we want to perform (bit mask):
25-
1->Chi-square;
26-
2--> ContBinDiff;
27-
3 (combination of 1 and 2)--> Chi-square+MeanDiff;
28-
4-> N entries;
29-
5 (combination of 1 and 4) --> Nentries + Chi2;
30-
6 (combination of 1 and 2)--> N entries + MeanDiff;
31-
7 (combination of 1, 2 and 3)--> Nentries + Chi2 + MeanDiff
19+
There are 5 different test severities per test:
20+
1. `GOOD` if the threshold was not exceeded,
21+
1. `WARNING`: if a non-critical test exceeds the threshold (in this case only when comparing the number of entries),
22+
1. `NONCRIT_NC` if the histograms could not be compared e.g. due to different binning or axis ranges **and** if the test is considered as **non-critical**,
23+
1. `CRIT_NC` if the histograms could not be compared e.g. due to different binning or axis ranges **and** if the test is considered as **critical**,
24+
1. `BAD` if a critical test exceeds the threshold.
3225

33-
- threshold values for checks on Chi-square and on content of bins
26+
## Python wrapper and usage
3427

35-
- choose if we want to work on the grid or on local laptop (to be fixed)
28+
Although the above macro can be used on its own, its application was also wrapped into a [Python script](o2dpg_release_validation.py) for convenience. By doing so, it offers significantly more functionality.
3629

37-
- tell the script it there are "critical "histograms (the list of names of critical plots has to be written in a txt file), which we need to keep separated from the other histograms. The corresponding plots will be saved in a separated pdf file
30+
The full help message of this script can be seen by typing
31+
```bash
32+
python o2dpg_release_validation.py [<sub-command>] --help
33+
```
34+
The wrapper includes 3 different sub-commands for now
35+
1. `rel-val` to steer the RelVal,
36+
1. `inspect` to print histograms of specified severity (if any),
37+
1. `influx` to convert the summary into a format that can be understood by and sent to an InfluxDB instance.
3838

39+
### Basic usage
3940

40-
The macro is currently working only on real data (will be fixed soon)
41+
If you would like to compare 2 files, simply run
42+
```bash
43+
python o2dpg_release_validation.py rel-val -i <file1> <file2> [-o <output/dir>]
44+
```
45+
This performs all of the above mentioned tests. If only certain tests should be performed, this can be achieved with the flags `--with-<which-test>` where `<which-test>` is one of
46+
1. `chi2`,
47+
1. `bincont`,
48+
1. `numentries`.
49+
By default, all of them are switched on.
50+
51+
### Apply to entire simulation outcome
52+
53+
In addition to simply comparing 2 ROOT files, the script offers the possibility of comparing 2 corresponding directories that contain simulation artifacts (and potentially QC and analysis results). This then automatically runs the RelVal on
54+
1. QC output,
55+
1. analysis results output,
56+
1. TPC tracks output,
57+
1. MC kinematics,
58+
1. MC hits.
59+
**NOTE** That each single one of the comparison types if only done if mutual files were found in the 2 corresponding directories. As an example, one could do
60+
```bash
61+
cd ${DIR1}
62+
python o2dpg_workflow_runner.py -f <workflow-json1>
63+
cd ${DIR2}
64+
# potentially something has changed in the software or the simulation/reconstruction parameters
65+
python o2dpg_workflow_runner.py -f <workflow-json2>
66+
python ${O2DPG_ROOT}/ReleaseValidation/o2dpg_release_validation.py rel-val -i ${DIR1} ${DIR2} [-o <output/dir>] [<test-flags>]
67+
```
68+
Again, also here it can be specified explicitly on what the tests should be run by specifying one or more `<test-flags` such as
69+
1. `--with-qc`,
70+
1. `--with-analysis`,
71+
1. `--with-tpctracks`,
72+
1. `--with-kine`,
73+
1. `--with-hits`.
74+
75+
### Quick inspection
76+
77+
This is done via
78+
```bash
79+
python ${O2DPG_ROOT}/ReleaseValidation/o2dpg_release_validation.py inspect <path-to-outputdir-or-file> [--severity <severity>]
80+
```
81+
The latter optional argument could be a list of any of the above mentioned severities. If a directory is passed as input, it is expected that there is either a file named `SummaryGlobal.json` or - if that cannot be found - a file named `Summary.json`.
82+
83+
### Make ready for InfluxDB
84+
85+
To convert the final output to something that can be digested by InfluxDB, use
86+
```bash
87+
python ${O2DPG_ROOT}/ReleaseValidation/o2dpg_release_validation.py influx --dir <rel-val-out-dir> [--tags k1=v1 k2=v2 ...] [--table-name <chosen-table-name>]
88+
```
89+
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`.

RelVal/o2dpg_release_validation.py

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
import sys
5858
import argparse
5959
from os import environ, makedirs
60-
from os.path import join, abspath, exists, isfile, isdir, dirname
60+
from os.path import join, abspath, exists, isfile, isdir, dirname, relpath
6161
from glob import glob
6262
from subprocess import Popen
6363
from pathlib import Path
@@ -295,26 +295,33 @@ def has_severity(filename, severity=("BAD", "CRIT_NC")):
295295
"""
296296
Check if any 2 histograms have a given severity level after RelVal
297297
"""
298+
counter = {s: 0 for s in severity + ["ALL"]}
299+
298300
def rel_val_summary(d):
299301
ret = False
300-
for s in severity:
302+
for s in REL_VAL_SEVERITY_MAP:
301303
names = d.get(s)
304+
counter["ALL"] += len(names)
302305
if not names:
303306
continue
307+
if s not in severity:
308+
continue
304309
print(f"Histograms for severity {s}:")
305310
for n in names:
306311
print(f" {n}")
312+
counter[s] = len(names)
307313
ret = True
308314
return ret
309315

310316
def rel_val_summary_global(d):
311317
ret = False
312-
to_print = {k: [] for k in severity}
313-
for s in severity:
314-
for h in d:
315-
if h["test_summary"] in severity:
316-
to_print[s].append(h["name"])
317-
ret = True
318+
to_print = {s: [] for s in severity}
319+
counter["ALL"] = len(d)
320+
for h in d:
321+
if h["test_summary"] in severity:
322+
to_print[h["test_summary"]].append(h["name"])
323+
counter[h["test_summary"]] += 1
324+
ret = True
318325
for s, names in to_print.items():
319326
if not names:
320327
continue
@@ -325,13 +332,16 @@ def rel_val_summary_global(d):
325332

326333
res = None
327334
with open(filename, "r") as f:
328-
# NOTE For now care about the summary. However, we have each test individually, so we could do a more detailed check in the future
329335
res = json.load(f)
330336

331337
# decide whether that is an overall summary or from 2 files only
332-
if "histograms" in res:
333-
return rel_val_summary_global(res["histograms"])
334-
return rel_val_summary(res["test_summary"])
338+
ret = rel_val_summary_global(res["histograms"]) if "histograms" in res else rel_val_summary(res["test_summary"])
339+
if ret:
340+
print(f"\nNumber of compared histograms: {counter['ALL']} out of which")
341+
for s in severity:
342+
print(f" {counter[s]} histograms have severity {s}")
343+
print("as printed above.\n")
344+
return ret
335345

336346

337347
def rel_val_ttree(dir1, dir2, files, output_dir, args, treename="o2sim", *, combine_patterns=None):
@@ -389,8 +399,8 @@ def make_summary(in_dir):
389399
current_summary = json.load(f)
390400
# remove the file name, used as the top key for this collection
391401
rel_val_path = "/".join(path.split("/")[:-1])
392-
type_global = path.split("/")[1]
393-
type_specific = "/".join(path.split("/")[1:-1])
402+
type_specific = relpath(rel_val_path, in_dir)
403+
type_global = type_specific.split("/")[0]
394404
make_summary = {}
395405
for which_test, flagged_histos in current_summary.items():
396406
# loop over tests done
@@ -483,9 +493,6 @@ def rel_val_sim_dirs(args):
483493
makedirs(output_dir_qc)
484494
rel_val_histograms(dir_qc1, dir_qc2, qc_files, output_dir_qc, args)
485495

486-
with open(join(output_dir, "SummaryGlobal.json"), "w") as f:
487-
json.dump(make_summary(output_dir), f, indent=2)
488-
489496

490497
def rel_val(args):
491498
"""
@@ -502,14 +509,33 @@ def rel_val(args):
502509
return 1
503510
if not exists(args.output):
504511
makedirs(args.output)
505-
return func(args)
512+
func(args)
513+
with open(join(args.output, "SummaryGlobal.json"), "w") as f:
514+
json.dump(make_summary(args.output), f, indent=2)
506515

507516

508517
def inspect(args):
509518
"""
510519
Inspect a Summary.json in view of RelVal severity
511520
"""
512-
return has_severity(args.file, args.severity)
521+
path = args.path
522+
523+
def get_filepath(d):
524+
summary_global = join(path, "SummaryGlobal.json")
525+
if exists(summary_global):
526+
return summary_global
527+
summary = join(path, "Summary.json")
528+
if exists(summary):
529+
return summary
530+
print(f"Can neither find {summary_global} nor {summary}. Nothing to work with.")
531+
return None
532+
533+
if isdir(path):
534+
path = get_filepath(path)
535+
if not path:
536+
return 1
537+
538+
return not has_severity(path, args.severity)
513539

514540

515541
def influx(args):
@@ -521,13 +547,14 @@ def influx(args):
521547
if not exists(json_in):
522548
print(f"Cannot find expected JSON summary {json_in}.")
523549
return 1
524-
525-
table_name = f"{args.table_prefix}_ReleaseValidation"
550+
table_name = "O2DPG_MC_ReleaseValidation"
551+
if args.table_suffix:
552+
table_name = f"{table_name}_{args.table_suffix}"
526553
tags_out = ""
527554
if args.tags:
528555
for t in args.tags:
529556
t_split = t.split("=")
530-
if len(t_split) != 2:
557+
if len(t_split) != 2 or not t_split[0] or not t_split[1]:
531558
print(f"ERROR: Invalid format of tags {t} for InfluxDB")
532559
return 1
533560
# we take it apart and put it back together again to make sure there are no whitespaces etc
@@ -542,8 +569,8 @@ def influx(args):
542569
with open(json_in, "r") as f:
543570
in_list = json.load(f)["histograms"]
544571
with open(out_file, "w") as f:
545-
for h in in_list:
546-
s = f"{row_tags},type_global={h['type_global']},type_specific={h['type_specific']} histogram_name={h['name']}"
572+
for i, h in enumerate(in_list):
573+
s = f"{row_tags},type_global={h['type_global']},type_specific={h['type_specific']},id={i} histogram_name=\"{h['name']}\""
547574
for k, v in h.items():
548575
# add all tests - do it dynamically because more might be added in the future
549576
if "test_" not in k:
@@ -578,14 +605,14 @@ def main():
578605
rel_val_parser.set_defaults(func=rel_val)
579606

580607
inspect_parser = sub_parsers.add_parser("inspect")
581-
inspect_parser.add_argument("file", help="pass a JSON produced from ReleaseValidation (rel-val)")
608+
inspect_parser.add_argument("path", help="either complete file path to a Summary.json or SummaryGlobal.json or directory where one of the former is expected to be")
582609
inspect_parser.add_argument("--severity", nargs="*", default=["BAD", "CRIT_NC"], choices=REL_VAL_SEVERITY_MAP.keys(), help="Choose severity levels to search for")
583610
inspect_parser.set_defaults(func=inspect)
584611

585612
influx_parser = sub_parsers.add_parser("influx")
586613
influx_parser.add_argument("--dir", help="directory where ReleaseValidation was run", required=True)
587614
influx_parser.add_argument("--tags", nargs="*", help="tags to be added for influx, list of key=value")
588-
influx_parser.add_argument("--table-prefix", dest="table_prefix", help="prefix for table name", default="O2DPG_MC")
615+
influx_parser.add_argument("--table-suffix", dest="table_suffix", help="prefix for table name")
589616
influx_parser.set_defaults(func=influx)
590617

591618
args = parser.parse_args()

0 commit comments

Comments
 (0)