blob: b0efa13e2b3fb6302a2a2343e0f6531747dff728 [file] [log] [blame]
# Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
#
# Use of this source code is governed by a BSD-style license
# that can be found in the LICENSE file in the root of the source
# tree. An additional intellectual property rights grant can be found
# in the file PATENTS. All contributing project authors may
# be found in the AUTHORS file in the root of the source tree.
import logging
import os
import re
class HtmlExport(object):
"""HTML exporter class for APM quality scores.
"""
# Path to CSS and JS files.
_PATH = os.path.dirname(os.path.realpath(__file__))
# CSS file parameters.
_CSS_FILEPATH = os.path.join(_PATH, 'results.css')
_INLINE_CSS = False
# JS file parameters.
_JS_FILEPATH = os.path.join(_PATH, 'results.js')
_INLINE_JS = False
_NEW_LINE = '\n'
def __init__(self, output_filepath):
self._test_data_generator_names = None
self._test_data_generator_params = None
self._output_filepath = output_filepath
def Export(self, scores):
"""Exports the scores into an HTML file.
Args:
scores: nested dictionary containing the scores.
"""
# Generate one table for each evaluation score.
tables = []
for score_name in sorted(scores.keys()):
tables.append(self._BuildScoreTable(score_name, scores[score_name]))
# Create the html file.
html = (
'<html>' +
self._BuildHeader() +
'<body onload="initialize()">' +
'<h1>Results from {}</h1>'.format(self._output_filepath) +
self._NEW_LINE.join(tables) +
'</body>' +
'</html>')
self._Save(self._output_filepath, html)
def _BuildHeader(self):
"""Builds the <head> section of the HTML file.
The header contains the page title and either embedded or linked CSS and JS
files.
Returns:
A string with <head>...</head> HTML.
"""
html = ['<head>', '<title>Results</title>']
# Function to append the lines of a text file to html.
def EmbedFile(filepath):
with open(filepath) as f:
for l in f:
html.append(l.strip())
# CSS.
if self._INLINE_CSS:
# Embed.
html.append('<style>')
EmbedFile(self._CSS_FILEPATH)
html.append('</style>')
else:
# Link.
html.append('<link rel="stylesheet" type="text/css" '
'href="file://{}?">'.format(self._CSS_FILEPATH))
# Javascript.
if self._INLINE_JS:
# Embed.
html.append('<script>')
EmbedFile(self._JS_FILEPATH)
html.append('</script>')
else:
# Link.
html.append('<script src="file://{}?"></script>'.format(
self._JS_FILEPATH))
html.append('</head>')
return self._NEW_LINE.join(html)
def _BuildScoreTable(self, score_name, scores):
"""Builds a table for a specific evaluation score (e.g., POLQA).
Args:
score_name: name of the score.
scores: nested dictionary of scores.
Returns:
A string with <table>...</table> HTML.
"""
config_names = sorted(scores.keys())
input_names = sorted(scores[config_names[0]].keys())
rows = [self._BuildTableRow(
score_name, config_name, scores[config_name], input_names) for (
config_name) in config_names]
html = (
'<table celpadding="0" cellspacing="0">' +
'<thead><tr>{}</tr></thead>'.format(
self._BuildTableHeader(score_name, input_names)) +
'<tbody>' +
'<tr>' + '</tr><tr>'.join(rows) + '</tr>' +
'</tbody>' +
'</table>' + self._BuildLegend())
return html
def _BuildTableHeader(self, score_name, input_names):
"""Builds the cells of a table header.
A table header starts with a cell containing the name of the evaluation
score, and then it includes one column for each probing signal.
Args:
score_name: name of the score.
input_names: list of probing signal names.
Returns:
A string with a list of <th>...</th> HTML elements.
"""
html = (
'<th>{}</th>'.format(self._FormatName(score_name)) +
'<th>' + '</th><th>'.join(
[self._FormatName(name) for name in input_names]) + '</th>')
return html
def _BuildTableRow(self, score_name, config_name, scores, input_names):
"""Builds the cells of a table row.
A table row starts with the name of the APM configuration file, and then it
includes one column for each probing singal.
Args:
score_name: name of the score.
config_name: name of the APM configuration.
scores: nested dictionary of scores.
input_names: list of probing signal names.
Returns:
A string with a list of <td>...</td> HTML elements.
"""
cells = [self._BuildTableCell(
scores[input_name], score_name, config_name, input_name) for (
input_name) in input_names]
html = ('<td>{}</td>'.format(self._FormatName(config_name)) +
'<td>' + '</td><td>'.join(cells) + '</td>')
return html
def _BuildTableCell(self, scores, score_name, config_name, input_name):
"""Builds the inner content of a table cell.
A table cell includes all the scores computed for a specific evaluation
score (e.g., POLQA), APM configuration (e.g., default), and probing signal.
Args:
scores: dictionary of score data.
score_name: name of the score.
config_name: name of the APM configuration.
input_name: name of the probing signal.
Returns:
A string with the HTML of a table body cell.
"""
# Init test data generator names and parameters cache (if not done).
if self._test_data_generator_names is None:
self._test_data_generator_names = sorted(scores.keys())
self._test_data_generator_params = {test_data_generator_name: sorted(
scores[test_data_generator_name].keys()) for (
test_data_generator_name) in self._test_data_generator_names}
# For each noisy input (that is a pair of test data generator and
# generator parameters), add an item with the score and its metadata.
items = []
for name_index, test_data_generator_name in enumerate(
self._test_data_generator_names):
for params_index, test_data_generator_params in enumerate(
self._test_data_generator_params[test_data_generator_name]):
# Init.
score_value = '?'
metadata = ''
# Extract score value and its metadata.
try:
data = scores[test_data_generator_name][test_data_generator_params]
score_value = '{0:f}'.format(data['score'])
metadata = (
'<input type="hidden" name="gen_name" value="{}"/>'
'<input type="hidden" name="gen_params" value="{}"/>'
'<input type="hidden" name="audio_in" value="file://{}"/>'
'<input type="hidden" name="audio_out" value="file://{}"/>'
'<input type="hidden" name="audio_ref" value="file://{}"/>'
).format(
test_data_generator_name,
test_data_generator_params,
data['audio_in_filepath'],
data['audio_out_filepath'],
data['audio_ref_filepath'])
except TypeError:
logging.warning(
'missing score found: <score:%s> <config:%s> <input:%s> '
'<generator:%s> <params:%s>', score_name, config_name, input_name,
test_data_generator_name, test_data_generator_params)
# Add the score.
items.append(
'<div class="test-data-gen-desc">[{0:d}, {1:d}]{2}</div>'
'<div class="value">{3}</div>'.format(
name_index, params_index, metadata, score_value))
html = (
'<div class="score">' +
'</div><div class="score">'.join(items) +
'</div>')
return html
def _BuildLegend(self):
"""Builds the legend.
The legend details test data generator name and parameter pairs.
Returns:
A string with a <div class="legend">...</div> HTML element.
"""
items = []
for name_index, test_data_generator_name in enumerate(
self._test_data_generator_names):
for params_index, test_data_generator_params in enumerate(
self._test_data_generator_params[test_data_generator_name]):
items.append(
'<div class="test-data-gen-desc">[{0:d}, {1:d}]</div>: {2}, '
'{3}'.format(name_index, params_index, test_data_generator_name,
test_data_generator_params))
html = (
'<div class="legend"><div>' +
'</div><div>'.join(items) + '</div></div>')
return html
@classmethod
def _Save(cls, output_filepath, html):
"""Writes the HTML file.
Args:
output_filepath: output file path.
html: string with the HTML content.
"""
with open(output_filepath, 'w') as f:
f.write(html)
@classmethod
def _FormatName(cls, name):
"""Formats a name.
Args:
name: a string.
Returns:
A copy of name in which underscores and dashes are replaced with a space.
"""
return re.sub(r'[_\-]', ' ', name)