diff --git a/tools/python_charts/data/vp8_hw.py b/tools/python_charts/data/vp8_hw.py index 48c5770817..b8c6cc03e6 100644 --- a/tools/python_charts/data/vp8_hw.py +++ b/tools/python_charts/data/vp8_hw.py @@ -1,7 +1,7 @@ # Sample output from the video_quality_measurment program, included only for # reference. Geneate your own by running with the --python flag and then change # the filenames in main.py -test_configuration = [{'name': 'name', 'value': 'Quality test'}, +test_configuration = [{'name': 'name', 'value': 'VP8 hardware test'}, {'name': 'description', 'value': ''}, {'name': 'test_number', 'value': '0'}, {'name': 'input_filename', 'value': 'foreman_cif.yuv'}, diff --git a/tools/python_charts/data/vp8_sw.py b/tools/python_charts/data/vp8_sw.py index 1cece43ce6..0f29137247 100644 --- a/tools/python_charts/data/vp8_sw.py +++ b/tools/python_charts/data/vp8_sw.py @@ -1,7 +1,7 @@ # Sample output from the video_quality_measurment program, included only for # reference. Geneate your own by running with the --python flag and then change # the filenames in main.py -test_configuration = [{'name': 'name', 'value': 'Quality test'}, +test_configuration = [{'name': 'name', 'value': 'VP8 software test'}, {'name': 'description', 'value': ''}, {'name': 'test_number', 'value': '0'}, {'name': 'input_filename', 'value': 'foreman_cif.yuv'}, diff --git a/tools/python_charts/templates/chart_page_template.html b/tools/python_charts/templates/chart_page_template.html index f241fffdaa..1cb395121a 100644 --- a/tools/python_charts/templates/chart_page_template.html +++ b/tools/python_charts/templates/chart_page_template.html @@ -8,10 +8,10 @@ in the file PATENTS. All contributing project authors may be found in the AUTHORS file in the root of the source tree. - Template file to be used to generate Charts for Video Quality Metrics. + Template file to be used to generate Charts for Video Quality Metrics. --> - @@ -20,7 +20,9 @@ google.setOnLoadCallback(drawTable); function drawTable() { - /* Build data table and views */ + /* Build data tables and views */ + var configurations_data_table = + new google.visualization.DataTable(%(json_configurations)s); var ssim_data_table = new google.visualization.DataTable(%(json_ssim_data)s); var psnr_data_table = @@ -28,47 +30,55 @@ var packet_loss_data_table = new google.visualization.DataTable(%(json_packet_loss_data)s); var bit_rate_data_table = - new google.visualization.DataTable(%(json_bit_rate_data)s); - + new google.visualization.DataTable(%(json_bit_rate_data)s); + /* Display tables and charts */ + var configurations_table = new google.visualization.Table( + document.getElementById('table_div_configurations')); + configurations_table.draw(configurations_data_table, { + height: 200 + }); + var ssim_chart = new google.visualization.LineChart( document.getElementById('table_div_ssim')); ssim_chart.draw(ssim_data_table, { - colors: ['blue', 'orange'], - vAxis: {title: 'SSIM'}, - hAxis: {title: 'Frame'}, + colors: ['blue', 'red', 'lightblue', 'pink'], + vAxis: {title: 'SSIM'}, + hAxis: {title: 'Frame'}, width: 1200, height: 300, }); - + var psnr_chart = new google.visualization.LineChart( document.getElementById('table_div_psnr')); psnr_chart.draw(psnr_data_table, { - colors: ['blue', 'orange'], - vAxis: {title: 'PSNR(dB)'}, - hAxis: {title: 'Frame'}, + colors: ['blue', 'red', 'lightblue', 'pink'], + vAxis: {title: 'PSNR (dB)'}, + hAxis: {title: 'Frame'}, width: 1200, height: 300, }); - + var packet_loss_chart = new google.visualization.LineChart( document.getElementById('table_div_packet_loss')); packet_loss_chart.draw(packet_loss_data_table, { - colors: ['blue', 'orange'], - vAxis: {title: 'Packets dropped'}, - hAxis: {title: 'Frame'}, + colors: ['blue', 'red', 'lightblue', 'pink'], + vAxis: {title: 'Packets dropped'}, + hAxis: {title: 'Frame'}, width: 1200, height: 300, }); - + var bit_rate_chart = new google.visualization.LineChart( document.getElementById('table_div_bit_rate')); bit_rate_chart.draw(bit_rate_data_table, { - colors: ['blue', 'orange', 'red'], - vAxis: {title: 'Bit rate'}, - hAxis: {title: 'Frame'}, + colors: ['blue', 'red', 'lightblue', 'pink', 'green'], + vAxis: {title: 'Bit rate'}, + hAxis: {title: 'Frame'}, width: 1200, height: 300, }); } +

Test Configurations:

+

Messages:

%(messages)s

Metrics measured per frame:

@@ -77,4 +87,4 @@
- \ No newline at end of file + diff --git a/tools/python_charts/webrtc/data_helper.py b/tools/python_charts/webrtc/data_helper.py index 17daf7dcba..fce949f98c 100644 --- a/tools/python_charts/webrtc/data_helper.py +++ b/tools/python_charts/webrtc/data_helper.py @@ -107,9 +107,9 @@ class DataHelper(object): self.messages.append("Couldn't find frame data for row %d " "for %s" % (row_number, self.names_list[dataset_index])) break - return (result_table_description, result_data_table) + return result_table_description, result_data_table - def GetOrdering(self, table_description): + def GetOrdering(self, table_description): """ Creates a list of column names, ordered alphabetically except for the frame_number column which always will be the first column. @@ -131,4 +131,53 @@ class DataHelper(object): for column in sorted(table_description.keys()): if column != 'frame_number': columns_ordering.append(column) - return columns_ordering \ No newline at end of file + return columns_ordering + + def CreateConfigurationTable(self, configurations): + """ Combines multiple test data configurations for display. + + Args: + configurations: List of one ore more configurations. Each configuration + is required to be a list of dictionaries with two keys: 'name' and + 'value'. + Example of a single configuration: + [ + {'name': 'name', 'value': 'VP8 software'}, + {'name': 'test_number', 'value': '0'}, + {'name': 'input_filename', 'value': 'foreman_cif.yuv'}, + ] + Returns: + A tuple containing: + - a dictionary describing the columns in the configuration table to be + displayed. All columns will have string as data type. + Example: + { + 'name': 'string', + 'test_number': 'string', + 'input_filename': 'string', + } + - a list containing dictionaries (one per configuration) with the + configuration column names mapped to the value for each test run: + + Example matching the columns above: + [ + {'name': 'VP8 software', + 'test_number': '12', + 'input_filename': 'foreman_cif.yuv' }, + {'name': 'VP8 hardware', + 'test_number': '5', + 'input_filename': 'foreman_cif.yuv' }, + ] + """ + result_description = {} + result_data = [] + + for configuration in configurations: + data = {} + result_data.append(data) + for dict in configuration: + name = dict['name'] + value = dict['value'] + result_description[name] = 'string' + data[name] = value + return result_description, result_data diff --git a/tools/python_charts/webrtc/data_helper_test.py b/tools/python_charts/webrtc/data_helper_test.py index 9aa020e3a3..6282f7b052 100644 --- a/tools/python_charts/webrtc/data_helper_test.py +++ b/tools/python_charts/webrtc/data_helper_test.py @@ -30,7 +30,17 @@ class Test(unittest.TestCase): 'psnr': ('number', 'PSRN'), } self.names = ["Test 0", "Test 1"] - + self.configurations = [ + [{'name': 'name', 'value': 'Test 0'}, + {'name': 'test_number', 'value': '13'}, + {'name': 'input_filename', 'value': 'foreman_cif.yuv'}, + ], + [{'name': 'name', 'value': 'Test 1'}, + {'name': 'test_number', 'value': '5'}, + {'name': 'input_filename', 'value': 'foreman_cif.yuv'}, + ], + ] + def testCreateData(self): messages = [] helper = webrtc.data_helper.DataHelper(self.all_data, self.type_description, @@ -44,9 +54,9 @@ class Test(unittest.TestCase): self.assertTrue('ssim_1' in description) self.assertTrue('number' in description['ssim_1'][0]) self.assertTrue('Test 1' in description['ssim_1'][1]) - - self.assertEqual(0, len(messages)) - + + self.assertEqual(0, len(messages)) + self.assertEquals(2, len(data_table)) row = data_table[0] self.assertEquals(0, row['frame_number']) @@ -88,5 +98,18 @@ class Test(unittest.TestCase): self.assertEqual('ssim_0', columns[1]) self.assertEqual('ssim_1', columns[2]) + def testCreateConfigurationTable(self): + messages = [] + helper = webrtc.data_helper.DataHelper(self.all_data, self.type_description, + self.names, messages) + description, data = helper.CreateConfigurationTable(self.configurations) + self.assertEqual(3, len(description)) # 3 columns + self.assertEqual(2, len(data)) # 2 data sets + self.assertTrue(description.has_key('name')) + self.assertTrue(description.has_key('test_number')) + self.assertTrue(description.has_key('input_filename')) + self.assertEquals('Test 0', data[0]['name']) + self.assertEquals('Test 1', data[1]['name']) + if __name__ == "__main__": unittest.main() diff --git a/tools/python_charts/webrtc/main.py b/tools/python_charts/webrtc/main.py index a06e9600c3..82d8831b30 100644 --- a/tools/python_charts/webrtc/main.py +++ b/tools/python_charts/webrtc/main.py @@ -24,7 +24,7 @@ def main(): - A data file in Python format, containing the following: - test_configuration - a dictionary of test configuration names and values. - frame_data_types - a dictionary that maps the different metrics to their - data types + data types. - frame_data - a list of dictionaries where each dictionary maps a metric to it's value. - The gviz_api.py of the Google Visualization Python API, available at @@ -60,7 +60,7 @@ def main(): # Read data from all existing input files. data_list = [] - test_configurations_list = [] + test_configurations = [] names = [] for filename in data_filenames: @@ -74,7 +74,7 @@ def main(): # Verify the data in the file loaded properly. if not table_description or not table_data: messages.append('Invalid input file: %s. Missing description list or ' - 'data dictionary variables.', filename) + 'data dictionary variables.' % filename) continue # Frame numbers appear as number type in the data, but Chart API requires @@ -86,16 +86,25 @@ def main(): row['frame_number'] = str(row['frame_number']) # Store the unique data from this file in the high level lists. - test_configurations_list.append(test_configuration) + test_configurations.append(test_configuration) data_list.append(table_data) - # Use the filenames for name; strip away directory path and extension. - names.append(filename[filename.rfind('/')+1:filename.rfind('.')]) + # Name of the test run must be present. + test_name = FindConfiguration(test_configuration, 'name') + if not test_name: + messages.append('Invalid input file: %s. Missing configuration key ' + '"name"', filename) + continue + names.append(test_name) # Create data helper and build data tables for each graph. helper = webrtc.data_helper.DataHelper(data_list, table_description, names, messages) # Loading it into gviz_api.DataTable objects and create JSON strings. + description, data = helper.CreateConfigurationTable(test_configurations) + configurations = gviz_api.DataTable(description, data) + json_configurations = configurations.ToJSon() + description, data = helper.CreateData('ssim') ssim = gviz_api.DataTable(description, data) json_ssim_data = ssim.ToJSon(helper.GetOrdering(description)) @@ -112,13 +121,11 @@ def main(): # Add a column of data points for the desired bit rate to be plotted. # (uses test configuration from the last data set, assuming it is the same # for all of them) - desired_bit_rate = -1 - for row in test_configuration: - if row['name'] == 'bit_rate_in_kbps': - desired_bit_rate = int(row['value']) - if desired_bit_rate == -1: - ShowErrorPage('Cannot find bit rate in the test configuration.') + desired_bit_rate = FindConfiguration(test_configuration, 'bit_rate_in_kbps') + if not desired_bit_rate: + ShowErrorPage('Cannot configuration field named "bit_rate_in_kbps"') return + desired_bit_rate = int(desired_bit_rate) # Add new column data type description. description['desired_bit_rate'] = ('number', 'Desired bit rate (kbps)') for row in data: @@ -132,6 +139,17 @@ def main(): # Put the variables as JSon strings into the template. print page_template % vars() +def FindConfiguration(configuration, name): + """ Finds a configuration value using it's name. + Returns the first configuration with a matching name. Returns None if no + matching configuration is found. """ + return_value = None + for row in configuration: + if row['name'] == name: + return_value = row['value'] + break + return return_value + def ShowErrorPage(error_message): print '%s' % error_message