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 10__author__ = 'kjellander@webrtc.org (Henrik Kjellander)' 11 12class DataHelper(object): 13 """ 14 Helper class for managing table data. 15 This class does not verify the consistency of the data tables sent into it. 16 """ 17 18 def __init__(self, data_list, table_description, names_list, messages): 19 """ Initializes the DataHelper with data. 20 21 Args: 22 data_list: List of one or more data lists in the format that the 23 Google Visualization Python API expects (list of dictionaries, one 24 per row of data). See the gviz_api.DataTable documentation for more 25 info. 26 table_description: dictionary describing the data types of all 27 columns in the data lists, as defined in the gviz_api.DataTable 28 documentation. 29 names_list: List of strings of what we're going to name the data 30 columns after. Usually different runs of data collection. 31 messages: List of strings we might append error messages to. 32 """ 33 self.data_list = data_list 34 self.table_description = table_description 35 self.names_list = names_list 36 self.messages = messages 37 self.number_of_datasets = len(data_list) 38 self.number_of_frames = len(data_list[0]) 39 40 def CreateData(self, field_name, start_frame=0, end_frame=0): 41 """ Creates a data structure for a specified data field. 42 43 Creates a data structure (data type description dictionary and a list 44 of data dictionaries) to be used with the Google Visualization Python 45 API. The frame_number column is always present and one column per data 46 set is added and its field name is suffixed by _N where N is the number 47 of the data set (0, 1, 2...) 48 49 Args: 50 field_name: String name of the field, must be present in the data 51 structure this DataHelper was created with. 52 start_frame: Frame number to start at (zero indexed). Default: 0. 53 end_frame: Frame number to be the last frame. If zero all frames 54 will be included. Default: 0. 55 56 Returns: 57 A tuple containing: 58 - a dictionary describing the columns in the data result_data_table below. 59 This description uses the name for each data set specified by 60 names_list. 61 62 Example with two data sets named 'Foreman' and 'Crew': 63 { 64 'frame_number': ('number', 'Frame number'), 65 'ssim_0': ('number', 'Foreman'), 66 'ssim_1': ('number', 'Crew'), 67 } 68 - a list containing dictionaries (one per row) with the frame_number 69 column and one column of the specified field_name column per data 70 set. 71 72 Example with two data sets named 'Foreman' and 'Crew': 73 [ 74 {'frame_number': 0, 'ssim_0': 0.98, 'ssim_1': 0.77 }, 75 {'frame_number': 1, 'ssim_0': 0.81, 'ssim_1': 0.53 }, 76 ] 77 """ 78 79 # Build dictionary that describes the data types 80 result_table_description = {'frame_number': ('string', 'Frame number')} 81 for dataset_index in range(self.number_of_datasets): 82 column_name = '%s_%s' % (field_name, dataset_index) 83 column_type = self.table_description[field_name][0] 84 column_description = self.names_list[dataset_index] 85 result_table_description[column_name] = (column_type, column_description) 86 87 # Build data table of all the data 88 result_data_table = [] 89 # We're going to have one dictionary per row. 90 # Create that and copy frame_number values from the first data set 91 for source_row in self.data_list[0]: 92 row_dict = {'frame_number': source_row['frame_number']} 93 result_data_table.append(row_dict) 94 95 # Pick target field data points from the all data tables 96 if end_frame == 0: # Default to all frames 97 end_frame = self.number_of_frames 98 99 for dataset_index in range(self.number_of_datasets): 100 for row_number in range(start_frame, end_frame): 101 column_name = '%s_%s' % (field_name, dataset_index) 102 # Stop if any of the data sets are missing the frame 103 try: 104 result_data_table[row_number][column_name] = \ 105 self.data_list[dataset_index][row_number][field_name] 106 except IndexError: 107 self.messages.append("Couldn't find frame data for row %d " 108 "for %s" % (row_number, self.names_list[dataset_index])) 109 break 110 return result_table_description, result_data_table 111 112 def GetOrdering(self, table_description): # pylint: disable=R0201 113 """ Creates a list of column names, ordered alphabetically except for the 114 frame_number column which always will be the first column. 115 116 Args: 117 table_description: A dictionary of column definitions as defined by the 118 gviz_api.DataTable documentation. 119 Returns: 120 A list of column names, where frame_number is the first and the 121 remaining columns are sorted alphabetically. 122 """ 123 # The JSON data representation generated from gviz_api.DataTable.ToJSon() 124 # must have frame_number as its first column in order for the chart to 125 # use it as it's X-axis value series. 126 # gviz_api.DataTable orders the columns by name by default, which will 127 # be incorrect if we have column names that are sorted before frame_number 128 # in our data table. 129 columns_ordering = ['frame_number'] 130 # add all other columns: 131 for column in sorted(table_description.keys()): 132 if column != 'frame_number': 133 columns_ordering.append(column) 134 return columns_ordering 135 136 def CreateConfigurationTable(self, configurations): # pylint: disable=R0201 137 """ Combines multiple test data configurations for display. 138 139 Args: 140 configurations: List of one ore more configurations. Each configuration 141 is required to be a list of dictionaries with two keys: 'name' and 142 'value'. 143 Example of a single configuration: 144 [ 145 {'name': 'name', 'value': 'VP8 software'}, 146 {'name': 'test_number', 'value': '0'}, 147 {'name': 'input_filename', 'value': 'foreman_cif.yuv'}, 148 ] 149 Returns: 150 A tuple containing: 151 - a dictionary describing the columns in the configuration table to be 152 displayed. All columns will have string as data type. 153 Example: 154 { 155 'name': 'string', 156 'test_number': 'string', 157 'input_filename': 'string', 158 } 159 - a list containing dictionaries (one per configuration) with the 160 configuration column names mapped to the value for each test run: 161 162 Example matching the columns above: 163 [ 164 {'name': 'VP8 software', 165 'test_number': '12', 166 'input_filename': 'foreman_cif.yuv' }, 167 {'name': 'VP8 hardware', 168 'test_number': '5', 169 'input_filename': 'foreman_cif.yuv' }, 170 ] 171 """ 172 result_description = {} 173 result_data = [] 174 175 for configuration in configurations: 176 data = {} 177 result_data.append(data) 178 for values in configuration: 179 name = values['name'] 180 value = values['value'] 181 result_description[name] = 'string' 182 data[name] = value 183 return result_description, result_data 184