Skip to content

Commit 30f26ed

Browse files
committed
tools: add trendplot script
This script can be used to plot trendline test results from results.xml files. It may be a useful visualization tool for regression analysis. Signed-off-by: U. Artie Eoff <[email protected]>
1 parent 47258bc commit 30f26ed

File tree

1 file changed

+161
-0
lines changed

1 file changed

+161
-0
lines changed

tools/trendplot

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#!/usr/bin/env python3
2+
3+
###
4+
### Copyright (C) 2023 Intel Corporation
5+
###
6+
### SPDX-License-Identifier: BSD-3-Clause
7+
###
8+
9+
###
10+
### kate: syntax python;
11+
###
12+
13+
import argparse
14+
import glob
15+
import lxml.etree as et
16+
import numpy as np
17+
import matplotlib.pyplot as plt
18+
import os
19+
import re
20+
import sys
21+
22+
__MYPATH__ = os.path.abspath(os.path.dirname(__file__))
23+
sys.path.append(os.path.dirname(__MYPATH__))
24+
25+
from lib.metrics2.psnr import trend_models
26+
27+
def loadxml(filenames):
28+
for filename in filenames:
29+
if os.path.isdir(filename):
30+
search = os.path.join(filename, "**", "results.xml")
31+
yield from loadxml(glob.glob(search, recursive = True))
32+
else:
33+
yield et.parse(filename).getroot()
34+
35+
# script command-line options
36+
parser = argparse.ArgumentParser()
37+
parser.add_argument("files", nargs = "+", type = str)
38+
parser.add_argument("--case", nargs = "+", default = [])
39+
parser.add_argument("--codec", nargs="+", default = [])
40+
parser.add_argument("--rc", nargs="+", default = [])
41+
parser.add_argument("--gop", nargs="+", type = int, default = [])
42+
parser.add_argument("--bf", nargs="+", type = int, default = [])
43+
parser.add_argument("--tu", nargs="+", type = int, default = [])
44+
parser.add_argument("--component", nargs="+", default = [])
45+
parser.add_argument("--platform", nargs="+", default = [])
46+
parser.add_argument("--bias", type = float, default = 0.0)
47+
parser.add_argument("--tolerance", type = float, default = 0.0)
48+
parser.add_argument("--failures", action = "store_true")
49+
parser.add_argument("--add-trendline", nargs="+", type = str, default = [])
50+
51+
# initialize
52+
plt.rcParams['figure.figsize'] = [20, 10]
53+
args = parser.parse_args()
54+
trendlines = dict()
55+
plotdata = dict()
56+
minx = 10000
57+
maxx = 0
58+
roots = loadxml(args.files)
59+
60+
# add user defined trendline
61+
if len(args.add_trendline):
62+
trendname = args.add_trendline[0] # model function name
63+
trendopts = [float(o) for o in args.add_trendline[1:]] # model function opts
64+
trendlines.setdefault(trendname, set()).add(
65+
tuple(["", "", "", args.tolerance+5.0]) + tuple(trendopts))
66+
67+
# filter and aggregate test results from xml
68+
for root in roots:
69+
platform = root.get("platform")
70+
driver = root.get("driver")
71+
suite = root.get("name")
72+
73+
if len(args.platform) and platform not in args.platform:
74+
continue
75+
76+
for testcase in root:
77+
if testcase.get("skipped") == "1": continue
78+
79+
# disassemble test case name and classname
80+
name = testcase.get("name")
81+
rc = name.split('.')[0]
82+
pattern = re.compile("(?P<key>[\w]+)=(?P<value>[\S\s]*?)(,|\))", re.VERBOSE)
83+
params = {m.group("key"): m.group("value") for m in pattern.finditer(name)}
84+
case = params["case"]
85+
tu = int(params.get("quality", -1))
86+
bf = int(params.get("bframes", -1))
87+
gop = int(params.get("gop", -1))
88+
classname = testcase.get("classname")
89+
parts = classname.split('.')
90+
component = parts[2]
91+
codec = '.'.join(parts[4:])
92+
93+
# check for failure tag
94+
failure = testcase.xpath("failure")
95+
96+
# filter testcase
97+
if len(args.codec) and codec not in args.codec:
98+
continue
99+
if len(args.rc) and rc not in args.rc:
100+
continue
101+
if len(args.case) and params["case"] not in args.case:
102+
continue
103+
if len(args.component) and component not in args.component:
104+
continue
105+
if len(args.gop) and gop not in args.gop:
106+
continue
107+
if len(args.tu) and tu not in args.tu:
108+
continue
109+
if len(args.bf) and bf not in args.bf:
110+
continue
111+
if args.failures and len(failure) < 1:
112+
continue
113+
114+
# convert details to key:value
115+
details = dict()
116+
for detail in testcase.iter("detail"):
117+
details[detail.get("name")] = detail.get("value")
118+
119+
# find necessary trendline datapoints
120+
bias = float(details.get("compression:bias", 0.0)) + args.bias
121+
tolerance = float(details.get("psnr:tolerance", 5.0)) + args.tolerance
122+
psnr = float(details.get("psnr:actual", -1.0))
123+
log = float(details.get("compression:log", -1.0))
124+
label = f"{platform}:{rc}:{codec}:{case}:{component}:gop={gop}:bf={bf}:tu={tu}:bias={bias}"
125+
126+
# missing datapoints
127+
if psnr < 0 or log < 0: continue
128+
129+
minx = min(minx, log+bias)
130+
maxx = max(maxx, log+bias)
131+
132+
data = plotdata.setdefault(label, dict())
133+
data.setdefault("ydata", list()).append(psnr)
134+
data.setdefault("xdata", list()).append(log+bias)
135+
136+
trendname = details.get("model:trend:name", None)
137+
trendopts = details.get("model:trend:opts", None)
138+
if trendname is not None and trendopts is not None:
139+
trendlines.setdefault(trendname, set()).add(tuple([case, codec, gop, tolerance]) + tuple(eval(trendopts)))
140+
141+
for label, data in plotdata.items():
142+
plt.scatter(data["xdata"], data["ydata"], label = label)
143+
144+
for fn, trendline in trendlines.items():
145+
for opts in trendline:
146+
case, codec, gop, tolerance, *opt = opts
147+
sopt = tuple(float(f"{p:.2f}") for p in opt)
148+
label = f"REF:{codec}:{case}:gop={gop} {fn}{sopt} T={tolerance}"
149+
xpower = np.linspace(min(0, minx), max(maxx, 10), 100)
150+
ypower = trend_models[fn](xpower, *opt)
151+
ypower = [y-tolerance for y in ypower]
152+
plt.plot(xpower, ypower, label = label, linestyle = "dashed", linewidth = 3)
153+
154+
plt.ylim([15, 80])
155+
plt.xlim([0, 11])
156+
plt.ylabel("PSNR")
157+
plt.xlabel("Compression Ratio (ln x)")
158+
plt.legend()
159+
plt.show()
160+
161+

0 commit comments

Comments
 (0)