Source code for sigment.quantifiers

# -*- coding: utf-8 -*-

from copy import copy
from .internals import _Validator
from .base import _Base

__all__ = ['Quantifier', 'Pipeline', 'Sometimes', 'SomeOf', 'OneOf']

[docs]class Quantifier(_Base): """Specifies how to execute transformation or quantifier steps. .. note:: As this is a base class, it should **not** be directly instantiated. Parameters ---------- steps: List[Transform, Quantifier] A collection of transformation or quantifier steps to apply. random_order: bool Whether or not to randomize the order of execution of `steps`. random_state: numpy.RandomState, int, optional A random state object or seed for reproducible randomness. """ def __init__(self, steps, random_order=False, random_state=None): if self.__class__ == Quantifier: raise NotImplementedError('Quantifier is a base class and cannot be instantiated') from .transforms import Transform if isinstance(steps, dict): steps = steps.values() else: try: (step for step in steps) except TypeError as e: raise TypeError('Expected steps to be an iterable') from e if not all(isinstance(step, (Quantifier, Transform)) for step in steps): raise TypeError('Expected each step to be a subclass of Quantifier (Pipeline, Sometimes, SomeOf or OneOf) or Transform') self.steps = steps self._val = _Validator() self.random_order = self._val.boolean(random_order, 'random_order (whether to randomize the order of steps)') self.random_state = self._val.random_state(random_state)
[docs] def __call__(self, X, sr=None): """Runs the transformations or quantifiers on a provided input signal. Parameters ---------- X: numpy.ndarray [shape (T,) or (1xT) for mono, (2xT) for stereo] The input signal to transform. sr: int [sr > 0], optional The sample rate for the input signal. .. note:: Not required if using transformations that **do not** require a sample rate. Returns ------- transformed: numpy.ndarray [shape (T,) for mono, (2xT) for stereo] The transformed signal, clipped so that it fits into the :math:`[-1,1]` range required for 32-bit floating point WAVs. .. note:: If a mono signal `X` of shape `(1xT)` was used, the output is reshaped to `(T,)`. Examples -------- >>> import numpy as np >>> from sigment.quantifiers import SomeOf >>> from sigment.transforms import GaussianWhiteNoise, PitchShift, EdgeCrop >>> # Create an example stereo signal. >>> X = np.array([ >>> [0.325, 0.53 , 0.393, 0.211], >>> [0.21 , 0.834, 0.022, 0.38 ] >>> ]) >>> # Use the SomeOf quantifier to run only 1 to 2 of the transformations. >>> transform = SomeOf([ >>> GaussianWhiteNoise(scale=(0.05, 0.15)), >>> PitchShift(n_steps=(-1., 1.)), >>> EdgeCrop(side='start', crop_size=(0.02, 0.05)) >>> ], n=(1, 2)) >>> # Run the __call__ method on the quantifier object to transform X. >>> # NOTE: Pitch shifting requires a sample rate when called, therefore >>> # we must call the quantifier with a specified sample rate parameter. >>> X_transform = transform(X, sr=10) """ X = self._val.signal(X) sr = sr if sr is None else self._val.restricted_integer( sr, 'sr (sample rate)', lambda x: x > 0, 'positive') steps = copy(self._generate_steps()) if self.random_order: self.random_state.shuffle(steps) for step in steps: step.random_state = self.random_state X = step(X, sr) return X
[docs] def generate(self, X, n, sr=None): """Runs the transformations or quantifiers on a provided input signal, producing multiple augmented copies of the input signal. Parameters ---------- X: numpy.ndarray [shape (T,) or (1xT) for mono, (2xT) for stereo] The input signal to transform. n: int [n > 0] Number of augmented copies of `X` to generate. sr: int [sr > 0], optional The sample rate for the input signal. .. note:: Not required if not using transformations that require a sample rate. Returns ------- augmented: List[numpy.ndarray] or numpy.ndarray The augmented copies (or copy if `n=1`) of the signal `X`, clipped so that they fit into the :math:`[-1,1]` range required for 32-bit floating point WAVs. .. note:: If a mono signal `X` of shape `(1xT)` was used, the output is reshaped to `(T,)`. Examples -------- >>> import numpy as np >>> from sigment.quantifiers import Sometimes, OneOf >>> from sigment.transforms import LinearFade, GaussianWhiteNoise >>> # Create an example stereo signal. >>> X = np.array([ >>> [0.325, 0.53 , 0.393, 0.211], >>> [0.21 , 0.834, 0.022, 0.38 ] >>> ]) >>> # Use the Sometimes and OneOf quantifiers to sometimes (with probability 0.65) >>> # apply a Gaussian white noise transformation and either a fade-in or fade-out. >>> transform = Sometimes([ >>> GaussianWhiteNoise(scale=(0.05, 0.15)), >>> OneOf([ >>> LinearFade(direction='in', fade_size=(0.05, 0.1)), >>> LinearFade(direction='out', fade_size=(0.05, 0.1)) >>> ]) >>> ], p=0.65) >>> # Generate 5 augmented versions of X, using the quantifier object. >>> Xs_transform = transform.generate(X, n=5) """ X = self._val.signal(X) n = self._val.restricted_integer( n, 'n (number of augmented copies)', lambda x: x > 0, 'positive') sr = sr if sr is None else self._val.restricted_integer( sr, 'sr (sample rate)', lambda x: x > 0, 'positive') X = [self.__call__(X, sr) for _ in range(n)] return X[0] if n == 1 else X
def _generate_steps(self): raise NotImplementedError def __repr__(self, indent=4, level=0): attrs = [(k, v) for k, v in self.__dict__.items() if k not in ['steps', 'random_order', 'random_state'] and not k.startswith('_')] padding = ' ' * (indent * level) return padding + '{}.{}([\n'.format( self.__class__.__module__, self.__class__.__name__ ) + '{}\n'.format( (', \n').join(step.__repr__(indent=indent, level=level+1) for step in self.steps) ) + padding + ('], {}, random_order={})'.format( ', '.join('{}={}'.format(k, v) for k, v in attrs), self.random_order ) if len(attrs) > 0 else '], random_order={})'.format( self.random_order) ) if len(self.steps) > 0 else '{}.{}([]{}, random_order={})'.format( self.__class__.__module__, self.__class__.__name__, (', ' if len(attrs) > 0 else '') + ', '.join('{}={}'.format(k, v) for k, v in attrs), self.random_order ) def __str__(self): attrs = [(k, v) for k, v in self.__dict__.items() if k not in ['steps', 'random_order', 'random_state'] and not k.startswith('_')] return self.__repr__() if len(self.steps) == 0 else '{}.{}([...], {}random_order={})'.format( self.__class__.__module__, self.__class__.__name__, (', ' if len(attrs) > 0 else '') + ', '.join('{}={}'.format(k, v) for k, v in attrs), self.random_order ) def __len__(self): return len(self.steps) def __getitem__(self, idx): return self.steps.__getitem__(idx)
[docs]class Pipeline(Quantifier): """Sequentially executes each transformation or quantifier step.""" def _generate_steps(self): return self.steps
[docs]class Sometimes(Quantifier): """Probabilistically applies the provided transformation or quantifier steps. Parameters ---------- p: float [0 ≤ p ≤ 1] The probability of executing the transformations or quantifiers. """ def __init__(self, steps, p=0.5, random_order=False, random_state=None): super().__init__(steps, random_order, random_state) self.p = self._val.restricted_float( p, 'p (probability)', lambda x: 0. <= x <= 1., 'between zero and one') def _generate_steps(self): return self.steps if self._apply() else []
[docs]class SomeOf(Quantifier): """Randomly applies a number of the provided transformation or quantifier steps. Parameters ---------- n: int [n > 0] or (int, int) The number of transformation or quantifier steps to apply. Notes ----- - The chosen steps will still be applied in the same order they were defined by default. """ def __init__(self, steps, n, random_order=False, random_state=None): super().__init__(steps, random_order, random_state) self._val.integer_value( n, 'n (number of steps to run)', lambda a, b: 0 < a <= b, 'positive') if n[1] > len(steps): raise ValueError('Quantifier upper limit for n (number of steps) cannot exceed the number of available augmentations') self.n = n def _generate_steps(self): n = self.random_state.randint(self.n[0], self.n[1] + 1) idxs = range(len(self.steps)) return [self.steps[idx] for idx in self.random_state.choice(idxs, n, replace=False)]
[docs]class OneOf(SomeOf): """Randomly applies a single step from the provided transformation or quantifier steps. Notes ----- - This is a special case of the :class:`SomeOf` quantifier, with `n=1`. """ def __init__(self, steps, random_order=False, random_state=None): super().__init__(steps, (1, 1), random_order, random_state)