openwakeword/tests/test_models.py

331 lines
15 KiB
Python
Raw Permalink Normal View History

# Copyright 2022 David Scripka. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Copyright 2022 David Scripka. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Imports
import openwakeword
import os
2023-08-24 11:02:29 -04:00
import sys
import logging
import numpy as np
from pathlib import Path
import collections
2022-12-23 14:51:49 -05:00
import pytest
import platform
2023-08-24 11:02:29 -04:00
import pickle
import tempfile
import mock
2024-02-11 15:08:27 -05:00
import wave
2023-10-09 19:54:53 -04:00
# Download models needed for tests
openwakeword.utils.download_models()
# Tests
class TestModels:
def test_load_models_by_path(self):
# Load model with defaults
2023-06-09 16:20:10 -04:00
owwModel = openwakeword.Model(wakeword_models=[
os.path.join("openwakeword", "resources", "models", "alexa_v0.1.onnx")
2023-06-09 16:20:10 -04:00
], inference_framework="onnx")
# Prediction on random data
prediction = owwModel.predict(np.random.randint(-1000, 1000, 1280).astype(np.int16))
assert prediction["alexa_v0.1"] >= 0 and prediction["alexa_v0.1"] <= 1
2023-06-09 16:20:10 -04:00
owwModel = openwakeword.Model(wakeword_models=[
os.path.join("openwakeword", "resources", "models", "alexa_v0.1.tflite")
], inference_framework="tflite")
# Prediction on random data
prediction = owwModel.predict(np.random.randint(-1000, 1000, 1280).astype(np.int16))
assert prediction["alexa_v0.1"] >= 0 and prediction["alexa_v0.1"] <= 1
2023-08-24 11:02:29 -04:00
def test_predict_with_different_frame_sizes(self):
# Test with binary model
owwModel1 = openwakeword.Model(wakeword_models=[
os.path.join("openwakeword", "resources", "models", "alexa_v0.1.onnx")
], inference_framework="onnx")
owwModel2 = openwakeword.Model(wakeword_models=[
2023-08-24 11:02:29 -04:00
os.path.join("openwakeword", "resources", "models", "alexa_v0.1.onnx")
], inference_framework="onnx")
# Prediction on random data with integer multiples of standard chunk size (1280 samples)
predictions1 = owwModel1.predict_clip(os.path.join("tests", "data", "alexa_test.wav"), chunk_size=1280)
predictions2 = owwModel2.predict_clip(os.path.join("tests", "data", "alexa_test.wav"), chunk_size=1280*2)
np.testing.assert_approx_equal(max([i['alexa_v0.1'] for i in predictions1]), max([i['alexa_v0.1'] for i in predictions2]), 5)
2023-08-24 11:02:29 -04:00
# Prediction on data with a chunk size not an integer multiple of 1280
predictions1 = owwModel1.predict_clip(os.path.join("tests", "data", "alexa_test.wav"), chunk_size=1024)
predictions2 = owwModel2.predict_clip(os.path.join("tests", "data", "alexa_test.wav"), chunk_size=1024*2)
np.testing.assert_approx_equal(max([i['alexa_v0.1'] for i in predictions1]), max([i['alexa_v0.1'] for i in predictions2]), 5)
2023-08-24 11:02:29 -04:00
# Test with multiclass model
owwModel1 = openwakeword.Model(wakeword_models=["timer"], inference_framework="onnx")
owwModel2 = openwakeword.Model(wakeword_models=["timer"], inference_framework="onnx")
# Prediction on random data with integer multiples of standard chunk size (1280 samples)
predictions1 = owwModel1.predict_clip(os.path.join("tests", "data", "alexa_test.wav"), chunk_size=1280)
predictions2 = owwModel2.predict_clip(os.path.join("tests", "data", "alexa_test.wav"), chunk_size=1280*2)
assert abs(max([i['1_minute_timer'] for i in predictions1]) - max([i['1_minute_timer'] for i in predictions2])) < 0.00001
# Prediction on data with a chunk size not an integer multiple of 1280
predictions1 = owwModel1.predict_clip(os.path.join("tests", "data", "alexa_test.wav"), chunk_size=1024)
predictions2 = owwModel2.predict_clip(os.path.join("tests", "data", "alexa_test.wav"), chunk_size=1024*2)
assert abs(max([i['1_minute_timer'] for i in predictions1]) - max([i['1_minute_timer'] for i in predictions2])) < 0.00001
2023-08-24 11:02:29 -04:00
def test_exception_handling_for_inference_framework(self):
with mock.patch.dict(sys.modules, {'onnxruntime': None}):
with pytest.raises(ValueError):
openwakeword.Model(wakeword_models=[
os.path.join("openwakeword", "resources", "models", "alexa_v0.1.onnx")
], inference_framework="onnx")
with mock.patch.dict(sys.modules, {'tflite_runtime': None}):
openwakeword.Model(wakeword_models=[
os.path.join("openwakeword", "resources", "models", "alexa_v0.1.tflite")
], inference_framework="tflite")
def test_predict_with_custom_verifier_model(self):
with tempfile.TemporaryDirectory() as tmp_dir:
# Train custom verifier model with random data
verifier_model = openwakeword.custom_verifier_model.train_verifier_model(np.random.random((2, 1536)), np.array([0, 1]))
2023-08-24 11:02:29 -04:00
pickle.dump(verifier_model, open(os.path.join(tmp_dir, "test_verifier.pkl"), "wb"))
# Load model with verifier
owwModel = openwakeword.Model(
wakeword_models=[os.path.join("openwakeword", "resources", "models", "alexa_v0.1.onnx")],
inference_framework="onnx",
custom_verifier_models={"alexa_v0.1": os.path.join(tmp_dir, "test_verifier.pkl")},
custom_verifier_threshold=0.0
)
2023-08-24 11:02:29 -04:00
owwModel.predict(np.random.randint(-1000, 1000, 1280).astype(np.int16))
2023-06-09 16:20:10 -04:00
def test_load_pretrained_model_by_name(self):
# Load model with defaults
owwModel = openwakeword.Model(wakeword_models=["alexa", "hey mycroft"], inference_framework="onnx")
owwModel = openwakeword.Model(wakeword_models=["alexa", "hey mycroft"], inference_framework="tflite")
# Prediction on random data
owwModel.predict(np.random.randint(-1000, 1000, 1280).astype(np.int16))
def test_custom_model_label_mapping_dict(self):
# Load model with model path
2023-06-09 16:20:10 -04:00
owwModel = openwakeword.Model(wakeword_models=[
os.path.join("openwakeword", "resources", "models", "alexa_v0.1.onnx")
],
2023-06-09 16:20:10 -04:00
class_mapping_dicts=[{"alexa_v0.1": {"0": "positive"}}],
inference_framework="onnx"
)
# Prediction on random data
owwModel.predict(np.random.randint(-1000, 1000, 1280).astype(np.int16))
def test_models(self):
2022-12-23 14:51:49 -05:00
# Load model with defaults
owwModel = openwakeword.Model()
# Get clips for each model (assumes that test clips will have the model name in the filename)
test_dict = {}
for mdl_name in owwModel.models.keys():
all_clips = [str(i) for i in Path(os.path.join("tests", "data")).glob("*.wav")]
test_dict[mdl_name] = [i for i in all_clips if mdl_name in i]
# Predict
for model, clips in test_dict.items():
for clip in clips:
# Get predictions for reach frame in the clip
predictions = owwModel.predict_clip(clip)
owwModel.reset() # reset after each clip to ensure independent results
# Make predictions dictionary flatter
predictions_flat = collections.defaultdict(list)
[predictions_flat[key].append(i[key]) for i in predictions for key in i.keys()]
# Check scores against default threshold (0.5)
for key in predictions_flat.keys():
if key in clip:
assert max(predictions_flat[key]) >= 0.5
else:
assert max(predictions_flat[key]) < 0.5
def test_models_with_speex_noise_cancellation(self):
# Skip test on Windows for now
if platform.system() == "Windows":
assert 1 == 1
else:
# Load model with defaults
try:
owwModel = openwakeword.Model(enable_speex_noise_suppression=True)
# Get clips for each model (assumes that test clips will have the model name in the filename)
test_dict = {}
for mdl_name in owwModel.models.keys():
all_clips = [str(i) for i in Path(os.path.join("tests", "data")).glob("*.wav")]
test_dict[mdl_name] = [i for i in all_clips if mdl_name in i]
# Predict
for model, clips in test_dict.items():
for clip in clips:
# Get predictions for reach frame in the clip
predictions = owwModel.predict_clip(clip)
owwModel.reset() # reset after each clip to ensure independent results
# Make predictions dictionary flatter
predictions_flat = collections.defaultdict(list)
[predictions_flat[key].append(i[key]) for i in predictions for key in i.keys()]
# Check scores against default threshold (0.5)
for key in predictions_flat.keys():
if key in clip:
assert max(predictions_flat[key]) >= 0.5
else:
assert max(predictions_flat[key]) < 0.5
except ImportError:
logging.warning("Attemped to test Speex noise cancelling functionality, but the 'speexdsp_ns' library was not installed!"
" If you want these tests to be run, install this library as shown in the openwakeword documentation."
)
assert 1 == 1
2024-02-11 12:45:59 -05:00
def test_models_with_debounce(self):
# Load model with defaults
owwModel = openwakeword.Model()
# Predict with chunks of 1280 with and without debounce
predictions = owwModel.predict_clip(os.path.join("tests", "data", "alexa_test.wav"),
debounce_time=0, threshold={"alexa_v0.1": 0.5})
scores = np.array([i['alexa'] for i in predictions])
predictions = owwModel.predict_clip(os.path.join("tests", "data", "alexa_test.wav"),
debounce_time=1.25, threshold={"alexa": 0.5})
scores_with_debounce = np.array([i['alexa'] for i in predictions])
print(scores, scores_with_debounce)
assert (scores >= 0.5).sum() > 1
assert (scores_with_debounce >= 0.5).sum() == 1
2024-02-11 15:08:27 -05:00
def test_model_reset(self):
# Load the model
owwModel = openwakeword.Model()
# Get test clip and load it
clip = os.path.join("tests", "data", "alexa_test.wav")
with wave.open(clip, mode='rb') as f:
data = np.frombuffer(f.readframes(f.getnframes()), dtype=np.int16)
# Predict frame by frame
for i in range(0, len(data), 1280):
prediction = owwModel.predict(data[i:i+1280])
if prediction['alexa'] > 0.5:
break
# Assert that next prediction is still > 0.5
prediction = owwModel.predict(data[i:i+1280])
assert prediction['alexa'] > 0.5
# Reset the model
owwModel.reset()
# Assert that next prediction is < 0.5
prediction = owwModel.predict(data[i:i+1280])
assert prediction['alexa'] < 0.5
2023-01-18 08:01:25 -05:00
def test_models_with_vad(self):
# Load model with defaults
2023-01-22 20:56:24 -05:00
owwModel = openwakeword.Model(vad_threshold=0.5)
2023-01-18 08:01:25 -05:00
# Get clips for each model (assumes that test clips will have the model name in the filename)
test_dict = {}
for mdl_name in owwModel.models.keys():
all_clips = [str(i) for i in Path(os.path.join("tests", "data")).glob("*.wav")]
test_dict[mdl_name] = [i for i in all_clips if mdl_name in i]
# Predict
for model, clips in test_dict.items():
for clip in clips:
# Get predictions for reach frame in the clip
predictions = owwModel.predict_clip(clip)
owwModel.reset() # reset after each clip to ensure independent results
# Make predictions dictionary flatter
predictions_flat = collections.defaultdict(list)
[predictions_flat[key].append(i[key]) for i in predictions for key in i.keys()]
# Check scores against default threshold (0.5)
for key in predictions_flat.keys():
if key in clip:
assert max(predictions_flat[key]) >= 0.5
else:
assert max(predictions_flat[key]) < 0.5
def test_predict_clip_with_array(self):
# Load model with defaults
owwModel = openwakeword.Model()
# Make random array and predict
dat = np.random.random(16000)
predictions = owwModel.predict_clip(dat)
assert isinstance(predictions[0], dict)
def test_models_with_timing(self):
2023-01-15 21:44:35 -05:00
# Load model with defaults
2023-01-18 08:01:25 -05:00
owwModel = openwakeword.Model(vad_threshold=0.5)
2023-01-22 20:56:24 -05:00
owwModel.predict(np.zeros(1280).astype(np.int16), timing=True)
2022-12-23 14:51:49 -05:00
def test_prediction_with_patience(self):
owwModel = openwakeword.Model()
target_model_name = list(owwModel.models.keys())[0]
with pytest.raises(ValueError):
owwModel.predict(
np.zeros(1280),
2022-12-23 16:39:26 -05:00
patience={target_model_name: 5}
2022-12-23 14:51:49 -05:00
)
owwModel.predict(
np.zeros(1280),
2022-12-23 16:39:26 -05:00
patience={target_model_name: 5},
threshold={target_model_name: 0.5}
2022-12-23 14:51:49 -05:00
)
def test_get_parent_model_from_prediction_label(self):
owwModel = openwakeword.Model()
target_model_name = list(owwModel.models.keys())[0]
owwModel.get_parent_model_from_label(target_model_name)
2023-01-24 09:57:02 -05:00
def test_get_positive_prediction_frames(self):
2023-06-11 14:57:24 -04:00
owwModel = openwakeword.Model(wakeword_models=[
os.path.join("openwakeword", "resources", "models", "alexa_v0.1.onnx")
], inference_framework="onnx")
2023-01-24 09:57:02 -05:00
2023-06-11 14:57:24 -04:00
clip = os.path.join("tests", "data", "alexa_test.wav")
2023-01-24 09:57:02 -05:00
features = owwModel._get_positive_prediction_frames(clip)
assert list(features.values())[0].shape[0] > 0