1#!/usr/bin/env python
2#  Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
3#
4#  Use of this source code is governed by a BSD-style license
5#  that can be found in the LICENSE file in the root of the source
6#  tree. An additional intellectual property rights grant can be found
7#  in the file PATENTS.  All contributing project authors may
8#  be found in the AUTHORS file in the root of the source tree.
9
10import os
11import gviz_api
12import webrtc.data_helper
13
14def main():
15  """
16  This Python script displays a web page with test created with the
17  video_quality_measurement program, which is a tool in WebRTC.
18
19  The script requires on two external files and one Python library:
20  - A HTML template file with layout and references to the json variables
21    defined in this script
22  - A data file in Python format, containing the following:
23    - test_configuration - a dictionary of test configuration names and values.
24    - frame_data_types - a dictionary that maps the different metrics to their
25      data types.
26    - frame_data - a list of dictionaries where each dictionary maps a metric to
27      it's value.
28  - The gviz_api.py of the Google Visualization Python API, available at
29    http://code.google.com/p/google-visualization-python/
30
31  The HTML file is shipped with the script, while the data file must be
32  generated by running video_quality_measurement with the --python flag
33  specified.
34  """
35  print 'Content-type: text/html\n' # the newline is required!
36
37  page_template_filename = '../templates/chart_page_template.html'
38  # The data files must be located in the project tree for app engine being
39  # able to access them.
40  data_filenames = ['../data/vp8_sw.py', '../data/vp8_hw.py']
41  # Will contain info/error messages to be displayed on the resulting page.
42  messages = []
43  # Load the page HTML template.
44  try:
45    f = open(page_template_filename)
46    page_template = f.read()
47    f.close()
48  except IOError as e:
49    ShowErrorPage('Cannot open page template file: %s<br>Details: %s' %
50                  (page_template_filename, e))
51    return
52
53  # Read data from external Python script files. First check that they exist.
54  for filename in data_filenames:
55    if not os.path.exists(filename):
56      messages.append('Cannot open data file: %s' % filename)
57      data_filenames.remove(filename)
58
59  # Read data from all existing input files.
60  data_list = []
61  test_configurations = []
62  names = []
63
64  for filename in data_filenames:
65    read_vars = {} # empty dictionary to load the data into.
66    execfile(filename, read_vars, read_vars)
67
68    test_configuration = read_vars['test_configuration']
69    table_description = read_vars['frame_data_types']
70    table_data = read_vars['frame_data']
71
72    # Verify the data in the file loaded properly.
73    if not table_description or not table_data:
74      messages.append('Invalid input file: %s. Missing description list or '
75                      'data dictionary variables.' % filename)
76      continue
77
78    # Frame numbers appear as number type in the data, but Chart API requires
79    # values of the X-axis to be of string type.
80    # Change the frame_number column data type:
81    table_description['frame_number'] = ('string', 'Frame number')
82    # Convert all the values to string types:
83    for row in table_data:
84      row['frame_number'] = str(row['frame_number'])
85
86    # Store the unique data from this file in the high level lists.
87    test_configurations.append(test_configuration)
88    data_list.append(table_data)
89    # Name of the test run must be present.
90    test_name = FindConfiguration(test_configuration, 'name')
91    if not test_name:
92      messages.append('Invalid input file: %s. Missing configuration key '
93                      '"name"', filename)
94      continue
95    names.append(test_name)
96
97  # Create data helper and build data tables for each graph.
98  helper = webrtc.data_helper.DataHelper(data_list, table_description,
99                                         names, messages)
100
101  # Loading it into gviz_api.DataTable objects and create JSON strings.
102  description, data = helper.CreateConfigurationTable(test_configurations)
103  configurations = gviz_api.DataTable(description, data)
104  json_configurations = configurations.ToJSon()  # pylint: disable=W0612
105
106  description, data = helper.CreateData('ssim')
107  ssim = gviz_api.DataTable(description, data)
108  # pylint: disable=W0612
109  json_ssim_data = ssim.ToJSon(helper.GetOrdering(description))
110
111  description, data = helper.CreateData('psnr')
112  psnr = gviz_api.DataTable(description, data)
113  # pylint: disable=W0612
114  json_psnr_data = psnr.ToJSon(helper.GetOrdering(description))
115
116  description, data = helper.CreateData('packets_dropped')
117  packet_loss = gviz_api.DataTable(description, data)
118  # pylint: disable=W0612
119  json_packet_loss_data = packet_loss.ToJSon(helper.GetOrdering(description))
120
121  description, data = helper.CreateData('bit_rate')
122  # Add a column of data points for the desired bit rate to be plotted.
123  # (uses test configuration from the last data set, assuming it is the same
124  # for all of them)
125  desired_bit_rate = FindConfiguration(test_configuration, 'bit_rate_in_kbps')
126  if not desired_bit_rate:
127    ShowErrorPage('Cannot configuration field named "bit_rate_in_kbps"')
128    return
129  desired_bit_rate = int(desired_bit_rate)
130  # Add new column data type description.
131  description['desired_bit_rate'] = ('number', 'Desired bit rate (kbps)')
132  for row in data:
133    row['desired_bit_rate'] = desired_bit_rate
134  bit_rate = gviz_api.DataTable(description, data)
135  # pylint: disable=W0612
136  json_bit_rate_data = bit_rate.ToJSon(helper.GetOrdering(description))
137
138  # Format the messages list with newlines.
139  messages = '\n'.join(messages)
140
141  # Put the variables as JSon strings into the template.
142  print page_template % vars()
143
144def FindConfiguration(configuration, name):
145  """ Finds a configuration value using it's name.
146      Returns the first configuration with a matching name. Returns None if no
147      matching configuration is found. """
148  return_value = None
149  for row in configuration:
150    if row['name'] == name:
151      return_value = row['value']
152      break
153  return return_value
154
155def ShowErrorPage(error_message):
156  print '<html><body>%s</body></html>' % error_message
157
158if __name__ == '__main__':
159  main()
160