44import logging
55
66import numpy as np
7- from scipy import signal
7+ import scipy .signal
8+ import scipy .ndimage
89from scipy .io import wavfile
910
11+
1012from neurodsp .utils import WindowGenerator
1113from neurodsp import fourier
1214import ibllib .io .raw_data_loaders as ioraw
1315from ibllib .io .extractors .training_trials import GoCueTimes
1416
17+
1518logger_ = logging .getLogger ('ibllib' )
1619
1720NS_WIN = 2 ** 18 # 2 ** np.ceil(np.log2(1 * fs))
2225READY_TONE_SPL = 85
2326
2427
25- def _running_mean (x , N ):
26- cumsum = np .cumsum (np .insert (x , 0 , 0 ))
27- return (cumsum [N :] - cumsum [:- N ]) / N
28-
29-
30- def _detect_ready_tone (w , fs ):
28+ def detect_ready_tone (w , fs , ftone = FTONE , threshold = 0.8 ):
29+ """
30+ Detects a transient sinusoid signal in a time-serie
31+ :param w: audio time seried
32+ :param fs: sampling frequency (Hz)
33+ :param ftone: frequency of the tone to detect
34+ :param threshold: ratio of the Hilbert to the signal, between 0 and 1 (set to 0.8)
35+ :return:
36+ """
3137 # get envelope of DC free signal and envelope of BP signal around freq of interest
32- h = np .abs (signal .hilbert (w - np .median (w )))
33- fh = np .abs (signal .hilbert (fourier .bp (w , si = 1 / fs , b = FTONE * np .array ([0.9 , 0.95 , 1.15 , 1.1 ]))))
34- dtect = _running_mean (fh / (h + 1e-3 ), int (fs * 0.1 )) > 0.8
38+ h = np .abs (scipy . signal .hilbert (w - np .median (w )))
39+ fh = np .abs (scipy . signal .hilbert (fourier .bp (w , si = 1 / fs , b = ftone * np .array ([0.9 , 0.95 , 1.15 , 1.1 ]))))
40+ dtect = scipy . ndimage . uniform_filter1d (fh / (h + 1e-3 ), int (fs * 0.1 )) > threshold
3541 return np .where (np .diff (dtect .astype (int )) == 1 )[0 ]
3642 # tone = np.sin(2 * np.pi * FTONE * np.arange(0, fs * 0.1) / fs)
3743 # tone = tone / np.sum(tone ** 2)
@@ -56,7 +62,7 @@ def _get_conversion_factor(unit=UNIT, ready_tone_spl=READY_TONE_SPL):
5662 return fac
5763
5864
59- def welchogram (fs , wav , nswin = NS_WIN , overlap = OVERLAP , nperseg = NS_WELCH ):
65+ def welchogram (fs , wav , nswin = NS_WIN , overlap = OVERLAP , nperseg = NS_WELCH , detect_kwargs = None ):
6066 """
6167 Computes a spectrogram on a very large audio file.
6268
@@ -65,6 +71,7 @@ def welchogram(fs, wav, nswin=NS_WIN, overlap=OVERLAP, nperseg=NS_WELCH):
6571 :param nswin: n samples of the sliding window
6672 :param overlap: n samples of the overlap between windows
6773 :param nperseg: n samples for the computation of the spectrogram
74+ :param detect_kwargs: specified paramaters for detection
6875 :return: tscale, fscale, downsampled_spectrogram
6976 """
7077 ns = wav .shape [0 ]
@@ -78,16 +85,17 @@ def welchogram(fs, wav, nswin=NS_WIN, overlap=OVERLAP, nperseg=NS_WELCH):
7885 # load the current window into memory
7986 w = np .float64 (wav [first :last ]) * _get_conversion_factor ()
8087 # detection of ready tones
81- a = [d + first for d in _detect_ready_tone (w , fs )]
88+ detect_kwargs = detect_kwargs or {}
89+ a = [d + first for d in detect_ready_tone (w , fs , ** detect_kwargs )]
8290 if len (a ):
8391 detect += a
8492 # the last window may not allow a pwelch
8593 if (last - first ) < nperseg :
8694 continue
8795 # compute PSD estimate for the current window
8896 iw = window_generator .iw
89- _ , W [iw , :] = signal .welch (w , fs = fs , window = 'hann' , nperseg = nperseg , axis = - 1 ,
90- detrend = 'constant' , return_onesided = True , scaling = 'density' )
97+ _ , W [iw , :] = scipy . signal .welch (w , fs = fs , window = 'hann' , nperseg = nperseg , axis = - 1 ,
98+ detrend = 'constant' , return_onesided = True , scaling = 'density' )
9199 # the onset detection may have duplicates with sliding window, average them and remove
92100 detect = np .sort (np .array (detect )) / fs
93101 ind = np .where (np .diff (detect ) < 0.1 )[0 ]
0 commit comments