POLQA evaluation score.

BUG=webrtc:7218
NOTRY=True

Review-Url: https://codereview.webrtc.org/2718063003
Cr-Commit-Position: refs/heads/master@{#17509}
This commit is contained in:
alessiob 2017-04-03 08:29:09 -07:00 committed by Commit bot
parent c533df203c
commit 0deb594b25
7 changed files with 90 additions and 12 deletions

View File

@ -40,6 +40,7 @@ copy("lib") {
"quality_assessment/eval_scores_factory.py",
"quality_assessment/eval_scores_unittest.py",
"quality_assessment/evaluation.py",
"quality_assessment/exceptions.py",
"quality_assessment/noise_generation.py",
"quality_assessment/noise_generation_factory.py",
"quality_assessment/noise_generation_unittest.py",

View File

@ -14,7 +14,7 @@ import logging
import os
import subprocess
from .data_access import AudioProcConfigFile
from . import data_access
class AudioProcWrapper(object):
@ -57,7 +57,7 @@ class AudioProcWrapper(object):
return
# Load configuration.
self._config = AudioProcConfigFile.load(config_filepath)
self._config = data_access.AudioProcConfigFile.load(config_filepath)
# Set remaining parametrs.
self._config['-i'] = self._input_signal_filepath

View File

@ -11,8 +11,11 @@
import logging
import os
import re
import subprocess
from . import data_access
from . import exceptions
from . import signal_processing
@ -123,12 +126,62 @@ class PolqaScore(EvaluationScore):
"""
NAME = 'polqa'
_BIN_FILENAME = 'PolqaOem64'
def __init__(self, polqa_tool_path):
EvaluationScore.__init__(self)
# Path to the POLQA directory with binary and license files.
self._polqa_tool_path = polqa_tool_path
# POLQA binary file path.
self._polqa_bin_filepath = os.path.join(
self._polqa_tool_path, self._BIN_FILENAME)
if not os.path.exists(self._polqa_bin_filepath):
logging.error('cannot find POLQA tool binary file')
raise exceptions.FileNotFoundError()
def _run(self, output_path):
# TODO(alessio): implement.
self._score = 0.0
polqa_out_filepath = os.path.join(output_path, 'polqa.out')
if os.path.exists(polqa_out_filepath):
os.unlink(polqa_out_filepath)
args = [
self._polqa_bin_filepath, '-t', '-q', '-Overwrite',
'-Ref', self._reference_signal_filepath,
'-Test', self._tested_signal_filepath,
'-LC', 'NB',
'-Out', polqa_out_filepath,
]
logging.debug(' '.join(args))
subprocess.call(args, cwd=self._polqa_tool_path)
# Parse POLQA tool output and extract the score.
polqa_output = self._parse_output_file(polqa_out_filepath)
self._score = float(polqa_output['PolqaScore'])
self._save_score()
@classmethod
def _parse_output_file(cls, polqa_out_filepath):
"""
Parse the POLQA tool output formatted as a table ('-t' option).
"""
data = []
with open(polqa_out_filepath) as f:
for line in f:
line = line.strip()
if len(line) == 0 or line.startswith('*'):
# Ignore comments.
continue
# Read fields.
data.append(re.split(r'\t+', line))
# Two rows expected (header and values).
assert len(data) == 2, 'Cannot parse POLQA output'
number_of_fields = len(data[0])
assert number_of_fields == len(data[1])
# Build and return a dictionary with field names (header) as keys and the
# corresponding field values as values.
return {data[0][index]: data[1][index] for index in range(number_of_fields)}

View File

@ -0,0 +1,18 @@
# 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.
"""Exception classes.
"""
class FileNotFoundError(Exception):
pass
class SignalProcessingException(Exception):
pass

View File

@ -32,6 +32,7 @@ except ImportError:
sys.exit(1)
from . import data_access
from . import exceptions
from . import signal_processing
class NoiseGenerator(object):
@ -319,7 +320,7 @@ class EnvironmentalNoiseGenerator(NoiseGenerator):
self._NOISE_TRACKS_PATH, noise_track_filename)
if not os.path.exists(noise_track_filepath):
logging.error('cannot find the <%s> noise track', noise_track_filename)
continue
raise exceptions.FileNotFoundError()
noise_signal = signal_processing.SignalProcessingUtils.load_wav(
noise_track_filepath)

View File

@ -8,6 +8,7 @@
import array
import logging
import os
import sys
try:
@ -29,9 +30,7 @@ except ImportError:
logging.critical('Cannot import the third-party Python package scipy')
sys.exit(1)
class SignalProcessingException(Exception):
pass
from . import exceptions
class SignalProcessingUtils(object):
@ -46,6 +45,9 @@ class SignalProcessingUtils(object):
Return:
AudioSegment instance.
"""
if not os.path.exists(filepath):
logging.error('cannot find the <%s> audio track file', filepath)
raise exceptions.FileNotFoundError()
return pydub.AudioSegment.from_file(
filepath, format='wav', channels=channels)
@ -175,10 +177,12 @@ class SignalProcessingUtils(object):
noise_power = float(noise.dBFS)
if signal_power == -np.Inf:
logging.error('signal has -Inf power, cannot mix')
raise SignalProcessingException('cannot mix a signal with -Inf power')
raise exceptions.SignalProcessingException(
'cannot mix a signal with -Inf power')
if noise_power == -np.Inf:
logging.error('noise has -Inf power, cannot mix')
raise SignalProcessingException('cannot mix a signal with -Inf power')
raise exceptions.SignalProcessingException(
'cannot mix a signal with -Inf power')
# Pad signal (if necessary). If noise is the shortest, the AudioSegment
# overlay() method implictly pads noise. Hence, the only case to handle

View File

@ -14,6 +14,7 @@ import unittest
import numpy as np
import pydub
from . import exceptions
from . import signal_processing
@ -65,10 +66,10 @@ class TestSignalProcessing(unittest.TestCase):
signal = signal_processing.SignalProcessingUtils.generate_white_noise(
silence)
with self.assertRaises(signal_processing.SignalProcessingException):
with self.assertRaises(exceptions.SignalProcessingException):
_ = signal_processing.SignalProcessingUtils.mix_signals(
signal, silence, 0.0)
with self.assertRaises(signal_processing.SignalProcessingException):
with self.assertRaises(exceptions.SignalProcessingException):
_ = signal_processing.SignalProcessingUtils.mix_signals(
silence, signal, 0.0)