def profile_plot(self, bbox=None, start_freq=None, end_freq=None, start_time=None, end_time=None, t = None, ): """ generate plot of all of or bbox of spectrogram self ... Fingerprinter object """ spc = self.spectrum if bbox is None: bbox = Bbox(spc, start_freq, end_freq, start_time, end_time) freq_slice, time_slice = bbox.ix() x2 = spc.db_arr[freq_slice, time_slice] mn, mx = np.percentile(x2,[25,99.9]) Z = np.clip(x2,mn,mx) X,Y = np.meshgrid(spc.times[time_slice], spc.freqs[freq_slice],) # --build plot-- plt.clf() # spectrograph heatmap plt.pcolormesh(X,Y,Z, cmap='gist_heat_r', shading='gouraud') # place dots on detected harmonic peaks if hasattr(self, 'harmonic_intvl') and self.harmonic_intvl is not None: for i in self.harmonics: plt.plot(spc.times, self.harmonic_intvl*i, '.', color='g', alpha=.25) # shade areas of interest for start, stop in self.interest_areas: plt.axvspan(start, stop, facecolor='y', alpha=.5, edgecolor='k') if t is not None: plt.axvline(t, color='b') plt.grid(b=True, which='major',linestyle='-', alpha=.5) plt.xlim(bbox.start_time, bbox.end_time) plt.xlabel('Time (s)') plt.ylabel('Frequency (Hz)') return plt.gcf()
def get_volume_power(self, start_freq=None, end_freq=None, method='avg_pct'): """ get the 1D volume of the sound and normalize it to "background" volume so that "signal" is > 1 and "background" is ~ 1 this is highly dependant on how much of your sound file is composed of signal. Some methods do better than others depending on this ratio """ box = Bbox(self.spectrum, start_freq=start_freq, end_freq=end_freq) freq_slice, time_slice = box.ix() vol = np.sum(self.spectrum.abs_arr[freq_slice, :], axis=0) if method == 'rms': base = np.sqrt(np.average(vol**2)) elif method == 'avg': base = np.average(vol) elif method == 'pct': base = np.percentile(vol, 10) elif method == 'avg_pct': base = np.average(np.sort(vol)[:vol.shape[0] / 10]) elif method == 'absolute': base = 1. else: raise IndexError( '%s is not a valid method to compute volume_power' % (method, )) self.volume_power = vol / base
def peaks_plot( self, t=0., bbox=None, start_time=None, end_time=None, start_freq=None, end_freq=None, ): """ Create a plot of frequency strength in dB (y-axis) vs frequency(x-axis) place found harmonic peaks as dots on the plot a thin window of the spectrograph is shown above the plot giving some context self .. Fingerprinter object """ import matplotlib.gridspec as gridspec if bbox is None: bbox = Bbox(self.spectrum, start_freq, end_freq, start_time, end_time) offset = int(t * self.spectrum.samplerate) freq_slice, time_slice = bbox.ix() db_arr = self.spectrum.db_arr db_arr2 = np.zeros((db_arr.shape[0], db_arr.shape[1] + 20), dtype=float) db_arr2[:, :] = np.average(db_arr) db_arr2[:, 10:-10] = db_arr db_band = db_arr2[freq_slice, offset:offset + 20] mn, mx = np.percentile(db_band, [25, 99.9]) db_band = np.clip(db_band, mn, mx) db_slice = db_arr[freq_slice, offset] harmonics_strength = self.harmonics * self.harmonic_intvl[offset] # --build plot-- plt.clf() gs = gridspec.GridSpec(2, 1, height_ratios=[1, 8], hspace=.1) ax2 = plt.subplot(gs[1]) ax1 = plt.subplot(gs[0], sharex=ax2) ax2.set_ylabel('Signal (dB)') ax2.set_xlabel('Frequency (Hz)') Z = db_band.T X, Y = np.meshgrid(np.linspace(bbox.start_freq, bbox.end_freq, Z.shape[1]), np.linspace(-1., 1., Z.shape[0])) ax1.pcolormesh(X, Y, Z, cmap='gist_heat_r', shading='gouraud') ax1.axhline(0, color='b') ax1.set_yticklabels([]) ax2.plot(self.spectrum.freqs[freq_slice], db_slice) interp_fn = interp1d(self.spectrum.freqs[freq_slice], db_slice, kind='linear', bounds_error=False) values = interp_fn(harmonics_strength) ax2.plot(harmonics_strength, values, linestyle='', marker='o', color='g', alpha=.5) ax2.set_ylim(-75, db_arr.max()) ax2.set_xlim(bbox.start_freq, bbox.end_freq) return plt.gcf()
def get_peaks(self, t, harmonics=None, interval_range=(None, None)): ''' find harmonic peaks in spectrum at given time t .. time value (seconds) to sample spectrum harmonics (1d array) .. harmonics to match interval_range (2-tuple) .. hz returns list of freqencies where harmonic peaks are ''' spc = self.spectrum arr = self.spectrum.timeslice(t) bbox = Bbox(spc, start_freq=interval_range[0], end_freq=interval_range[1]) freq_ix_slice = bbox.ix()[0] mn_freq = bbox.start_freq mx_freq = bbox.end_freq if harmonics is None: harmonics = self.harmonics interp_func = interp1d(spc.freqs, arr, kind='linear', bounds_error=False) def opt_func(x): """ x ... guessed frequency interval returns .. value to minimize """ freq_intvl = float(x) fqs = harmonics * freq_intvl return 1. / np.average(interp_func(fqs)) # there are faster solvers than this, but needed the global solver # TODO? try an annealing solver result = scipy.optimize.brute( opt_func, ranges=(slice(mn_freq, mx_freq, (mx_freq - mn_freq) / 400.), )) freq_intvl = result[0] peak_freqs = harmonics * freq_intvl peak_mags = interp_func(peak_freqs) overall_mag = np.average(arr[freq_ix_slice]) peaks_mag = np.average(peak_mags) harmonic_power = peaks_mag / overall_mag if not interval_range[0] < freq_intvl < interval_range[1] or np.isnan( harmonic_power): freq_intvl = 0. harmonic_power = 0. return peak_freqs, peak_mags, harmonic_power, freq_intvl
def get_peaks(self, t, harmonics=None, interval_range=(None, None)): ''' find harmonic peaks in spectrum at given time t .. time value (seconds) to sample spectrum harmonics (1d array) .. harmonics to match interval_range (2-tuple) .. hz returns list of freqencies where harmonic peaks are ''' spc = self.spectrum arr = self.spectrum.timeslice(t) bbox = Bbox(spc, start_freq=interval_range[0], end_freq=interval_range[1]) freq_ix_slice = bbox.ix()[0] mn_freq = bbox.start_freq mx_freq = bbox.end_freq if harmonics is None: harmonics = self.harmonics interp_func = interp1d(spc.freqs, arr, kind='linear', bounds_error=False) def opt_func(x): """ x ... guessed frequency interval returns .. value to minimize """ freq_intvl = float(x) fqs = harmonics * freq_intvl return 1./np.average(interp_func(fqs)) # there are faster solvers than this, but needed the global solver # TODO? try an annealing solver result = scipy.optimize.brute( opt_func, ranges=(slice(mn_freq,mx_freq,(mx_freq-mn_freq)/400.),) ) freq_intvl = result[0] peak_freqs = harmonics * freq_intvl peak_mags = interp_func(peak_freqs) overall_mag = np.average(arr[freq_ix_slice]) peaks_mag = np.average(peak_mags) harmonic_power = peaks_mag/overall_mag if not interval_range[0]<freq_intvl<interval_range[1] or np.isnan(harmonic_power): freq_intvl = 0. harmonic_power = 0. return peak_freqs, peak_mags, harmonic_power, freq_intvl
def get_harmonic_power2(self, interval_range=(15.,50.), harmonics=None, n_control_pts=10): """ root-find a function (with x control points) that best matches harmonics in a given spectrum. (don't root-find every sample) TODO - in-work """ cntrl_ixs = np.arange(n_control_pts) f0,f1 = interval_range bbox = Bbox(spc, start_freq=f0, end_freq=f1) freq_ix_slice = bbox.ix()[0] mn_freq = bbox.start_freq mx_freq = bbox.end_freq if harmonics is None: harmonics = self.harmonics
def power_plot( self, t=None, bbox=None, start_time=None, end_time=None, start_freq=None, end_freq=None, ): """ self .. Fingerprinter object """ spc = self.spectrum if bbox is None: bbox = Bbox(spc, start_freq, end_freq, start_time, end_time) plt.cla() plt.clf() plt.plot(spc.times, self.harmonic_power, color='orange', label='harmonic power', alpha=.5) plt.plot(spc.times, self.volume_power, color='cyan', label='volume power', alpha=.5) plt.plot(spc.times, self.total_power, color='k', label='power') plt.ylabel('Power') plt.xlabel('Time (s)') #plt.legend(loc=2) ax1 = plt.gca() plt.twinx() plt.xlim(bbox.start_time, bbox.end_time) plt.plot(spc.times, self.harmonic_intvl, marker='o', markersize=3, ls='', color='g', alpha=.5, label='harmonic interval') ax2 = plt.gca() plt.xlim(bbox.start_time, bbox.end_time) plt.ylim(bbox.start_freq, bbox.end_freq) #plt.legend(loc=1) plt.ylabel('Frequency (Hz)') for start, stop in self.interest_areas: plt.axvspan(start, stop, color='y', alpha=.25) if t: plt.axvline(t, color='b') lines1, labels1 = ax1.get_legend_handles_labels() lines2, labels2 = ax2.get_legend_handles_labels() legend = ax2.legend(lines1 + lines2, labels1 + labels2, loc=1) legend.legendPatch._facecolor = (1., 1., 1., .75) return plt.gcf()
def peaks_plot(self, t=0., bbox=None, start_time=None, end_time=None, start_freq=None, end_freq=None, ): """ Create a plot of frequency strength in dB (y-axis) vs frequency(x-axis) place found harmonic peaks as dots on the plot a thin window of the spectrograph is shown above the plot giving some context self .. Fingerprinter object """ import matplotlib.gridspec as gridspec if bbox is None: bbox = Bbox(self.spectrum, start_freq, end_freq, start_time, end_time) offset = int(t*self.spectrum.samplerate) freq_slice, time_slice = bbox.ix() db_arr = self.spectrum.db_arr db_arr2 = np.zeros((db_arr.shape[0],db_arr.shape[1]+20),dtype=float) db_arr2[:,:] = np.average(db_arr) db_arr2[:,10:-10] = db_arr db_band = db_arr2[freq_slice, offset:offset+20] mn, mx = np.percentile(db_band,[25,99.9]) db_band = np.clip(db_band,mn,mx) db_slice = db_arr[freq_slice, offset] harmonics_strength = self.harmonics * self.harmonic_intvl[offset] # --build plot-- plt.clf() gs = gridspec.GridSpec(2, 1, height_ratios=[1,8], hspace=.1) ax2 = plt.subplot(gs[1]) ax1 = plt.subplot(gs[0], sharex=ax2) ax2.set_ylabel('Signal (dB)') ax2.set_xlabel('Frequency (Hz)') Z = db_band.T X,Y = np.meshgrid( np.linspace(bbox.start_freq,bbox.end_freq,Z.shape[1]), np.linspace(-1.,1.,Z.shape[0]) ) ax1.pcolormesh(X,Y,Z, cmap='gist_heat_r', shading='gouraud') ax1.axhline(0, color='b') ax1.set_yticklabels([]) ax2.plot(self.spectrum.freqs[freq_slice], db_slice ) interp_fn = interp1d(self.spectrum.freqs[freq_slice], db_slice, kind='linear', bounds_error=False) values = interp_fn(harmonics_strength) ax2.plot(harmonics_strength, values, linestyle='', marker='o', color='g', alpha=.5) ax2.set_ylim(-75, db_arr.max()) ax2.set_xlim(bbox.start_freq, bbox.end_freq) return plt.gcf()
def profile_plot( self, bbox=None, start_freq=None, end_freq=None, start_time=None, end_time=None, t=None, ): """ generate plot of all of or bbox of spectrogram self ... Fingerprinter object """ spc = self.spectrum if bbox is None: bbox = Bbox(spc, start_freq, end_freq, start_time, end_time) freq_slice, time_slice = bbox.ix() x2 = spc.db_arr[freq_slice, time_slice] mn, mx = np.percentile(x2, [25, 99.9]) Z = np.clip(x2, mn, mx) X, Y = np.meshgrid( spc.times[time_slice], spc.freqs[freq_slice], ) # --build plot-- plt.clf() # spectrograph heatmap plt.pcolormesh(X, Y, Z, cmap='gist_heat_r', shading='gouraud') # place dots on detected harmonic peaks if hasattr(self, 'harmonic_intvl') and self.harmonic_intvl is not None: for i in self.harmonics: plt.plot(spc.times, self.harmonic_intvl * i, '.', color='g', alpha=.25) # shade areas of interest for start, stop in self.interest_areas: plt.axvspan(start, stop, facecolor='y', alpha=.5, edgecolor='k') if t is not None: plt.axvline(t, color='b') plt.grid(b=True, which='major', linestyle='-', alpha=.5) plt.xlim(bbox.start_time, bbox.end_time) plt.xlabel('Time (s)') plt.ylabel('Frequency (Hz)') return plt.gcf()
def get_harmonic_power2(self, interval_range=(15., 50.), harmonics=None, n_control_pts=10): """ root-find a function (with x control points) that best matches harmonics in a given spectrum. (don't root-find every sample) TODO - in-work """ cntrl_ixs = np.arange(n_control_pts) f0, f1 = interval_range bbox = Bbox(spc, start_freq=f0, end_freq=f1) freq_ix_slice = bbox.ix()[0] mn_freq = bbox.start_freq mx_freq = bbox.end_freq if harmonics is None: harmonics = self.harmonics
def get_harmonic_power3(self, interval_range=(15., 100.), harmonics=None): """ Instead of using root-finding methods, do a brute sweep on a numpy array """ if harmonics is None: harmonics = self.harmonics spc = self.spectrum a = spc.abs_arr bbox = Bbox(spc, start_freq=interval_range[0], end_freq=interval_range[1]) freq_ix_slice = bbox.ix()[0] strt, stop = interval_range intvls = np.linspace(strt, stop, 1000) X, Y = np.meshgrid(intvls, harmonics) b = X * Y arr = np.arange(len(spc.freqs)) freq2ix = interp1d(spc.freqs, arr, kind='linear', bounds_error=False) ix = freq2ix(b).astype(int) z = a[ix].sum(0) win = hann(7) z = convolve1d(z, win, axis=1) x = np.argmax(z, 0) h = intvls[x] peaks_ix = np.outer(harmonics, freq2ix(h)) peaks_mag = np.zeros_like(peaks_ix) peaks_ix = peaks_ix.astype(int) for i in range(peaks_ix.shape[1]): ix = peaks_ix[:, i] peaks_mag[:, i] = a[ix, i] peaks_avg = np.average(peaks_mag, 0) overall_avg = np.average(a[0:stop * harmonics[-1], :], axis=0) h_power = peaks_avg / overall_avg self.fingerprint = peaks_mag self.harmonic_intvl = h self.harmonic_power = h_power
def get_harmonic_power3(self, interval_range=(15.,100.), harmonics=None): """ Instead of using root-finding methods, do a brute sweep on a numpy array """ if harmonics is None: harmonics=self.harmonics spc = self.spectrum a = spc.abs_arr bbox = Bbox(spc, start_freq=interval_range[0], end_freq=interval_range[1]) freq_ix_slice = bbox.ix()[0] strt, stop = interval_range intvls = np.linspace(strt,stop,1000) X, Y = np.meshgrid(intvls, harmonics) b = X * Y arr = np.arange(len(spc.freqs)) freq2ix = interp1d(spc.freqs, arr, kind='linear', bounds_error=False) ix = freq2ix(b).astype(int) z = a[ix].sum(0) win = hann(7) z = convolve1d(z,win, axis=1) x = np.argmax(z,0) h = intvls[x] peaks_ix = np.outer(harmonics, freq2ix(h)) peaks_mag = np.zeros_like(peaks_ix) peaks_ix = peaks_ix.astype(int) for i in range(peaks_ix.shape[1]): ix = peaks_ix[:,i] peaks_mag[:,i] = a[ix,i] peaks_avg = np.average(peaks_mag, 0) overall_avg = np.average(a[0:stop*harmonics[-1],:], axis=0) h_power = peaks_avg/overall_avg self.fingerprint = peaks_mag self.harmonic_intvl = h self.harmonic_power = h_power
def get_volume_power(self, start_freq=None, end_freq=None, method='avg_pct'): """ get the 1D volume of the sound and normalize it to "background" volume so that "signal" is > 1 and "background" is ~ 1 this is highly dependant on how much of your sound file is composed of signal. Some methods do better than others depending on this ratio """ box = Bbox(self.spectrum, start_freq=start_freq, end_freq=end_freq) freq_slice, time_slice = box.ix() vol = np.sum(self.spectrum.abs_arr[freq_slice,:], axis=0) if method == 'rms': base = np.sqrt(np.average(vol**2)) elif method == 'avg': base = np.average(vol) elif method == 'pct': base = np.percentile(vol, 10) elif method == 'avg_pct': base = np.average(np.sort(vol)[:vol.shape[0]/10]) elif method == 'absolute': base = 1. else: raise IndexError('%s is not a valid method to compute volume_power'%(method,)) self.volume_power = vol/base
def make_video(profile, name='test', bbox=None, plot_type='profile_plot'): """ plot_type .. 'power_plot', 'profile_plot', 'peaks_plot' """ if bbox is None: bbox = Bbox(profile.spectrum, end_freq=500.) registry = ['power_plot', 'profile_plot', 'peaks_plot'] if plot_type == 'all': plot_type = registry if type(plot_type) is not str: for ptype in plot_type: make_video(profile, name, bbox, ptype) return import moviepy.editor as mpy # deferred import in case moviepy is not installed from moviepy.video.io.bindings import mplfig_to_npimage sound = profile.spectrum.sound # normalize in case very quiet sound_array = sound.data / (2 * np.abs(sound.data).max()) def video_fn(t): """make one frame of video""" t += bbox.start_time fn = getattr(profile, plot_type) fig = fn(t=t, bbox=bbox) return mplfig_to_npimage(fig) def audio_fn(t): """make one "frame" of audio""" t += bbox.start_time if type(t) is int: i = t * sound.samplerate elif type(t) is float: i = int(t * sound.samplerate) else: i = (t * sound.samplerate).astype(int) return sound_array[i] duration = bbox.end_time - bbox.start_time video_clip = mpy.VideoClip(video_fn, duration=duration) audio_clip = mpy.AudioClip(audio_fn, duration=duration) animation = video_clip.set_audio(audio_clip) animation.to_videofile(name + '_' + plot_type + '.avi', codec='libx264', fps=24) # codec='mpeg4'