| Index: webrtc/modules/audio_processing/test/py_quality_assessment/quality_assessment/export.py
|
| diff --git a/webrtc/modules/audio_processing/test/py_quality_assessment/quality_assessment/export.py b/webrtc/modules/audio_processing/test/py_quality_assessment/quality_assessment/export.py
|
| index 5891b5fe24ca71867a96cdfad1ab1ccc174bd2ac..4b025023a3859d7ddafa38c864cf00653597f575 100644
|
| --- a/webrtc/modules/audio_processing/test/py_quality_assessment/quality_assessment/export.py
|
| +++ b/webrtc/modules/audio_processing/test/py_quality_assessment/quality_assessment/export.py
|
| @@ -8,19 +8,196 @@
|
|
|
| import logging
|
| import os
|
| +import re
|
| +
|
|
|
| class HtmlExport(object):
|
|
|
| - def __init__(self, output_path, output_filename):
|
| - self._output_path = output_path
|
| - self._output_filename = output_filename
|
| + # 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 = True
|
| +
|
| + _NEW_LINE = '\n'
|
| +
|
| + def __init__(self, output_filepath):
|
| + self._noise_names = None
|
| + self._noise_params = None
|
| + self._output_filepath = output_filepath
|
|
|
| def export(self, scores):
|
| - logging.debug('%d score names found', len(scores))
|
| - output_filepath = os.path.join(self._output_path, self._output_filename)
|
| + """
|
| + Export 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._build_score_table(score_name, scores[score_name]))
|
| +
|
| + # Create the html file.
|
| + html = (
|
| + '<html>' +
|
| + self._build_header() +
|
| + '<body>' +
|
| + '<h1>Results from {}</h1>'.format(self._output_filepath) +
|
| + self._NEW_LINE.join(tables) +
|
| + '</body>' +
|
| + '</html>')
|
| +
|
| + self._save(self._output_filepath, html)
|
| +
|
| + def _build_header(self):
|
| + """
|
| + HTML file header with page title and either embedded or linked CSS and JS
|
| + files.
|
| + """
|
| + html = ['<head>', '<title>Results</title>']
|
| +
|
| + # CSS.
|
| + if self._INLINE_CSS:
|
| + # Embed.
|
| + html.append('<style>')
|
| + with open(self._CSS_FILEPATH) as f:
|
| + for l in f:
|
| + html.append(l.strip())
|
| + html.append('</style>')
|
| + else:
|
| + # Link.
|
| + html.append('<link rel="stylesheet" type="text/css" '
|
| + 'href="file://{}?">'.format(self._CSS_FILEPATH))
|
| +
|
| + html.append('</head>')
|
| +
|
| + return self._NEW_LINE.join(html)
|
| +
|
| + def _build_score_table(self, score_name, scores):
|
| + """
|
| + Generate a table for a specific evaluation score (e.g., POLQA).
|
| + """
|
| + config_names = sorted(scores.keys())
|
| + input_names = sorted(scores[config_names[0]].keys())
|
| + rows = [self._table_row(
|
| + 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._table_header(score_name, input_names)) +
|
| + '<tbody>' +
|
| + '<tr>' + '</tr><tr>'.join(rows) + '</tr>' +
|
| + '</tbody>' +
|
| + '</table>' + self._legend())
|
| +
|
| + return html
|
| +
|
| + def _table_header(self, score_name, input_names):
|
| + """
|
| + Generate a table header with the name of the evaluation score in the first
|
| + column and then one column for each probing signal.
|
| + """
|
| + html = (
|
| + '<th>{}</th>'.format(self._format_name(score_name)) +
|
| + '<th>' + '</th><th>'.join(
|
| + [self._format_name(name) for name in input_names]) + '</th>')
|
| + return html
|
| +
|
| + def _table_row(self, score_name, config_name, scores, input_names):
|
| + """
|
| + Generate a table body row with the name of the APM configuration file in the
|
| + first column and then one column for each probing singal.
|
| + """
|
| + cells = [self._table_cell(
|
| + scores[input_name], score_name, config_name, input_name) for (
|
| + input_name) in input_names]
|
| + html = ('<td>{}</td>'.format(self._format_name(config_name)) +
|
| + '<td>' + '</td><td>'.join(cells) + '</td>')
|
| + return html
|
| +
|
| + def _table_cell(self, scores, score_name, config_name, input_name):
|
| + """
|
| + Generate a table cell content with all the scores for the current evaluation
|
| + score, APM configuration, and probing signal.
|
| + """
|
| + # Init noise generator names and noise parameters cache (if not done).
|
| + if self._noise_names is None:
|
| + self._noise_names = sorted(scores.keys())
|
| + self._noise_params = {noise_name: sorted(scores[noise_name].keys()) for (
|
| + noise_name) in self._noise_names}
|
| +
|
| + # For each noisy input (that is a pair of noise generator name and noise
|
| + # generator parameters), add an item with the score and its metadata.
|
| + items = []
|
| + for name_index, noise_name in enumerate(self._noise_names):
|
| + for params_index, noise_params in enumerate(
|
| + self._noise_params[noise_name]):
|
| +
|
| + # Init.
|
| + score_value = '?'
|
| + metadata = ''
|
| +
|
| + # Extract score value and its metadata.
|
| + try:
|
| + data = scores[noise_name][noise_params]
|
| + score_value = '{0:f}'.format(data['score'])
|
| + metadata = (
|
| + '<input type="hidden" name="noise_name" value="{}"/>'
|
| + '<input type="hidden" name="noise_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(
|
| + noise_name,
|
| + noise_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> '
|
| + '<noise:%s> <params:%s>', score_name, config_name, input_name,
|
| + noise_name, noise_params)
|
| +
|
| + # Add the score.
|
| + items.append(
|
| + '<div class="noise-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 _legend(self):
|
| + """
|
| + Generate the legend for each noise generator name and parameters pair.
|
| + """
|
| + items = []
|
| + for name_index, noise_name in enumerate(self._noise_names):
|
| + for params_index, noise_params in enumerate(
|
| + self._noise_params[noise_name]):
|
| + items.append('<div class="noise-desc">[{0:d}, {1:d}]</div>: {2} noise, '
|
| + '{3}'.format(name_index, params_index, noise_name,
|
| + noise_params))
|
| + html = (
|
| + '<div class="legend"><div>' +
|
| + '</div><div>'.join(items) + '</div></div>')
|
| +
|
| + return html
|
|
|
| - # TODO(alessio): remove once implemented
|
| + @classmethod
|
| + def _save(cls, output_filepath, html):
|
| with open(output_filepath, 'w') as f:
|
| - f.write('APM Quality Assessment scores\n')
|
| + f.write(html)
|
|
|
| - return output_filepath
|
| + @classmethod
|
| + def _format_name(cls, name):
|
| + return re.sub(r'[_\-]', ' ', name)
|
|
|