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:
parent
c533df203c
commit
0deb594b25
@ -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",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)}
|
||||
|
||||
@ -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
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user