2019-11-08 18:35:02 -05:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
# coding: utf8
|
|
|
|
|
|
|
|
|
|
""" Unit testing for Separator class. """
|
|
|
|
|
|
2020-07-17 13:30:42 +02:00
|
|
|
__email__ = 'spleeter@deezer.com'
|
2019-11-08 18:35:02 -05:00
|
|
|
__author__ = 'Deezer Research'
|
|
|
|
|
__license__ = 'MIT License'
|
|
|
|
|
|
2019-11-14 14:44:19 -05:00
|
|
|
import filecmp
|
2020-03-26 14:23:41 +01:00
|
|
|
import itertools
|
2019-11-23 15:42:40 -08:00
|
|
|
from os.path import splitext, basename, exists, join
|
2019-11-08 18:35:02 -05:00
|
|
|
from tempfile import TemporaryDirectory
|
|
|
|
|
|
2019-11-08 20:03:16 -05:00
|
|
|
import pytest
|
2020-02-27 15:38:46 +01:00
|
|
|
import numpy as np
|
2019-11-08 20:03:16 -05:00
|
|
|
|
2020-03-27 11:12:05 +01:00
|
|
|
import tensorflow as tf
|
|
|
|
|
|
2019-11-20 15:18:53 +01:00
|
|
|
from spleeter import SpleeterError
|
2019-11-08 18:35:02 -05:00
|
|
|
from spleeter.audio.adapter import get_default_audio_adapter
|
|
|
|
|
from spleeter.separator import Separator
|
|
|
|
|
|
2020-03-26 14:23:41 +01:00
|
|
|
TEST_AUDIO_DESCRIPTORS = ['audio_example.mp3', 'audio_example_mono.mp3']
|
|
|
|
|
BACKENDS = ["tensorflow", "librosa"]
|
|
|
|
|
MODELS = ['spleeter:2stems', 'spleeter:4stems', 'spleeter:5stems']
|
2020-03-27 11:12:05 +01:00
|
|
|
|
2020-03-26 14:23:41 +01:00
|
|
|
MODEL_TO_INST = {
|
|
|
|
|
'spleeter:2stems': ('vocals', 'accompaniment'),
|
|
|
|
|
'spleeter:4stems': ('vocals', 'drums', 'bass', 'other'),
|
|
|
|
|
'spleeter:5stems': ('vocals', 'drums', 'bass', 'piano', 'other'),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2020-03-27 11:12:05 +01:00
|
|
|
MODELS_AND_TEST_FILES = list(itertools.product(TEST_AUDIO_DESCRIPTORS, MODELS))
|
2020-03-26 14:23:41 +01:00
|
|
|
TEST_CONFIGURATIONS = list(itertools.product(TEST_AUDIO_DESCRIPTORS, MODELS, BACKENDS))
|
2019-11-08 18:35:02 -05:00
|
|
|
|
|
|
|
|
|
2020-03-27 11:12:05 +01:00
|
|
|
print("RUNNING TESTS WITH TF VERSION {}".format(tf.__version__))
|
|
|
|
|
|
2020-06-18 18:12:43 +02:00
|
|
|
|
2020-06-18 18:01:03 +02:00
|
|
|
@pytest.mark.parametrize('test_file', TEST_AUDIO_DESCRIPTORS)
|
|
|
|
|
def test_separator_backends(test_file):
|
|
|
|
|
adapter = get_default_audio_adapter()
|
|
|
|
|
waveform, _ = adapter.load(test_file)
|
|
|
|
|
|
|
|
|
|
separator_lib = Separator("spleeter:2stems", stft_backend="librosa")
|
|
|
|
|
separator_tf = Separator("spleeter:2stems", stft_backend="tensorflow")
|
|
|
|
|
|
|
|
|
|
# Test the stft and inverse stft provides exact reconstruction
|
|
|
|
|
stft_matrix = separator_lib._stft(waveform)
|
2020-06-18 18:12:43 +02:00
|
|
|
reconstructed = separator_lib._stft(
|
|
|
|
|
stft_matrix, inverse=True, length=waveform.shape[0])
|
2020-06-18 18:01:03 +02:00
|
|
|
assert np.allclose(reconstructed, waveform, atol=1e-2)
|
|
|
|
|
|
|
|
|
|
# # now also test that tensorflow and librosa STFT provide same results
|
|
|
|
|
from spleeter.audio.spectrogram import compute_spectrogram_tf
|
|
|
|
|
tf_waveform = tf.convert_to_tensor(waveform, tf.float32)
|
|
|
|
|
spectrogram_tf = compute_spectrogram_tf(tf_waveform,
|
2020-06-18 18:12:43 +02:00
|
|
|
separator_tf._params['frame_length'],
|
|
|
|
|
separator_tf._params['frame_step'],)
|
2020-06-18 18:01:03 +02:00
|
|
|
with tf.Session() as sess:
|
|
|
|
|
spectrogram_tf_eval = spectrogram_tf.eval()
|
|
|
|
|
|
|
|
|
|
# check that stfts are equivalent up to the padding in the librosa case
|
|
|
|
|
assert stft_matrix.shape[0] == spectrogram_tf_eval.shape[0] + 2
|
|
|
|
|
assert stft_matrix.shape[1:] == spectrogram_tf_eval.shape[1:]
|
2020-06-18 18:12:43 +02:00
|
|
|
assert np.allclose(
|
|
|
|
|
np.abs(stft_matrix[1:-1]), spectrogram_tf_eval, atol=1e-2)
|
2020-06-18 18:01:03 +02:00
|
|
|
|
|
|
|
|
# compare both separation, it should be close
|
|
|
|
|
out_tf = separator_tf._separate_tensorflow(waveform, test_file)
|
|
|
|
|
out_lib = separator_lib._separate_librosa(waveform, test_file)
|
|
|
|
|
|
|
|
|
|
for instrument in out_lib.keys():
|
|
|
|
|
# test that both outputs are not null
|
|
|
|
|
assert np.sum(np.abs(out_tf[instrument])) > 1000
|
|
|
|
|
assert np.sum(np.abs(out_lib[instrument])) > 1000
|
|
|
|
|
assert np.allclose(out_tf[instrument], out_lib[instrument], atol=0.1)
|
2020-03-27 11:12:05 +01:00
|
|
|
|
2020-06-18 18:12:43 +02:00
|
|
|
|
2020-03-26 14:27:19 +01:00
|
|
|
@pytest.mark.parametrize('test_file, configuration, backend', TEST_CONFIGURATIONS)
|
|
|
|
|
def test_separate(test_file, configuration, backend):
|
2019-11-08 18:35:02 -05:00
|
|
|
""" Test separation from raw data. """
|
2020-06-26 11:03:41 +02:00
|
|
|
instruments = MODEL_TO_INST[configuration]
|
|
|
|
|
adapter = get_default_audio_adapter()
|
|
|
|
|
waveform, _ = adapter.load(test_file)
|
|
|
|
|
separator = Separator(configuration, stft_backend=backend, multiprocess=False)
|
|
|
|
|
prediction = separator.separate(waveform, test_file)
|
|
|
|
|
assert len(prediction) == len(instruments)
|
|
|
|
|
for instrument in instruments:
|
|
|
|
|
assert instrument in prediction
|
|
|
|
|
for instrument in instruments:
|
|
|
|
|
track = prediction[instrument]
|
|
|
|
|
assert waveform.shape[:-1] == track.shape[:-1]
|
|
|
|
|
assert not np.allclose(waveform, track)
|
|
|
|
|
for compared in instruments:
|
|
|
|
|
if instrument != compared:
|
|
|
|
|
assert not np.allclose(track, prediction[compared])
|
|
|
|
|
|
2019-11-08 18:35:02 -05:00
|
|
|
|
|
|
|
|
|
2020-03-26 14:27:19 +01:00
|
|
|
@pytest.mark.parametrize('test_file, configuration, backend', TEST_CONFIGURATIONS)
|
|
|
|
|
def test_separate_to_file(test_file, configuration, backend):
|
2019-11-08 18:35:02 -05:00
|
|
|
""" Test file based separation. """
|
2020-06-26 11:03:41 +02:00
|
|
|
instruments = MODEL_TO_INST[configuration]
|
|
|
|
|
separator = Separator(configuration, stft_backend=backend, multiprocess=False)
|
|
|
|
|
name = splitext(basename(test_file))[0]
|
|
|
|
|
with TemporaryDirectory() as directory:
|
|
|
|
|
separator.separate_to_file(
|
|
|
|
|
test_file,
|
|
|
|
|
directory)
|
|
|
|
|
for instrument in instruments:
|
|
|
|
|
assert exists(join(
|
|
|
|
|
directory,
|
|
|
|
|
'{}/{}.wav'.format(name, instrument)))
|
2019-11-20 15:18:53 +01:00
|
|
|
|
|
|
|
|
|
2020-03-26 14:27:19 +01:00
|
|
|
@pytest.mark.parametrize('test_file, configuration, backend', TEST_CONFIGURATIONS)
|
|
|
|
|
def test_filename_format(test_file, configuration, backend):
|
2019-11-20 15:18:53 +01:00
|
|
|
""" Test custom filename format. """
|
2020-06-26 11:03:41 +02:00
|
|
|
instruments = MODEL_TO_INST[configuration]
|
|
|
|
|
separator = Separator(configuration, stft_backend=backend, multiprocess=False)
|
|
|
|
|
name = splitext(basename(test_file))[0]
|
|
|
|
|
with TemporaryDirectory() as directory:
|
|
|
|
|
separator.separate_to_file(
|
|
|
|
|
test_file,
|
|
|
|
|
directory,
|
|
|
|
|
filename_format='export/{filename}/{instrument}.{codec}')
|
|
|
|
|
for instrument in instruments:
|
|
|
|
|
assert exists(join(
|
2019-11-20 15:18:53 +01:00
|
|
|
directory,
|
2020-06-26 11:03:41 +02:00
|
|
|
'export/{}/{}.wav'.format(name, instrument)))
|
2019-11-20 15:18:53 +01:00
|
|
|
|
|
|
|
|
|
2020-03-27 11:12:05 +01:00
|
|
|
@pytest.mark.parametrize('test_file, configuration', MODELS_AND_TEST_FILES)
|
|
|
|
|
def test_filename_conflict(test_file, configuration):
|
2019-11-20 15:18:53 +01:00
|
|
|
""" Test error handling with static pattern. """
|
2020-06-26 11:03:41 +02:00
|
|
|
separator = Separator(configuration, multiprocess=False)
|
|
|
|
|
with TemporaryDirectory() as directory:
|
|
|
|
|
with pytest.raises(SpleeterError):
|
|
|
|
|
separator.separate_to_file(
|
|
|
|
|
test_file,
|
|
|
|
|
directory,
|
|
|
|
|
filename_format='I wanna be your lover')
|