blob: 73198689179158ae5f314e626ffb86ccfb3177a8 [file] [log] [blame]
#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
# -----------------------------------------------------------------------------
# Copyright 2020-2021 Arm Limited
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# -----------------------------------------------------------------------------
"""
The ``astc_test_result_plot.py`` script consolidates all current sets of
reference results into a single graphical plot.
"""
import re
import os
import sys
import numpy as np
import matplotlib.pyplot as plt
import testlib.resultset as trs
from collections import defaultdict as ddict
def find_reference_results():
"""
Scrape the Test/Images directory for result CSV files and return an
mapping of the result sets.
Returns:
Returns a three deep tree of dictionaries, with the final dict
pointing at a `ResultSet` object. The hierarchy is:
imageSet => quality => encoder => result
"""
scriptDir = os.path.dirname(__file__)
imageDir = os.path.join(scriptDir, "Images")
# Pattern for extracting useful data from the CSV file name
filePat = re.compile(r"astc_reference-(.+)_(.+)_results\.csv")
# Build a three level dictionary we can write into
results = ddict(lambda: ddict(lambda: ddict()))
# Final all CSVs, load them and store them in the dict tree
for root, dirs, files in os.walk(imageDir):
for name in files:
match = filePat.match(name)
if match:
fullPath = os.path.join(root, name)
encoder = match.group(1)
quality = match.group(2)
imageSet = os.path.basename(root)
if imageSet != "Kodak":
continue
testRef = trs.ResultSet(imageSet)
testRef.load_from_file(fullPath)
results[imageSet][quality]["ref-%s" % encoder] = testRef
return results
def get_series(results, tgtEncoder, tgtQuality, resFilter=lambda x: True):
psnrData = []
mtsData = []
marker = []
records = []
for imageSet, iResults in results.items():
for quality, qResults in iResults.items():
if quality != tgtQuality:
continue
for encoder, eResults in qResults.items():
if encoder != tgtEncoder:
continue
for record in eResults.records:
if resFilter(record):
records.append(record)
psnrData.append(record.psnr)
mtsData.append(record.cRate)
if "ldr-xy" in record.name:
marker.append('$N$')
elif "ldr-l" in record.name:
marker.append('$G$')
elif "ldr" in record.name:
marker.append('$L$')
elif "hdr" in record.name:
marker.append('$H$')
else:
marker.append('$?$')
return mtsData, psnrData, marker, records
def get_series_rel(results, refEncoder, refQuality, tgtEncoder, tgtQuality, resFilter=lambda x: True):
mts1, psnr1, marker1, rec1 = get_series(results, tgtEncoder, tgtQuality, resFilter)
if refEncoder is None:
refEncoder = tgtEncoder
if refQuality is None:
refQuality = tgtQuality
mts2, psnr2, marker2, rec2 = get_series(results, refEncoder, refQuality, resFilter)
mtsm = [x/mts2[i] for i, x in enumerate(mts1)]
psnrm = [x - psnr2[i] for i, x in enumerate(psnr1)]
return mtsm, psnrm, marker1, rec1
def get_human_eq_name(encoder, quality):
parts = encoder.split("-")
if len(parts) == 2:
return "astcenc %s -%s" % (parts[1], quality)
else:
return "astcenc-%s %s -%s" % (parts[2], parts[1], quality)
def get_human_e_name(encoder):
parts = encoder.split("-")
if len(parts) == 2:
return "astcenc %s" % parts[1]
else:
return "astcenc-%s %s" % (parts[2], parts[1])
def get_human_q_name(quality):
return "-%s" % quality
def plot(results, chartRows, chartCols, blockSizes,
relative, pivotEncoder, pivotQuality, fileName, limits):
fig, axs = plt.subplots(nrows=len(chartRows), ncols=len(chartCols),
sharex=True, sharey=True, figsize=(15, 8.43))
for a in fig.axes:
a.tick_params(
axis="x", which="both",
bottom=True, top=False, labelbottom=True)
a.tick_params(
axis="y", which="both",
left=True, right=False, labelleft=True)
for i, row in enumerate(chartRows):
for j, col in enumerate(chartCols):
if row == "fastest" and (("1.7" in col) or ("2.0" in col)):
if len(chartCols) == 1:
fig.delaxes(axs[i])
else:
fig.delaxes(axs[i][j])
continue
if len(chartRows) == 1 and len(chartCols) == 1:
ax = axs
elif len(chartCols) == 1:
ax = axs[i]
else:
ax = axs[i, j]
title = get_human_eq_name(col, row)
if not relative:
ax.set_title(title, y=0.97, backgroundcolor="white")
ax.set_xlabel('Coding performance (MTex/s)')
ax.set_ylabel('PSNR (dB)')
else:
if pivotEncoder and pivotQuality:
tag = get_human_eq_name(pivotEncoder, pivotQuality)
elif pivotEncoder:
tag = get_human_e_name(pivotEncoder)
else:
assert(pivotQuality)
tag = get_human_q_name(pivotQuality)
ax.set_title("%s vs. %s" % (title, tag), y=0.97, backgroundcolor="white")
ax.set_xlabel('Performance scaling')
ax.set_ylabel('PSNR delta (dB)')
for k, series in enumerate(blockSizes):
fn = lambda x: x.blkSz == series
if not relative:
x, y, m, r = get_series(results, col, row, fn)
else:
x, y, m, r = get_series_rel(results, pivotEncoder, pivotQuality,
col, row, fn)
color = None
label = "%s blocks" % series
for xp, yp, mp in zip(x, y, m):
ax.scatter([xp],[yp], s=16, marker=mp,
color="C%u" % k, label=label)
label = None
if i == 0 and j == 0:
ax.legend(loc="lower right")
for i, row in enumerate(chartRows):
for j, col in enumerate(chartCols):
if len(chartRows) == 1 and len(chartCols) == 1:
ax = axs
elif len(chartCols) == 1:
ax = axs[i]
else:
ax = axs[i, j]
ax.grid(ls=':')
if not relative:
ax.set_xlim(left=0, right=limits[0])
else:
ax.set_xlim(left=1, right=limits[0])
fig.tight_layout()
fig.savefig(fileName)
def main():
"""
The main function.
Returns:
int: The process return code.
"""
absoluteXLimit = 60
charts = [
# --------------------------------------------------------
# Latest in stable series charts
[
# Relative scores
["thorough", "medium", "fast"],
["ref-2.5-avx2", "ref-3.0-avx2"],
["4x4", "6x6", "8x8"],
True,
"ref-1.7",
None,
"relative-stable-series.png",
(None, None)
], [
# Absolute scores
["thorough", "medium", "fast"],
["ref-1.7", "ref-2.5-avx2", "ref-3.0-avx2"],
["4x4", "6x6", "8x8"],
False,
None,
None,
"absolute-stable-series.png",
(absoluteXLimit, None)
],
# --------------------------------------------------------
# Latest 2.x vs 1.7 release charts
[
# Relative scores
["thorough", "medium", "fast", "fastest"],
["ref-2.5-avx2"],
["4x4", "6x6", "8x8"],
True,
"ref-1.7",
None,
"relative-2.x-vs-1.x.png",
(None, None)
], [
# Absolute scores
["thorough", "medium", "fast", "fastest"],
["ref-1.7", "ref-2.5-avx2"],
["4x4", "6x6", "8x8"],
False,
None,
None,
"absolute-2.x-vs-1.x.png",
(absoluteXLimit, None)
], [
# Relative ISAs of latest
["thorough", "medium", "fast", "fastest"],
["ref-2.5-sse4.1", "ref-2.5-avx2"],
["4x4", "6x6", "8x8"],
True,
"ref-2.5-sse2",
None,
"relative-2.x-isa.png",
(None, None)
], [
# Relative quality of latest
["medium", "fast", "fastest"],
["ref-2.5-avx2"],
["4x4", "6x6", "8x8"],
True,
None,
"thorough",
"relative-2.x-quality.png",
(None, None)
],
# --------------------------------------------------------
# Latest 3.x vs 2.5 release charts
[
# Relative scores
["thorough", "medium", "fast", "fastest"],
["ref-3.0-avx2"],
["4x4", "6x6", "8x8"],
True,
"ref-2.5-avx2",
None,
"relative-3.x-vs-2.x.png",
(None, None)
], [
# Absolute scores
["thorough", "medium", "fast", "fastest"],
["ref-2.5-avx2", "ref-3.0-avx2"],
["4x4", "6x6", "8x8"],
False,
None,
None,
"absolute-3.x-vs-2.x.png",
(absoluteXLimit, None)
], [
# Relative ISAs of latest
["thorough", "medium", "fast", "fastest"],
["ref-3.0-sse4.1", "ref-3.0-avx2"],
["4x4", "6x6", "8x8"],
True,
"ref-3.0-sse2",
None,
"relative-3.x-isa.png",
(None, None)
], [
# Relative quality of latest
["medium", "fast", "fastest"],
["ref-3.0-avx2"],
["4x4", "6x6", "8x8"],
True,
None,
"thorough",
"relative-3.x-quality.png",
(None, None)
],
# --------------------------------------------------------
# Latest 3.x vs 2.5 release charts
[
# Relative scores
["thorough", "medium", "fast", "fastest"],
["ref-main-avx2"],
["4x4", "6x6", "8x8"],
True,
"ref-3.0-avx2",
None,
"relative-main-vs-3.x.png",
(None, None),
1
],
[
# Relative scores
["thorough", "medium", "fast"],
["ref-main-avx2"],
["4x4", "6x6", "8x8"],
True,
"ref-1.7",
None,
"relative-main-vs-1.x.png",
(None, None),
1
],
]
results = find_reference_results()
# Force select is triggered by adding a trailing entry to the argument list
# of the charts that you want rendered; designed for debugging use cases
maxIndex = 0
expectedLength = 8
for chart in charts:
maxIndex = max(maxIndex, len(chart))
for chart in charts:
# If force select is enabled then only keep the forced ones
if len(chart) != maxIndex:
print("Skipping %s" % chart[6])
continue
else:
print("Generating %s" % chart[6])
# If force select is enabled then strip the dummy force option
if maxIndex != expectedLength:
chart = chart[:expectedLength]
plot(results, *chart)
return 0
if __name__ == "__main__":
sys.exit(main())