import numpy as np
from . import frequencyscale
from .frequencyscale import Hertz
from zounds.core import Dimension
from zounds.spectral.frequencyscale import ExplicitScale
[docs]class FrequencyDimension(Dimension):
"""
When applied to an axis of :class:`~zounds.core.ArrayWithUnits`, that axis
can be viewed as representing the energy present in a series of frequency
bands
Args:
scale (FrequencyScale): A scale whose frequency bands correspond to the
items along the frequency axis
Examples:
>>> from zounds import LinearScale, FrequencyBand, ArrayWithUnits
>>> from zounds import FrequencyDimension
>>> import numpy as np
>>> band = FrequencyBand(20, 20000)
>>> scale = LinearScale(frequency_band=band, n_bands=100)
>>> raw = np.hanning(100)
>>> arr = ArrayWithUnits(raw, [FrequencyDimension(scale)])
>>> sliced = arr[FrequencyBand(100, 1000)]
>>> sliced.shape
(5,)
>>> sliced.dimensions
(FrequencyDimension(scale=LinearScale(band=FrequencyBand(
start_hz=20.0,
stop_hz=1019.0,
center=519.5,
bandwidth=999.0), n_bands=5)),)
"""
def __init__(self, scale):
super(FrequencyDimension, self).__init__()
self.scale = scale
def copy(self):
return FrequencyDimension(self.scale)
def weights(self, weights, arr, i):
shape = [1] * arr.ndim
shape[i] = weights.size
return weights.reshape(shape)
def modified_dimension(self, size, windowsize, stepsize=None):
raise NotImplementedError()
[docs] def integer_based_slice(self, index):
return self.scale.get_slice(index)
[docs] def validate(self, size):
"""
Ensure that the size of the dimension matches the number of bands in the
scale
Raises:
ValueError: when the dimension size and number of bands don't match
"""
msg = 'scale and array size must match, ' \
'but were scale: {self.scale.n_bands}, array size: {size}'
if size != len(self.scale):
raise ValueError(msg.format(**locals()))
def __eq__(self, other):
return self.scale == other.scale
def __str__(self):
return 'FrequencyDimension(scale={self.scale})'.format(**locals())
def __repr__(self):
return self.__str__()
[docs]class ExplicitFrequencyDimension(Dimension):
"""
A frequency dimension where the mapping from frequency bands to integer
indices is provided explicitly, rather than computed
Args:
scale (ExplicitScale): the explicit frequency scale that defines how
slices are extracted from this dimension
slices (iterable of slices): An iterable of :class:`python.slice`
instances which correspond to each frequency band from scale
Raises:
ValueError: when the number of slices and number of bands in scale don't
match
"""
def __init__(self, scale, slices):
super(ExplicitFrequencyDimension, self).__init__()
if len(scale) != len(slices):
raise ValueError('scale and slices must have same length')
self.scale = scale
self.slices = slices
self._lookup = dict(list(zip(self.scale, self.slices)))
def copy(self):
return ExplicitFrequencyDimension(self.scale, self.slices)
def weights(self, weights, arr, i):
w = np.zeros(self.slices[-1].stop - self.slices[0].start)
for weight, sl in zip(weights, self.slices):
w[sl] = weight
return w
def modified_dimension(self, size, windowsize, stepsize=None):
raise NotImplementedError()
[docs] def integer_based_slice(self, index):
if not isinstance(index, frequencyscale.FrequencyBand):
return index
try:
return self._lookup[index]
except KeyError:
pass
slce = self.scale.get_slice(index)
slices = self.slices[slce]
return slice(slices[0].start, slices[-1].stop)
[docs] def validate(self, size):
return size == self.slices[-1].stop
def __eq__(self, other):
return self.scale == other.scale and self.slices == other.slices
def __str__(self):
return \
'ExplicitFrequencyDimension(scale={self.scale}, slices=self.slices)' \
.format(**locals())
def __repr__(self):
return self.__str__()