xref: /aosp_15_r20/external/libaom/test/visual_metrics.py (revision 77c1e3ccc04c968bd2bc212e87364f250e820521)
1*77c1e3ccSAndroid Build Coastguard Worker#!/usr/bin/python
2*77c1e3ccSAndroid Build Coastguard Worker#
3*77c1e3ccSAndroid Build Coastguard Worker# Copyright (c) 2016, Alliance for Open Media. All rights reserved.
4*77c1e3ccSAndroid Build Coastguard Worker#
5*77c1e3ccSAndroid Build Coastguard Worker# This source code is subject to the terms of the BSD 2 Clause License and
6*77c1e3ccSAndroid Build Coastguard Worker# the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
7*77c1e3ccSAndroid Build Coastguard Worker# was not distributed with this source code in the LICENSE file, you can
8*77c1e3ccSAndroid Build Coastguard Worker# obtain it at www.aomedia.org/license/software. If the Alliance for Open
9*77c1e3ccSAndroid Build Coastguard Worker# Media Patent License 1.0 was not distributed with this source code in the
10*77c1e3ccSAndroid Build Coastguard Worker# PATENTS file, you can obtain it at www.aomedia.org/license/patent.
11*77c1e3ccSAndroid Build Coastguard Worker#
12*77c1e3ccSAndroid Build Coastguard Worker
13*77c1e3ccSAndroid Build Coastguard Worker"""Converts video encoding result data from text files to visualization
14*77c1e3ccSAndroid Build Coastguard Workerdata source."""
15*77c1e3ccSAndroid Build Coastguard Worker
16*77c1e3ccSAndroid Build Coastguard Worker__author__ = "[email protected] (James Zern),"
17*77c1e3ccSAndroid Build Coastguard Worker__author__ += "[email protected] (Jim Bankoski)"
18*77c1e3ccSAndroid Build Coastguard Worker
19*77c1e3ccSAndroid Build Coastguard Workerimport fnmatch
20*77c1e3ccSAndroid Build Coastguard Workerimport numpy as np
21*77c1e3ccSAndroid Build Coastguard Workerimport scipy as sp
22*77c1e3ccSAndroid Build Coastguard Workerimport scipy.interpolate
23*77c1e3ccSAndroid Build Coastguard Workerimport os
24*77c1e3ccSAndroid Build Coastguard Workerimport re
25*77c1e3ccSAndroid Build Coastguard Workerimport string
26*77c1e3ccSAndroid Build Coastguard Workerimport sys
27*77c1e3ccSAndroid Build Coastguard Workerimport math
28*77c1e3ccSAndroid Build Coastguard Workerimport warnings
29*77c1e3ccSAndroid Build Coastguard Worker
30*77c1e3ccSAndroid Build Coastguard Workerimport gviz_api
31*77c1e3ccSAndroid Build Coastguard Worker
32*77c1e3ccSAndroid Build Coastguard Workerfrom os.path import basename
33*77c1e3ccSAndroid Build Coastguard Workerfrom os.path import splitext
34*77c1e3ccSAndroid Build Coastguard Worker
35*77c1e3ccSAndroid Build Coastguard Workerwarnings.simplefilter('ignore', np.RankWarning)
36*77c1e3ccSAndroid Build Coastguard Workerwarnings.simplefilter('ignore', RuntimeWarning)
37*77c1e3ccSAndroid Build Coastguard Worker
38*77c1e3ccSAndroid Build Coastguard Workerdef bdsnr2(metric_set1, metric_set2):
39*77c1e3ccSAndroid Build Coastguard Worker  """
40*77c1e3ccSAndroid Build Coastguard Worker  BJONTEGAARD    Bjontegaard metric calculation adapted
41*77c1e3ccSAndroid Build Coastguard Worker  Bjontegaard's snr metric allows to compute the average % saving in decibels
42*77c1e3ccSAndroid Build Coastguard Worker  between two rate-distortion curves [1].  This is an adaptation of that
43*77c1e3ccSAndroid Build Coastguard Worker  method that fixes inconsistencies when the curve fit operation goes awry
44*77c1e3ccSAndroid Build Coastguard Worker  by replacing the curve fit function with a Piecewise Cubic Hermite
45*77c1e3ccSAndroid Build Coastguard Worker  Interpolating Polynomial and then integrating that by evaluating that
46*77c1e3ccSAndroid Build Coastguard Worker  function at small intervals using the trapezoid method to calculate
47*77c1e3ccSAndroid Build Coastguard Worker  the integral.
48*77c1e3ccSAndroid Build Coastguard Worker
49*77c1e3ccSAndroid Build Coastguard Worker  metric_set1 - list of tuples ( bitrate,  metric ) for first graph
50*77c1e3ccSAndroid Build Coastguard Worker  metric_set2 - list of tuples ( bitrate,  metric ) for second graph
51*77c1e3ccSAndroid Build Coastguard Worker  """
52*77c1e3ccSAndroid Build Coastguard Worker
53*77c1e3ccSAndroid Build Coastguard Worker  if not metric_set1 or not metric_set2:
54*77c1e3ccSAndroid Build Coastguard Worker    return 0.0
55*77c1e3ccSAndroid Build Coastguard Worker
56*77c1e3ccSAndroid Build Coastguard Worker  try:
57*77c1e3ccSAndroid Build Coastguard Worker
58*77c1e3ccSAndroid Build Coastguard Worker    # pchip_interlopate requires keys sorted by x axis. x-axis will
59*77c1e3ccSAndroid Build Coastguard Worker    # be our metric not the bitrate so sort by metric.
60*77c1e3ccSAndroid Build Coastguard Worker    metric_set1.sort()
61*77c1e3ccSAndroid Build Coastguard Worker    metric_set2.sort()
62*77c1e3ccSAndroid Build Coastguard Worker
63*77c1e3ccSAndroid Build Coastguard Worker    # Pull the log of the rate and clamped psnr from metric_sets.
64*77c1e3ccSAndroid Build Coastguard Worker    log_rate1 = [math.log(x[0]) for x in metric_set1]
65*77c1e3ccSAndroid Build Coastguard Worker    metric1 = [100.0 if x[1] == float('inf') else x[1] for x in metric_set1]
66*77c1e3ccSAndroid Build Coastguard Worker    log_rate2 = [math.log(x[0]) for x in metric_set2]
67*77c1e3ccSAndroid Build Coastguard Worker    metric2 = [100.0 if x[1] == float('inf') else x[1] for x in metric_set2]
68*77c1e3ccSAndroid Build Coastguard Worker
69*77c1e3ccSAndroid Build Coastguard Worker    # Integration interval.  This metric only works on the area that's
70*77c1e3ccSAndroid Build Coastguard Worker    # overlapping.   Extrapolation of these things is sketchy so we avoid.
71*77c1e3ccSAndroid Build Coastguard Worker    min_int = max([min(log_rate1), min(log_rate2)])
72*77c1e3ccSAndroid Build Coastguard Worker    max_int = min([max(log_rate1), max(log_rate2)])
73*77c1e3ccSAndroid Build Coastguard Worker
74*77c1e3ccSAndroid Build Coastguard Worker    # No overlap means no sensible metric possible.
75*77c1e3ccSAndroid Build Coastguard Worker    if max_int <= min_int:
76*77c1e3ccSAndroid Build Coastguard Worker      return 0.0
77*77c1e3ccSAndroid Build Coastguard Worker
78*77c1e3ccSAndroid Build Coastguard Worker    # Use Piecewise Cubic Hermite Interpolating Polynomial interpolation to
79*77c1e3ccSAndroid Build Coastguard Worker    # create 100 new samples points separated by interval.
80*77c1e3ccSAndroid Build Coastguard Worker    lin = np.linspace(min_int, max_int, num=100, retstep=True)
81*77c1e3ccSAndroid Build Coastguard Worker    interval = lin[1]
82*77c1e3ccSAndroid Build Coastguard Worker    samples = lin[0]
83*77c1e3ccSAndroid Build Coastguard Worker    v1 = scipy.interpolate.pchip_interpolate(log_rate1, metric1, samples)
84*77c1e3ccSAndroid Build Coastguard Worker    v2 = scipy.interpolate.pchip_interpolate(log_rate2, metric2, samples)
85*77c1e3ccSAndroid Build Coastguard Worker
86*77c1e3ccSAndroid Build Coastguard Worker    # Calculate the integral using the trapezoid method on the samples.
87*77c1e3ccSAndroid Build Coastguard Worker    int_v1 = np.trapz(v1, dx=interval)
88*77c1e3ccSAndroid Build Coastguard Worker    int_v2 = np.trapz(v2, dx=interval)
89*77c1e3ccSAndroid Build Coastguard Worker
90*77c1e3ccSAndroid Build Coastguard Worker    # Calculate the average improvement.
91*77c1e3ccSAndroid Build Coastguard Worker    avg_exp_diff = (int_v2 - int_v1) / (max_int - min_int)
92*77c1e3ccSAndroid Build Coastguard Worker
93*77c1e3ccSAndroid Build Coastguard Worker  except (TypeError, ZeroDivisionError, ValueError, np.RankWarning) as e:
94*77c1e3ccSAndroid Build Coastguard Worker    return 0
95*77c1e3ccSAndroid Build Coastguard Worker
96*77c1e3ccSAndroid Build Coastguard Worker  return avg_exp_diff
97*77c1e3ccSAndroid Build Coastguard Worker
98*77c1e3ccSAndroid Build Coastguard Workerdef bdrate2(metric_set1, metric_set2):
99*77c1e3ccSAndroid Build Coastguard Worker  """
100*77c1e3ccSAndroid Build Coastguard Worker  BJONTEGAARD    Bjontegaard metric calculation adapted
101*77c1e3ccSAndroid Build Coastguard Worker  Bjontegaard's metric allows to compute the average % saving in bitrate
102*77c1e3ccSAndroid Build Coastguard Worker  between two rate-distortion curves [1].  This is an adaptation of that
103*77c1e3ccSAndroid Build Coastguard Worker  method that fixes inconsistencies when the curve fit operation goes awry
104*77c1e3ccSAndroid Build Coastguard Worker  by replacing the curve fit function with a Piecewise Cubic Hermite
105*77c1e3ccSAndroid Build Coastguard Worker  Interpolating Polynomial and then integrating that by evaluating that
106*77c1e3ccSAndroid Build Coastguard Worker  function at small intervals using the trapezoid method to calculate
107*77c1e3ccSAndroid Build Coastguard Worker  the integral.
108*77c1e3ccSAndroid Build Coastguard Worker
109*77c1e3ccSAndroid Build Coastguard Worker  metric_set1 - list of tuples ( bitrate,  metric ) for first graph
110*77c1e3ccSAndroid Build Coastguard Worker  metric_set2 - list of tuples ( bitrate,  metric ) for second graph
111*77c1e3ccSAndroid Build Coastguard Worker  """
112*77c1e3ccSAndroid Build Coastguard Worker
113*77c1e3ccSAndroid Build Coastguard Worker  if not metric_set1 or not metric_set2:
114*77c1e3ccSAndroid Build Coastguard Worker    return 0.0
115*77c1e3ccSAndroid Build Coastguard Worker
116*77c1e3ccSAndroid Build Coastguard Worker  try:
117*77c1e3ccSAndroid Build Coastguard Worker
118*77c1e3ccSAndroid Build Coastguard Worker    # pchip_interlopate requires keys sorted by x axis. x-axis will
119*77c1e3ccSAndroid Build Coastguard Worker    # be our metric not the bitrate so sort by metric.
120*77c1e3ccSAndroid Build Coastguard Worker    metric_set1.sort(key=lambda tup: tup[1])
121*77c1e3ccSAndroid Build Coastguard Worker    metric_set2.sort(key=lambda tup: tup[1])
122*77c1e3ccSAndroid Build Coastguard Worker
123*77c1e3ccSAndroid Build Coastguard Worker    # Pull the log of the rate and clamped psnr from metric_sets.
124*77c1e3ccSAndroid Build Coastguard Worker    log_rate1 = [math.log(x[0]) for x in metric_set1]
125*77c1e3ccSAndroid Build Coastguard Worker    metric1 = [100.0 if x[1] == float('inf') else x[1] for x in metric_set1]
126*77c1e3ccSAndroid Build Coastguard Worker    log_rate2 = [math.log(x[0]) for x in metric_set2]
127*77c1e3ccSAndroid Build Coastguard Worker    metric2 = [100.0 if x[1] == float('inf') else x[1] for x in metric_set2]
128*77c1e3ccSAndroid Build Coastguard Worker
129*77c1e3ccSAndroid Build Coastguard Worker    # Integration interval.  This metric only works on the area that's
130*77c1e3ccSAndroid Build Coastguard Worker    # overlapping.   Extrapolation of these things is sketchy so we avoid.
131*77c1e3ccSAndroid Build Coastguard Worker    min_int = max([min(metric1), min(metric2)])
132*77c1e3ccSAndroid Build Coastguard Worker    max_int = min([max(metric1), max(metric2)])
133*77c1e3ccSAndroid Build Coastguard Worker
134*77c1e3ccSAndroid Build Coastguard Worker    # No overlap means no sensible metric possible.
135*77c1e3ccSAndroid Build Coastguard Worker    if max_int <= min_int:
136*77c1e3ccSAndroid Build Coastguard Worker      return 0.0
137*77c1e3ccSAndroid Build Coastguard Worker
138*77c1e3ccSAndroid Build Coastguard Worker    # Use Piecewise Cubic Hermite Interpolating Polynomial interpolation to
139*77c1e3ccSAndroid Build Coastguard Worker    # create 100 new samples points separated by interval.
140*77c1e3ccSAndroid Build Coastguard Worker    lin = np.linspace(min_int, max_int, num=100, retstep=True)
141*77c1e3ccSAndroid Build Coastguard Worker    interval = lin[1]
142*77c1e3ccSAndroid Build Coastguard Worker    samples = lin[0]
143*77c1e3ccSAndroid Build Coastguard Worker    v1 = scipy.interpolate.pchip_interpolate(metric1, log_rate1, samples)
144*77c1e3ccSAndroid Build Coastguard Worker    v2 = scipy.interpolate.pchip_interpolate(metric2, log_rate2, samples)
145*77c1e3ccSAndroid Build Coastguard Worker
146*77c1e3ccSAndroid Build Coastguard Worker    # Calculate the integral using the trapezoid method on the samples.
147*77c1e3ccSAndroid Build Coastguard Worker    int_v1 = np.trapz(v1, dx=interval)
148*77c1e3ccSAndroid Build Coastguard Worker    int_v2 = np.trapz(v2, dx=interval)
149*77c1e3ccSAndroid Build Coastguard Worker
150*77c1e3ccSAndroid Build Coastguard Worker    # Calculate the average improvement.
151*77c1e3ccSAndroid Build Coastguard Worker    avg_exp_diff = (int_v2 - int_v1) / (max_int - min_int)
152*77c1e3ccSAndroid Build Coastguard Worker
153*77c1e3ccSAndroid Build Coastguard Worker  except (TypeError, ZeroDivisionError, ValueError, np.RankWarning) as e:
154*77c1e3ccSAndroid Build Coastguard Worker    return 0
155*77c1e3ccSAndroid Build Coastguard Worker
156*77c1e3ccSAndroid Build Coastguard Worker  # Convert to a percentage.
157*77c1e3ccSAndroid Build Coastguard Worker  avg_diff = (math.exp(avg_exp_diff) - 1) * 100
158*77c1e3ccSAndroid Build Coastguard Worker
159*77c1e3ccSAndroid Build Coastguard Worker  return avg_diff
160*77c1e3ccSAndroid Build Coastguard Worker
161*77c1e3ccSAndroid Build Coastguard Worker
162*77c1e3ccSAndroid Build Coastguard Worker
163*77c1e3ccSAndroid Build Coastguard Workerdef FillForm(string_for_substitution, dictionary_of_vars):
164*77c1e3ccSAndroid Build Coastguard Worker  """
165*77c1e3ccSAndroid Build Coastguard Worker  This function substitutes all matches of the command string //%% ... %%//
166*77c1e3ccSAndroid Build Coastguard Worker  with the variable represented by ...  .
167*77c1e3ccSAndroid Build Coastguard Worker  """
168*77c1e3ccSAndroid Build Coastguard Worker  return_string = string_for_substitution
169*77c1e3ccSAndroid Build Coastguard Worker  for i in re.findall("//%%(.*)%%//", string_for_substitution):
170*77c1e3ccSAndroid Build Coastguard Worker    return_string = re.sub("//%%" + i + "%%//", dictionary_of_vars[i],
171*77c1e3ccSAndroid Build Coastguard Worker                           return_string)
172*77c1e3ccSAndroid Build Coastguard Worker  return return_string
173*77c1e3ccSAndroid Build Coastguard Worker
174*77c1e3ccSAndroid Build Coastguard Worker
175*77c1e3ccSAndroid Build Coastguard Workerdef HasMetrics(line):
176*77c1e3ccSAndroid Build Coastguard Worker  """
177*77c1e3ccSAndroid Build Coastguard Worker  The metrics files produced by aomenc are started with a B for headers.
178*77c1e3ccSAndroid Build Coastguard Worker  """
179*77c1e3ccSAndroid Build Coastguard Worker  # If the first char of the first word on the line is a digit
180*77c1e3ccSAndroid Build Coastguard Worker  if len(line) == 0:
181*77c1e3ccSAndroid Build Coastguard Worker    return False
182*77c1e3ccSAndroid Build Coastguard Worker  if len(line.split()) == 0:
183*77c1e3ccSAndroid Build Coastguard Worker    return False
184*77c1e3ccSAndroid Build Coastguard Worker  if line.split()[0][0:1].isdigit():
185*77c1e3ccSAndroid Build Coastguard Worker    return True
186*77c1e3ccSAndroid Build Coastguard Worker  return False
187*77c1e3ccSAndroid Build Coastguard Worker
188*77c1e3ccSAndroid Build Coastguard Workerdef GetMetrics(file_name):
189*77c1e3ccSAndroid Build Coastguard Worker  metric_file = open(file_name, "r")
190*77c1e3ccSAndroid Build Coastguard Worker  return metric_file.readline().split();
191*77c1e3ccSAndroid Build Coastguard Worker
192*77c1e3ccSAndroid Build Coastguard Workerdef ParseMetricFile(file_name, metric_column):
193*77c1e3ccSAndroid Build Coastguard Worker  metric_set1 = set([])
194*77c1e3ccSAndroid Build Coastguard Worker  metric_file = open(file_name, "r")
195*77c1e3ccSAndroid Build Coastguard Worker  for line in metric_file:
196*77c1e3ccSAndroid Build Coastguard Worker    metrics = string.split(line)
197*77c1e3ccSAndroid Build Coastguard Worker    if HasMetrics(line):
198*77c1e3ccSAndroid Build Coastguard Worker      if metric_column < len(metrics):
199*77c1e3ccSAndroid Build Coastguard Worker        try:
200*77c1e3ccSAndroid Build Coastguard Worker          tuple = float(metrics[0]), float(metrics[metric_column])
201*77c1e3ccSAndroid Build Coastguard Worker        except:
202*77c1e3ccSAndroid Build Coastguard Worker          tuple = float(metrics[0]), 0
203*77c1e3ccSAndroid Build Coastguard Worker      else:
204*77c1e3ccSAndroid Build Coastguard Worker        tuple = float(metrics[0]), 0
205*77c1e3ccSAndroid Build Coastguard Worker      metric_set1.add(tuple)
206*77c1e3ccSAndroid Build Coastguard Worker  metric_set1_sorted = sorted(metric_set1)
207*77c1e3ccSAndroid Build Coastguard Worker  return metric_set1_sorted
208*77c1e3ccSAndroid Build Coastguard Worker
209*77c1e3ccSAndroid Build Coastguard Worker
210*77c1e3ccSAndroid Build Coastguard Workerdef FileBetter(file_name_1, file_name_2, metric_column, method):
211*77c1e3ccSAndroid Build Coastguard Worker  """
212*77c1e3ccSAndroid Build Coastguard Worker  Compares two data files and determines which is better and by how
213*77c1e3ccSAndroid Build Coastguard Worker  much. Also produces a histogram of how much better, by PSNR.
214*77c1e3ccSAndroid Build Coastguard Worker  metric_column is the metric.
215*77c1e3ccSAndroid Build Coastguard Worker  """
216*77c1e3ccSAndroid Build Coastguard Worker  # Store and parse our two files into lists of unique tuples.
217*77c1e3ccSAndroid Build Coastguard Worker
218*77c1e3ccSAndroid Build Coastguard Worker  # Read the two files, parsing out lines starting with bitrate.
219*77c1e3ccSAndroid Build Coastguard Worker  metric_set1_sorted = ParseMetricFile(file_name_1, metric_column)
220*77c1e3ccSAndroid Build Coastguard Worker  metric_set2_sorted = ParseMetricFile(file_name_2, metric_column)
221*77c1e3ccSAndroid Build Coastguard Worker
222*77c1e3ccSAndroid Build Coastguard Worker
223*77c1e3ccSAndroid Build Coastguard Worker  def GraphBetter(metric_set1_sorted, metric_set2_sorted, base_is_set_2):
224*77c1e3ccSAndroid Build Coastguard Worker    """
225*77c1e3ccSAndroid Build Coastguard Worker    Search through the sorted metric file for metrics on either side of
226*77c1e3ccSAndroid Build Coastguard Worker    the metric from file 1.  Since both lists are sorted we really
227*77c1e3ccSAndroid Build Coastguard Worker    should not have to search through the entire range, but these
228*77c1e3ccSAndroid Build Coastguard Worker    are small files."""
229*77c1e3ccSAndroid Build Coastguard Worker    total_bitrate_difference_ratio = 0.0
230*77c1e3ccSAndroid Build Coastguard Worker    count = 0
231*77c1e3ccSAndroid Build Coastguard Worker    for bitrate, metric in metric_set1_sorted:
232*77c1e3ccSAndroid Build Coastguard Worker      if bitrate == 0:
233*77c1e3ccSAndroid Build Coastguard Worker        continue
234*77c1e3ccSAndroid Build Coastguard Worker      for i in range(len(metric_set2_sorted) - 1):
235*77c1e3ccSAndroid Build Coastguard Worker        s2_bitrate_0, s2_metric_0 = metric_set2_sorted[i]
236*77c1e3ccSAndroid Build Coastguard Worker        s2_bitrate_1, s2_metric_1 = metric_set2_sorted[i + 1]
237*77c1e3ccSAndroid Build Coastguard Worker        # We have a point on either side of our metric range.
238*77c1e3ccSAndroid Build Coastguard Worker        if metric > s2_metric_0 and metric <= s2_metric_1:
239*77c1e3ccSAndroid Build Coastguard Worker
240*77c1e3ccSAndroid Build Coastguard Worker          # Calculate a slope.
241*77c1e3ccSAndroid Build Coastguard Worker          if s2_metric_1 - s2_metric_0 != 0:
242*77c1e3ccSAndroid Build Coastguard Worker            metric_slope = ((s2_bitrate_1 - s2_bitrate_0) /
243*77c1e3ccSAndroid Build Coastguard Worker                            (s2_metric_1 - s2_metric_0))
244*77c1e3ccSAndroid Build Coastguard Worker          else:
245*77c1e3ccSAndroid Build Coastguard Worker            metric_slope = 0
246*77c1e3ccSAndroid Build Coastguard Worker
247*77c1e3ccSAndroid Build Coastguard Worker          estimated_s2_bitrate = (s2_bitrate_0 + (metric - s2_metric_0) *
248*77c1e3ccSAndroid Build Coastguard Worker                                  metric_slope)
249*77c1e3ccSAndroid Build Coastguard Worker
250*77c1e3ccSAndroid Build Coastguard Worker          if estimated_s2_bitrate == 0:
251*77c1e3ccSAndroid Build Coastguard Worker            continue
252*77c1e3ccSAndroid Build Coastguard Worker          # Calculate percentage difference as given by base.
253*77c1e3ccSAndroid Build Coastguard Worker          if base_is_set_2 == 0:
254*77c1e3ccSAndroid Build Coastguard Worker            bitrate_difference_ratio = ((bitrate - estimated_s2_bitrate) /
255*77c1e3ccSAndroid Build Coastguard Worker                                        bitrate)
256*77c1e3ccSAndroid Build Coastguard Worker          else:
257*77c1e3ccSAndroid Build Coastguard Worker            bitrate_difference_ratio = ((bitrate - estimated_s2_bitrate) /
258*77c1e3ccSAndroid Build Coastguard Worker                                        estimated_s2_bitrate)
259*77c1e3ccSAndroid Build Coastguard Worker
260*77c1e3ccSAndroid Build Coastguard Worker          total_bitrate_difference_ratio += bitrate_difference_ratio
261*77c1e3ccSAndroid Build Coastguard Worker          count += 1
262*77c1e3ccSAndroid Build Coastguard Worker          break
263*77c1e3ccSAndroid Build Coastguard Worker
264*77c1e3ccSAndroid Build Coastguard Worker    # Calculate the average improvement between graphs.
265*77c1e3ccSAndroid Build Coastguard Worker    if count != 0:
266*77c1e3ccSAndroid Build Coastguard Worker      avg = total_bitrate_difference_ratio / count
267*77c1e3ccSAndroid Build Coastguard Worker
268*77c1e3ccSAndroid Build Coastguard Worker    else:
269*77c1e3ccSAndroid Build Coastguard Worker      avg = 0.0
270*77c1e3ccSAndroid Build Coastguard Worker
271*77c1e3ccSAndroid Build Coastguard Worker    return avg
272*77c1e3ccSAndroid Build Coastguard Worker
273*77c1e3ccSAndroid Build Coastguard Worker  # Be fair to both graphs by testing all the points in each.
274*77c1e3ccSAndroid Build Coastguard Worker  if method == 'avg':
275*77c1e3ccSAndroid Build Coastguard Worker    avg_improvement = 50 * (
276*77c1e3ccSAndroid Build Coastguard Worker                       GraphBetter(metric_set1_sorted, metric_set2_sorted, 1) -
277*77c1e3ccSAndroid Build Coastguard Worker                       GraphBetter(metric_set2_sorted, metric_set1_sorted, 0))
278*77c1e3ccSAndroid Build Coastguard Worker  elif method == 'dsnr':
279*77c1e3ccSAndroid Build Coastguard Worker      avg_improvement = bdsnr2(metric_set1_sorted, metric_set2_sorted)
280*77c1e3ccSAndroid Build Coastguard Worker  else:
281*77c1e3ccSAndroid Build Coastguard Worker      avg_improvement = bdrate2(metric_set2_sorted, metric_set1_sorted)
282*77c1e3ccSAndroid Build Coastguard Worker
283*77c1e3ccSAndroid Build Coastguard Worker  return avg_improvement
284*77c1e3ccSAndroid Build Coastguard Worker
285*77c1e3ccSAndroid Build Coastguard Worker
286*77c1e3ccSAndroid Build Coastguard Workerdef HandleFiles(variables):
287*77c1e3ccSAndroid Build Coastguard Worker  """
288*77c1e3ccSAndroid Build Coastguard Worker  This script creates html for displaying metric data produced from data
289*77c1e3ccSAndroid Build Coastguard Worker  in a video stats file,  as created by the AOM project when enable_psnr
290*77c1e3ccSAndroid Build Coastguard Worker  is turned on:
291*77c1e3ccSAndroid Build Coastguard Worker
292*77c1e3ccSAndroid Build Coastguard Worker  Usage: visual_metrics.py template.html pattern base_dir sub_dir [ sub_dir2 ..]
293*77c1e3ccSAndroid Build Coastguard Worker
294*77c1e3ccSAndroid Build Coastguard Worker  The script parses each metrics file [see below] that matches the
295*77c1e3ccSAndroid Build Coastguard Worker  statfile_pattern  in the baseline directory and looks for the file that
296*77c1e3ccSAndroid Build Coastguard Worker  matches that same file in each of the sub_dirs, and compares the resultant
297*77c1e3ccSAndroid Build Coastguard Worker  metrics bitrate, avg psnr, glb psnr, and ssim. "
298*77c1e3ccSAndroid Build Coastguard Worker
299*77c1e3ccSAndroid Build Coastguard Worker  It provides a table in which each row is a file in the line directory,
300*77c1e3ccSAndroid Build Coastguard Worker  and a column for each subdir, with the cells representing how that clip
301*77c1e3ccSAndroid Build Coastguard Worker  compares to baseline for that subdir.   A graph is given for each which
302*77c1e3ccSAndroid Build Coastguard Worker  compares filesize to that metric.  If you click on a point in the graph it
303*77c1e3ccSAndroid Build Coastguard Worker  zooms in on that point.
304*77c1e3ccSAndroid Build Coastguard Worker
305*77c1e3ccSAndroid Build Coastguard Worker  a SAMPLE metrics file:
306*77c1e3ccSAndroid Build Coastguard Worker
307*77c1e3ccSAndroid Build Coastguard Worker  Bitrate  AVGPsnr  GLBPsnr  AVPsnrP  GLPsnrP  VPXSSIM    Time(us)
308*77c1e3ccSAndroid Build Coastguard Worker   25.911   38.242   38.104   38.258   38.121   75.790    14103
309*77c1e3ccSAndroid Build Coastguard Worker  Bitrate  AVGPsnr  GLBPsnr  AVPsnrP  GLPsnrP  VPXSSIM    Time(us)
310*77c1e3ccSAndroid Build Coastguard Worker   49.982   41.264   41.129   41.255   41.122   83.993    19817
311*77c1e3ccSAndroid Build Coastguard Worker  Bitrate  AVGPsnr  GLBPsnr  AVPsnrP  GLPsnrP  VPXSSIM    Time(us)
312*77c1e3ccSAndroid Build Coastguard Worker   74.967   42.911   42.767   42.899   42.756   87.928    17332
313*77c1e3ccSAndroid Build Coastguard Worker  Bitrate  AVGPsnr  GLBPsnr  AVPsnrP  GLPsnrP  VPXSSIM    Time(us)
314*77c1e3ccSAndroid Build Coastguard Worker  100.012   43.983   43.838   43.881   43.738   89.695    25389
315*77c1e3ccSAndroid Build Coastguard Worker  Bitrate  AVGPsnr  GLBPsnr  AVPsnrP  GLPsnrP  VPXSSIM    Time(us)
316*77c1e3ccSAndroid Build Coastguard Worker  149.980   45.338   45.203   45.184   45.043   91.591    25438
317*77c1e3ccSAndroid Build Coastguard Worker  Bitrate  AVGPsnr  GLBPsnr  AVPsnrP  GLPsnrP  VPXSSIM    Time(us)
318*77c1e3ccSAndroid Build Coastguard Worker  199.852   46.225   46.123   46.113   45.999   92.679    28302
319*77c1e3ccSAndroid Build Coastguard Worker  Bitrate  AVGPsnr  GLBPsnr  AVPsnrP  GLPsnrP  VPXSSIM    Time(us)
320*77c1e3ccSAndroid Build Coastguard Worker  249.922   46.864   46.773   46.777   46.673   93.334    27244
321*77c1e3ccSAndroid Build Coastguard Worker  Bitrate  AVGPsnr  GLBPsnr  AVPsnrP  GLPsnrP  VPXSSIM    Time(us)
322*77c1e3ccSAndroid Build Coastguard Worker  299.998   47.366   47.281   47.317   47.220   93.844    27137
323*77c1e3ccSAndroid Build Coastguard Worker  Bitrate  AVGPsnr  GLBPsnr  AVPsnrP  GLPsnrP  VPXSSIM    Time(us)
324*77c1e3ccSAndroid Build Coastguard Worker  349.769   47.746   47.677   47.722   47.648   94.178    32226
325*77c1e3ccSAndroid Build Coastguard Worker  Bitrate  AVGPsnr  GLBPsnr  AVPsnrP  GLPsnrP  VPXSSIM    Time(us)
326*77c1e3ccSAndroid Build Coastguard Worker  399.773   48.032   47.971   48.013   47.946   94.362    36203
327*77c1e3ccSAndroid Build Coastguard Worker
328*77c1e3ccSAndroid Build Coastguard Worker  sample use:
329*77c1e3ccSAndroid Build Coastguard Worker  visual_metrics.py template.html "*stt" aom aom_b aom_c > metrics.html
330*77c1e3ccSAndroid Build Coastguard Worker  """
331*77c1e3ccSAndroid Build Coastguard Worker
332*77c1e3ccSAndroid Build Coastguard Worker  # The template file is the html file into which we will write the
333*77c1e3ccSAndroid Build Coastguard Worker  # data from the stats file, formatted correctly for the gviz_api.
334*77c1e3ccSAndroid Build Coastguard Worker  template_file = open(variables[1], "r")
335*77c1e3ccSAndroid Build Coastguard Worker  page_template = template_file.read()
336*77c1e3ccSAndroid Build Coastguard Worker  template_file.close()
337*77c1e3ccSAndroid Build Coastguard Worker
338*77c1e3ccSAndroid Build Coastguard Worker  # This is the path match pattern for finding stats files amongst
339*77c1e3ccSAndroid Build Coastguard Worker  # all the other files it could be.  eg: *.stt
340*77c1e3ccSAndroid Build Coastguard Worker  file_pattern = variables[2]
341*77c1e3ccSAndroid Build Coastguard Worker
342*77c1e3ccSAndroid Build Coastguard Worker  # This is the directory with files that we will use to do the comparison
343*77c1e3ccSAndroid Build Coastguard Worker  # against.
344*77c1e3ccSAndroid Build Coastguard Worker  baseline_dir = variables[3]
345*77c1e3ccSAndroid Build Coastguard Worker  snrs = ''
346*77c1e3ccSAndroid Build Coastguard Worker  filestable = {}
347*77c1e3ccSAndroid Build Coastguard Worker
348*77c1e3ccSAndroid Build Coastguard Worker  filestable['dsnr'] = ''
349*77c1e3ccSAndroid Build Coastguard Worker  filestable['drate'] = ''
350*77c1e3ccSAndroid Build Coastguard Worker  filestable['avg'] = ''
351*77c1e3ccSAndroid Build Coastguard Worker
352*77c1e3ccSAndroid Build Coastguard Worker  # Dirs is directories after the baseline to compare to the base.
353*77c1e3ccSAndroid Build Coastguard Worker  dirs = variables[4:len(variables)]
354*77c1e3ccSAndroid Build Coastguard Worker
355*77c1e3ccSAndroid Build Coastguard Worker  # Find the metric files in the baseline directory.
356*77c1e3ccSAndroid Build Coastguard Worker  dir_list = sorted(fnmatch.filter(os.listdir(baseline_dir), file_pattern))
357*77c1e3ccSAndroid Build Coastguard Worker
358*77c1e3ccSAndroid Build Coastguard Worker  metrics = GetMetrics(baseline_dir + "/" + dir_list[0])
359*77c1e3ccSAndroid Build Coastguard Worker
360*77c1e3ccSAndroid Build Coastguard Worker  metrics_js = 'metrics = ["' + '", "'.join(metrics) + '"];'
361*77c1e3ccSAndroid Build Coastguard Worker
362*77c1e3ccSAndroid Build Coastguard Worker  for column in range(1, len(metrics)):
363*77c1e3ccSAndroid Build Coastguard Worker
364*77c1e3ccSAndroid Build Coastguard Worker    for metric in ['avg','dsnr','drate']:
365*77c1e3ccSAndroid Build Coastguard Worker      description = {"file": ("string", "File")}
366*77c1e3ccSAndroid Build Coastguard Worker
367*77c1e3ccSAndroid Build Coastguard Worker      # Go through each directory and add a column header to our description.
368*77c1e3ccSAndroid Build Coastguard Worker      countoverall = {}
369*77c1e3ccSAndroid Build Coastguard Worker      sumoverall = {}
370*77c1e3ccSAndroid Build Coastguard Worker
371*77c1e3ccSAndroid Build Coastguard Worker      for directory in dirs:
372*77c1e3ccSAndroid Build Coastguard Worker        description[directory] = ("number", directory)
373*77c1e3ccSAndroid Build Coastguard Worker        countoverall[directory] = 0
374*77c1e3ccSAndroid Build Coastguard Worker        sumoverall[directory] = 0
375*77c1e3ccSAndroid Build Coastguard Worker
376*77c1e3ccSAndroid Build Coastguard Worker      # Data holds the data for the visualization, name given comes from
377*77c1e3ccSAndroid Build Coastguard Worker      # gviz_api sample code.
378*77c1e3ccSAndroid Build Coastguard Worker      data = []
379*77c1e3ccSAndroid Build Coastguard Worker      for filename in dir_list:
380*77c1e3ccSAndroid Build Coastguard Worker        row = {'file': splitext(basename(filename))[0] }
381*77c1e3ccSAndroid Build Coastguard Worker        baseline_file_name = baseline_dir + "/" + filename
382*77c1e3ccSAndroid Build Coastguard Worker
383*77c1e3ccSAndroid Build Coastguard Worker        # Read the metric file from each of the directories in our list.
384*77c1e3ccSAndroid Build Coastguard Worker        for directory in dirs:
385*77c1e3ccSAndroid Build Coastguard Worker          metric_file_name = directory + "/" + filename
386*77c1e3ccSAndroid Build Coastguard Worker
387*77c1e3ccSAndroid Build Coastguard Worker          # If there is a metric file in the current directory, open it
388*77c1e3ccSAndroid Build Coastguard Worker          # and calculate its overall difference between it and the baseline
389*77c1e3ccSAndroid Build Coastguard Worker          # directory's metric file.
390*77c1e3ccSAndroid Build Coastguard Worker          if os.path.isfile(metric_file_name):
391*77c1e3ccSAndroid Build Coastguard Worker            overall = FileBetter(baseline_file_name, metric_file_name,
392*77c1e3ccSAndroid Build Coastguard Worker                                 column, metric)
393*77c1e3ccSAndroid Build Coastguard Worker            row[directory] = overall
394*77c1e3ccSAndroid Build Coastguard Worker
395*77c1e3ccSAndroid Build Coastguard Worker            sumoverall[directory] += overall
396*77c1e3ccSAndroid Build Coastguard Worker            countoverall[directory] += 1
397*77c1e3ccSAndroid Build Coastguard Worker
398*77c1e3ccSAndroid Build Coastguard Worker        data.append(row)
399*77c1e3ccSAndroid Build Coastguard Worker
400*77c1e3ccSAndroid Build Coastguard Worker      # Add the overall numbers.
401*77c1e3ccSAndroid Build Coastguard Worker      row = {"file": "OVERALL" }
402*77c1e3ccSAndroid Build Coastguard Worker      for directory in dirs:
403*77c1e3ccSAndroid Build Coastguard Worker        row[directory] = sumoverall[directory] / countoverall[directory]
404*77c1e3ccSAndroid Build Coastguard Worker      data.append(row)
405*77c1e3ccSAndroid Build Coastguard Worker
406*77c1e3ccSAndroid Build Coastguard Worker      # write the tables out
407*77c1e3ccSAndroid Build Coastguard Worker      data_table = gviz_api.DataTable(description)
408*77c1e3ccSAndroid Build Coastguard Worker      data_table.LoadData(data)
409*77c1e3ccSAndroid Build Coastguard Worker
410*77c1e3ccSAndroid Build Coastguard Worker      filestable[metric] = ( filestable[metric] + "filestable_" + metric +
411*77c1e3ccSAndroid Build Coastguard Worker                             "[" + str(column) + "]=" +
412*77c1e3ccSAndroid Build Coastguard Worker                             data_table.ToJSon(columns_order=["file"]+dirs) + "\n" )
413*77c1e3ccSAndroid Build Coastguard Worker
414*77c1e3ccSAndroid Build Coastguard Worker    filestable_avg = filestable['avg']
415*77c1e3ccSAndroid Build Coastguard Worker    filestable_dpsnr = filestable['dsnr']
416*77c1e3ccSAndroid Build Coastguard Worker    filestable_drate = filestable['drate']
417*77c1e3ccSAndroid Build Coastguard Worker
418*77c1e3ccSAndroid Build Coastguard Worker    # Now we collect all the data for all the graphs.  First the column
419*77c1e3ccSAndroid Build Coastguard Worker    # headers which will be Datarate and then each directory.
420*77c1e3ccSAndroid Build Coastguard Worker    columns = ("datarate",baseline_dir)
421*77c1e3ccSAndroid Build Coastguard Worker    description = {"datarate":("number", "Datarate")}
422*77c1e3ccSAndroid Build Coastguard Worker    for directory in dirs:
423*77c1e3ccSAndroid Build Coastguard Worker      description[directory] = ("number", directory)
424*77c1e3ccSAndroid Build Coastguard Worker
425*77c1e3ccSAndroid Build Coastguard Worker    description[baseline_dir] = ("number", baseline_dir)
426*77c1e3ccSAndroid Build Coastguard Worker
427*77c1e3ccSAndroid Build Coastguard Worker    snrs = snrs + "snrs[" + str(column) + "] = ["
428*77c1e3ccSAndroid Build Coastguard Worker
429*77c1e3ccSAndroid Build Coastguard Worker    # Now collect the data for the graphs, file by file.
430*77c1e3ccSAndroid Build Coastguard Worker    for filename in dir_list:
431*77c1e3ccSAndroid Build Coastguard Worker
432*77c1e3ccSAndroid Build Coastguard Worker      data = []
433*77c1e3ccSAndroid Build Coastguard Worker
434*77c1e3ccSAndroid Build Coastguard Worker      # Collect the file in each directory and store all of its metrics
435*77c1e3ccSAndroid Build Coastguard Worker      # in the associated gviz metrics table.
436*77c1e3ccSAndroid Build Coastguard Worker      all_dirs = dirs + [baseline_dir]
437*77c1e3ccSAndroid Build Coastguard Worker      for directory in all_dirs:
438*77c1e3ccSAndroid Build Coastguard Worker
439*77c1e3ccSAndroid Build Coastguard Worker        metric_file_name = directory + "/" + filename
440*77c1e3ccSAndroid Build Coastguard Worker        if not os.path.isfile(metric_file_name):
441*77c1e3ccSAndroid Build Coastguard Worker          continue
442*77c1e3ccSAndroid Build Coastguard Worker
443*77c1e3ccSAndroid Build Coastguard Worker        # Read and parse the metrics file storing it to the data we'll
444*77c1e3ccSAndroid Build Coastguard Worker        # use for the gviz_api.Datatable.
445*77c1e3ccSAndroid Build Coastguard Worker        metrics = ParseMetricFile(metric_file_name, column)
446*77c1e3ccSAndroid Build Coastguard Worker        for bitrate, metric in metrics:
447*77c1e3ccSAndroid Build Coastguard Worker          data.append({"datarate": bitrate, directory: metric})
448*77c1e3ccSAndroid Build Coastguard Worker
449*77c1e3ccSAndroid Build Coastguard Worker      data_table = gviz_api.DataTable(description)
450*77c1e3ccSAndroid Build Coastguard Worker      data_table.LoadData(data)
451*77c1e3ccSAndroid Build Coastguard Worker      snrs = snrs + "'" + data_table.ToJSon(
452*77c1e3ccSAndroid Build Coastguard Worker         columns_order=tuple(["datarate",baseline_dir]+dirs)) + "',"
453*77c1e3ccSAndroid Build Coastguard Worker
454*77c1e3ccSAndroid Build Coastguard Worker    snrs = snrs + "]\n"
455*77c1e3ccSAndroid Build Coastguard Worker
456*77c1e3ccSAndroid Build Coastguard Worker    formatters = ""
457*77c1e3ccSAndroid Build Coastguard Worker    for i in range(len(dirs)):
458*77c1e3ccSAndroid Build Coastguard Worker      formatters = "%s   formatter.format(better, %d);" % (formatters, i+1)
459*77c1e3ccSAndroid Build Coastguard Worker
460*77c1e3ccSAndroid Build Coastguard Worker  print FillForm(page_template, vars())
461*77c1e3ccSAndroid Build Coastguard Worker  return
462*77c1e3ccSAndroid Build Coastguard Worker
463*77c1e3ccSAndroid Build Coastguard Workerif len(sys.argv) < 3:
464*77c1e3ccSAndroid Build Coastguard Worker  print HandleFiles.__doc__
465*77c1e3ccSAndroid Build Coastguard Workerelse:
466*77c1e3ccSAndroid Build Coastguard Worker  HandleFiles(sys.argv)
467