Bird Song Analysis

This package is a port of the Sound Analysis Toolbox for Matlab (SAT) in Python 3. It provides function to measure features of Zebra Finches songs, and to compare their similarities.

It does not give the exact same results as SAT, but gives qualitatively similar results. The variations in the results are due to the fact I removed some hackish code that was hard to port in Python.

Imports


In [1]:
%matplotlib inline

In [2]:
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.io import wavfile
from scipy.io import matlab

import birdsonganalysis as bsa

In [3]:
sns.set_palette('muted')

In [4]:
songname = 'simple'

First we load a wavfile, the output is a simple 1D array


In [5]:
sr, sig = wavfile.read('../songs/{}.wav'.format(songname))

Now, we load the mat file with the data computed with SAT for comparison


In [6]:
sat = matlab.loadmat('../birdsonganalysis/tests/{}.mat'.format(songname),
                                  squeeze_me=True, struct_as_record=False)[songname]

Spectral derivatives

We compute the spectral derivatives of the song. It is like a sonogram but we plot the slope of the power and not the power itself. It makes the sound features more visible. It uses multitaper spectral analysis.


In [7]:
spec_der = bsa.spectral_derivs(sig)

In [8]:
contrast = 0.02  # A smaller contrast saturates more but makes visible less saillant changes in the song spectrogram

fig = plt.figure(figsize=(16, 5))
ax = fig.gca()
ax = bsa.spectral_derivs_plot(spec_der, contrast, ax)  # Draws the spectral derivatives on the `ax`
plt.show(fig)


Song Features

Frequency Modulation

Frequency modulation is the absolute slope of the frequency along time. see Frequency Modulation on SAP2011.

The blue curve are the values computed by birdsonganalysis. The green curve are the values computed by SAT. The values are normalized before being drawn.


In [9]:
fig = plt.figure(figsize=(16, 5))
ax = fig.gca()
ax = bsa.spectral_derivs_plot(spec_der, contrast, ax) 
fm = bsa.song_frequency_modulation(sig)
ax = bsa.plot_over_spec(fm, ax, label='bsa')
ax = bsa.plot_over_spec(sat.FM, ax, label='sat', alpha=0.8, ls='--')
ax.legend(frameon=True)
plt.show(fig)

print('bsa: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(fm), np.max(fm), np.median(fm)))
print('SAT: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(sat.FM), np.max(sat.FM), np.median(sat.FM)))


bsa: min: -0.00, max: 1.50, med: 0.64
SAT: min: 0.00, max: 1.49, med: 0.63

Amplitude


In [10]:
fig = plt.figure(figsize=(16, 5))
ax = fig.gca()
ax = bsa.spectral_derivs_plot(spec_der, 0.01, ax)
amp = bsa.song_amplitude(sig)
amp[amp < 0] = 0
ax = bsa.plot_over_spec(amp, ax, label='bsa')
sat_amp = np.copy(sat.amplitude)
sat_amp[sat_amp < 0] = 0
ax = bsa.plot_over_spec(sat_amp, ax, label='sat', alpha=0.8, ls='--')
ax.legend(frameon=True)
plt.show(fig)

print('bsa: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(amp), np.max(amp), np.mean(amp)))
print('SAT: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(sat_amp), np.max(sat_amp), np.median(sat_amp)))


bsa: min: 0.00, max: 95.77, med: 72.83
SAT: min: 0.00, max: 98.23, med: 79.66

Pitch

Pitch is computed with the Yin Algorithm, both in SAT and birdsonganalysis.


In [11]:
fig = plt.figure(figsize=(16, 5))
ax = fig.gca()
ax = bsa.spectral_derivs_plot(spec_der, 0.01, ax)
pitches = bsa.song_pitch(sig, sr, threshold=0.6)
pitches[pitches > 44100] = 0
ax = bsa.plot_over_spec(pitches, ax, label='bsa')
ax = bsa.plot_over_spec(sat.pitch, ax,  label='sat', alpha=0.8, ls='--')
ax.legend(frameon=True)
plt.show(fig)

print('bsa: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(pitches), np.max(pitches), np.median(pitches)))
print('SAT: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(sat.pitch), np.max(sat.pitch), np.median(sat.pitch)))


bsa: min: 0.00, max: 5570.58, med: 724.10
SAT: min: 0.00, max: 6440.10, med: 4022.37

Wiener Entropy


In [12]:
fig = plt.figure(figsize=(16, 5))
ax = fig.gca()
ax = bsa.spectral_derivs_plot(spec_der, contrast, ax) 
wiener = bsa.song_wiener_entropy(sig)
ax = bsa.plot_over_spec(wiener, ax, label='bsa')
ax = bsa.plot_over_spec(sat.entropy, ax,  label='sat', alpha=0.8, ls='--')
ax.legend(frameon=True)

plt.show(fig)
print('bsa: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(wiener), np.max(wiener), np.median(wiener)))
print('SAT: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(sat.entropy),
                                                          np.max(sat.entropy), np.median(sat.entropy)))


bsa: min: -8.12, max: 0.00, med: -2.90
SAT: min: -6.43, max: 0.00, med: -2.39

Amplitude Modulation


In [13]:
fig = plt.figure(figsize=(16, 5))
ax = fig.gca()
ax = bsa.spectral_derivs_plot(spec_der, contrast, ax) 
am = bsa.song_amplitude_modulation(sig)
ax = bsa.plot_over_spec(am, ax, label='bsa')
ax = bsa.plot_over_spec(sat.AM, ax,  label='sat', alpha=0.8, ls='--')
ax.legend(frameon=True)

plt.show(fig)
print('bsa: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(am), np.max(am), np.median(am)))
print('SAT: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(sat.AM), np.max(sat.AM), np.median(sat.AM)))


bsa: min: -56.78, max: 64.63, med: -0.00
SAT: min: -25.71, max: 46.52, med: -0.00

Goodness


In [14]:
fig = plt.figure(figsize=(16, 5))
ax = fig.gca()
ax = bsa.spectral_derivs_plot(spec_der, contrast, ax) 
good = bsa.song_goodness(sig)
ax = bsa.plot_over_spec(good, ax, label='bsa')
ax = bsa.plot_over_spec(sat.goodness, ax,  label='sat', alpha=0.8, ls='--')
ax.legend(frameon=True)

plt.show(fig)
print('bsa: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(good), np.max(good), np.median(good)))
print('SAT: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(sat.goodness), np.max(sat.goodness),
                                                          np.median(sat.goodness)))


bsa: min: 0.00, max: 0.55, med: 0.12
SAT: min: 0.00, max: 0.63, med: 0.13

Ignoring silence


In [15]:
fig = plt.figure(figsize=(16, 5))
ax = fig.gca()
ax = bsa.spectral_derivs_plot(spec_der, contrast, ax) 
amp = bsa.song_amplitude(sig)
amp[amp < np.percentile(amp, 15)] = 0
ax = bsa.plot_over_spec(amp, ax, label='bsa')



In [17]:



---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-17-a53402ecc05e> in <module>()
----> 1 sat['FM']

TypeError: 'mat_struct' object is not subscriptable

In [24]:
sfeat =  bsa.all_song_features(sig, 44100, freq_range=256, fft_size=1024, fft_step=40)

In [25]:
mfeat = {'am': sat.AM, 'amplitude': sat.amplitude, 'entropy': sat.entropy, 'fm': sat.FM, 'goodness': sat.goodness, 'pitch': sat.pitch}

In [27]:
for key in sfeat:
    plt.figure(figsize=(14, 4))
    plt.plot(sfeat[key])
    plt.plot(mfeat[key], alpha=0.4)
    plt.title(key)



In [ ]: