1cc1dc7a3Sopenharmony_ci#!/usr/bin/env python3
2cc1dc7a3Sopenharmony_ci# SPDX-License-Identifier: Apache-2.0
3cc1dc7a3Sopenharmony_ci# -----------------------------------------------------------------------------
4cc1dc7a3Sopenharmony_ci# Copyright 2020-2021 Arm Limited
5cc1dc7a3Sopenharmony_ci#
6cc1dc7a3Sopenharmony_ci# Licensed under the Apache License, Version 2.0 (the "License"); you may not
7cc1dc7a3Sopenharmony_ci# use this file except in compliance with the License. You may obtain a copy
8cc1dc7a3Sopenharmony_ci# of the License at:
9cc1dc7a3Sopenharmony_ci#
10cc1dc7a3Sopenharmony_ci#     http://www.apache.org/licenses/LICENSE-2.0
11cc1dc7a3Sopenharmony_ci#
12cc1dc7a3Sopenharmony_ci# Unless required by applicable law or agreed to in writing, software
13cc1dc7a3Sopenharmony_ci# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14cc1dc7a3Sopenharmony_ci# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15cc1dc7a3Sopenharmony_ci# License for the specific language governing permissions and limitations
16cc1dc7a3Sopenharmony_ci# under the License.
17cc1dc7a3Sopenharmony_ci# -----------------------------------------------------------------------------
18cc1dc7a3Sopenharmony_ci"""
19cc1dc7a3Sopenharmony_ciThe ``astc_test_result_report.py`` script consolidates all current sets of
20cc1dc7a3Sopenharmony_cireference results into a single report giving PSNR diffs (absolute) and
21cc1dc7a3Sopenharmony_ciperformance diffs (relative speedup, 1 = no change).
22cc1dc7a3Sopenharmony_ci"""
23cc1dc7a3Sopenharmony_ci
24cc1dc7a3Sopenharmony_ciimport re
25cc1dc7a3Sopenharmony_ciimport os
26cc1dc7a3Sopenharmony_ciimport sys
27cc1dc7a3Sopenharmony_ci
28cc1dc7a3Sopenharmony_ci
29cc1dc7a3Sopenharmony_ciimport testlib.resultset as trs
30cc1dc7a3Sopenharmony_cifrom collections import defaultdict as ddict
31cc1dc7a3Sopenharmony_ci
32cc1dc7a3Sopenharmony_ci
33cc1dc7a3Sopenharmony_ciCONFIG_FILTER = [
34cc1dc7a3Sopenharmony_ci    re.compile(r"^.*1\.7.*$"),
35cc1dc7a3Sopenharmony_ci    re.compile(r"^.*sse.*$")
36cc1dc7a3Sopenharmony_ci]
37cc1dc7a3Sopenharmony_ci
38cc1dc7a3Sopenharmony_ciTESTSET_FILTER = [
39cc1dc7a3Sopenharmony_ci    re.compile(r"^Small$"),
40cc1dc7a3Sopenharmony_ci    re.compile(r"^Frymire$"),
41cc1dc7a3Sopenharmony_ci]
42cc1dc7a3Sopenharmony_ci
43cc1dc7a3Sopenharmony_ciQUALITY_FILTER = [
44cc1dc7a3Sopenharmony_ci]
45cc1dc7a3Sopenharmony_ci
46cc1dc7a3Sopenharmony_ciBLOCKSIZE_FILTER = [
47cc1dc7a3Sopenharmony_ci    re.compile(r"^12x12$")
48cc1dc7a3Sopenharmony_ci]
49cc1dc7a3Sopenharmony_ci
50cc1dc7a3Sopenharmony_ci
51cc1dc7a3Sopenharmony_cidef find_reference_results():
52cc1dc7a3Sopenharmony_ci    """
53cc1dc7a3Sopenharmony_ci    Scrape the Test/Images directory for result CSV files and return an
54cc1dc7a3Sopenharmony_ci    mapping of the result sets.
55cc1dc7a3Sopenharmony_ci
56cc1dc7a3Sopenharmony_ci    Returns:
57cc1dc7a3Sopenharmony_ci        Returns a three deep tree of dictionaries, with the final dict
58cc1dc7a3Sopenharmony_ci        pointing at a `ResultSet` object. The hierarchy is:
59cc1dc7a3Sopenharmony_ci
60cc1dc7a3Sopenharmony_ci            imageSet => quality => encoder => result
61cc1dc7a3Sopenharmony_ci    """
62cc1dc7a3Sopenharmony_ci    scriptDir = os.path.dirname(__file__)
63cc1dc7a3Sopenharmony_ci    imageDir = os.path.join(scriptDir, "Images")
64cc1dc7a3Sopenharmony_ci
65cc1dc7a3Sopenharmony_ci    # Pattern for extracting useful data from the CSV file name
66cc1dc7a3Sopenharmony_ci    filePat = re.compile(r"astc_reference-(.+)_(.+)_results\.csv")
67cc1dc7a3Sopenharmony_ci
68cc1dc7a3Sopenharmony_ci    # Build a three level dictionary we can write into
69cc1dc7a3Sopenharmony_ci    results = ddict(lambda: ddict(lambda: ddict()))
70cc1dc7a3Sopenharmony_ci
71cc1dc7a3Sopenharmony_ci    # Final all CSVs, load them and store them in the dict tree
72cc1dc7a3Sopenharmony_ci    for root, dirs, files in os.walk(imageDir):
73cc1dc7a3Sopenharmony_ci        for name in files:
74cc1dc7a3Sopenharmony_ci            match = filePat.match(name)
75cc1dc7a3Sopenharmony_ci            if match:
76cc1dc7a3Sopenharmony_ci
77cc1dc7a3Sopenharmony_ci                # Skip results set in the filter
78cc1dc7a3Sopenharmony_ci                skip = [1 for filt in CONFIG_FILTER if filt.match(name)]
79cc1dc7a3Sopenharmony_ci                if skip:
80cc1dc7a3Sopenharmony_ci                    continue
81cc1dc7a3Sopenharmony_ci
82cc1dc7a3Sopenharmony_ci                fullPath = os.path.join(root, name)
83cc1dc7a3Sopenharmony_ci
84cc1dc7a3Sopenharmony_ci                encoder = match.group(1)
85cc1dc7a3Sopenharmony_ci                quality = match.group(2)
86cc1dc7a3Sopenharmony_ci                imageSet = os.path.basename(root)
87cc1dc7a3Sopenharmony_ci
88cc1dc7a3Sopenharmony_ci                # Skip results set in the filter
89cc1dc7a3Sopenharmony_ci                skip = [1 for filt in TESTSET_FILTER if filt.match(imageSet)]
90cc1dc7a3Sopenharmony_ci                if skip:
91cc1dc7a3Sopenharmony_ci                    continue
92cc1dc7a3Sopenharmony_ci
93cc1dc7a3Sopenharmony_ci                # Skip results set in the filter
94cc1dc7a3Sopenharmony_ci                skip = [1 for filt in QUALITY_FILTER if filt.match(quality)]
95cc1dc7a3Sopenharmony_ci                if skip:
96cc1dc7a3Sopenharmony_ci                    continue
97cc1dc7a3Sopenharmony_ci
98cc1dc7a3Sopenharmony_ci                testRef = trs.ResultSet(imageSet)
99cc1dc7a3Sopenharmony_ci                testRef.load_from_file(fullPath)
100cc1dc7a3Sopenharmony_ci
101cc1dc7a3Sopenharmony_ci                patchedRef = trs.ResultSet(imageSet)
102cc1dc7a3Sopenharmony_ci
103cc1dc7a3Sopenharmony_ci                for result in testRef.records:
104cc1dc7a3Sopenharmony_ci                    skip = [1 for filt in BLOCKSIZE_FILTER if filt.match(result.blkSz)]
105cc1dc7a3Sopenharmony_ci                    if not skip:
106cc1dc7a3Sopenharmony_ci                        patchedRef.add_record(result)
107cc1dc7a3Sopenharmony_ci
108cc1dc7a3Sopenharmony_ci                results[imageSet][quality]["ref-%s" % encoder] = patchedRef
109cc1dc7a3Sopenharmony_ci
110cc1dc7a3Sopenharmony_ci    return results
111cc1dc7a3Sopenharmony_ci
112cc1dc7a3Sopenharmony_ci
113cc1dc7a3Sopenharmony_ciclass DeltaRecord():
114cc1dc7a3Sopenharmony_ci    """
115cc1dc7a3Sopenharmony_ci    Record a single image result from N different encoders.
116cc1dc7a3Sopenharmony_ci
117cc1dc7a3Sopenharmony_ci    Attributes:
118cc1dc7a3Sopenharmony_ci        imageSet: The image set this cme from.
119cc1dc7a3Sopenharmony_ci        quality: The compressor quality used.
120cc1dc7a3Sopenharmony_ci        encoders: The names of the encoders used. The first encoder in this
121cc1dc7a3Sopenharmony_ci            list will be used as the reference result.
122cc1dc7a3Sopenharmony_ci        records: The raw records for the encoders. The order of records in this
123cc1dc7a3Sopenharmony_ci            list matches the order of the `encoders` list.
124cc1dc7a3Sopenharmony_ci    """
125cc1dc7a3Sopenharmony_ci
126cc1dc7a3Sopenharmony_ci    def __init__(self, imageSet, quality, encoders, records):
127cc1dc7a3Sopenharmony_ci        self.imageSet = imageSet
128cc1dc7a3Sopenharmony_ci        self.quality = quality
129cc1dc7a3Sopenharmony_ci
130cc1dc7a3Sopenharmony_ci        self.encoders = list(encoders)
131cc1dc7a3Sopenharmony_ci        self.records = list(records)
132cc1dc7a3Sopenharmony_ci
133cc1dc7a3Sopenharmony_ci        assert(len(self.encoders) == len(self.records))
134cc1dc7a3Sopenharmony_ci
135cc1dc7a3Sopenharmony_ci    def get_delta_header(self, tag):
136cc1dc7a3Sopenharmony_ci        """
137cc1dc7a3Sopenharmony_ci        Get the delta encoding header.
138cc1dc7a3Sopenharmony_ci
139cc1dc7a3Sopenharmony_ci        Args:
140cc1dc7a3Sopenharmony_ci            tag: The field name to include in the tag.
141cc1dc7a3Sopenharmony_ci
142cc1dc7a3Sopenharmony_ci        Return:
143cc1dc7a3Sopenharmony_ci            The array of strings, providing the header names.
144cc1dc7a3Sopenharmony_ci        """
145cc1dc7a3Sopenharmony_ci        result = []
146cc1dc7a3Sopenharmony_ci
147cc1dc7a3Sopenharmony_ci        for encoder in self.encoders[1:]:
148cc1dc7a3Sopenharmony_ci            result.append("%s %s" % (tag, encoder))
149cc1dc7a3Sopenharmony_ci
150cc1dc7a3Sopenharmony_ci        return result
151cc1dc7a3Sopenharmony_ci
152cc1dc7a3Sopenharmony_ci    def get_abs_delta(self, field):
153cc1dc7a3Sopenharmony_ci        """
154cc1dc7a3Sopenharmony_ci        Get an absolute delta result.
155cc1dc7a3Sopenharmony_ci
156cc1dc7a3Sopenharmony_ci        Args:
157cc1dc7a3Sopenharmony_ci            field: The Record attribute name to diff.
158cc1dc7a3Sopenharmony_ci
159cc1dc7a3Sopenharmony_ci        Return:
160cc1dc7a3Sopenharmony_ci            The array of float delta values.
161cc1dc7a3Sopenharmony_ci        """
162cc1dc7a3Sopenharmony_ci        result = []
163cc1dc7a3Sopenharmony_ci
164cc1dc7a3Sopenharmony_ci        root = self.records[0]
165cc1dc7a3Sopenharmony_ci        for record in self.records[1:]:
166cc1dc7a3Sopenharmony_ci            result.append(getattr(record, field) - getattr(root, field))
167cc1dc7a3Sopenharmony_ci
168cc1dc7a3Sopenharmony_ci        return result
169cc1dc7a3Sopenharmony_ci
170cc1dc7a3Sopenharmony_ci    def get_rel_delta(self, field):
171cc1dc7a3Sopenharmony_ci        """
172cc1dc7a3Sopenharmony_ci        Get an relative delta result (score / ref).
173cc1dc7a3Sopenharmony_ci
174cc1dc7a3Sopenharmony_ci        Args:
175cc1dc7a3Sopenharmony_ci            field: The Record attribute name to diff.
176cc1dc7a3Sopenharmony_ci
177cc1dc7a3Sopenharmony_ci        Return:
178cc1dc7a3Sopenharmony_ci            The array of float delta values.
179cc1dc7a3Sopenharmony_ci        """
180cc1dc7a3Sopenharmony_ci        result = []
181cc1dc7a3Sopenharmony_ci
182cc1dc7a3Sopenharmony_ci        root = self.records[0]
183cc1dc7a3Sopenharmony_ci        for record in self.records[1:]:
184cc1dc7a3Sopenharmony_ci            result.append(getattr(record, field) / getattr(root, field))
185cc1dc7a3Sopenharmony_ci
186cc1dc7a3Sopenharmony_ci        return result
187cc1dc7a3Sopenharmony_ci
188cc1dc7a3Sopenharmony_ci    def get_irel_delta(self, field):
189cc1dc7a3Sopenharmony_ci        """
190cc1dc7a3Sopenharmony_ci        Get an inverse relative delta result (ref / score).
191cc1dc7a3Sopenharmony_ci
192cc1dc7a3Sopenharmony_ci        Args:
193cc1dc7a3Sopenharmony_ci            field: The Record attribute name to diff.
194cc1dc7a3Sopenharmony_ci
195cc1dc7a3Sopenharmony_ci        Return:
196cc1dc7a3Sopenharmony_ci            The array of float delta values.
197cc1dc7a3Sopenharmony_ci        """
198cc1dc7a3Sopenharmony_ci        return [1.0 / x for x in self.get_rel_delta(field)]
199cc1dc7a3Sopenharmony_ci
200cc1dc7a3Sopenharmony_ci    def get_full_row_header_csv(self):
201cc1dc7a3Sopenharmony_ci        """
202cc1dc7a3Sopenharmony_ci        Get a CSV encoded delta record header.
203cc1dc7a3Sopenharmony_ci
204cc1dc7a3Sopenharmony_ci        Return:
205cc1dc7a3Sopenharmony_ci            The string for the row.
206cc1dc7a3Sopenharmony_ci        """
207cc1dc7a3Sopenharmony_ci        rows = [
208cc1dc7a3Sopenharmony_ci            "Image Set",
209cc1dc7a3Sopenharmony_ci            "Quality",
210cc1dc7a3Sopenharmony_ci            "Size",
211cc1dc7a3Sopenharmony_ci            "Name"
212cc1dc7a3Sopenharmony_ci        ]
213cc1dc7a3Sopenharmony_ci
214cc1dc7a3Sopenharmony_ci        rows.append("")
215cc1dc7a3Sopenharmony_ci        rows.extend(self.get_delta_header("PSNR"))
216cc1dc7a3Sopenharmony_ci
217cc1dc7a3Sopenharmony_ci        rows.append("")
218cc1dc7a3Sopenharmony_ci        rows.extend(self.get_delta_header("Speed"))
219cc1dc7a3Sopenharmony_ci
220cc1dc7a3Sopenharmony_ci        return ",".join(rows)
221cc1dc7a3Sopenharmony_ci
222cc1dc7a3Sopenharmony_ci    def get_full_row_csv(self):
223cc1dc7a3Sopenharmony_ci        """
224cc1dc7a3Sopenharmony_ci        Get a CSV encoded delta record.
225cc1dc7a3Sopenharmony_ci
226cc1dc7a3Sopenharmony_ci        Return:
227cc1dc7a3Sopenharmony_ci            The string for the row.
228cc1dc7a3Sopenharmony_ci        """
229cc1dc7a3Sopenharmony_ci        rows = [
230cc1dc7a3Sopenharmony_ci            self.imageSet,
231cc1dc7a3Sopenharmony_ci            self.quality,
232cc1dc7a3Sopenharmony_ci            self.records[0].name,
233cc1dc7a3Sopenharmony_ci            self.records[0].blkSz
234cc1dc7a3Sopenharmony_ci        ]
235cc1dc7a3Sopenharmony_ci
236cc1dc7a3Sopenharmony_ci        rows.append("")
237cc1dc7a3Sopenharmony_ci        data = ["%0.3f" % x for x in self.get_abs_delta("psnr")]
238cc1dc7a3Sopenharmony_ci        rows.extend(data)
239cc1dc7a3Sopenharmony_ci
240cc1dc7a3Sopenharmony_ci        rows.append("")
241cc1dc7a3Sopenharmony_ci        data = ["%0.3f" % x for x in self.get_irel_delta("cTime")]
242cc1dc7a3Sopenharmony_ci        rows.extend(data)
243cc1dc7a3Sopenharmony_ci
244cc1dc7a3Sopenharmony_ci        return ",".join(rows)
245cc1dc7a3Sopenharmony_ci
246cc1dc7a3Sopenharmony_ci
247cc1dc7a3Sopenharmony_cidef print_result_set(imageSet, quality, encoders, results, printHeader):
248cc1dc7a3Sopenharmony_ci    """
249cc1dc7a3Sopenharmony_ci    Attributes:
250cc1dc7a3Sopenharmony_ci        imageSet: The image set name.
251cc1dc7a3Sopenharmony_ci        quality: The compressor quality used.
252cc1dc7a3Sopenharmony_ci        encoders: The names of the encoders used. The first encoder in this
253cc1dc7a3Sopenharmony_ci            list will be used as the reference result.
254cc1dc7a3Sopenharmony_ci        results: The dict of results, indexed by encoder.
255cc1dc7a3Sopenharmony_ci        printHeader: True if the table header should be printed, else False.
256cc1dc7a3Sopenharmony_ci    """
257cc1dc7a3Sopenharmony_ci    results = [results[x] for x in encoders]
258cc1dc7a3Sopenharmony_ci    recordSizes = [len(x.records) for x in results]
259cc1dc7a3Sopenharmony_ci
260cc1dc7a3Sopenharmony_ci    # Skip result sets that are not the same size
261cc1dc7a3Sopenharmony_ci    # TODO: We can take the set intersection here to report what we can
262cc1dc7a3Sopenharmony_ci    if min(recordSizes) != max(recordSizes):
263cc1dc7a3Sopenharmony_ci        return
264cc1dc7a3Sopenharmony_ci
265cc1dc7a3Sopenharmony_ci    # Interleave all result records
266cc1dc7a3Sopenharmony_ci    recordSets = zip(*[x.records for x in results])
267cc1dc7a3Sopenharmony_ci
268cc1dc7a3Sopenharmony_ci    # Iterate each image
269cc1dc7a3Sopenharmony_ci    for recordSet in recordSets:
270cc1dc7a3Sopenharmony_ci        base = recordSet[0]
271cc1dc7a3Sopenharmony_ci
272cc1dc7a3Sopenharmony_ci        # Sanity check consistency
273cc1dc7a3Sopenharmony_ci        for record in recordSet[1:]:
274cc1dc7a3Sopenharmony_ci            assert(record.blkSz == base.blkSz)
275cc1dc7a3Sopenharmony_ci            assert(record.name == base.name)
276cc1dc7a3Sopenharmony_ci
277cc1dc7a3Sopenharmony_ci        dr = DeltaRecord(imageSet, quality, encoders, recordSet)
278cc1dc7a3Sopenharmony_ci
279cc1dc7a3Sopenharmony_ci        if printHeader:
280cc1dc7a3Sopenharmony_ci            print(dr.get_full_row_header_csv())
281cc1dc7a3Sopenharmony_ci            printHeader = False
282cc1dc7a3Sopenharmony_ci
283cc1dc7a3Sopenharmony_ci        print(dr.get_full_row_csv())
284cc1dc7a3Sopenharmony_ci
285cc1dc7a3Sopenharmony_ci
286cc1dc7a3Sopenharmony_cidef main():
287cc1dc7a3Sopenharmony_ci    """
288cc1dc7a3Sopenharmony_ci    The main function.
289cc1dc7a3Sopenharmony_ci
290cc1dc7a3Sopenharmony_ci    Returns:
291cc1dc7a3Sopenharmony_ci        int: The process return code.
292cc1dc7a3Sopenharmony_ci    """
293cc1dc7a3Sopenharmony_ci
294cc1dc7a3Sopenharmony_ci    results = find_reference_results()
295cc1dc7a3Sopenharmony_ci
296cc1dc7a3Sopenharmony_ci    imageSet = sorted(results.keys())
297cc1dc7a3Sopenharmony_ci
298cc1dc7a3Sopenharmony_ci    first = True
299cc1dc7a3Sopenharmony_ci    for image in imageSet:
300cc1dc7a3Sopenharmony_ci        qualityTree = results[image]
301cc1dc7a3Sopenharmony_ci        qualitySet = sorted(qualityTree.keys())
302cc1dc7a3Sopenharmony_ci
303cc1dc7a3Sopenharmony_ci        for qual in qualitySet:
304cc1dc7a3Sopenharmony_ci            encoderTree = qualityTree[qual]
305cc1dc7a3Sopenharmony_ci            encoderSet = sorted(encoderTree.keys())
306cc1dc7a3Sopenharmony_ci
307cc1dc7a3Sopenharmony_ci            if len(encoderSet) > 1:
308cc1dc7a3Sopenharmony_ci                print_result_set(image, qual, encoderSet, encoderTree, first)
309cc1dc7a3Sopenharmony_ci                first = False
310cc1dc7a3Sopenharmony_ci
311cc1dc7a3Sopenharmony_ci    return 0
312cc1dc7a3Sopenharmony_ci
313cc1dc7a3Sopenharmony_ci
314cc1dc7a3Sopenharmony_ciif __name__ == "__main__":
315cc1dc7a3Sopenharmony_ci    sys.exit(main())
316