Made edits to tests, random seed, documentation.
MODIFY
nussl/separation/mask_separation_base.py
nussl/separation/mask_separation_base.py
@@ -6,9 +6,13 @@ Base class for separation algorithms that make masks. Most algorithms in nussl a
"""
import warnings
+import json
import separation_base
import masks
+import nussl.constants
+import nussl.utils
+import nussl.audio_signal
class MaskSeparationBase(separation_base.SeparationBase):
@@ -49,6 +53,7 @@ class MaskSeparationBase(separation_base.SeparationBase):
self.mask_type = mask_type
self._mask_threshold = None
self.mask_threshold = mask_threshold
+ self.result_masks = []
@property
def mask_type(self):
@@ -166,7 +171,7 @@ class MaskSeparationBase(separation_base.SeparationBase):
@mask_threshold.setter
def mask_threshold(self, value):
- if not isinstance(value, float) or not (0.0 < value and value < 1.0):
+ if not isinstance(value, float) or not (0.0 < value < 1.0):
raise ValueError('Mask threshold must be a float between [0.0, 1.0]!')
self._mask_threshold = value
@@ -224,3 +229,53 @@ class MaskSeparationBase(separation_base.SeparationBase):
NotImplementedError: Cannot call base class!
"""
raise NotImplementedError('Cannot call base class!')
+
+ @classmethod
+ def from_json(cls, json_string):
+ """
+ Creates a new :class:`SeparationBase` object from the parameters stored in this JSON string.
+
+ Args:
+ json_string (str): A JSON string containing all the data to create a new :class:`SeparationBase`
+ object.
+
+ Returns:
+ (:class:`SeparationBase`) A new :class:`SeparationBase` object from the JSON string.
+
+ See Also:
+ :func:`to_json` to make a JSON string to freeze this object.
+
+ """
+ mask_sep_decoder = MaskSeparationBaseDecoder(cls)
+ return mask_sep_decoder.decode(json_string)
+
+
+class MaskSeparationBaseDecoder(separation_base.SeparationBaseDecoder):
+ """ Object to decode a :class:`MaskSeparationBase`-derived object from JSON serialization.
+ You should never have to instantiate this object by hand.
+ """
+
+ def __init__(self, separation_class):
+ self.separation_class = separation_class
+ json.JSONDecoder.__init__(self, object_hook=self._json_separation_decoder)
+
+ def _json_separation_decoder(self, json_dict):
+ if '__class__' in json_dict and '__module__' in json_dict:
+ json_dict, separator = self._inspect_json_and_create_new_instance(json_dict)
+
+ # fill out the rest of the fields
+ for k, v in json_dict.items():
+ if isinstance(v, dict) and nussl.constants.NUMPY_JSON_KEY in v:
+ separator.__dict__[k] = nussl.utils.json_numpy_obj_hook(v[nussl.constants.NUMPY_JSON_KEY])
+ elif isinstance(v, (str, bytes)) and nussl.audio_signal.__name__ in v: # TODO: test this
+ separator.__dict__[k] = nussl.audio_signal.AudioSignal.from_json(v)
+ elif k == 'result_masks':
+ # for mask_json in v:
+
+ separator.result_masks = [masks.MaskBase.from_json(itm) for itm in v]
+ else:
+ separator.__dict__[k] = v if not isinstance(v, unicode) else v.encode('ascii')
+
+ return separator
+ else:
+ return json_dict

MODIFY
nussl/separation/nmf_mfcc.py
nussl/separation/nmf_mfcc.py
@@ -25,6 +25,10 @@ class NMF_MFCC(mask_separation_base.MaskSeparationBase):
References:
Mel Frequency Cepstral Coefficients(MFCC): https://en.wikipedia.org/wiki/Mel-frequency_cepstrum
+ Volker Gnann, Martin Spiertz. "Source-filter based clustering for monaural blind source separation"
+ October 2002: https://www.researchgate.net/profile/Martin_Spiertz/publication/228567105_Source-filter_based
+ _clustering_for_monaural_blind_source_separation/links/0c960526b56dae0c69000000.pdf
+ scikit-learn's KMeans: http://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html
Parameters:
input_audio_signal (np.array): a 2-row Numpy matrix containing samples of the two-channel mixture.
@@ -33,20 +37,20 @@ class NMF_MFCC(mask_separation_base.MaskSeparationBase):
distance_measure (str): The type of distance measure to use in NMF - euclidean or divergence.
num_iterations (int): The number of iterations to go through in NMF.
random_seed (int): The seed to use in the numpy random generator
- convert_to_mono (bool): Given a stereo signal, convert to mono
+ kmeans_kwargs (dict): The kwargs for KMeans parameters.
+ convert_to_mono (bool): Given a stereo signal, convert to mono.
+ mask_type (object): A soft or binary mask object used for the source separation.
+ mfcc_range (int/list/tuple): The range of mfcc to use for clustering.
+ n_mfcc (int): The max number of mfccs to use.
Attributes:
- input_audio_signal (object): An Audio Signal object of the input audio signal
- clusterer (object): A KMeans object for clustering the templates and activations
- signal_stft (np.matrix): The stft data for the input audio signal
- templates_matrix (np.matrix): A Numpy matrix containing the templates matrix from running NMF on the
- current channel from the input signal stft data.
- activation_matrix (np.matrix): A Numpy matrix containing the activation matrix from running NMF on the
- current channel from the input signal stft data.
+ input_audio_signal (object): An Audio Signal object of the input audio signal.
+ clusterer (object): A KMeans object for clustering the templates and activations.
+ signal_stft (np.matrix): The stft data for the input audio signal.
labeled_templates (np.array): A Numpy array containing the labeled templates columns
- from the templates matrix for a particular source
- sources (np.array): A Numpy array containing the list of Audio Signal objects for each source
- masks (np.array): A Numpy array containing the lists of Binary Mask objects for each channel
+ from the templates matrix for a particular source.
+ sources (np.array): A Numpy array containing the list of Audio Signal objects for each source.
+ masks (np.array): A Numpy array containing the lists of Binary Mask objects for each channel.
"""
def __init__(self, input_audio_signal, num_sources, num_templates=50, distance_measure='euclidean',
@@ -66,8 +70,6 @@ class NMF_MFCC(mask_separation_base.MaskSeparationBase):
self.signal_stft = None
self.input_audio_signal = input_audio_signal
- self.templates_matrix = None
- self.activation_matrix = None
self.labeled_templates = None
self.sources = []
self.masks = []
@@ -84,21 +86,18 @@ class NMF_MFCC(mask_separation_base.MaskSeparationBase):
else:
raise ValueError('mfcc_range is not set correctly! Must be a tuple or list with min and max, or int (max)')
- # If provided, add random_seed to kmeans_kwargs
- if self.random_seed is not None:
- if self.kmeans_kwargs is None:
- self.kmeans_kwargs = {'random_state': self.random_seed}
- if 'random_state' not in self.kmeans_kwargs or self.kmeans_kwargs['random_state'] is None:
- self.kmeans_kwargs['random_state'] = self.random_seed
+ # If kmeans_kwargs does not include the 'random_state', use the random_seed instead. Else, use the value
+ # provided in the dictionary. If kmeans_kwargs is None, use the random_seed.
- # Set number of clusters to number of sources
- if self.kmeans_kwargs is None:
- self.kmeans_kwargs = {'n_clusters': self.num_sources}
- else:
- self.kmeans_kwargs['n_clusters'] = self.num_sources
+ self.kmeans_random_seed = kmeans_kwargs.pop('random_state', random_seed) \
+ if isinstance(kmeans_kwargs, dict) else random_seed
# Initialize the K Means clusterer
- self.clusterer = sklearn.cluster.KMeans(**self.kmeans_kwargs)
+ if isinstance(self.kmeans_kwargs, dict):
+ self.clusterer = sklearn.cluster.KMeans(n_clusters=self.num_sources, random_state=self.kmeans_random_seed,
+ **kmeans_kwargs)
+ else:
+ self.clusterer = sklearn.cluster.KMeans(n_clusters=self.num_sources, random_state=self.kmeans_random_seed)
def run(self):
""" This function calls TransformerNMF on the magnitude spectrogram of each channel in the input audio signal.
@@ -108,6 +107,25 @@ class NMF_MFCC(mask_separation_base.MaskSeparationBase):
Returns:
self.masks (np.array): A list of binary mask objects that can be used to extract the sources
+
+ Example:
+
+ .. code-block:: python
+ :linenos:
+
+ signal = nussl.AudioSignal(path_to_input_file='input_name.wav')
+
+ # Set up and run Repet
+ nmf_mfcc = nussl.NMF_MFCC(signal, num_sources=2) # Returns a binary mask by default
+ masks = nmf_mfcc.run()
+
+ # Get audio signals
+ sources = nmf_mfcc.make_audio_signals()
+
+ # output the background
+ for i, source in enumerate(sources):
+ output_file_name = str(i) + '.wav'
+ source.write_audio_to_file(output_file_name)
"""
self.masks = []
self.input_audio_signal.stft_params = self.stft_params

MODIFY
nussl/separation/separation_base.py
nussl/separation/separation_base.py
@@ -11,6 +11,7 @@ import warnings
import nussl.audio_signal
import nussl.spectral_utils
import nussl.constants
+import nussl.separation.masks
class SeparationBase(object):
@@ -39,7 +40,7 @@ class SeparationBase(object):
# initialize to empty arrays so that we don't crash randomly
self.audio_signal.audio_data = np.array([])
- self.audio_signal.stft_data = np.array([])
+ self.audio_signal.stft_data = np.array([[]])
@property
def sample_rate(self):
@@ -110,14 +111,22 @@ class SeparationBase(object):
@staticmethod
def _to_json_helper(o):
if not isinstance(o, SeparationBase):
- raise TypeError
+ raise TypeError('SeparationBase._to_json_helper() got foreign object!')
d = copy.copy(o.__dict__)
for k, v in d.items():
if isinstance(v, np.ndarray):
d[k] = nussl.utils.json_ready_numpy_array(v)
- if isinstance(v, nussl.audio_signal.AudioSignal) or isinstance(v, nussl.spectral_utils.StftParams):
+ elif hasattr(v, 'to_json'):
d[k] = v.to_json()
+ elif isinstance(v, (list, tuple, set)) and any(hasattr(itm, 'to_json') for itm in v):
+ s = []
+ for itm in v:
+ if hasattr(itm, 'to_json'):
+ s.append(itm.to_json())
+ else:
+ s.append(itm)
+ d[k] = s
d['__class__'] = o.__class__.__name__
d['__module__'] = o.__module__
@@ -173,31 +182,23 @@ class SeparationBaseDecoder(json.JSONDecoder):
def __init__(self, separation_class):
self.separation_class = separation_class
- json.JSONDecoder.__init__(self, object_hook=self.json_separation_decoder)
-
- def json_separation_decoder(self, json_dict):
- """
- Helper method for :class:`SeparationBaseDecoder`. Don't you worry your pretty little head about this.
+ json.JSONDecoder.__init__(self, object_hook=self._json_separation_decoder)
- NEVER CALL THIS DIRECTLY!!
-
- Args:
- json_dict (dict): JSON dictionary provided by ``object_hook``
-
- Returns:
- A new :class:`SeparationBase`-derived object from JSON serialization
-
- """
- if '__class__' in json_dict:
+ def _inspect_json_and_create_new_instance(self, json_dict):
class_name = json_dict.pop('__class__')
module_name = json_dict.pop('__module__')
if class_name != self.separation_class.__name__ or module_name != self.separation_class.__module__:
- raise TypeError
+ raise TypeError('Expected {}.{} but got {}.{} from json!'.format(self.separation_class.__module__,
+ self.separation_class.__name__,
+ module_name, class_name))
# load the module and import the class
module = __import__(module_name)
class_ = getattr(module, class_name)
+ if '_audio_signal' not in json_dict:
+ raise TypeError('JSON string from {} does not have an AudioSignal object!'.format(class_name))
+
# we know 'input_audio_signal' is always the first argument
signal_json = json_dict.pop('_audio_signal') # this is the AudioSignal object
signal = nussl.audio_signal.AudioSignal.from_json(signal_json)
@@ -210,17 +211,35 @@ class SeparationBaseDecoder(json.JSONDecoder):
args = dict((k.encode('ascii'), json_dict[k]) for k in required_args)
# make a new instance of separation class
- seperator = class_(signal, **args)
+ separator = class_(signal, **args)
+
+ return json_dict, separator
+
+ def _json_separation_decoder(self, json_dict):
+ """
+ Helper method for :class:`SeparationBaseDecoder`. Don't you worry your pretty little head about this.
+
+ NEVER CALL THIS DIRECTLY!!
+
+ Args:
+ json_dict (dict): JSON dictionary provided by `object_hook`
+
+ Returns:
+ A new :class:`SeparationBase`-derived object from JSON serialization
+
+ """
+ if '__class__' in json_dict and '__module__' in json_dict:
+ json_dict, separator = self._inspect_json_and_create_new_instance(json_dict)
# fill out the rest of the fields
for k, v in json_dict.items():
if isinstance(v, dict) and nussl.constants.NUMPY_JSON_KEY in v:
- seperator.__dict__[k] = nussl.utils.json_numpy_obj_hook(v[nussl.constants.NUMPY_JSON_KEY])
+ separator.__dict__[k] = nussl.utils.json_numpy_obj_hook(v[nussl.constants.NUMPY_JSON_KEY])
elif isinstance(v, (str, bytes)) and nussl.audio_signal.__name__ in v: # TODO: test this
- seperator.__dict__[k] = nussl.audio_signal.AudioSignal.from_json(v)
+ separator.__dict__[k] = nussl.audio_signal.AudioSignal.from_json(v)
else:
- seperator.__dict__[k] = v if not isinstance(v, unicode) else v.encode('ascii')
+ separator.__dict__[k] = v if not isinstance(v, unicode) else v.encode('ascii')
- return seperator
+ return separator
else:
return json_dict

MODIFY
tests/freeze_nmf_mfcc.py
tests/freeze_nmf_mfcc.py
@@ -9,13 +9,13 @@ import os
def main():
path_to_benchmark_file = os.path.join('..', 'Input', 'piano_and_synth_arp_chord_mono.wav')
signal = nussl.AudioSignal(path_to_benchmark_file)
+
+ # Set random seed in NMF and KMeans to 0
nmf_mfcc = nussl.NMF_MFCC(signal, num_sources=2, num_templates=6, distance_measure="euclidean",
- num_iterations=150, random_seed=0)
- output_folder = os.path.abspath('nmf_mfcc_reference/nmf_mfcc_benchmarks')
+ num_iterations=100, random_seed=0)
+ output_folder = os.path.abspath('nmf_mfcc_reference/new')
nmf_mfcc.run()
- np.save(os.path.join(output_folder, "benchmark_activation_matrix"), nmf_mfcc.activation_matrix)
- np.save(os.path.join(output_folder, "benchmark_templates_matrix"), nmf_mfcc.templates_matrix)
np.save(os.path.join(output_folder, "benchmark_labeled_templates"), nmf_mfcc.labeled_templates)
np.save(os.path.join(output_folder, "benchmark_masks"), nmf_mfcc.masks)

MODIFY
tests/nmf_mfcc_reference/nmf_mfcc_benchmarks/benchmark_masks.npy.REMOVED.git-id
tests/nmf_mfcc_reference/nmf_mfcc_benchmarks/benchmark_masks.npy.REMOVED.git-id
-5ee5d25fb0e0d4e73bd13e7a76e10ea77cc60ce9
\ No newline at end of file
+c74944e0486dee30085a755b662c03f19821ea95
\ No newline at end of file

MODIFY
tests/nmf_mfcc_reference/nmf_mfcc_benchmarks/benchmark_sources.npy.REMOVED.git-id
tests/nmf_mfcc_reference/nmf_mfcc_benchmarks/benchmark_sources.npy.REMOVED.git-id
-e80ca319a52501f5b340d3d34821120f19a790d5
\ No newline at end of file
+198eb8502ac846b3f3f28e3b9986edce15df270d
\ No newline at end of file

DELETE
tests/nmf_mfcc_reference/test/benchmark_masks.npy.REMOVED.git-id
-650d69d03451df290fda2fff1ba0b59e23b23a89
\ No newline at end of file

DELETE
tests/nmf_mfcc_reference/test/benchmark_sources.npy.REMOVED.git-id
-14573679a559967da97103e306816f114cb3c1b4
\ No newline at end of file

MODIFY
tests/test_json.py
tests/test_json.py
@@ -57,3 +57,16 @@ class TestJson(unittest.TestCase):
f = nussl.RepetSim.from_json(j)
worked = r == f
return worked
+
+ def test_nmf_mfcc(self):
+ path = os.path.join('..', 'Input', 'piano_and_synth_arp_chord_mono.wav')
+ a = nussl.AudioSignal(path)
+ n = nussl.NMF_MFCC(a, num_sources=2)
+ n()
+
+ j = n.to_json()
+ f = nussl.NMF_MFCC.from_json(j)
+ worked = n == f
+ return worked
+
+

MODIFY
tests/test_nmf_mfcc.py
tests/test_nmf_mfcc.py
@@ -150,5 +150,3 @@ class NMFMFCCUnitTests(unittest.TestCase):
assert np.all(benchmark_labeled_templates == nmf_mfcc.labeled_templates)
assert np.all(benchmark_masks == nmf_mfcc.masks)
assert np.all(benchmark_sources == nmf_mfcc.sources)
-
-

Made edits to tests, random seed, documentation.