Skip to content

Commit

Permalink
kwargs & bugs (#40)
Browse files Browse the repository at this point in the history
* exclude dynamicripple from pytest

* test workflow

* add flake8 config file

* exclude soundfile from flake8

* exclude soundcard from flake8

* ignore soundcard for pytest

* ignore soundfile for pytest

* fix bu in test routine

* don't check sound level

* add testing for filters

* change filter documentation

* import re

* change filters to filter in documentation

* change f_lower to low_cutoff

* remove brownnoise

* ignore build

* adding filter doc

* ignore build

* ignore build

* ignore build

* removing _build folder

* convert tabs to spaces

* debug calibrate

* remove time_windows from test

* remove matlabdoc builder

* add matlabdoc builder to requirements

* remove matlabdoc

* resolve some warnings

* syntax fixes

* .

* test recording

* add play method

* do test

* move test folder one level up

* addplotting code in the .rst file

* one mire plotting example

* add filter docu

* add example for equalization filter

* fix plotting

* fix plotting

* remove pyplots folder

* debug plotting

* ignoring cached scripts

* simplify plot_tf

* change header

* remake intro

* add reference for spectral features

* move audiogram to examples

* add some references

* add fft filter test

* chnage header

* merge

* fix reference

* add psychoacoustics test

* integrate mmn in Trialsequence class

* for mmn sequence n_reps = n_trials

* condition numbers start at 1

* condtions start at 1

* rename function to oddball, encode deviant as 0

* change oddball to deviant_indices

* deviants should work now

* make random permutation if n_conds = 1

* give correct trialnr

* write response in list of lists

* make list of None elementrs fro response

* make list of None for response

* add deviants to n conditions

* fix bug

* trial sequence should always be a list

* dont insert empty array to avoid warning

* add test routines

* more tests

* fix bug when loading sequence

* fix import order

* copy _init_ from upstream

* all functions except curses now tested

* fix syntax error

* use uniform syntax for plotting

* add hrtf tests

* add h5netcdf

* add SoundFile

* add 	sudo apt-get install sndfile-tools

* fix syntax

* separate system and python dependencies

* fix syntax error

* return condition in __next__

* fix bug in deviant list generation

* in self.data change None to -999 to avoid error when writing json files

* make triallist python int

* syntax error

* return 0 in __next__() when trial is deviant

* resolve type error

* never modify anything in place, remove copy_channel

* fix syntax error

* remove copychannel, add am method

* changing doku

* overhaul of the psychoacoustics documentation

* fix flake8 error

* fix syntax error

* fix assertion error

* remove redundant function

* writing stuff

* add bibliography

* change paper

* new paper version

* new unittests for Trialsequence + debugging

* debug variable name

* convert trials to list

* raise error when condition is a string but not a file

* add method for saving and loading pickle files and rename to LoadSave_mixin

* when writing JSON, catch value AND type errors

* when making a Trialsequence instance from a file, try to load JSON, catch exception and then load pickle

* catch JSONDecodeError when loading trialsequence from file

* JSON decode error is part of json module

* fix indentation error

* change class names and add docstrings

* rename classes according to python conventions

* removing attributes this_trial_n and this_rep_n from trialsequence

* adding docstrings

* add docstrings

* adding docstrings

* docstrings

* move simulate_response to mixin

* refactoring docstrings

* debug test_psychoacoustics

* avoid endless loop when computing deviant indices

* enable plotting to axis when plotting an "image"

* change plotting routine to avoid timeout when pytesting

* add docstrings

* change nchannels to n_channels and nsamples to n_samples

* change Signal.resize to return a new instance

* changed the crossfade function to deal with multiple inputs (still a BUG)

* dont transpose array if data array is empty

* add unit test for crossfading

* docstrings

* add test for frames and frametimes

* finished docstring overhaul

* add tests for signal.py

* add test routine for generating and reading/writing sound

* add test for power law noise

* add tests for binaural cues

* pack get and set envelope into one function

* change envelope and itd/ild to get/set scheme

* add docstrings for binaural generator functions

* adding tests for band filters

* finish filter class refractor

* dont use unittest class

* debug test_tone

* add type in docstring

* negative ild means sound to the left

* add test for loading/saving filters

* add test routines for externalize and interaural level spectrum

* add the interaural level spectrum file to gitignore

* add test for making HRTF instances

* overhaul hrtf docstrings & tests

* resolve some TODOs

* remove normalise argument

* debuging tests

* debug endless loop

* debig test_envelope

* debug assertion errors

* debug assertion errors

* change filter n_samples

* debug assertion error

* renamed the n_ parameters

* debug sound docs

* implement writing objects to resultsfile

* debugging docs

* debug save results file

* add import statements to docs

* debug tests

* debug test frames

* add default level, changel handling of default sampling rate

* hide default level and samplerate variables

* remove conditions from setup dependencies

* remove level argument from all functions

* add SoudCard

* remove soundfile

* remove soundcard from setup.py

* install pulseaudio

* debug

* remove SounCard from pipeline

* debug test

* debug

* debug get_future_trial

* improve documentation

* uninstall soundcard after setup.py

* add soundcard to requirements

* say yes to pip uninstall

* fix some warnings from pytest

* add error message if libsndfile is not installed

* add data_path function

* change usage of data_path in docs

* Update paper.bib

* Update paper.bib

* dont print every iteration in Trialsequence.transitions

* some small fixes in psychoacoustics docs

* catch h5py warning when loading sofa file

* change docstrings for proper sphinx rendering

* add level argument to generator methods

* add kemar

* remove all plot kwargs and add kwargs to spectrogram

* debug binaural.externalize

* debug hrtf plot_tf

* make assert condition more liberal

Co-authored-by: Marc <[email protected]>
  • Loading branch information
OleBialas and DrMarc authored May 28, 2021
1 parent 21a81dc commit b5b11cf
Show file tree
Hide file tree
Showing 7 changed files with 30 additions and 34 deletions.
2 changes: 1 addition & 1 deletion slab/binaural.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ def externalize(self, hrtf=None):
idx_frontal = numpy.where((hrtf.sources[:, 1] == 0) & (hrtf.sources[:, 0] == 0))[0][0]
if not idx_frontal.size: # idx_frontal is empty
raise ValueError('No frontal direction [0,0] found in HRTF.')
_, h = hrtf.data[idx_frontal].tf(channels=0, nbins=12, show=False) # get low-res version of HRTF spectrum
_, h = hrtf.data[idx_frontal].tf(channels=0, n_bins=12, show=False) # get low-res version of HRTF spectrum
h[0] = 1 # avoids low-freq attenuation in KEMAR HRTF (unproblematic for other HRTFs)
resampled_signal = copy.deepcopy(self)
# if sound and HRTF has different samplerates, resample the sound, apply the HRTF, and resample back:
Expand Down
5 changes: 2 additions & 3 deletions slab/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def apply(self, sig):
'Number of filters must equal number of sound channels, or either one of them must be equal to 1.')
return out

def tf(self, channels='all', n_bins=None, show=True, axis=None, **kwargs):
def tf(self, channels='all', n_bins=None, show=True, axis=None):
"""
Compute a filter's transfer function (magnitude over frequency) and optionally plot it.
Expand All @@ -201,7 +201,6 @@ def tf(self, channels='all', n_bins=None, show=True, axis=None, **kwargs):
If None, use the maximum number of bins.
show (bool): whether to show the plot right after drawing.
axis (matplotlib.axes.Axes | None): axis to plot to. If None create a new plot.
** kwargs: keyword arguments for the plot, see documentation of matplotlib.pyplot.plot for details.
Returns:
(numpy.ndarray): the frequency bins in the range from 0 Hz to the Nyquist frequency.
(numpy.ndarray: the magnitude of each frequency in `w`.
Expand Down Expand Up @@ -253,7 +252,7 @@ def tf(self, channels='all', n_bins=None, show=True, axis=None, **kwargs):
raise ImportError('Plotting transfer functions requires matplotlib.')
if axis is None:
_, axis = plt.subplots()
axis.plot(w, h, **kwargs)
axis.plot(w, h)
axis.set(title='Frequency [Hz]', xlabel='Amplitude [dB]', ylabel='Frequency Response')
axis.grid(True)
if show:
Expand Down
4 changes: 2 additions & 2 deletions slab/hrtf.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ def plot_tf(self, sourceidx, ear='left', xlim=(1000, 18000), n_bins=None, kind='
vlines = numpy.arange(0, len(sourceidx)) * linesep
for idx, s in enumerate(sourceidx):
filt = self.data[s]
freqs, h = filt.tf(channels=chan, nbins=n_bins, show=False)
freqs, h = filt.tf(channels=chan, n_bins=n_bins, show=False)
axis.plot(freqs, h + vlines[idx],
linewidth=0.75, color='0.0', alpha=0.7)
ticks = vlines[::3] # plot every third elevation
Expand All @@ -329,7 +329,7 @@ def plot_tf(self, sourceidx, ear='left', xlim=(1000, 18000), n_bins=None, kind='
elevations = self.sources[sourceidx, 1]
for idx, source in enumerate(sourceidx):
filt = self.data[source]
freqs, h = filt.tf(channels=chan, nbins=n_bins, show=False)
freqs, h = filt.tf(channels=chan, n_bins=n_bins, show=False)
img[:, idx] = h.flatten()
img[img < -25] = -25 # clip at -40 dB transfer
contour = axis.contourf(freqs, elevations, img.T, cmap='hot', origin='upper', levels=20)
Expand Down
10 changes: 4 additions & 6 deletions slab/psychoacoustics.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,20 +595,19 @@ def response_summary(self):
else:
return None

def plot(self, axis=None, show=True, **kwargs):
def plot(self, axis=None, show=True):
"""
Plot the trial sequence as scatter plot.
Arguments:
axis (matplotlib.pyplot.Axes): plot axis to draw on, if none a new plot is generated
show (bool): show the plot immediately, defaults to True
**kwargs: pyplot.plot keyword arguments, see matplotlib documentation
"""
if plt is None:
raise ImportError('Plotting requires matplotlib!')
if axis is None:
axis = plt.subplot()
axis.scatter(range(self.n_trials), self.trials, **kwargs)
axis.scatter(range(self.n_trials), self.trials)
axis.set(title='Trial sequence', xlabel='Trials', ylabel='Condition index')
if show:
plt.show()
Expand Down Expand Up @@ -864,14 +863,13 @@ def save_csv(self, filename):
f.write(responses)
return True

def plot(self, axis=None, show=True, **kwargs):
def plot(self, axis=None, show=True):
"""
Plot the staircase. If called after each trial, one plot is created and updated.
Arguments:
axis (matplotlib.pyplot.Axes): plot axis to draw on, if none a new plot is generated
show (bool): whether to show the plot right after drawing.
**kwargs: pyplot.plot keyword arguments, see matplotlib documentation.
"""
if plt is None:
raise ImportError('Plotting requires matplotlib!')
Expand All @@ -883,7 +881,7 @@ def plot(self, axis=None, show=True, **kwargs):
fig = plt.figure('stairs') # figure 'stairs' is created or made current
axis = fig.gca()
axis.clear()
axis.plot(x, y, **kwargs)
axis.plot(x, y)
axis.set_xlim(-self.n_pretrials, max(20, (self.this_trial_n + 15)//10*10))
axis.set_ylim(min(0, min(y)) if self.min_val == -numpy.Inf else self.min_val,
max(y) if self.max_val == numpy.Inf else self.max_val)
Expand Down
7 changes: 3 additions & 4 deletions slab/signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,24 +403,23 @@ def delay(self, duration=1, channel=0, filter_length=2048):
new.data[i, channel] = numpy.convolve(sig_portion, tap_weight, mode='valid')
return new

def plot_samples(self, show=True, axis=None, **kwargs):
def plot_samples(self, show=True, axis=None):
"""
Stem plot of the samples of the signal.
Arguments:
show (bool): whether to show the plot right after drawing.
axis (matplotlib.axes.Axes | None): axis to plot to. If None create a new plot.
** kwargs: keyword arguments for the plot, see documentation of matplotlib.pyplot.plot for details.
"""
if matplotlib is False:
raise ImportError('Plotting signals requires matplotlib.')
if axis is None:
_, axis = plt.subplots()
if self.n_channels == 1:
axis.stem(self.channel(0), **kwargs)
axis.stem(self.channel(0))
else:
for i in range(self.n_channels):
axis.stem(self.channel(i), label=f'channel {i}', **kwargs)
axis.stem(self.channel(i), label=f'channel {i}')
plt.legend()
axis.set(title='Samples', xlabel='Number', ylabel='Value')
if show:
Expand Down
34 changes: 17 additions & 17 deletions slab/sound.py
Original file line number Diff line number Diff line change
Expand Up @@ -925,7 +925,7 @@ def play_file(filename):
'Playing from files on Linux without SoundCard module requires SoX. '
'Install: sudo apt-get install sox libsox-fmt-all or pip install SoundCard')

def waveform(self, start=0, end=None, show=True, axis=None, **kwargs):
def waveform(self, start=0, end=None, show=True, axis=None):
"""
Plot the waveform of the sound.
Expand All @@ -934,7 +934,6 @@ def waveform(self, start=0, end=None, show=True, axis=None, **kwargs):
end (int | float | None): the end of the plot in seconds (float) or samples (int), defaults to None.
show (bool): whether to show the plot right after drawing.
axis (matplotlib.axes.Axes | None): axis to plot to. If None create a new plot.
** kwargs: keyword arguments for the plot, see documentation of matplotlib.pyplot.plot for details.
"""
if matplotlib is False:
raise ImportError('Plotting waveforms requires matplotlib.')
Expand All @@ -945,14 +944,14 @@ def waveform(self, start=0, end=None, show=True, axis=None, **kwargs):
if axis is None:
_, axis = plt.subplots()
if self.n_channels == 1:
axis.plot(self.times[start:end], self.channel(0)[start:end], **kwargs)
axis.plot(self.times[start:end], self.channel(0)[start:end])
elif self.n_channels == 2:
axis.plot(self.times[start:end], self.channel(0)[start:end], label='left', **kwargs)
axis.plot(self.times[start:end], self.channel(1)[start:end], label='right', **kwargs)
axis.plot(self.times[start:end], self.channel(0)[start:end], label='left')
axis.plot(self.times[start:end], self.channel(1)[start:end], label='right')
axis.legend()
else:
for i in range(self.n_channels):
axis.plot(self.times[start:end], self.channel(i)[start:end], label=f'channel {i}', **kwargs)
axis.plot(self.times[start:end], self.channel(i)[start:end], label=f'channel {i}')
plt.legend()
axis.set(title='Waveform', xlabel='Time [sec]', ylabel='Amplitude')
if show:
Expand All @@ -971,7 +970,7 @@ def spectrogram(self, window_dur=0.005, dyn_range=120, upper_frequency=None, oth
show (bool): whether to show the plot right after drawing. Note that if show is False and no `axis` is
passed, no plot will be created.
axis (matplotlib.axes.Axes | None): axis to plot to. If None create a new plot.
**kwargs: keyword arguments for the plot. See documentation for matplotlib.pyplot.imshow.
**kwargs: keyword arguments for computing the spectrogram. See documentation for scipy.signal.spectrogram.
Returns:
(None | tuple): If `show == True` or an axis was passed, a plot is drawn and nothing is returned. Else,
a tuple is returned which contains frequencies, time bins and a 2D array of powers.
Expand All @@ -993,10 +992,13 @@ def spectrogram(self, window_dur=0.005, dyn_range=120, upper_frequency=None, oth
window = scipy.signal.windows.gaussian(window_n_samples, window_sigma)
# convert step size into number of overlapping samples in adjacent analysis frames
n_overlap = window_n_samples - step_n_samples
# compute the power spectral density
freqs, times, power = scipy.signal.spectrogram(
x, mode='psd', fs=self.samplerate, scaling='density', noverlap=n_overlap, window=window,
nperseg=window_n_samples)
# compute the power spectral density, use default configuration if no values were specified
kwargs.setdefault("mode", "psd")
kwargs.setdefault("scaling", "density")
kwargs.setdefault("noverlap", n_overlap)
kwargs.setdefault("window", window)
kwargs.setdefault("nperseg", window_n_samples)
freqs, times, power = scipy.signal.spectrogram(x, fs=self.samplerate, **kwargs)
if show or (axis is not None):
if matplotlib is False:
raise ImportError('Ploting spectrograms requires matplotlib.')
Expand All @@ -1011,14 +1013,14 @@ def spectrogram(self, window_dur=0.005, dyn_range=120, upper_frequency=None, oth
if axis is None:
_, axis = plt.subplots()
axis.imshow(power, origin='lower', aspect='auto',
cmap=cmap, extent=extent, vmin=vmin, vmax=None, **kwargs)
cmap=cmap, extent=extent, vmin=vmin, vmax=None)
axis.set(title='Spectrogram', xlabel='Time [sec]', ylabel='Frequency [Hz]')
if show:
plt.show()
else:
return freqs, times, power

def cochleagram(self, bandwidth=1 / 5, show=True, axis=None, **kwargs):
def cochleagram(self, bandwidth=1 / 5, show=True, axis=None):
"""
Computes a cochleagram of the sound by filtering with a bank of cosine-shaped filters with given bandwidth
and applying a cube-root compression to the resulting envelopes.
Expand All @@ -1028,7 +1030,6 @@ def cochleagram(self, bandwidth=1 / 5, show=True, axis=None, **kwargs):
show (bool): whether to show the plot right after drawing. Note that if show is False and no `axis` is
passed, no plot will be created
axis (matplotlib.axes.Axes | None): axis to plot to. If None create a new plot.
**kwargs: keyword arguments for the plot. See documentation for matplotlib.pyplot.imshow.
Returns:
(None | numpy.ndarray): If `show == True` or an axis was passed, a plot is drawn and nothing is returned.
Else, an array with the envelope is returned.
Expand Down Expand Up @@ -1057,7 +1058,7 @@ def cochleagram(self, bandwidth=1 / 5, show=True, axis=None, **kwargs):
else:
return envs

def spectrum(self, low_cutoff=16, high_cutoff=None, log_power=True, axis=None, show=True, **kwargs):
def spectrum(self, low_cutoff=16, high_cutoff=None, log_power=True, axis=None, show=True):
"""
Compute the spectrum of the sound.
Expand All @@ -1069,7 +1070,6 @@ def spectrum(self, low_cutoff=16, high_cutoff=None, log_power=True, axis=None, s
show (bool): whether to show the plot right after drawing. Note that if show is False and no `axis` is
passed, no plot will be created.
axis (matplotlib.axes.Axes | None): axis to plot to. If None create a new plot.
**kwargs: keyword arguments for the plot. see documentation for matplotlib.pyplot.semilogx.
Returns:
If show=False, returns `Z, freqs`, where `Z` is a 1D array of powers
and `freqs` are the corresponding frequencies.n
Expand Down Expand Up @@ -1100,7 +1100,7 @@ def spectrum(self, low_cutoff=16, high_cutoff=None, log_power=True, axis=None, s
raise ImportError('Plotting spectra requires matplotlib.')
if axis is None:
_, axis = plt.subplots()
axis.semilogx(freqs, Z, **kwargs)
axis.semilogx(freqs, Z)
ticks_freqs = numpy.round(32000 * 2 **
(numpy.arange(12, dtype=float) * -1))
axis.set_xticks(ticks_freqs)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_binaural.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def test_externalize():
filtered = hrtf.data[idx_frontal].apply(sound)
external = sound.externalize()
assert numpy.abs(filtered.data-external.data).sum() < numpy.abs(filtered.data-sound.data).sum()
assert numpy.abs(sound.level - external.level).max() < 0.25
assert numpy.abs(sound.level - external.level).max() < 0.6


def test_interaural_level_spectrum():
Expand Down

0 comments on commit b5b11cf

Please sign in to comment.