├── README.md ├── TimeSeriesDenoising ├── codeChallenge.m ├── denoising_codeChallenge.mat ├── emg4TKEO.mat ├── eyedat.mat ├── sigprocMXC_GauSmoothSpikes.m ├── sigprocMXC_Gaussian_smooth.m ├── sigprocMXC_TKEO.m ├── sigprocMXC_averaging.m ├── sigprocMXC_detrend.m ├── sigprocMXC_mean_smooth.m ├── sigprocMXC_median_filter.m ├── sigprocMXC_polynomialDetrend.m ├── sigprocMXC_template_projection.m ├── sigprocMXC_timeSeriesDenoising.ipynb └── templateProjection.mat ├── complex ├── signprocMXC_complexNumbers.ipynb ├── sigprocMXC_complexAddSub.m ├── sigprocMXC_complexConj.m ├── sigprocMXC_complexDivision.m ├── sigprocMXC_complexIntro.m ├── sigprocMXC_complexMult.m └── sigprocMXC_complexPolar.m ├── convolution ├── conv_codeChallenge.m ├── convtheremfigure.m ├── sigprocMXC_FreqDomainGaus.m ├── sigprocMXC_TimeDomainGaus.m ├── sigprocMXC_convolution.ipynb ├── sigprocMXC_convolutionTheorem.m ├── sigprocMXC_planckBandPass.m └── sigprocMXC_timeConvolution.m ├── featdet ├── EMGRT.mat ├── get_emg_rt_data.m ├── sigprocMXC_AUC.m ├── sigprocMXC_EMGonsets.m ├── sigprocMXC_FWHM.m ├── sigprocMXC_featuredetection.ipynb ├── sigprocMXC_localMinMax.m ├── sigprocMXC_signalFromNoise.m └── sigprocMXC_waveletFeatureEx.m ├── filtering ├── XC403881.mp3 ├── XC403881.wav ├── filtering_codeChallenge.mat ├── lineNoiseData.mat ├── sigprocMXC_2stageWide.m ├── sigprocMXC_butter.m ├── sigprocMXC_causal0phase.m ├── sigprocMXC_filterTheBirds.m ├── sigprocMXC_filtering_part1.ipynb ├── sigprocMXC_filtering_part2.ipynb ├── sigprocMXC_fir1.m ├── sigprocMXC_firls.m ├── sigprocMXC_highpass.m ├── sigprocMXC_linenoise.m ├── sigprocMXC_lowpass.m ├── sigprocMXC_narrowband.m ├── sigprocMXC_reflection.m ├── sigprocMXC_rolloff.m ├── sigprocMXC_signalLength.m ├── sigprocMXC_windowSinc.m └── sigproc_Filtering.m ├── outliers ├── codeChallenge.m ├── forex.mat ├── sigprocMXC_RMSoutlierWindows.m ├── sigprocMXC_StandardDevThresh.m ├── sigprocMXC_localThreshold.m └── sigprocMXC_outliers.ipynb ├── quickwin ├── GlassDance.mp3 ├── glassDance.mat ├── sigprocMXC_filterGlass.ipynb ├── sigprocMXC_filterGlass.m └── sigproc_mp3.m ├── resample ├── resample_codeChallenge.m ├── resample_codeChallenge.mat ├── sigprocMXC_downsample.m ├── sigprocMXC_dtw.m ├── sigprocMXC_extrap.m ├── sigprocMXC_interp.m ├── sigprocMXC_irregular.m ├── sigprocMXC_multirate.m ├── sigprocMXC_resample.ipynb ├── sigprocMXC_spectralInterp.m └── sigprocMXC_upsample.m ├── spectral ├── EEGrestingState.mat ├── XC403881.mp3 ├── XC403881.wav ├── sigprocMXC_FourierTransform.m ├── sigprocMXC_SpectBirdcall.m ├── sigprocMXC_Welch.m ├── sigprocMXC_spectral.ipynb ├── spectral_codeChallenge.mat └── spectral_code_challenge.m ├── variability ├── SNRdata.mat ├── codechallenge.m ├── mutualinformationx.m ├── sigprocMXC_CV.m ├── sigprocMXC_SNR.m ├── sigprocMXC_entropy.m ├── sigprocMXC_variability.ipynb ├── sigprocMXC_windowedVar.m └── v1_laminar.mat └── wavelet ├── Solomon_Time_History.txt ├── data4TF.mat ├── sigprocMXC_timefreq.m ├── sigprocMXC_timefreqBrain.m ├── sigprocMXC_wavelet.ipynb ├── sigprocMXC_waveletConv.m ├── sigprocMXC_waveletTF.m ├── sigprocMXC_wavelets.m ├── sigprocMXC_wavelets4narrowband.m ├── wavelet_codeChallenge.m └── wavelet_codeChallenge.mat /README.md: -------------------------------------------------------------------------------- 1 | # Signal processing problems, solved in MATLAB and Python 2 | MATLAB and Python code accompanying the course "Signal processing problems, solved in MATLAB and Python" 3 | 4 | 12+ hours of instruction about applied signal-processing methods, including filtering, denoising, convolution, resampling, interpolation, and outlier detection. 5 | 6 | See (https://www.udemy.com/course/signal-processing/?couponCode=202305) for more details, preview videos, and to enroll in the full course. 7 | -------------------------------------------------------------------------------- /TimeSeriesDenoising/codeChallenge.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing and image processing in MATLAB and Python 3 | % SECTION: Time-domain denoising 4 | % VIDEO: Code challenge: Denoise these signals! 5 | % Instructor: mikexcohen.com 6 | % 7 | %% 8 | 9 | N = 4000; 10 | 11 | origSignal = linspace(-1,1,N) .* sin(linspace(0,10*pi,N)) + randn(1,N); 12 | r = randperm(N); 13 | nn = round(N*.05); 14 | origSignal(r(1:nn)) = (1+rand(1,nn))*10; 15 | origSignal(r(end-nn+1:end)) = -(1+rand(1,nn))*10; 16 | 17 | cleanedSignal = origSignal; 18 | 19 | % remove positive noise spikes 20 | p2r = find(origSignal>5); 21 | k = 5; 22 | for i=1:length(p2r) 23 | cleanedSignal(p2r(i)) = median(cleanedSignal(max(1,p2r(i)-k):min(p2r(i)+k,N))); 24 | end 25 | 26 | % remove negative noise spikes 27 | p2r = find(origSignal<-5); 28 | k = 5; 29 | for i=1:length(p2r) 30 | cleanedSignal(p2r(i)) = median(cleanedSignal(max(1,p2r(i)-k):min(p2r(i)+k,N))); 31 | end 32 | 33 | % mean-smooth 34 | k = 150; 35 | for i=1:N 36 | cleanedSignal(i) = mean(cleanedSignal(max(1,i-k):min(N,i+k))); 37 | end 38 | 39 | 40 | clf 41 | subplot(211) 42 | plot(1:N,origSignal,'linew',2) 43 | 44 | subplot(212) 45 | plot(1:N,cleanedSignal,'linew',2) 46 | 47 | %% done. 48 | -------------------------------------------------------------------------------- /TimeSeriesDenoising/denoising_codeChallenge.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/TimeSeriesDenoising/denoising_codeChallenge.mat -------------------------------------------------------------------------------- /TimeSeriesDenoising/emg4TKEO.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/TimeSeriesDenoising/emg4TKEO.mat -------------------------------------------------------------------------------- /TimeSeriesDenoising/eyedat.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/TimeSeriesDenoising/eyedat.mat -------------------------------------------------------------------------------- /TimeSeriesDenoising/sigprocMXC_GauSmoothSpikes.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Time-domain denoising 4 | % VIDEO: Gaussian-smooth a spike time series 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% generate time series of random spikes 10 | 11 | % number of spikes 12 | n = 300; 13 | 14 | % inter-spike intervals (exponential distribution for bursts) 15 | isi = round(exp( randn(n,1) )*10); 16 | 17 | % generate spike time series 18 | spikets = 0; 19 | for i=1:n 20 | spikets(sum(isi(1:i))) = 1; 21 | end 22 | 23 | 24 | % plot 25 | figure(1), clf, hold on 26 | 27 | h = plot(spikets); 28 | set(gca,'ylim',[0 1.01],'xlim',[0 length(spikets)+1]) 29 | set(h,'color',[1 1 1]*.7) 30 | xlabel('Time (a.u.)') 31 | 32 | %% create and implement Gaussian window 33 | 34 | % full-width half-maximum: the key Gaussian parameter 35 | fwhm = 25; % in points 36 | 37 | % normalized time vector in ms 38 | k = 100; 39 | gtime = -k:k; 40 | 41 | % create Gaussian window 42 | gauswin = exp( -(4*log(2)*gtime.^2) / fwhm^2 ); 43 | gauswin = gauswin / sum(gauswin); 44 | 45 | % initialize filtered signal vector 46 | filtsigG = zeros(size(spikets)); 47 | 48 | % implement the weighted running mean filter 49 | for i=k+1:length(spikets)-k-1 50 | filtsigG(i) = sum( spikets(i-k:i+k).*gauswin ); 51 | end 52 | 53 | 54 | % plot the filtered signal (spike probability density) 55 | plot(filtsigG,'r','linew',2) 56 | legend({'Spikes','Spike p.d.'}) 57 | title('Spikes and spike probability density') 58 | zoom on 59 | 60 | %% done. 61 | -------------------------------------------------------------------------------- /TimeSeriesDenoising/sigprocMXC_Gaussian_smooth.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Time-domain denoising 4 | % VIDEO: Gaussian-smooth a time series 5 | % Instructor: mikexcohen.com 6 | % 7 | %% 8 | 9 | %% create the signal 10 | 11 | % create signal 12 | srate = 1000; % Hz 13 | time = 0:1/srate:3; 14 | n = length(time); 15 | p = 15; % poles for random interpolation 16 | 17 | % noise level, measured in standard deviations 18 | noiseamp = 5; 19 | 20 | % amplitude modulator and noise level 21 | ampl = interp1(rand(p,1)*30,linspace(1,p,n)); 22 | noise = noiseamp * randn(size(time)); 23 | signal = ampl + noise; 24 | 25 | %% create Gaussian kernel 26 | 27 | % full-width half-maximum: the key Gaussian parameter 28 | fwhm = 25; % in ms 29 | 30 | % normalized time vector in ms 31 | k = 100; 32 | gtime = 1000*(-k:k)/srate; 33 | 34 | % create Gaussian window 35 | gauswin = exp( -(4*log(2)*gtime.^2) / fwhm^2 ); 36 | 37 | % compute empirical FWHM 38 | pstPeakHalf = k+dsearchn(gauswin(k+1:end)',.5); 39 | prePeakHalf = dsearchn(gauswin(1:k)',.5); 40 | 41 | empFWHM = gtime(pstPeakHalf) - gtime(prePeakHalf); 42 | 43 | % show the Gaussian 44 | figure(1), clf, hold on 45 | plot(gtime,gauswin,'ko-','markerfacecolor','w','linew',2) 46 | plot(gtime([prePeakHalf pstPeakHalf]),gauswin([prePeakHalf pstPeakHalf]),'m','linew',3) 47 | 48 | % then normalize Gaussian to unit energy 49 | gauswin = gauswin / sum(gauswin); 50 | title([ 'Gaussian kernel with requeted FWHM ' num2str(fwhm) ' ms (' num2str(empFWHM) ' ms achieved)' ]) 51 | xlabel('Time (ms)'), ylabel('Gain') 52 | 53 | %% implement the filter 54 | 55 | % initialize filtered signal vector 56 | filtsigG = signal; 57 | 58 | % implement the running mean filter 59 | for i=k+1:n-k-1 60 | % each point is the weighted average of k surrounding points 61 | filtsigG(i) = sum( signal(i-k:i+k).*gauswin ); 62 | end 63 | 64 | % plot 65 | figure(2), clf, hold on 66 | plot(time,signal,'r') 67 | plot(time,filtsigG,'k','linew',3) 68 | 69 | xlabel('Time (s)'), ylabel('amp. (a.u.)') 70 | legend({'Original signal';'Gaussian-filtered'}) 71 | title('Gaussian smoothing filter') 72 | 73 | %% for comparison, plot mean smoothing filter 74 | 75 | % initialize filtered signal vector 76 | filtsigMean = zeros(size(signal)); 77 | 78 | % implement the running mean filter 79 | k = 20; % filter window is actually k*2+1 80 | for i=k+1:n-k-1 81 | % each point is the average of k surrounding points 82 | filtsigMean(i) = mean(signal(i-k:i+k)); 83 | end 84 | 85 | plot(time,filtsigMean,'b','linew',2) 86 | legend({'Original signal';'Gaussian-filtered';'Running mean'}) 87 | zoom on 88 | 89 | %% done. 90 | -------------------------------------------------------------------------------- /TimeSeriesDenoising/sigprocMXC_TKEO.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Time-domain denoising 4 | % VIDEO: Denoising via TKEO 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % import data 10 | load emg4TKEO.mat 11 | 12 | % initialize filtered signal 13 | emgf = emg; 14 | 15 | % the loop version for interpretability 16 | for i=2:length(emgf)-1 17 | emgf(i) = emg(i)^2 - emg(i-1)*emg(i+1); 18 | end 19 | 20 | % the vectorized version for speed and elegance 21 | emgf2 = emg; 22 | emgf2(2:end-1) = emg(2:end-1).^2 - emg(1:end-2).*emg(3:end); 23 | 24 | %% convert both signals to zscore 25 | 26 | % find timepoint zero 27 | time0 = dsearchn(emgtime',0); 28 | 29 | % convert original EMG to z-score from time-zero 30 | emgZ = (emg-mean(emg(1:time0))) / std(emg(1:time0)); 31 | 32 | % same for filtered EMG energy 33 | emgZf = (emgf-mean(emgf(1:time0))) / std(emgf(1:time0)); 34 | 35 | %% plot 36 | 37 | figure(1), clf 38 | 39 | % plot "raw" (normalized to max-1) 40 | subplot(211), hold on 41 | plot(emgtime,emg./max(emg),'b','linew',2) 42 | plot(emgtime,emgf./max(emgf),'m','linew',2) 43 | 44 | xlabel('Time (ms)'), ylabel('Amplitude or energy') 45 | legend({'EMG';'EMG energy (TKEO)'}) 46 | 47 | 48 | % plot zscored 49 | subplot(212), hold on 50 | plot(emgtime,emgZ,'b','linew',2) 51 | plot(emgtime,emgZf,'m','linew',2) 52 | 53 | xlabel('Time (ms)'), ylabel('Zscore relative to pre-stimulus') 54 | legend({'EMG';'EMG energy (TKEO)'}) 55 | 56 | %% done. 57 | -------------------------------------------------------------------------------- /TimeSeriesDenoising/sigprocMXC_averaging.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Time-domain denoising 4 | % VIDEO: Averaging multiple repetitions (time-synchronous averaging) 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% simulate data 10 | 11 | % create event (derivative of Gaussian) 12 | k = 100; % duration of event in time points 13 | event = diff(exp( -linspace(-2,2,k+1).^2 )); 14 | event = event./max(event); % normalize to max=1 15 | 16 | % event onset times 17 | Nevents = 30; 18 | onsettimes = randperm(10000-k); 19 | onsettimes = onsettimes(1:Nevents); 20 | 21 | % put event into data 22 | data = zeros(1,10000); 23 | for ei=1:Nevents 24 | data(onsettimes(ei):onsettimes(ei)+k-1) = event; 25 | end 26 | 27 | % add noise 28 | data = data + .5*randn(size(data)); 29 | 30 | % plot data 31 | figure(1), clf 32 | subplot(211) 33 | plot(data) 34 | 35 | % plot one event 36 | subplot(212) 37 | plot(1:k, data(onsettimes(3):onsettimes(3)+k-1),... 38 | 1:k, event,'linew',3) 39 | 40 | %% extract all events into a matrix 41 | 42 | datamatrix = zeros(Nevents,k); 43 | 44 | for ei=1:Nevents 45 | datamatrix(ei,:) = data(onsettimes(ei):onsettimes(ei)+k-1); 46 | end 47 | 48 | figure(2), clf 49 | subplot(4,1,1:3) 50 | imagesc(datamatrix) 51 | xlabel('Time'), ylabel('Event number') 52 | title('All events') 53 | 54 | subplot(414) 55 | plot(1:k,mean(datamatrix), 1:k,event,'linew',3) 56 | xlabel('Time'), ylabel('Amplitude') 57 | legend({'Averaged';'Ground-truth'}) 58 | title('Average events') 59 | 60 | %% done. 61 | -------------------------------------------------------------------------------- /TimeSeriesDenoising/sigprocMXC_detrend.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Time-domain denoising 4 | % VIDEO: Remove linear trend 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % create signal with linear trend imposed 10 | n = 2000; 11 | signal = cumsum(randn(1,n)) + linspace(-30,30,n); 12 | 13 | % linear detrending 14 | detsignal = detrend(signal); 15 | 16 | % plot signal and detrended signal 17 | figure(1), clf 18 | plot(1:n,signal, 1:n,detsignal,'linew',3) 19 | legend({ ['Original (mean=' num2str(mean(signal)) ')' ];[ 'Detrended (mean=' num2str(mean(detsignal)) ')' ]}) 20 | 21 | %% done. 22 | -------------------------------------------------------------------------------- /TimeSeriesDenoising/sigprocMXC_mean_smooth.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Time-domain denoising 4 | % VIDEO: Mean-smooth a time series 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % create signal 10 | srate = 1000; % Hz 11 | time = 0:1/srate:3; 12 | n = length(time); 13 | p = 15; % poles for random interpolation 14 | 15 | % noise level, measured in standard deviations 16 | noiseamp = 5; 17 | 18 | % amplitude modulator and noise level 19 | ampl = interp1(rand(p,1)*30,linspace(1,p,n)); 20 | noise = noiseamp * randn(size(time)); 21 | signal = ampl + noise; 22 | 23 | % initialize filtered signal vector 24 | filtsig = zeros(size(signal)); 25 | 26 | % implement the running mean filter 27 | k = 20; % filter window is actually k*2+1 28 | for i=k+1:n-k-1 29 | % each point is the average of k surrounding points 30 | filtsig(i) = mean(signal(i-k:i+k)); 31 | end 32 | 33 | % compute window size in ms 34 | windowsize = 1000*(k*2+1) / srate; 35 | 36 | 37 | % plot the noisy and filtered signals 38 | figure(1), clf, hold on 39 | plot(time,signal, time,filtsig, 'linew',2) 40 | 41 | % draw a patch to indicate the window size 42 | tidx = dsearchn(time',1); 43 | ylim = get(gca,'ylim'); 44 | patch(time([ tidx-k tidx-k tidx+k tidx+k ]),ylim([ 1 2 2 1 ]),'k','facealpha',.25,'linestyle','none') 45 | plot(time([tidx tidx]),ylim,'k--') 46 | 47 | xlabel('Time (sec.)'), ylabel('Amplitude') 48 | title([ 'Running-mean filter with a k=' num2str(round(windowsize)) '-ms filter' ]) 49 | legend({'Signal';'Filtered';'Window';'window center'}) 50 | 51 | zoom on 52 | 53 | %% done. 54 | -------------------------------------------------------------------------------- /TimeSeriesDenoising/sigprocMXC_median_filter.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Time-domain denoising 4 | % VIDEO: Median filter to remove spike noise 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % create signal 10 | n = 2000; 11 | signal = cumsum(randn(n,1)); 12 | 13 | 14 | % proportion of time points to replace with noise 15 | propnoise = .05; 16 | 17 | % find noise points 18 | noisepnts = randperm(n); 19 | noisepnts = noisepnts(1:round(n*propnoise)); 20 | 21 | % generate signal and replace points with noise 22 | signal(noisepnts) = 50+rand(size(noisepnts))*100; 23 | 24 | 25 | % use hist to pick threshold 26 | figure(1), clf 27 | histogram(signal,100) 28 | zoom on 29 | 30 | % visual-picked threshold 31 | threshold = 40; 32 | 33 | 34 | % find data values above the threshold 35 | suprathresh = find( signal>threshold ); 36 | 37 | % initialize filtered signal 38 | filtsig = signal; 39 | 40 | % loop through suprathreshold points and set to median of k 41 | k = 20; % actual window is k*2+1 42 | for ti=1:length(suprathresh) 43 | 44 | % find lower and upper bounds 45 | lowbnd = max(1,suprathresh(ti)-k); 46 | uppbnd = min(suprathresh(ti)+k,n); 47 | 48 | % compute median of surrounding points 49 | filtsig(suprathresh(ti)) = median(signal(lowbnd:uppbnd)); 50 | end 51 | 52 | % plot 53 | figure(2), clf 54 | plot(1:n,signal, 1:n,filtsig, 'linew',2) 55 | zoom on 56 | 57 | %% done. 58 | 59 | -------------------------------------------------------------------------------- /TimeSeriesDenoising/sigprocMXC_polynomialDetrend.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Time-domain denoising 4 | % VIDEO: Remove slow trends with polynomial fitting 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% polynomial intuition 10 | 11 | order = 0; 12 | x = linspace(-15,15,100); 13 | 14 | y = zeros(size(x)); 15 | 16 | for i=1:order+1 17 | y = y + randn*x.^(i-1); 18 | end 19 | 20 | figure(1), clf 21 | hold on 22 | plot(x,y,'linew',4) 23 | title([ 'Order-' num2str(order) ' polynomial' ]) 24 | 25 | 26 | %% generate signal with slow polynomial artifact 27 | 28 | n = 10000; 29 | t = (1:n)'; 30 | k = 10; % number of poles for random amplitudes 31 | slowdrift = interp1(100*randn(k,1),linspace(1,k,n),'pchip')'; 32 | signal = slowdrift + 20*randn(n,1); 33 | 34 | 35 | figure(2), clf, hold on 36 | h = plot(t,signal); 37 | set(h,'color',[1 1 1]*.6) 38 | xlabel('Time (a.u.)'), ylabel('Amplitude') 39 | 40 | %% fit a 3-order polynomial 41 | 42 | % polynomial fit (returns coefficients) 43 | p = polyfit(t,signal,3); 44 | 45 | % predicted data is evaluation of polynomial 46 | yHat = polyval(p,t); 47 | 48 | % compute residual (the cleaned signal) 49 | residual = signal - yHat; 50 | 51 | 52 | % now plot the fit (the function that will be removed) 53 | plot(t,yHat,'r','linew',4) 54 | plot(t,residual,'k','linew',2) 55 | 56 | legend({'Original';'Polyfit';'Filtered signal'}) 57 | 58 | %% 59 | 60 | %% Bayes information criterion to find optimal order 61 | 62 | % possible orders 63 | orders = (5:40)'; 64 | 65 | % sum of squared errors (sse is reserved!) 66 | sse1 = zeros(length(orders),1); 67 | 68 | % loop through orders 69 | for ri=1:length(orders) 70 | 71 | % compute polynomial (fitting time series) 72 | yHat = polyval(polyfit(t,signal,orders(ri)),t); 73 | 74 | % compute fit of model to data (sum of squared errors) 75 | sse1(ri) = sum( (yHat-signal).^2 )/n; 76 | end 77 | 78 | % Bayes information criterion 79 | bic = n*log(sse1) + orders*log(n); 80 | 81 | % best parameter has lowest BIC 82 | [bestP,idx] = min(bic); 83 | 84 | % would continue getting smaller without adding parameters 85 | 86 | % plot the BIC 87 | figure(4), clf, hold on 88 | plot(orders,bic,'ks-','markerfacecolor','w','markersize',8) 89 | plot(orders(idx),bestP,'ro','markersize',10,'markerfacecolor','r') 90 | xlabel('Polynomial order'), ylabel('Bayes information criterion') 91 | zoom on 92 | 93 | %% now repeat filter for best (smallest) BIC 94 | 95 | % polynomial fit 96 | polycoefs = polyfit(t,signal,orders(idx)); 97 | 98 | % estimated data based on the coefficients 99 | yHat = polyval(polycoefs,t); 100 | 101 | % filtered signal is residual 102 | filtsig = signal - yHat; 103 | 104 | 105 | %%% plotting 106 | figure(5), clf, hold on 107 | h = plot(t,signal); 108 | set(h,'color',[1 1 1]*.6) 109 | plot(t,yHat,'r','linew',2) 110 | plot(t,filtsig,'k') 111 | set(gca,'xlim',t([1 end])) 112 | 113 | xlabel('Time (a.u.)'), ylabel('Amplitude') 114 | legend({'Original';'Polynomial fit';'Filtered'}) 115 | 116 | %% done. 117 | -------------------------------------------------------------------------------- /TimeSeriesDenoising/sigprocMXC_template_projection.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Time-domain denoising 4 | % VIDEO: Remove artifact via least-squares template-matching 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % load dataset 10 | load templateProjection.mat 11 | 12 | 13 | % initialize residual data 14 | resdat = zeros(size(EEGdat)); 15 | 16 | % loop over trials 17 | for triali=1:size(resdat,2) 18 | 19 | % build the least-squares model as intercept and EOG from this trial 20 | X = [ ones(npnts,1) eyedat(:,triali) ]; 21 | 22 | % compute regression coefficients for EEG channel 23 | b = (X'*X) \ (X'*EEGdat(:,triali)); 24 | 25 | % predicted data 26 | yHat = X*b; 27 | 28 | % new data are the residuals after projecting out the best EKG fit 29 | resdat(:,triali) = ( EEGdat(:,triali) - yHat )'; 30 | end 31 | 32 | %% plotting 33 | 34 | % trial averages 35 | figure(1), clf 36 | plot(timevec,mean(eyedat,2), timevec,mean(EEGdat,2), timevec,mean(resdat,2),'linew',2) 37 | legend({'EOG';'EEG';'Residual'}) 38 | xlabel('Time (ms)') 39 | 40 | 41 | % show all trials in a map 42 | clim = [-1 1]*20; 43 | 44 | figure(2), clf 45 | subplot(131) 46 | imagesc(timevec,[],eyedat') 47 | set(gca,'clim',clim) 48 | xlabel('Time (ms)'), ylabel('Trials') 49 | title('EOG') 50 | 51 | 52 | subplot(132) 53 | imagesc(timevec,[],EEGdat') 54 | set(gca,'clim',clim) 55 | xlabel('Time (ms)'), ylabel('Trials') 56 | title('EOG') 57 | 58 | 59 | subplot(133) 60 | imagesc(timevec,[],resdat') 61 | set(gca,'clim',clim) 62 | xlabel('Time (ms)'), ylabel('Trials') 63 | title('Residual') 64 | 65 | %% done. 66 | -------------------------------------------------------------------------------- /TimeSeriesDenoising/templateProjection.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/TimeSeriesDenoising/templateProjection.mat -------------------------------------------------------------------------------- /complex/sigprocMXC_complexAddSub.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Working with complex numbers 4 | % VIDEO: Addition and subtraction with complex numbers 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % create two complex numbers 10 | a = complex(4,5); 11 | b = 3+2i; 12 | 13 | % let MATLAB do the hard work 14 | z1 = a+b; 15 | 16 | % the "manual" way 17 | z2 = complex( real(a)+real(b) , imag(a)+imag(b) ); 18 | 19 | %% subtraction is the same as addition... 20 | 21 | % let MATLAB do the hard work 22 | z3 = a-b; 23 | 24 | % the "manual" way 25 | z4 = complex( real(a)-real(b) , imag(a)-imag(b) ); 26 | 27 | %% done. 28 | -------------------------------------------------------------------------------- /complex/sigprocMXC_complexConj.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Working with complex numbers 4 | % VIDEO: The complex conjugate 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % create a complex number 10 | a = complex(4,-5); 11 | 12 | % let MATLAB do the hard work 13 | ac1 = conj(a); 14 | 15 | % the "manual" way 16 | ac2 = complex( real(a) , -imag(a) ); 17 | 18 | %% magnitude squared of a complex number 19 | 20 | amag1 = a*conj(a); 21 | 22 | amag2 = real(a)^2 + imag(a)^2; 23 | 24 | amag3 = abs(a)^2; 25 | 26 | %% done. 27 | -------------------------------------------------------------------------------- /complex/sigprocMXC_complexDivision.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Working with complex numbers 4 | % VIDEO: Division with complex numbers 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % create two complex numbers 10 | a = complex(4,-5); 11 | b = complex(7,8); 12 | 13 | % let MATLAB do the hard work 14 | adb1 = a/b; 15 | 16 | % the "manual" way 17 | adb2 = (a*conj(b)) / (b*conj(b)); 18 | 19 | %% done. 20 | -------------------------------------------------------------------------------- /complex/sigprocMXC_complexIntro.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Working with complex numbers 4 | % VIDEO: From the number line to the complex number plane 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% the imaginary operator 10 | 11 | clear 12 | i 13 | j 14 | 1i 15 | 1j 16 | sqrt(-1) 17 | 18 | % but... 19 | i = 2; 20 | 1i = 2; 21 | 22 | 23 | %% creating complex numbers 24 | 25 | % several ways to create a complex number 26 | z = 4 + 3i; 27 | z = 4 + 3*1i; 28 | z = 4 + 3*sqrt(-1); 29 | z = complex(4,3); 30 | 31 | disp([ 'Real part is ' num2str(real(z)) ' and imaginary part is ' num2str(imag(z)) '.' ]) 32 | 33 | 34 | % beware of a common programming error: 35 | i = 2; 36 | zz = 4 + 3*i; 37 | 38 | 39 | %% plotting a complex number 40 | 41 | figure(1), clf 42 | plot(real(z),imag(z),'s','markersize',12,'markerfacecolor','k') 43 | 44 | % make plot look nicer 45 | set(gca,'xlim',[-5 5],'ylim',[-5 5]) 46 | grid on, hold on, axis square 47 | plot(get(gca,'xlim'),[0 0],'k','linew',2) 48 | plot([0 0],get(gca,'ylim'),'k','linew',2) 49 | xlabel('Real axis') 50 | ylabel('Imaginary axis') 51 | title([ 'Number (' num2str(real(z)) ' ' num2str(imag(z)) 'i) on the complex plane' ]) 52 | 53 | 54 | %% done. 55 | -------------------------------------------------------------------------------- /complex/sigprocMXC_complexMult.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Working with complex numbers 4 | % VIDEO: Multiplication with complex numbers 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % create two complex numbers 10 | a = complex(4,5); 11 | b = 3+2i; 12 | 13 | % let MATLAB do the hard work 14 | z1 = a*b; 15 | 16 | % the intuitive-but-WRONG way 17 | z2 = complex( real(a)*real(b) , imag(a)*imag(b) ); 18 | 19 | % the less-intuitive-but-CORRECT way 20 | ar = real(a); 21 | ai = imag(a); 22 | br = real(b); 23 | bi = imag(b); 24 | 25 | z3 = (ar + 1i*ai) * (br + 1i*bi); 26 | z4 = (ar*br) + (ar*(1i*bi)) + ((1i*ai)*br) + ((1i*ai)*(1j*bi)); 27 | 28 | %% done. 29 | -------------------------------------------------------------------------------- /complex/sigprocMXC_complexPolar.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Working with complex numbers 4 | % VIDEO: Magnitude and phase of complex numbers 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % create a complex number 10 | z = 4 + 3i; 11 | 12 | % plot the complex number 13 | figure(1), clf 14 | plot(real(z),imag(z),'s','markersize',12,'markerfacecolor','k') 15 | 16 | % make plot look nicer 17 | set(gca,'xlim',[-5 5],'ylim',[-5 5]) 18 | grid on, hold on, axis square 19 | plot(get(gca,'xlim'),[0 0],'k','linew',2) 20 | plot([0 0],get(gca,'ylim'),'k','linew',2) 21 | xlabel('Real axis') 22 | ylabel('Imaginary axis') 23 | title([ 'Number (' num2str(real(z)) ' ' num2str(imag(z)) 'i) on the complex plane' ]) 24 | 25 | %% compute magnitude and phase of the complex number 26 | 27 | % magnitude of the number (distance to origin) 28 | magZ1 = sqrt( real(z)^2 + imag(z)^2 ); 29 | magZ2 = abs( z ); 30 | 31 | % angle of the line relative to positive real axis 32 | angZ1 = atan2( imag(z),real(z) ); 33 | angZ2 = angle( z ); 34 | 35 | % draw a line using polar notation 36 | h = polar([0 angZ1],[0 magZ1],'r'); 37 | 38 | %% done. 39 | -------------------------------------------------------------------------------- /convolution/conv_codeChallenge.m: -------------------------------------------------------------------------------- 1 | 2 | 3 | % conv first with planck then wavelet in there, vs. wavelet only. 4 | % different? why or why not, then test with data 5 | 6 | % generate broadband noise 7 | N = 10000; 8 | signal = randn(N,1); 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /convolution/convtheremfigure.m: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | t = linspace(-2*pi,2*pi,1000); 6 | s = detrend(cumsum(randn(length(t),1))); 7 | k = cos(6*t) .* exp(-t.^2); 8 | k = k*(sum(k)*2*pi/7.7); 9 | r = conv(s,k,'same'); 10 | hz = linspace(0,1,length(t)); 11 | 12 | clf 13 | subplot(321), plot(t,s), axis tight, axis off 14 | subplot(322), plot(t,k), axis tight, axis off 15 | 16 | subplot(323), plot(hz,abs(fft(s))), axis tight, axis off 17 | set(gca,'xlim',[0 .1]) 18 | 19 | subplot(324), plot(hz,abs(fft(k))), axis tight, axis off 20 | set(gca,'xlim',[0 .1]) 21 | 22 | 23 | subplot(325), plot(t,r), axis tight, axis off 24 | subplot(326), plot(hz,abs(fft(r))), axis tight, axis off 25 | set(gca,'xlim',[0 .1]) 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /convolution/sigprocMXC_FreqDomainGaus.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Convolution 4 | % VIDEO: Convolution with frequency-domain Gaussian (narrowband filter) 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% create the signal 10 | 11 | % create signal 12 | srate = 1000; % Hz 13 | time = 0:1/srate:3; 14 | n = length(time); 15 | p = 15; % poles for random interpolation 16 | 17 | % noise level, measured in standard deviations 18 | noiseamp = 5; 19 | 20 | % amplitude modulator and noise level 21 | ampl = interp1(rand(p,1)*30,linspace(1,p,n)); 22 | noise = noiseamp * randn(size(time)); 23 | signal = ampl + noise; 24 | 25 | % subtract mean to eliminate DC 26 | signal = signal - mean(signal); 27 | 28 | %% create Gaussian spectral shape 29 | 30 | % specify Gaussian parameters 31 | peakf = 11; 32 | fwhm = 5.2; 33 | 34 | % vector of frequencies 35 | hz = linspace(0,srate,n); 36 | 37 | % frequency-domain Gaussian 38 | s = fwhm*(2*pi-1)/(4*pi); % normalized width 39 | x = hz-peakf; % shifted frequencies 40 | fx = exp(-.5*(x/s).^2); % gaussian 41 | 42 | %% now for convolution 43 | 44 | % FFTs 45 | dataX = fft(signal); 46 | 47 | % IFFT 48 | convres = 2*real( ifft( dataX.*fx )); 49 | 50 | % frequencies vector 51 | hz = linspace(0,srate,n); 52 | 53 | 54 | %% plots 55 | 56 | 57 | %%% time-domain plot 58 | 59 | figure(1), clf, hold on 60 | 61 | % lines 62 | plot(time,signal,'r') 63 | plot(time,convres,'k','linew',2) 64 | 65 | % frills 66 | xlabel('Time (s)'), ylabel('amp. (a.u.)') 67 | legend({'Signal';'Smoothed'}) 68 | title('Narrowband filter') 69 | 70 | 71 | %%% frequency-domain plot 72 | 73 | figure(2), clf 74 | 75 | % plot Gaussian kernel 76 | subplot(511) 77 | plot(hz,fx,'k','linew',2) 78 | set(gca,'xlim',[0 30]) 79 | ylabel('Gain') 80 | title('Frequency-domain Gaussian') 81 | 82 | 83 | % raw and filtered data spectra 84 | subplot(5,1,[2:5]), hold on 85 | plot(hz,abs(dataX).^2,'rs-','markerfacecolor','w','markersize',13,'linew',2) 86 | plot(hz,abs(dataX.*fx).^2,'bo-','linew',2,'markerfacecolor','w','markersize',8) 87 | 88 | % frills 89 | xlabel('Frequency (Hz)'), ylabel('Power (a.u.)') 90 | legend({'Signal';'Convolution result'}) 91 | title('Frequency domain') 92 | set(gca,'xlim',[0 25]) 93 | 94 | %% done. 95 | -------------------------------------------------------------------------------- /convolution/sigprocMXC_TimeDomainGaus.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Convolution 4 | % VIDEO: Convolution with time-domain Gaussian (smoothing filter) 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% create the signal 10 | 11 | % create signal 12 | srate = 1000; % Hz 13 | time = 0:1/srate:3; 14 | n = length(time); 15 | p = 15; % poles for random interpolation 16 | 17 | % noise level, measured in standard deviations 18 | noiseamp = 5; 19 | 20 | % amplitude modulator and noise level 21 | ampl = interp1(rand(p,1)*30,linspace(1,p,n)); 22 | noise = noiseamp * randn(size(time)); 23 | signal = ampl + noise; 24 | 25 | % subtract mean to eliminate DC 26 | signal = signal - mean(signal); 27 | 28 | %% create Gaussian kernel 29 | 30 | % full-width half-maximum: the key Gaussian parameter 31 | fwhm = 25; % in ms 32 | 33 | % normalized time vector in ms 34 | k = 100; 35 | gtime = 1000*(-k:k)/srate; 36 | 37 | % create Gaussian window 38 | gauswin = exp( -(4*log(2)*gtime.^2) / fwhm^2 ); 39 | 40 | % then normalize Gaussian to unit energy 41 | gauswin = gauswin / sum(gauswin); 42 | 43 | %% filter as time-domain convolution 44 | 45 | % initialize filtered signal vector 46 | filtsigG = signal; 47 | 48 | % implement the running mean filter 49 | for i=k+1:n-k-1 50 | % each point is the weighted average of k surrounding points 51 | filtsigG(i) = sum( signal(i-k:i+k).*gauswin ); 52 | end 53 | 54 | %% now repeat in the frequency domain 55 | 56 | % compute N's 57 | nConv = n + 2*k+1 - 1; 58 | 59 | % FFTs 60 | dataX = fft(signal,nConv); 61 | gausX = fft(gauswin,nConv); 62 | 63 | % IFFT 64 | convres = ifft( dataX.*gausX ); 65 | 66 | % cut wings 67 | convres = convres(k+1:end-k); 68 | 69 | % frequencies vector 70 | hz = linspace(0,srate,nConv); 71 | 72 | 73 | %% plots 74 | 75 | 76 | %%% time-domain plot 77 | 78 | figure(1), clf, hold on 79 | 80 | % lines 81 | plot(time,signal,'r') 82 | plot(time,filtsigG,'k*-') 83 | plot(time,convres,'bo') 84 | 85 | % frills 86 | xlabel('Time (s)'), ylabel('amp. (a.u.)') 87 | legend({'Signal';'Time-domain';'Spectral multiplication'}) 88 | title('Gaussian smoothing filter') 89 | 90 | 91 | 92 | 93 | 94 | %%% frequency-domain plot 95 | 96 | figure(2), clf 97 | 98 | % plot Gaussian kernel 99 | subplot(511) 100 | plot(hz,abs(gausX).^2,'k','linew',2) 101 | set(gca,'xlim',[0 30]) 102 | ylabel('Gain') 103 | title('Power spectrum of Gaussian') 104 | 105 | 106 | % raw and filtered data spectra 107 | subplot(5,1,[2:5]), hold on 108 | plot(hz,abs(dataX).^2,'rs-','markerfacecolor','w','markersize',13,'linew',2) 109 | plot(hz,abs(dataX.*gausX).^2,'bo-','linew',2,'markerfacecolor','w','markersize',8) 110 | 111 | % frills 112 | xlabel('Frequency (Hz)'), ylabel('Power (a.u.)') 113 | legend({'Signal';'Convolution result'}) 114 | title('Frequency domain') 115 | set(gca,'xlim',[0 25]) 116 | 117 | %% done. 118 | -------------------------------------------------------------------------------- /convolution/sigprocMXC_convolutionTheorem.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Convolution 4 | % VIDEO: The convolution theorem 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% generate signal and kernel 10 | 11 | % signal 12 | signal = zeros(1,20); 13 | signal(8:15) = 1; 14 | 15 | % convolution kernel 16 | kernel = [1 .8 .6 .4 .2]; 17 | 18 | % convolution sizes 19 | nSign = length(signal); 20 | nKern = length(kernel); 21 | nConv = nSign + nKern - 1; 22 | 23 | %% time-domain convolution 24 | 25 | half_kern = floor(nKern/2); 26 | 27 | % flipped version of kernel 28 | kflip = kernel(end:-1:1); 29 | 30 | % zero-padded data for convolution 31 | dat4conv = [ zeros(1,half_kern) signal zeros(1,half_kern) ]; 32 | 33 | % initialize convolution output 34 | conv_res = zeros(1,nConv); 35 | 36 | % run convolution 37 | for ti=half_kern+1:nConv-half_kern 38 | 39 | % get a chunk of data 40 | tempdata = dat4conv(ti-half_kern:ti+half_kern); 41 | 42 | % compute dot product (don't forget to flip the kernel backwards!) 43 | conv_res(ti) = sum( tempdata.*kflip ); 44 | end 45 | 46 | % cut off edges 47 | conv_res = conv_res(half_kern+1:end-half_kern); 48 | 49 | %% convolution implemented in the frequency domain 50 | 51 | % spectra of signal and kernel 52 | signalX = fft(signal,nConv); 53 | kernelX = fft(kernel,nConv); 54 | 55 | % element-wise multiply 56 | sigXkern = signalX .* kernelX; 57 | 58 | % inverse FFT to get back to the time domain 59 | conv_resFFT = ifft( sigXkern ); 60 | 61 | 62 | % cut off edges 63 | conv_resFFT = conv_resFFT(half_kern+1:end-half_kern); 64 | 65 | 66 | %% plot for comparison 67 | 68 | figure(1), clf, hold on 69 | plot(conv_res,'o-','linew',2,'markerface','g','markersize',9) 70 | plot(conv_resFFT,'o-','linew',2,'markerface','r','markersize',3) 71 | 72 | legend({'Time domain';'Freq domain'}) 73 | 74 | %% done. 75 | -------------------------------------------------------------------------------- /convolution/sigprocMXC_planckBandPass.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Convolution 4 | % VIDEO: Convolution with Planck taper (bandpass filter) 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% create the signal 10 | 11 | % create signal 12 | srate = 1000; % Hz 13 | time = 0:1/srate:3; 14 | n = length(time); 15 | p = 15; % poles for random interpolation 16 | 17 | % noise level, measured in standard deviations 18 | noiseamp = 5; 19 | 20 | % amplitude modulator and noise level 21 | ampl = interp1(rand(p,1)*30,linspace(1,p,n)); 22 | noise = noiseamp * randn(size(time)); 23 | signal = ampl + noise; 24 | 25 | % subtract mean to eliminate DC 26 | signal = signal - mean(signal); 27 | 28 | %% create Planck spectral shape 29 | 30 | 31 | % frequencies 32 | hz = linspace(0,srate,n); 33 | 34 | % edge decay, must be between 0 and .5 35 | eta = .15; 36 | 37 | % spectral parameters 38 | fwhm = 6; 39 | peakf = 20; 40 | 41 | % convert fwhm to indices 42 | np = round( 2*fwhm*n/srate ); 43 | pt = 1:np; 44 | 45 | % find center point index 46 | fidx = dsearchn(hz',peakf); 47 | 48 | 49 | % define left and right exponentials 50 | Zl = eta*(np-1) * ( 1./pt + 1./(pt-eta*(np-1)) ); 51 | Zr = eta*(np-1) * ( 1./(np-1-pt) + 1./( (1-eta)*(np-1)-pt ) ); 52 | 53 | % create the taper 54 | bounds = [ floor(eta*(np-1))-mod(np,2) ceil((1-eta)*(np-~mod(np,2))) ]; 55 | plancktaper = [ 1./(exp(Zl(1:bounds(1)))+1) ... 56 | ones(1,diff(bounds)) ... 57 | 1./(exp(Zr(bounds(2):end-1))+1) ]; 58 | 59 | % put the taper inside zeros 60 | px = zeros( size(hz) ); 61 | pidx = max(1,fidx-floor(np/2)+1) : fidx+floor(np/2)-mod(np,2); 62 | px(pidx) = plancktaper; 63 | 64 | %% now for convolution 65 | 66 | % FFTs 67 | dataX = fft(signal); 68 | 69 | % IFFT 70 | convres = 2*real( ifft( dataX.*px )); 71 | 72 | % frequencies vector 73 | hz = linspace(0,srate,n); 74 | 75 | 76 | %% plots 77 | 78 | 79 | %%% time-domain plot 80 | 81 | figure(1), clf, hold on 82 | 83 | % lines 84 | plot(time,signal,'r') 85 | plot(time,convres,'k','linew',2) 86 | 87 | % frills 88 | xlabel('Time (s)'), ylabel('amp. (a.u.)') 89 | legend({'Signal';'Smoothed'}) 90 | title('Narrowband filter') 91 | 92 | 93 | %%% frequency-domain plot 94 | 95 | figure(2), clf 96 | 97 | % plot Gaussian kernel 98 | subplot(511) 99 | plot(hz,px,'k','linew',2) 100 | set(gca,'xlim',[0 peakf*2]) 101 | ylabel('Gain') 102 | title('Frequency-domain Planck taper') 103 | 104 | 105 | % raw and filtered data spectra 106 | subplot(5,1,[2:5]), hold on 107 | plot(hz,abs(dataX).^2,'rs-','markerfacecolor','w','markersize',13,'linew',2) 108 | plot(hz,abs(dataX.*px).^2,'bo-','linew',2,'markerfacecolor','w','markersize',8) 109 | 110 | % frills 111 | xlabel('Frequency (Hz)'), ylabel('Power (a.u.)') 112 | legend({'Signal';'Convolution result'}) 113 | title('Frequency domain') 114 | set(gca,'xlim',[0 peakf*2]) 115 | 116 | %% done. 117 | -------------------------------------------------------------------------------- /convolution/sigprocMXC_timeConvolution.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Convolution 4 | % VIDEO: Time-domain convolution 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% first example to build intuition 10 | 11 | signal = [ zeros(1,30) ones(1,2) zeros(1,20) ones(1,30) 2*ones(1,10) zeros(1,30) -ones(1,10) zeros(1,40) ]; 12 | kernel = exp( -linspace(-2,2,20).^2 ); 13 | kernel = kernel./sum(kernel); 14 | 15 | figure(1), clf 16 | subplot(311) 17 | plot(kernel,'k','linew',3) 18 | set(gca,'xlim',[0 length(signal)]) 19 | title('Kernel') 20 | 21 | subplot(312) 22 | plot(signal,'k','linew',3) 23 | set(gca,'xlim',[0 length(signal)]) 24 | title('Signal') 25 | 26 | 27 | subplot(313) 28 | plot( conv(signal,kernel,'same') ,'k','linew',3) 29 | set(gca,'xlim',[0 length(signal)]) 30 | title('Convolution result') 31 | 32 | %% a simple example in more detail 33 | 34 | 35 | %% generate signal and kernel 36 | 37 | % signal 38 | signal = zeros(1,20); 39 | signal(8:15) = 1; 40 | 41 | % convolution kernel 42 | kernel = [1 .8 .6 .4 .2]; 43 | 44 | % convolution sizes 45 | nSign = length(signal); 46 | nKern = length(kernel); 47 | nConv = nSign + nKern - 1; 48 | 49 | 50 | figure(2), clf 51 | % plot the signal 52 | subplot(311) 53 | plot(signal,'o-','linew',2,'markerface','g','markersize',9) 54 | set(gca,'ylim',[-.1 1.1],'xlim',[1 nSign]) 55 | title('Signal') 56 | 57 | % plot the kernel 58 | subplot(312) 59 | plot(kernel,'o-','linew',2,'markerface','r','markersize',9) 60 | set(gca,'xlim',[1 nSign],'ylim',[-.1 1.1]) 61 | title('Kernel') 62 | 63 | 64 | % plot the result of convolution 65 | subplot(313) 66 | plot(conv(signal,kernel,'same'),'o-','linew',2,'markerface','b','markersize',9) 67 | set(gca,'xlim',[1 nSign],'ylim',[-.1 3.6]) 68 | title('Result of convolution') 69 | 70 | %% convolution in animation 71 | 72 | half_kern = floor(nKern/2); 73 | 74 | % flipped version of kernel 75 | kflip = kernel(end:-1:1);%-mean(kernel); 76 | 77 | % zero-padded data for convolution 78 | dat4conv = [ zeros(1,half_kern) signal zeros(1,half_kern) ]; 79 | 80 | % initialize convolution output 81 | conv_res = zeros(1,nConv); 82 | 83 | 84 | %%% initialize plot 85 | figure(3), clf, hold on 86 | plot(dat4conv,'o-','linew',2,'markerface','g','markersize',9) 87 | hkern = plot(kernel,'o-','linew',2,'markerface','r','markersize',9); 88 | hcres = plot(kernel,'s-','linew',2,'markerface','k','markersize',15); 89 | set(gca,'ylim',[-1 1]*3,'xlim',[0 nConv+1]) 90 | plot([1 1]*(half_kern+1),get(gca,'ylim'),'k--') 91 | plot([1 1]*(nConv-2),get(gca,'ylim'),'k--') 92 | legend({'Signal';'Kernel (flip)';'Convolution'}) 93 | 94 | % run convolution 95 | for ti=half_kern+1:nConv-half_kern 96 | 97 | % get a chunk of data 98 | tempdata = dat4conv(ti-half_kern:ti+half_kern); 99 | 100 | % compute dot product (don't forget to flip the kernel backwards!) 101 | conv_res(ti) = sum( tempdata.*kflip ); 102 | 103 | % update plot 104 | set(hkern,'XData',ti-half_kern:ti+half_kern,'YData',kflip); 105 | set(hcres,'XData',half_kern+1:ti,'YData',conv_res(half_kern+1:ti)) 106 | 107 | pause(.5) 108 | 109 | end 110 | 111 | % cut off edges 112 | conv_res = conv_res(half_kern+1:end-half_kern); 113 | 114 | 115 | %% compare with MATLAB function 116 | 117 | figure(4), clf 118 | 119 | matlab_conv = conv(signal,kernel,'same'); 120 | 121 | plot(conv_res,'o-','linew',2,'markerface','g','markersize',9) 122 | hold on 123 | plot(matlab_conv,'o-','linew',2,'markerface','r','markersize',3) 124 | legend({'Time-domain convolution';'Matlab conv function'}) 125 | 126 | %% done. 127 | -------------------------------------------------------------------------------- /featdet/EMGRT.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/featdet/EMGRT.mat -------------------------------------------------------------------------------- /featdet/get_emg_rt_data.m: -------------------------------------------------------------------------------- 1 | 2 | load /home/mxc/apples/raw/pp24_ready.mat 3 | 4 | rts = zeros(200,1); 5 | emg = zeros(200,EEG.pnts); 6 | 7 | for i=1:200 8 | 9 | [~,loc0] = min(abs(cell2mat(EEG.epoch(i).eventlatency))); 10 | rts(i) = EEG.epoch(i).eventlatency{loc0+1}; 11 | 12 | emg(i,:) = EEG.data(64+EEG.epoch(i).eventtype{loc0+1},:,i); 13 | 14 | end 15 | 16 | % for i=1:200 17 | % plot(EEG.times,emg(i,:)) 18 | % pause(.1) 19 | % end 20 | 21 | timevec = EEG.times; 22 | save EMGRT.mat rts emg timevec 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /featdet/sigprocMXC_AUC.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Feature detection 4 | % VIDEO: Area under the curve 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % create signal 10 | srate = 1000; % Hz 11 | time = 0:1/srate:3; 12 | n = length(time); 13 | p = 20; % poles for random interpolation 14 | 15 | % amplitude modulator and noise level 16 | signal = interp1(randn(p,1)*30,linspace(1,p,n),'spline')'.^2; 17 | signal(signal<100) = 0; 18 | 19 | figure(1), clf 20 | plot(time,signal) 21 | 22 | %% demarcate each lobe 23 | 24 | % thresholded time series 25 | threshts = logical(signal); 26 | 27 | % find islands (in image processing toolbox!) 28 | islands = bwconncomp( logical(signal) ); 29 | 30 | % color each island 31 | for ii=1:islands.NumObjects 32 | patch(time([islands.PixelIdxList{ii}; islands.PixelIdxList{ii}(end-1:-1:1)]),[signal(islands.PixelIdxList{ii}); zeros(numel(islands.PixelIdxList{ii})-1,1)],rand(1,3)); 33 | end 34 | 35 | %% compute AUC under each curve 36 | 37 | auc = zeros(islands.NumObjects,1); 38 | 39 | for ii=1:islands.NumObjects 40 | auc(ii) = sum( signal(islands.PixelIdxList{ii}) ); 41 | end 42 | 43 | % scale by dt 44 | auc = auc * mean(diff(time)); 45 | 46 | 47 | 48 | % add text on top of each curve 49 | for ii=1:islands.NumObjects 50 | 51 | text(mean(time(islands.PixelIdxList{ii})),... 52 | 50 + max(signal(islands.PixelIdxList{ii})),... 53 | num2str( round(auc(ii),2) ),... 54 | 'HorizontalAlignment','center'); 55 | end 56 | 57 | %% done. 58 | -------------------------------------------------------------------------------- /featdet/sigprocMXC_EMGonsets.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Feature detection 4 | % VIDEO: Detect muscle movements from EMG recordings 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % load data 10 | load EMGRT.mat 11 | N = length(rts); 12 | 13 | % example trials 14 | trials2plot = [ 4 23 ]; 15 | 16 | 17 | %%% a few plots to get an idea of the data 18 | figure(1), clf 19 | 20 | % RTs (button presses) 21 | subplot(221) 22 | plot(rts,'s-','markerfacecolor','w') 23 | xlabel('Trials'), ylabel('Reaction times (ms)') 24 | set(gca,'xlim',[0 N+1]) 25 | 26 | 27 | % histogram of RTs 28 | subplot(222) 29 | histogram(rts,40) 30 | xlabel('Reaction times (ms)'), ylabel('Count') 31 | 32 | subplot(212) 33 | [~,sidx] = sort(rts,'descend'); 34 | plot(timevec,bsxfun(@plus,emg(sidx,:),(1:200)'*1500),'k') 35 | xlabel('Time (ms)') 36 | set(gca,'ytick',[]) 37 | axis tight 38 | 39 | 40 | 41 | figure(2), clf 42 | 43 | % two example trials 44 | for i=1:2 45 | subplot(2,1,i), hold on 46 | 47 | % plot EMG trace 48 | plot(timevec,emg(trials2plot(i),:),'r','linew',1) 49 | 50 | % overlay button press time 51 | plot([1 1]*rts(trials2plot(i)),get(gca,'ylim'),'k--','linew',1) 52 | 53 | xlabel('Time (ms)') 54 | legend({'EMG';'Button press'}) 55 | end 56 | 57 | %% detect EMG onsets 58 | 59 | % define baseline time window for normalization 60 | baseidx = dsearchn(timevec',[-500 0]'); 61 | 62 | % pick z-threshold 63 | zthresh = 100; 64 | 65 | % initialize outputs 66 | emgonsets = zeros(N,1); 67 | 68 | for triali=1:N 69 | 70 | % convert to energy via TKEO 71 | tkeo = emg(triali,2:end-1).^2 - emg(triali,1:end-2) .* emg(triali,3:end); 72 | 73 | % convert to zscore from pre-0 activity 74 | tkeo = ( tkeo-mean(tkeo(baseidx(1):baseidx(2))) ) ./ std(tkeo(baseidx(1):baseidx(2))); 75 | 76 | % find first suprathreshold point 77 | tkeoThresh = tkeo>zthresh; 78 | tkeoThresh(timevec<0) = 0; 79 | tkeoPnts = find(tkeoThresh); 80 | 81 | % grab the first suprathreshold point 82 | emgonsets(triali) = timevec( tkeoPnts(1)+1 ); 83 | end 84 | 85 | 86 | %% more plots 87 | 88 | % back to the EMG traces... 89 | figure(2) 90 | for i=1:2 91 | subplot(2,1,i) 92 | plot([1 1]*emgonsets(trials2plot(i)),get(gca,'ylim'),'b--','linew',2) 93 | legend({'EMG';'Button press';'EMG onset'}) 94 | end 95 | 96 | 97 | % plot onsets by RTs 98 | figure(3), clf 99 | 100 | subplot(211), hold on 101 | plot(emgonsets,'ks','markerfacecolor','k','markersize',10) 102 | plot(rts,'bo','markerfacecolor','b','markersize',10) 103 | xlabel('Trial'), ylabel('Time (ms)') 104 | legend({'EMG onsets';'Button times'}) 105 | 106 | 107 | subplot(212) 108 | plot(rts,emgonsets,'bo','markerfacecolor','b','markersize',5) 109 | xlabel('Button press time') 110 | ylabel('EMG onset time') 111 | axis square 112 | 113 | 114 | %% done. 115 | -------------------------------------------------------------------------------- /featdet/sigprocMXC_FWHM.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Feature detection 4 | % VIDEO: Full-width at half-maximum 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% Gaussian 10 | 11 | % simulation parameters 12 | srate = 1000; 13 | time = -2:1/srate:2; 14 | fwhmA = .93; % seconds 15 | 16 | gauswin = exp( -(4*log(2)*time.^2) / fwhmA^2 ); 17 | gauswinNorm = gauswin ./ max(gauswin); % normalize 18 | 19 | 20 | figure(1), clf, hold on 21 | plot(time,gauswinNorm,'k','linew',3) 22 | 23 | % find the peak point 24 | peakpnt = find( gauswin==max(gauswin) ); 25 | 26 | 27 | % find 50% PREpeak point 28 | prepeak = dsearchn(gauswinNorm(1:peakpnt)',.5); 29 | 30 | 31 | % find 50% POSTpeak point 32 | pstpeak = dsearchn(gauswinNorm(peakpnt:end)',.5); 33 | pstpeak = pstpeak + peakpnt - 1; % adjust 34 | 35 | 36 | % compute empirical FWHM 37 | fwhmE = time(pstpeak) - time(prepeak); 38 | 39 | 40 | 41 | % plot the points 42 | plot(time(peakpnt),gauswinNorm(peakpnt),'ko','markerfacecolor','r','markersize',15) 43 | plot(time(prepeak),gauswinNorm(prepeak),'ko','markerfacecolor','g','markersize',15) 44 | plot(time(pstpeak),gauswinNorm(pstpeak),'ko','markerfacecolor','g','markersize',15) 45 | 46 | % plot line for reference 47 | plot(time([prepeak pstpeak]),gauswinNorm([prepeak pstpeak]),'k--') 48 | plot([1 1]*time(prepeak),[0 gauswinNorm(prepeak)],'k:') 49 | plot([1 1]*time(pstpeak),[0 gauswinNorm(pstpeak)],'k:') 50 | set(gca,'ylim',[0 1.05]) 51 | xlabel('Time (sec.)') 52 | 53 | title([ 'Analytic: ' num2str(fwhmA) ', empirical: ' num2str(fwhmE) ]) 54 | 55 | %% example with asymmetric shape 56 | 57 | % generate asymmetric distribution 58 | [fx,x] = hist(exp(.5*randn(10000,1)),150); 59 | 60 | % normalization necessary here! 61 | fxNorm = fx./max(fx); 62 | 63 | % plot the function 64 | figure(2), clf, hold on 65 | plot(x,fx,'ks-','linew',3,'markerfacecolor','w') 66 | 67 | 68 | % find peak point 69 | peakpnt = find( fxNorm==max(fxNorm) ); 70 | 71 | 72 | % find 50% PREpeak point 73 | prepeak = dsearchn(fxNorm(1:peakpnt)',.5); 74 | 75 | 76 | % find 50% POSTpeak point 77 | pstpeak = dsearchn(fxNorm(peakpnt:end)',.5); 78 | pstpeak = pstpeak + peakpnt - 1; % adjust 79 | 80 | % compute empirical FWHM 81 | fwhmE = x(pstpeak) - x(prepeak); 82 | 83 | 84 | 85 | % plot the points 86 | plot(x(peakpnt),fx(peakpnt),'ko','markerfacecolor','r','markersize',15) 87 | plot(x(prepeak),fx(prepeak),'ko','markerfacecolor','g','markersize',15) 88 | plot(x(pstpeak),fx(pstpeak),'ko','markerfacecolor','g','markersize',15) 89 | 90 | 91 | % plot line for reference 92 | plot(x([prepeak pstpeak]),fx([prepeak pstpeak]),'k--') 93 | plot([1 1]*x(prepeak),[0 fx(prepeak)],'k:') 94 | plot([1 1]*x(pstpeak),[0 fx(pstpeak)],'k:') 95 | 96 | title([ 'Empirical FWHM: ' num2str(fwhmE) ]) 97 | 98 | %% an interesting aside... 99 | 100 | % a range of standard deviations 101 | sds = linspace(.1,.7,50); 102 | fwhmE = zeros(size(sds)); 103 | 104 | for i=1:length(sds) 105 | 106 | % new data 107 | [fx,x] = hist(exp(sds(i)*randn(10000,1)),150); 108 | 109 | % normalization necessary here! 110 | fxNorm = fx./max(fx); 111 | 112 | % find peak point 113 | peakpnt = find( fxNorm==max(fxNorm) ); 114 | prepeak = dsearchn(fxNorm(1:peakpnt)',.5); 115 | pstpeak = dsearchn(fxNorm(peakpnt:end)',.5); 116 | pstpeak = pstpeak + peakpnt - 1; % adjust 117 | 118 | % FWHM 119 | fwhmE(i) = x(pstpeak(1)) - x(prepeak(1)); 120 | end 121 | 122 | % plot 123 | figure(3), clf 124 | plot(sds,fwhmE,'s-','markersize',15,'markerfacecolor','k') 125 | xlabel('Stretch parameter') 126 | ylabel('Empirical FWHM') 127 | 128 | %% done. 129 | -------------------------------------------------------------------------------- /featdet/sigprocMXC_localMinMax.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Feature detection 4 | % VIDEO: Local maxima and minima 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % signal is sinc plus linear trend 10 | time = linspace(-4*pi,16*pi,1000); 11 | signal = sin(time)./time + linspace(1,-1,length(time)); 12 | 13 | 14 | % plot the signal 15 | figure(1), clf, hold on 16 | plot(time,signal,'k','linew',2) 17 | xlabel('Time'), ylabel('Amp. (a.u.)') 18 | % adjust axis limits 19 | set(gca,'xlim',time([1 end]),'ylim',[min(signal) max(signal)]*1.1) 20 | 21 | 22 | % find global maximum 23 | [maxval,maxidx] = max(signal); 24 | plot(time(maxidx),maxval,'ko','linew',2,'markersize',15,'markerfacecolor','g') 25 | 26 | %% "hack" method for local extrema 27 | 28 | % "local" minimum is the "global" minimum in a restricted range 29 | range4max = [0 5]; 30 | rangeidx = dsearchn(time',range4max'); 31 | 32 | [maxLval,maxLidx] = min(signal(rangeidx(1):rangeidx(2))); 33 | % plot it (note the -1 offset!) 34 | plot(time(maxLidx+rangeidx(1)-1),maxLval,'ko','linew',2,'markersize',15,'markerfacecolor','r') 35 | 36 | %% local minima/maxima 37 | 38 | % find local maxima and plot 39 | peeks1 = find(diff(sign(diff(signal)))<0)+1; 40 | plot(time(peeks1),signal(peeks1),'ro','linew',2,'markersize',10,'markerfacecolor','y') 41 | 42 | % try again using detrended signal 43 | peeks2 = find(diff(sign(diff( detrend(signal) )))<0)+1; 44 | plot(time(peeks2),signal(peeks2),'bs','linew',2,'markersize',10,'markerfacecolor','b') 45 | 46 | % in the signal processing toolbox: 47 | [~,peeks3] = findpeaks(signal); 48 | plot(time(peeks3),signal(peeks3),'gp','linew',2,'markersize',10,'markerfacecolor','b') 49 | 50 | %% done. 51 | -------------------------------------------------------------------------------- /featdet/sigprocMXC_signalFromNoise.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Feature detection 4 | % VIDEO: Recover signal from noise amplitude 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% create the signal 10 | 11 | % create signal 12 | srate = 1000; % Hz 13 | time = 0:1/srate:3; 14 | n = length(time); 15 | p = 20; % poles for random interpolation 16 | 17 | % amplitude modulator and noise level 18 | ampmod = interp1(rand(p,1)*30,linspace(1,p,n),'nearest')'; 19 | signal = ampmod .* randn(size(ampmod)); 20 | 21 | % plot 22 | figure(1), clf 23 | subplot(211) 24 | plot(time,signal, time,ampmod,'linew',2) 25 | xlabel('Time (s)'), ylabel('Amplitude (a.u.)') 26 | legend({'Signal';'Source'}) 27 | 28 | %% options for identifying the original signal 29 | 30 | 31 | subplot(212) 32 | 33 | 34 | % rectify and lowpass filter 35 | rectsig = abs(signal); 36 | k = 9; 37 | rectsig = filtfilt(ones(1,k)/k,1,rectsig); 38 | plot(time,rectsig, time,ampmod,'linew',2) 39 | legend({'Estimated';'True'}) 40 | 41 | 42 | 43 | % TKEO 44 | tkeosig = signal; 45 | tkeosig(2:end-1) = signal(2:end-1).^2 - signal(1:end-2).*signal(3:end); 46 | plot(time,tkeosig, time,ampmod,'linew',2) 47 | legend({'Estimated';'True'}) 48 | 49 | 50 | 51 | % magnitude of Hilbert transform 52 | maghilb = abs(hilbert( signal )); 53 | 54 | % running mean-filter 55 | k = 9; 56 | maghilb = filtfilt(ones(1,k)/k,1,maghilb); 57 | plot(time,maghilb, time,ampmod,'linew',2) 58 | legend({'Estimated';'True'}) 59 | 60 | 61 | 62 | % running-variance 63 | k = 9; 64 | runningVar = zeros(n,1); 65 | for i=1:n 66 | startp = max(1,i-round(k/2)); 67 | endp = min(round(k/2)+i,n); 68 | runningVar(i) = std(signal(startp:endp)); 69 | end 70 | plot(time,runningVar, time,ampmod,'linew',2) 71 | legend({'Estimated';'True'}) 72 | 73 | 74 | 75 | %%% plot all options 76 | figure(2), clf, hold on 77 | plot(time,rectsig,'b','linew',2) 78 | % plot(time,tkeosig,'m','linew',2) 79 | plot(time,maghilb,'k','linew',2) 80 | plot(time,runningVar,'g','linew',2) 81 | plot(time,ampmod,'r','linew',2) 82 | 83 | set(gca,'ylim',[-1 max(ampmod)*2]) 84 | % legend({'Rectify';'TKEO';'Hilbert';'Variance';'Ground truth'}) 85 | legend({'Rectify';'Hilbert';'Variance';'Ground truth'}) 86 | xlabel('Time (sec.)') 87 | 88 | %% compare the different algorithms to ground truth 89 | 90 | % rectify 91 | r2rect = corrcoef(ampmod,rectsig); 92 | r2rect = r2rect(2)^2; 93 | 94 | % TKEO 95 | r2tkeo = corrcoef(ampmod,tkeosig); 96 | r2tkeo = r2tkeo(2)^2; 97 | 98 | % Hilbert 99 | r2hilb = corrcoef(ampmod,maghilb); 100 | r2hilb = r2hilb(2)^2; 101 | 102 | % running variance 103 | r2varr = corrcoef(ampmod,runningVar); 104 | r2varr = r2varr(2)^2; 105 | 106 | 107 | % now plot 108 | figure(3), clf 109 | bar([ r2rect r2hilb r2tkeo r2varr ]) 110 | set(gca,'xtick',1:4,'xticklabel',{'Rectify';'Hilbert';'TKEO';'Variance'}) 111 | ylabel('R^2 fit to truth') 112 | title('Comparison of methods') 113 | 114 | %% done. 115 | -------------------------------------------------------------------------------- /featdet/sigprocMXC_waveletFeatureEx.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Feature detection 4 | % VIDEO: Wavelet convolution for feature extraction 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% simulate data 10 | % taken from video "Averaging multiple repetitions (time-synchronous averaging)" 11 | 12 | % total number of time points 13 | N = 10000; 14 | 15 | % create event (derivative of Gaussian) 16 | k = 100; % duration of event in time points 17 | event = diff(exp( -linspace(-2,2,k+1).^2 )); 18 | event = event./max(event); % normalize to max=1 19 | 20 | % event onset times 21 | Nevents = 30; 22 | onsettimes = randperm(N/10-k); 23 | onsettimes = onsettimes(1:Nevents)*10; 24 | 25 | % put event into data 26 | data = zeros(1,N); 27 | for ei=1:Nevents 28 | data(onsettimes(ei):onsettimes(ei)+k-1) = event; 29 | end 30 | 31 | % add noise 32 | data = data + 3*randn(size(data)); 33 | 34 | 35 | 36 | % plot data 37 | figure(1), clf 38 | subplot(211), hold on 39 | plot(data) 40 | plot(onsettimes,data(onsettimes),'ro') 41 | 42 | %% convolve with event (as template) 43 | 44 | % convolution 45 | convres = conv(data,event,'same'); 46 | 47 | 48 | % plot the convolution result and ground-truth event onsets 49 | subplot(212), hold on 50 | plot(convres) 51 | plot(onsettimes,convres(onsettimes),'o') 52 | 53 | %% find a threshold 54 | 55 | % histogram of all data values 56 | figure(2), clf 57 | hist(convres,N/20) 58 | 59 | % pick a threshold based on histogram and visual inspection 60 | thresh = -35; 61 | 62 | % plot the threshold 63 | figure(1) 64 | subplot(212) 65 | plot(get(gca,'xlim'),[1 1]*thresh,'k--') 66 | 67 | 68 | % find local minima 69 | thresh_ts = convres; 70 | thresh_ts(thresh_ts>thresh) = 0; 71 | 72 | % let's see what it looks like... 73 | figure(2), clf 74 | plot(thresh_ts,'s-') 75 | 76 | % find local minima 77 | localmin = find(diff(sign(diff( thresh_ts )))>0)+1; 78 | 79 | 80 | % plot local minima on top of the plot 81 | figure(1) 82 | % original data 83 | subplot(211), plot(localmin,data(localmin),'ks','markerfacecolor','m') 84 | 85 | % convolution result 86 | subplot(212), plot(localmin,convres(localmin),'ks','markerfacecolor','m') 87 | 88 | %% now extract time series for windowing 89 | 90 | % remove local minima that are too close to the edges 91 | localmin(localminN-round(k/2)) = []; 93 | 94 | 95 | % initialize data matrix 96 | datamatrix = zeros(length(localmin),k); 97 | % enter data snippets into matrix 98 | for ei=1:length(localmin) 99 | datamatrix(ei,:) = data(localmin(ei)-round(k/2):localmin(ei)+round(k/2)-1); 100 | end 101 | 102 | % show all snippets 103 | figure(3), clf 104 | subplot(4,1,1:3) 105 | imagesc(datamatrix) 106 | xlabel('Time'), ylabel('Event number') 107 | title('All events') 108 | 109 | % snippet average against ground truth 110 | subplot(414) 111 | plot(1:k,mean(datamatrix), 1:k,event,'linew',3) 112 | xlabel('Time'), ylabel('Amplitude') 113 | legend({'Averaged';'Ground-truth'}) 114 | title('Average events') 115 | 116 | %% done. 117 | -------------------------------------------------------------------------------- /filtering/XC403881.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/filtering/XC403881.mp3 -------------------------------------------------------------------------------- /filtering/XC403881.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/filtering/XC403881.wav -------------------------------------------------------------------------------- /filtering/filtering_codeChallenge.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/filtering/filtering_codeChallenge.mat -------------------------------------------------------------------------------- /filtering/lineNoiseData.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/filtering/lineNoiseData.mat -------------------------------------------------------------------------------- /filtering/sigprocMXC_2stageWide.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Filtering 4 | % VIDEO: Two-stage wide-band filter 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | clear 10 | 11 | % define filter parameters 12 | lower_bnd = 10; % Hz 13 | upper_bnd = 60; % Hz 14 | 15 | transw = .1; 16 | 17 | samprate = 2048; % Hz 18 | filtorder = 8*round(samprate/lower_bnd); 19 | 20 | filter_shape = [ 0 0 1 1 0 0 ]; 21 | filter_freqs = [ 0 lower_bnd*(1-transw) lower_bnd ... 22 | upper_bnd upper_bnd+upper_bnd*transw ... 23 | (samprate/2) ] / (samprate/2); 24 | 25 | filterkern = firls(filtorder,filter_freqs,filter_shape); 26 | hz = linspace(0,samprate/2,floor(length(filterkern)/2)+1); 27 | filterpow = abs(fft(filterkern)).^2; 28 | 29 | 30 | 31 | figure(1), clf 32 | subplot(221) 33 | plot(filterkern,'linew',2) 34 | xlabel('Time points') 35 | title('Filter kernel (firls)') 36 | axis square 37 | 38 | 39 | 40 | % plot amplitude spectrum of the filter kernel 41 | subplot(222), hold on 42 | plot(hz,filterpow(1:length(hz)),'ks-','linew',2,'markerfacecolor','w') 43 | 44 | plot(filter_freqs*samprate/2,filter_shape,'ro-','linew',2,'markerfacecolor','w') 45 | 46 | % make the plot look nicer 47 | set(gca,'xlim',[0 upper_bnd+40]) 48 | xlabel('Frequency (Hz)'), ylabel('Filter gain') 49 | legend({'Actual';'Ideal'}) 50 | title('Frequency response of filter (firls)') 51 | axis square 52 | 53 | %% generate white noise signal 54 | 55 | N = samprate*4; 56 | noise = randn(N,1); 57 | timevec = (0:length(noise)-1)/samprate; 58 | 59 | %% a better way... 60 | 61 | %%% first apply a high-pass filter 62 | forder = 14*round(samprate/lower_bnd); 63 | filtkern = fir1(forder,lower_bnd/(samprate/2),'high'); 64 | 65 | % spectrum of kernel 66 | subplot(212), hold on 67 | hz = linspace(0,samprate/2,floor(length(filtkern)/2)+1); 68 | filterpow = abs(fft(filtkern)).^2; 69 | plot(hz,filterpow(1:length(hz)),'k','linew',2) 70 | 71 | 72 | % zero-phase-shift filter with reflection 73 | noiseR = [noise(end:-1:1); noise; noise(end:-1:1)]; % reflect 74 | fnoise = filter(filtkern,1,noiseR); % forward filter 75 | fnoise = filter(filtkern,1,fnoise(end:-1:1)); % reverse filter 76 | fnoise = fnoise(end:-1:1); % reverse again for 0phase 77 | fnoise = fnoise(N+1:end-N); % chop off reflected parts 78 | 79 | 80 | 81 | 82 | %%% repeat for low-pass filter 83 | forder = 20*round(samprate/upper_bnd); 84 | filtkern = fir1(forder,upper_bnd/(samprate/2),'low'); 85 | 86 | % spectrum of kernel 87 | hz = linspace(0,samprate/2,floor(length(filtkern)/2)+1); 88 | filterpow = abs(fft(filtkern)).^2; 89 | plot(hz,filterpow(1:length(hz)),'r','linew',2) 90 | plot(repmat([lower_bnd upper_bnd],2,1),repmat([0; 1],1,2),'k--') 91 | set(gca,'xlim',[0 upper_bnd*2]) 92 | 93 | % zero-phase-shift filter with reflection 94 | noiseR = [fnoise(end:-1:1); fnoise; fnoise(end:-1:1)]; % reflect 95 | fnoise = filter(filtkern,1,noiseR); % forward filter 96 | fnoise = filter(filtkern,1,fnoise(end:-1:1)); % reverse filter 97 | fnoise = fnoise(end:-1:1); % reverse again for 0phase 98 | fnoise = fnoise(N+1:end-N); % chop off reflected parts 99 | 100 | 101 | % or with the signal-processing toolbox: 102 | % fnoise = filtfilt(filtkern,1,fnoise); 103 | 104 | %% plotting 105 | 106 | figure(2), clf 107 | 108 | subplot(211) 109 | plot(timevec,noise, timevec,fnoise) 110 | xlabel('Time (a.u.)'), ylabel('Amplitude') 111 | title('Filtered noise in the time domain') 112 | 113 | 114 | % plot power spectrum 115 | noiseX = abs(fft(noise)).^2; 116 | fnoiseX = abs(fft(fnoise)).^2; 117 | hz = linspace(0,samprate,length(fnoise)); 118 | 119 | subplot(212) 120 | plot(hz,noiseX, hz,fnoiseX) 121 | set(gca,'xlim',[0 upper_bnd*1.5]) 122 | xlabel('Frequency (Hz)'), ylabel('Power') 123 | title('Spectrum of filtered noise') 124 | 125 | %% done. 126 | -------------------------------------------------------------------------------- /filtering/sigprocMXC_butter.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Filtering 4 | % VIDEO: IIR Butterworth filters 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | clear 10 | 11 | srate = 1024; % hz 12 | nyquist = srate/2; 13 | frange = [20 45]; 14 | 15 | % create filter coefficients 16 | [fkernB,fkernA] = butter(4,frange/nyquist); 17 | 18 | % power spectrum of filter coefficients 19 | filtpow = abs(fft(fkernB)).^2; 20 | hz = linspace(0,srate/2,floor(length(fkernB)/2)+1); 21 | 22 | 23 | %%% plotting 24 | figure(1), clf 25 | subplot(221), hold on 26 | plot(fkernB*1e5,'ks-','linew',2,'markersize',10,'markerfacecolor','w') 27 | plot(fkernA,'rs-','linew',2,'markersize',10,'markerfacecolor','w') 28 | xlabel('Time points'), ylabel('Filter coeffs.') 29 | title('Time-domain filter coefficients') 30 | legend({'B';'A'}) 31 | 32 | 33 | subplot(222) 34 | stem(hz,filtpow(1:length(hz)),'ks-','linew',2,'markersize',10,'markerfacecolor','w') 35 | xlabel('Frequency (Hz)'), ylabel('Power') 36 | title('Power spectrum of filter coeffs.') 37 | 38 | 39 | %% how to evaluate an IIR filter: filter an impulse 40 | 41 | % generate the impulse 42 | impres = [ zeros(1,500) 1 zeros(1,500) ]; 43 | 44 | % apply the filter 45 | fimp = filter(fkernB,fkernA,impres); 46 | 47 | % compute power spectrum 48 | fimpX = abs(fft(fimp)).^2; 49 | hz = linspace(0,nyquist,floor(length(impres)/2)+1); 50 | 51 | 52 | %%% plot 53 | subplot(222), cla, hold on 54 | plot(impres,'k','linew',2) 55 | plot(fimp,'r','linew',2) 56 | set(gca,'xlim',[1 length(impres)],'ylim',[-1 1]*.06) 57 | legend({'Impulse';'Filtered'}) 58 | xlabel('Time points (a.u.)') 59 | title('Filtering an impulse') 60 | 61 | 62 | subplot(223), hold on 63 | plot(hz,fimpX(1:length(hz)),'ks-','linew',2,'markerfacecolor','w','markersize',10) 64 | plot([0 frange(1) frange frange(2) nyquist],[0 0 1 1 0 0],'r','linew',4) 65 | set(gca,'xlim',[0 100]) 66 | xlabel('Frequency (Hz)'), ylabel('Attenuation') 67 | title('Frequency response of filter (Butterworth)') 68 | 69 | 70 | subplot(224) 71 | plot(hz,10*log10(fimpX(1:length(hz))),'ks-','linew',2,'markerfacecolor','w','markersize',10) 72 | set(gca,'xlim',[0 100]) 73 | xlabel('Frequency (Hz)'), ylabel('Attenuation (log)') 74 | title('Frequency response of filter (Butterworth)') 75 | 76 | %% effects of order parameter 77 | 78 | orders = 2:7; 79 | 80 | fkernX = zeros(length(orders),1001); 81 | hz = linspace(0,srate,1001); 82 | 83 | figure(2), clf 84 | for oi=1:length(orders) 85 | 86 | % create filter kernel 87 | [fkernB,fkernA] = butter(orders(oi),frange/nyquist); 88 | n(oi) = length(fkernB); 89 | 90 | % filter the impulse response and take its power 91 | fimp = filter(fkernB,fkernA,impres); 92 | fkernX(oi,:) = abs(fft(fimp)).^2; 93 | 94 | 95 | % show in plot 96 | subplot(221), hold on 97 | plot((1:n(oi))-n(oi)/2,zscore(fkernB)+oi,'linew',2) 98 | 99 | subplot(222), hold on 100 | plot((1:n(oi))-n(oi)/2,zscore(fkernA)+oi,'linew',2) 101 | end 102 | 103 | % add plot labels 104 | subplot(221) 105 | xlabel('Time points') 106 | title('Filter coefficients (B)') 107 | 108 | subplot(222) 109 | xlabel('Time points') 110 | title('Filter coefficients (A)') 111 | 112 | 113 | % plot the spectra 114 | subplot(223), hold on 115 | plot(hz,fkernX,'linew',2) 116 | plot([0 frange(1) frange frange(2) nyquist],[0 0 1 1 0 0],'r','linew',4) 117 | set(gca,'xlim',[0 100]) 118 | xlabel('Frequency (Hz)'), ylabel('Attenuation') 119 | title('Frequency response of filter (Butterworth)') 120 | 121 | % in log space 122 | subplot(224) 123 | plot(hz,10*log10(fkernX),'linew',2) 124 | set(gca,'xlim',[0 100],'ylim',[-80 2]) 125 | xlabel('Frequency (Hz)'), ylabel('Attenuation (log)') 126 | title('Frequency response of filter (Butterworth)') 127 | 128 | %% done. 129 | -------------------------------------------------------------------------------- /filtering/sigprocMXC_causal0phase.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Filtering 4 | % VIDEO: Causal and zero-phase-shift filters 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | clear 10 | 11 | % create a simple signal 12 | signal = [ zeros(1,100) cos(linspace(pi/2,5*pi/2,10)) zeros(1,100) ]; 13 | n = length(signal); 14 | 15 | % plot it and its power spectrum 16 | figure(1), clf 17 | subplot(221) 18 | plot(1:n,signal,'ko-') 19 | set(gca,'xlim',[0 n+1]) 20 | title('Original signal') 21 | xlabel('Time points (a.u.)') 22 | 23 | 24 | subplot(222) 25 | plot(linspace(0,1,n),abs(fft(signal)),'ko-','markerfacecolor','w') 26 | set(gca,'xlim',[0 .5]) 27 | xlabel('Frequency (norm.)'), ylabel('Energy') 28 | title('Frequency-domain signal representation') 29 | 30 | %% apply a low-pass causal filter 31 | 32 | % note: frequency listed as fraction of Nyquist (not sampling rate!) 33 | fkern = fir1(50,.6,'low'); 34 | fsignal = filter(fkern,1,signal); 35 | subplot(234) 36 | plot(1:n,signal, 1:n,fsignal) 37 | set(gca,'xlim',[0 n+1]), axis square 38 | xlabel('Time (a.u.)') 39 | legend({'Original';'Forward filtered'}) 40 | 41 | 42 | % flip the signal backwards 43 | fsignalFlip = fsignal(end:-1:1); 44 | % and show its spectrum 45 | subplot(222), hold on 46 | plot(linspace(0,1,n),abs(fft(fsignal)),'r','linew',3) 47 | 48 | 49 | 50 | % filter the flipped signal 51 | fsignalF = filter(fkern,1,fsignalFlip); 52 | subplot(235) 53 | plot(1:n,signal, 1:n,fsignalF) 54 | set(gca,'xlim',[0 n+1]), axis square 55 | xlabel('Time (a.u.)') 56 | legend({'Original';'Backward filtered'}) 57 | 58 | 59 | % finally, flip the double-filtered signal 60 | fsignalF = fsignalF(end:-1:1); 61 | subplot(236) 62 | plot(1:n,signal, 1:n,fsignalF) 63 | set(gca,'xlim',[0 n+1]), axis square 64 | xlabel('Time (a.u.)') 65 | legend({'Original';'Zero-phase filtered'}) 66 | 67 | subplot(222) 68 | plot(linspace(0,1,n),abs(fft(fsignalF)),'mo','markersize',7,'markerfacecolor','w','linew',2) 69 | legend({'Original';'Forward filtered';'Zero-phase-shift'}) 70 | 71 | 72 | %% done. 73 | -------------------------------------------------------------------------------- /filtering/sigprocMXC_filterTheBirds.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Filtering 4 | % VIDEO: Use filtering to separate birds in a recording 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | [bc,fs] = audioread('XC403881.mp3'); 10 | N = length(bc); 11 | 12 | % compute and show spectrogram 13 | [powspect,frex,time] = spectrogram(detrend(bc(:,2)),hann(1000),100,[],fs); 14 | figure(1), clf 15 | imagesc(time,frex,abs(powspect).^2) 16 | axis xy 17 | set(gca,'clim',[0 1]*2,'ylim',frex([1 dsearchn(frex,15000)]),'xlim',time([1 end])) 18 | xlabel('Time (sec.)'), ylabel('Frequency (Hz)') 19 | colormap hot 20 | 21 | %% select frequency ranges (visual inspection) 22 | 23 | frange{1} = [1700 2600]; 24 | frange{2} = [5100 6100]; 25 | 26 | % draw boundary lines on the plot 27 | colorz = 'wm'; 28 | hold on 29 | for fi=1:length(frange) 30 | plot(get(gca,'xlim'),[1 1]*frange{fi}(1),[colorz(fi) '--' ]) 31 | plot(get(gca,'xlim'),[1 1]*frange{fi}(2),[colorz(fi) '--' ]) 32 | end 33 | 34 | %% compute and apply FIR filters 35 | 36 | % initialize output matrix 37 | filteredSig = cell(2,1); 38 | 39 | % loop over filters 40 | for filteri=1:length(frange) 41 | 42 | % design filter kernel 43 | order = round( 10*fs/frange{1}(1) ); 44 | filtkern = fir1(order,frange{filteri}/(fs/2)); 45 | 46 | % loop over channels 47 | for chani=1:2 48 | 49 | % get data from this channel 50 | dat1chan = bc(:,chani); 51 | 52 | % zero-phase-shift filter with reflection 53 | sigR = [dat1chan(end:-1:1); dat1chan; dat1chan(end:-1:1)]; % reflect 54 | fsig = filter(filtkern,1,sigR); % forward filter 55 | fsig = filter(filtkern,1,fsig(end:-1:1)); % reverse filter 56 | fsig = fsig(end:-1:1); % reverse again for 0phase 57 | fsig = fsig(N+1:end-N); % chop off reflected parts 58 | 59 | % enter into the matrix 60 | filteredSig{filteri}(:,chani) = fsig; 61 | end 62 | end 63 | 64 | %% play 65 | 66 | % original 67 | soundsc(bc,fs) 68 | 69 | % lower frequency range 70 | soundsc(filteredSig{1},fs) 71 | 72 | % higher frequency range 73 | soundsc(filteredSig{2},fs) 74 | 75 | %% done. 76 | -------------------------------------------------------------------------------- /filtering/sigprocMXC_fir1.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Filtering 4 | % VIDEO: FIR filters with fir1 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | clear 10 | 11 | srate = 1024; % hz 12 | nyquist = srate/2; 13 | frange = [20 45]; 14 | order = round( 5*srate/frange(1) ); 15 | 16 | % filter kernel 17 | filtkern = fir1(order,frange/nyquist); 18 | 19 | % compute the power spectrum of the filter kernel 20 | filtpow = abs(fft(filtkern)).^2; 21 | % compute the frequencies vector and remove negative frequencies 22 | hz = linspace(0,srate/2,floor(length(filtkern)/2)+1); 23 | filtpow = filtpow(1:length(hz)); 24 | 25 | 26 | 27 | 28 | figure(4), clf 29 | subplot(131) 30 | plot(filtkern,'linew',2) 31 | xlabel('Time points') 32 | title('Filter kernel (fir1)') 33 | axis square 34 | 35 | 36 | 37 | % plot amplitude spectrum of the filter kernel 38 | subplot(132), hold on 39 | plot(hz,filtpow,'ks-','linew',2,'markerfacecolor','w') 40 | 41 | plot([0 frange(1) frange frange(2) nyquist],[0 0 1 1 0 0],'ro-','linew',2,'markerfacecolor','w') 42 | 43 | % dotted line corresponding to the lower edge of the filter cut-off 44 | plot([1 1]*frange(1),get(gca,'ylim'),'k:') 45 | 46 | % make the plot look nicer 47 | set(gca,'xlim',[0 frange(1)*4])%,'ylim',[-.05 1.05]) 48 | xlabel('Frequency (Hz)'), ylabel('Filter gain') 49 | legend({'Actual';'Ideal'}) 50 | title('Frequency response of filter (fir1)') 51 | axis square 52 | 53 | 54 | subplot(133), hold on 55 | plot(hz,10*log10(filtpow),'ks-','linew',2,'markersize',10,'markerfacecolor','w') 56 | plot([1 1]*frange(1),get(gca,'ylim'),'k:') 57 | set(gca,'xlim',[0 frange(1)*4],'ylim',[-80 2]) 58 | xlabel('Frequency (Hz)'), ylabel('Filter gain (dB)') 59 | title('Frequency response of filter (fir1)') 60 | axis square 61 | 62 | 63 | %% effect of order parameter 64 | 65 | orders = round( linspace( (1*srate/frange(1)) / (srate/1000), ... 66 | (15*srate/frange(1)) / (srate/1000) ,10) ); 67 | 68 | fkern = cell(size(orders)); 69 | fkernX = zeros(length(orders),1000); 70 | hz = linspace(0,srate,1000); 71 | 72 | figure(5), clf 73 | for oi=1:length(orders) 74 | 75 | % create filter kernel 76 | fkern = fir1(orders(oi),frange/nyquist); 77 | n(oi) = length(fkern); 78 | 79 | % take its FFT 80 | fkernX(oi,:) = abs(fft(fkern,1000)).^2; 81 | 82 | % show in plot 83 | subplot(211), hold on 84 | plot((1:n(oi))-n(oi)/2,fkern+.01*oi,'linew',2) 85 | end 86 | xlabel('Time (ms)') 87 | title('Filter kernels (fir1)') 88 | 89 | 90 | 91 | subplot(223), hold on 92 | plot(hz,fkernX,'linew',2) 93 | plot([0 frange(1) frange frange(2) nyquist],[0 0 1 1 0 0],'ro-','linew',2,'markerfacecolor','w') 94 | set(gca,'xlim',[0 100]) 95 | xlabel('Frequency (Hz)'), ylabel('Attenuation') 96 | title('Frequency response of filter (fir1)') 97 | 98 | 99 | subplot(224) 100 | plot(hz,10*log10(fkernX),'linew',2) 101 | set(gca,'xlim',[0 100]) 102 | xlabel('Frequency (Hz)'), ylabel('Attenuation (log)') 103 | title('Frequency response of filter (fir1)') 104 | 105 | %% done. 106 | -------------------------------------------------------------------------------- /filtering/sigprocMXC_firls.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Filtering 4 | % VIDEO: FIR filters with firls 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | srate = 1024; % hz 10 | nyquist = srate/2; 11 | frange = [20 45]; 12 | transw = .1; 13 | order = round( 5*srate/frange(1) ); 14 | 15 | shape = [ 0 0 1 1 0 0 ]; 16 | frex = [ 0 frange(1)-frange(1)*transw frange frange(2)+frange(2)*transw nyquist ] / nyquist; 17 | 18 | % filter kernel 19 | filtkern = firls(order,frex,shape); 20 | 21 | % compute the power spectrum of the filter kernel 22 | filtpow = abs(fft(filtkern)).^2; 23 | % compute the frequencies vector and remove negative frequencies 24 | hz = linspace(0,srate/2,floor(length(filtkern)/2)+1); 25 | filtpow = filtpow(1:length(hz)); 26 | 27 | 28 | 29 | 30 | figure(1), clf 31 | subplot(131) 32 | plot(filtkern,'linew',2) 33 | xlabel('Time points') 34 | title('Filter kernel (firls)') 35 | axis square 36 | 37 | 38 | 39 | % plot amplitude spectrum of the filter kernel 40 | subplot(132), hold on 41 | plot(hz,filtpow,'ks-','linew',2,'markerfacecolor','w') 42 | plot(frex*nyquist,shape,'ro-','linew',2,'markerfacecolor','w') 43 | 44 | 45 | % make the plot look nicer 46 | set(gca,'xlim',[0 frange(1)*4]) 47 | xlabel('Frequency (Hz)'), ylabel('Filter gain') 48 | legend({'Actual';'Ideal'}) 49 | title('Frequency response of filter (firls)') 50 | axis square 51 | 52 | 53 | subplot(133), hold on 54 | plot(hz,10*log10(filtpow),'ks-','linew',2,'markersize',10,'markerfacecolor','w') 55 | plot([1 1]*frange(1),get(gca,'ylim'),'k:') 56 | set(gca,'xlim',[0 frange(1)*4],'ylim',[-50 2]) 57 | xlabel('Frequency (Hz)'), ylabel('Filter gain (dB)') 58 | title('Frequency response of filter (firls)') 59 | axis square 60 | 61 | %% effects of the filter kernel order 62 | 63 | % range of orders 64 | ordersF = ( 1*srate/frange(1)) / (srate/1000); 65 | ordersL = (15*srate/frange(1)) / (srate/1000); 66 | 67 | orders = round( linspace(ordersF,ordersL,10) ); 68 | 69 | 70 | % initialize 71 | fkernX = zeros(length(orders),1000); 72 | hz = linspace(0,srate,1000); 73 | 74 | figure(2), clf 75 | for oi=1:length(orders) 76 | 77 | % create filter kernel 78 | fkern = firls(orders(oi),frex,shape); 79 | n(oi) = length(fkern); 80 | 81 | % take its FFT 82 | fkernX(oi,:) = abs(fft(fkern,1000)).^2; 83 | 84 | % show in plot 85 | subplot(211), hold on 86 | plot((1:n(oi))-n(oi)/2,fkern+.01*oi,'linew',2) 87 | end 88 | xlabel('Time (ms)') 89 | set(gca,'ytick',[]) 90 | title('Filter kernels with different orders') 91 | 92 | 93 | subplot(223), hold on 94 | plot(hz,fkernX,'linew',2) 95 | plot(frex*nyquist,shape,'k','linew',4) 96 | set(gca,'xlim',[0 100]) 97 | xlabel('Frequency (Hz)'), ylabel('Attenuation') 98 | title('Frequency response of filter (firls)') 99 | 100 | 101 | subplot(224) 102 | plot(hz,10*log10(fkernX),'linew',2) 103 | set(gca,'xlim',[0 100]) 104 | xlabel('Frequency (Hz)'), ylabel('Attenuation (log)') 105 | title('Frequency response of filter (firls)') 106 | 107 | %% effects of the filter transition width 108 | 109 | % range of transitions 110 | transwidths = linspace(.01,.4,10); 111 | 112 | 113 | % initialize 114 | fkernX = zeros(length(transwidths),1000); 115 | hz = linspace(0,srate,1000); 116 | 117 | figure(3), clf 118 | for ti=1:length(transwidths) 119 | 120 | % create filter kernel 121 | frex = [ 0 frange(1)-frange(1)*transwidths(ti) frange(1) frange(2) frange(2)+frange(2)*transwidths(ti) nyquist ] / nyquist; 122 | fkern = firls(400,frex,shape); 123 | n(ti) = length(fkern); 124 | 125 | % take its FFT 126 | fkernX(ti,:) = abs(fft(fkern,1000)).^2; 127 | 128 | % show in plot 129 | subplot(211), hold on 130 | plot((1:n(ti))-n(ti)/2,fkern+.01*ti,'linew',2) 131 | end 132 | xlabel('Time (ms)') 133 | set(gca,'ytick',[]) 134 | title('Filter kernels with different transition widths') 135 | 136 | 137 | subplot(223), hold on 138 | plot(hz,fkernX,'linew',2) 139 | plot(frex*nyquist,shape,'k','linew',4) 140 | set(gca,'xlim',[0 100]) 141 | xlabel('Frequency (Hz)'), ylabel('Attenuation') 142 | title('Frequency response of filter (firls)') 143 | 144 | 145 | subplot(224) 146 | plot(hz,10*log10(fkernX),'linew',2) 147 | set(gca,'xlim',[0 100]) 148 | xlabel('Frequency (Hz)'), ylabel('Attenuation (log)') 149 | title('Frequency response of filter (firls)') 150 | 151 | %% done. 152 | -------------------------------------------------------------------------------- /filtering/sigprocMXC_highpass.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Filtering 4 | % VIDEO: High-pass filters 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % generate 1/f noise 10 | N = 8000; 11 | fs = 1000; 12 | as = rand(1,N) .* exp(-(0:N-1)/200); 13 | fc = as .* exp(1i*2*pi*rand(size(as))); 14 | noise = real(ifft(fc)) * N; 15 | 16 | 17 | 18 | %%% create frequency-domain Gaussian 19 | hz = linspace(0,fs,N); 20 | s = 4*(2*pi-1)/(4*pi); % normalized width 21 | x = hz-30; % shifted frequencies 22 | fg = exp(-.5*(x/s).^2); % gaussian 23 | 24 | fc = rand(1,N) .* exp(1i*2*pi*rand(1,N)); 25 | fc = fc .* fg; 26 | 27 | % generate signal from Fourier coefficients, and add noise 28 | signal = real( ifft(fc) )*N; 29 | data = signal + noise; 30 | time = (0:N-1)/fs; 31 | 32 | 33 | 34 | %%% plot the data 35 | figure(1), clf 36 | subplot(211) 37 | plot(time,data) 38 | xlabel('Time (s)'), ylabel('Amplitude') 39 | title('Data = signal + noise') 40 | 41 | 42 | subplot(212), hold on 43 | plot(hz,abs(fft(signal)/N).^2,'linew',2); 44 | plot(hz,abs(fft(noise)/N).^2,'linew',2); 45 | legend({'Signal';'Noise'}) 46 | set(gca,'xlim',[0 100]) 47 | title('Frequency domain representation of signal and noise') 48 | xlabel('Frequency (Hz)'), ylabel('Power') 49 | 50 | %% now for high-pass filter 51 | 52 | % specify filter cutoff (in Hz) 53 | filtcut = 25; 54 | 55 | % generate filter coefficients (Butterworth) 56 | [filtb,filta] = butter(7,filtcut/(fs/2),'high'); 57 | 58 | % test impulse response function (IRF) 59 | impulse = [ zeros(1,500) 1 zeros(1,500) ]; 60 | fimpulse = filtfilt(filtb,filta,impulse); 61 | imptime = (0:length(impulse)-1)/fs; 62 | 63 | 64 | % plot impulse and IRF 65 | figure(2), clf 66 | subplot(321) 67 | plot(imptime,impulse, imptime,fimpulse./max(fimpulse),'linew',2) 68 | xlabel('Time (s)') 69 | set(gca,'ylim',[-.1 .1]) 70 | legend({'Impulse';'Impulse response'}) 71 | title('Time domain filter characteristics') 72 | 73 | 74 | % plot spectrum of IRF 75 | subplot(322), hold on 76 | hz = linspace(0,fs/2,3000); 77 | imppow = abs(fft(fimpulse,2*length(hz))).^2; 78 | plot(hz,imppow(1:length(hz)),'k','linew',1) 79 | plot([1 1]*filtcut,get(gca,'ylim'),'r--') 80 | set(gca,'xlim',[0 60],'ylim',[0 1]) 81 | xlabel('Frequency (Hz)'), ylabel('Gain') 82 | title('Frequency domain filter characteristics') 83 | 84 | 85 | % now filter the data and compare against the original 86 | subplot(312) 87 | fdata = filtfilt(filtb,filta,data); 88 | plot(time,signal, time,fdata,'linew',1) 89 | legend({'Original';'Filtered'}) 90 | xlabel('Time (s)'), ylabel('Amplitude') 91 | title('Time domain') 92 | 93 | 94 | 95 | %%% power spectra of original and filtered signal 96 | signalX = abs(fft(signal)/N).^2; 97 | fdataX = abs(fft(fdata)/N).^2; 98 | hz = linspace(0,fs,N); 99 | 100 | subplot(313) 101 | plot(hz,signalX(1:length(hz)), hz,fdataX(1:length(hz)),'linew',1); 102 | set(gca,'xlim',[20 60]) 103 | legend({'Original';'Filtered'}) 104 | xlabel('Frequency (Hz)'), ylabel('Power') 105 | title('Frequency domain') 106 | 107 | %% done. 108 | -------------------------------------------------------------------------------- /filtering/sigprocMXC_linenoise.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Filtering 4 | % VIDEO: Remove electrical line noise and its harmonics 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % load data 10 | load lineNoiseData.mat 11 | 12 | % time vector 13 | pnts = length(data); 14 | time = (0:pnts-1)/srate; 15 | 16 | % compute power spectrum and frequencies vector 17 | pwr = abs(fft(data)/pnts).^2; 18 | hz = linspace(0,srate,pnts); 19 | 20 | 21 | %%% plotting 22 | figure(1), clf 23 | % time-domain signal 24 | subplot(211) 25 | plot(time,data,'k') 26 | xlabel('Time (s)'), ylabel('Amplitude') 27 | title('Time domain') 28 | 29 | % plot power spectrum 30 | subplot(212) 31 | plot(hz,pwr,'k') 32 | set(gca,'xlim',[0 400],'ylim',[0 2]) 33 | xlabel('Frequency (Hz)'), ylabel('Power') 34 | title('Frequency domain') 35 | 36 | %% narrowband filter to remove line noise 37 | 38 | frex2notch = [ 50 150 250 ]; 39 | 40 | % initialize filtered signal 41 | datafilt = data; 42 | 43 | % loop over frequencies 44 | for fi=1:length(frex2notch) 45 | 46 | % create filter kernel using fir1 47 | frange = [frex2notch(fi)-.5 frex2notch(fi)+.5]; 48 | order = round( 150*srate/frange(1) ); 49 | 50 | % filter kernel 51 | filtkern = fir1( order,frange/(srate/2),'stop' ); 52 | 53 | % visualize the kernel and its spectral response 54 | figure(2) 55 | subplot(length(frex2notch),2,(fi-1)*2+1) 56 | xlabel('Time points'), ylabel('Filter amplitude') 57 | 58 | plot(filtkern) 59 | subplot(length(frex2notch),2,(fi-1)*2+2) 60 | plot(linspace(0,srate,10000),abs(fft(filtkern,10000)).^2) 61 | set(gca,'xlim',[frex2notch(fi)-30 frex2notch(fi)+30]) 62 | xlabel('Frequency (Hz)'), ylabel('Filter gain') 63 | 64 | 65 | % recursively apply to data 66 | datafilt = filtfilt(filtkern,1,datafilt); 67 | 68 | end 69 | 70 | %%% plot the signal 71 | figure(3), clf 72 | subplot(211), hold on 73 | plot(time,data,'k') 74 | h = plot(time,datafilt); 75 | set(h,'color',[1 .9 1]*.8) 76 | xlabel('Time (s)') 77 | legend({'Original';'Notched'}) 78 | 79 | 80 | 81 | % compute the power spectrum of the filtered signal 82 | pwrfilt = abs(fft(datafilt)/pnts).^2; 83 | 84 | % plot power spectrum 85 | subplot(212), cla, hold on 86 | plot(hz,pwr,'k') 87 | h = plot(hz,pwrfilt); 88 | set(h,'color',[1 .7 1]*.6) 89 | set(gca,'xlim',[0 400],'ylim',[0 2]) 90 | xlabel('Frequency (Hz)'), ylabel('Power') 91 | title('Frequency domain') 92 | 93 | %% done. 94 | -------------------------------------------------------------------------------- /filtering/sigprocMXC_lowpass.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Filtering 4 | % VIDEO: Low-pass filters 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % simulation parameters 10 | fs = 350; % hz 11 | timevec = (0:fs*7-1)/fs; 12 | npnts = length(timevec); 13 | 14 | % generate signal 15 | yOrig = cumsum(randn(npnts,1)); 16 | y = yOrig + 50*randn(npnts,1) + 40*sin(2*pi*50*timevec)'; 17 | 18 | % power spectrum of signal 19 | yX = abs(fft(y)/npnts).^2; 20 | hz = linspace(0,fs/2,floor(npnts/2)+1); 21 | 22 | 23 | % plot the data 24 | figure(1), clf 25 | subplot(211) 26 | h = plot(timevec,y, timevec,yOrig,'linew',1); 27 | set(h(1),'Color',[1 1 1]*.7) 28 | set(h(2),'linew',2) 29 | xlabel('Time (sec.)'), ylabel('Power') 30 | title('Time domain') 31 | legend({'Measured';'Original'}) 32 | 33 | % plot its power spectrum 34 | subplot(212) 35 | plot(hz,yX(1:length(hz)),'k','linew',1) 36 | xlabel('Frequency (Hz)'), ylabel('Power') 37 | title('Frequency domain') 38 | set(gca,'yscale','log') 39 | 40 | %% now for lowpass filter 41 | 42 | fcutoff = 30; 43 | transw = .2; 44 | order = round( 7*fs/fcutoff ); 45 | 46 | shape = [ 1 1 0 0 ]; 47 | frex = [ 0 fcutoff fcutoff+fcutoff*transw fs/2 ] / (fs/2); 48 | 49 | % filter kernel 50 | filtkern = firls(order,frex,shape); 51 | 52 | % its power spectrum 53 | filtkernX = abs(fft(filtkern,npnts)).^2; 54 | 55 | 56 | 57 | figure(2), clf 58 | subplot(321) 59 | plot((-order/2:order/2)/fs,filtkern,'k','linew',3) 60 | xlabel('Time (s)') 61 | title('Filter kernel') 62 | 63 | subplot(322), hold on 64 | plot(frex*fs/2,shape,'r','linew',1) 65 | plot(hz,filtkernX(1:length(hz)),'k','linew',2) 66 | set(gca,'xlim',[0 60]) 67 | xlabel('Frequency (Hz)'), ylabel('Gain') 68 | title('Filter kernel spectrum') 69 | 70 | 71 | 72 | %%% now apply the filter to the data 73 | yFilt = filtfilt(filtkern,1,y); 74 | 75 | subplot(312) 76 | h = plot(timevec,y, timevec,yFilt,'linew',2); 77 | set(h(1),'color',[1 1 1]*.4) 78 | legend({'Signal';'Filtered'}) 79 | xlabel('Time (sec.)'), ylabel('Amplitude') 80 | 81 | 82 | %%% power spectra of original and filtered signal 83 | yOrigX = abs(fft(y)/npnts).^2; 84 | yFiltX = abs(fft(yFilt)/npnts).^2; 85 | 86 | subplot(313) 87 | plot(hz,yOrigX(1:length(hz)), hz,yFiltX(1:length(hz)),'linew',2); 88 | set(gca,'xlim',[0 fs/5],'yscale','log') 89 | legend({'Signal';'Filtered'}) 90 | xlabel('Frequency (Hz)'), ylabel('Power (log)') 91 | 92 | %% done. 93 | -------------------------------------------------------------------------------- /filtering/sigprocMXC_narrowband.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Filtering 4 | % VIDEO: Narrow-band filters 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % define filter parameters 10 | lower_bnd = 10; % Hz 11 | upper_bnd = 18; % Hz 12 | 13 | lower_trans = .1; 14 | upper_trans = .4; 15 | 16 | samprate = 2048; % Hz 17 | filtorder = 4*round(samprate/lower_bnd); 18 | 19 | filter_shape = [ 0 0 1 1 0 0 ]; 20 | filter_freqs = [ 0 lower_bnd*(1-lower_trans) lower_bnd ... 21 | upper_bnd upper_bnd+upper_bnd*upper_trans ... 22 | (samprate/2) ] / (samprate/2); 23 | 24 | filterkern = firls(filtorder,filter_freqs,filter_shape); 25 | hz = linspace(0,samprate/2,floor(length(filterkern)/2)+1); 26 | filterpow = abs(fft(filterkern)).^2; 27 | 28 | 29 | 30 | figure(1), clf 31 | subplot(221) 32 | plot(filterkern,'linew',2) 33 | xlabel('Time points') 34 | title('Filter kernel (firls)') 35 | axis square 36 | 37 | 38 | 39 | % plot amplitude spectrum of the filter kernel 40 | subplot(222), hold on 41 | plot(hz,filterpow(1:length(hz)),'ks-','linew',2,'markerfacecolor','w') 42 | 43 | plot(filter_freqs*samprate/2,filter_shape,'ro-','linew',2,'markerfacecolor','w') 44 | 45 | % make the plot look nicer 46 | set(gca,'xlim',[0 upper_bnd+20]) 47 | xlabel('Frequency (Hz)'), ylabel('Filter gain') 48 | legend({'Actual';'Ideal'}) 49 | title('Frequency response of filter (firls)') 50 | axis square 51 | 52 | %% now apply to random noise data 53 | 54 | filtnoise = filtfilt(filterkern,1,randn(samprate*4,1)); 55 | timevec = (0:length(filtnoise)-1)/samprate; 56 | 57 | % plot time series 58 | subplot(2,4,5:7) 59 | plot(timevec,filtnoise,'k','linew',2) 60 | xlabel('Time (a.u.)'), ylabel('Amplitude') 61 | title('Filtered noise in the time domain') 62 | 63 | 64 | % plot power spectrum 65 | subplot(248) 66 | noisepower = abs(fft(filtnoise)).^2; 67 | plot(linspace(0,samprate,length(noisepower)),noisepower,'k') 68 | set(gca,'xlim',[0 60]) 69 | xlabel('Frequency (Hz)'), ylabel('Power') 70 | title('Spectrum of filtered noise') 71 | 72 | %% done. 73 | -------------------------------------------------------------------------------- /filtering/sigprocMXC_reflection.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Filtering 4 | % VIDEO: Avoid edge effects with reflection 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | clear 10 | 11 | % create a signal 12 | N = 500; 13 | hz = linspace(0,1,N); 14 | gx = exp( -(4*log(2)*(hz-.1)/.1).^2 )*N/2; 15 | signal = real(ifft( gx.*exp(1i*rand(1,N)*2*pi) )) + ... 16 | randn(1,N); 17 | 18 | % plot it and its power spectrum 19 | figure(1), clf 20 | subplot(311) 21 | plot(1:N,signal,'k') 22 | set(gca,'xlim',[0 N+1]) 23 | title('Original signal') 24 | xlabel('Time points (a.u.)') 25 | 26 | 27 | subplot(324) 28 | plot(hz,abs(fft(signal)).^2,'k','markerfacecolor','w') 29 | set(gca,'xlim',[0 .5]) 30 | xlabel('Frequency (norm.)'), ylabel('Energy') 31 | title('Frequency-domain signal representation') 32 | 33 | %% apply a low-pass causal filter 34 | 35 | % generate filter kernel 36 | order = 150; 37 | fkern = fir1(order,.6,'low'); 38 | 39 | % zero-phase-shift filter 40 | fsignal = filter(fkern,1,signal); % forward 41 | fsignal = filter(fkern,1,fsignal(end:-1:1)); % reverse 42 | fsignal = fsignal(end:-1:1); % flip forward 43 | 44 | 45 | % plot the original signal and filtered version 46 | subplot(323), hold on 47 | plot(1:N,signal,'k') 48 | plot(1:N,fsignal,'m') 49 | set(gca,'xlim',[0 N+1]) 50 | xlabel('Time (a.u.)') 51 | title('Time domain') 52 | legend({'Original';'Filtered, no reflection'}) 53 | 54 | 55 | % power spectra 56 | subplot(324), hold on 57 | plot(hz,abs(fft(fsignal)).^2,'m') 58 | title('Frequency domain') 59 | legend({'Original';'Filtered, no reflection'}) 60 | 61 | %% now with reflection by filter order 62 | 63 | % reflect the signal 64 | reflectsig = [ signal(order:-1:1) signal signal(end:-1:end-order+1) ]; 65 | 66 | % zero-phase-shift filter on the reflected signal 67 | reflectsig = filter(fkern,1,reflectsig); 68 | reflectsig = filter(fkern,1,reflectsig(end:-1:1)); 69 | reflectsig = reflectsig(end:-1:1); 70 | 71 | % now chop off the reflected parts 72 | fsignal = reflectsig(order+1:end-order); 73 | 74 | % try again with filtfilt 75 | %fsignal1 = filtfilt(fkern,1,signal); 76 | 77 | % and plot 78 | subplot(325), hold on 79 | plot(1:N,signal,'k') 80 | plot(1:N,fsignal,'m') 81 | set(gca,'xlim',[0 N+1]) 82 | xlabel('Time (a.u.)') 83 | title('Time domain') 84 | legend({'Original';'Filtered, with reflection'}) 85 | 86 | % spectra 87 | subplot(326), hold on 88 | plot(hz,abs(fft(signal)).^2,'k') 89 | plot(hz,abs(fft(fsignal)).^2,'m') 90 | legend({'Original';'Filtered, with reflection'}) 91 | set(gca,'xlim',[0 .5]) 92 | xlabel('Frequency (norm.)'), ylabel('Energy') 93 | title('Frequency domain') 94 | 95 | %% done. 96 | -------------------------------------------------------------------------------- /filtering/sigprocMXC_rolloff.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Filtering 4 | % VIDEO: Quantifying roll-off characteristics 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% create a windowed sinc filter 10 | 11 | % simulation parameters 12 | srate = 1000; 13 | time = -4:1/srate:4; 14 | pnts = length(time); 15 | 16 | % FFT parameters 17 | nfft = 10000; 18 | hz = linspace(0,srate/2,floor(nfft/2)+1); 19 | 20 | filtcut = 15; 21 | sincfilt = sin(2*pi*filtcut*time) ./ time; 22 | 23 | % adjust NaN and normalize filter to unit-gain 24 | sincfilt(~isfinite(sincfilt)) = max(sincfilt); 25 | sincfilt = sincfilt./sum(sincfilt); 26 | 27 | % windowed sinc filter 28 | hannw = .5 - cos(2*pi*linspace(0,1,pnts))./2; 29 | sincfiltW = sincfilt .* hannw; 30 | 31 | % spectrum of filter 32 | sincX = 10*log10(abs(fft(sincfiltW,nfft)).^2); 33 | sincX = sincX(1:length(hz)); 34 | 35 | 36 | %% create a Butterworth low-pass filter 37 | 38 | % generate filter coefficients (Butterworth) 39 | [filtb,filta] = butter(5,filtcut/(srate/2),'low'); 40 | 41 | % test impulse response function (IRF) 42 | impulse = [ zeros(1,500) 1 zeros(1,500) ]; 43 | fimpulse = filtfilt(filtb,filta,impulse); 44 | 45 | % spectrum of filter response 46 | butterX = 10*log10(abs(fft(fimpulse,nfft)).^2); 47 | butterX = butterX(1:length(hz)); 48 | 49 | 50 | %% plot frequency responses 51 | 52 | % plot 53 | figure(1), clf, hold on 54 | plot(hz,sincX, hz,butterX, 'linew',2) 55 | plotedge = dsearchn(hz',filtcut*3); 56 | set(gca,'xlim',[0 filtcut*3],'ylim',[min([butterX(plotedge) sincX(plotedge)]) 5]) 57 | 58 | plot([1 1]*filtcut,get(gca,'ylim'),'k--','linew',2) 59 | 60 | 61 | 62 | % find -3 dB after filter edge 63 | filtcut_idx = dsearchn(hz',filtcut); 64 | 65 | sincX3db = dsearchn(sincX',-3); 66 | butterX3db = dsearchn(butterX',-3); 67 | 68 | % add to the plot 69 | plot([1 1]*hz(sincX3db),get(gca,'ylim'),'b--') 70 | plot([1 1]*hz(butterX3db),get(gca,'ylim'),'r--') 71 | 72 | 73 | 74 | % find double the frequency 75 | sincXoct = dsearchn(hz',hz(sincX3db)*2); 76 | butterXoct = dsearchn(hz',hz(butterX3db)*2); 77 | 78 | % add to the plot 79 | plot([1 1]*hz(sincXoct),get(gca,'ylim'),'b--') 80 | plot([1 1]*hz(butterXoct),get(gca,'ylim'),'r--') 81 | 82 | 83 | 84 | % find attenuation from that point to double its frequency 85 | sincXatten = sincX(sincX3db*2); 86 | butterXatten = butterX(butterX3db*2); 87 | 88 | sincXrolloff = (sincX(sincX3db)-sincX(sincXoct)) / (hz(sincXoct)-hz(sincX3db)); 89 | butterXrolloff = (butterX(butterX3db)-butterX(butterXoct)) / (hz(butterXoct)-hz(butterX3db)); 90 | 91 | % report! 92 | title([ 'Sinc: ' num2str(sincXrolloff) ', Butterworth: ' num2str(butterXrolloff) ]) 93 | legend({'Windowed sinc';'Butterworth'}) 94 | xlabel('Frequency (Hz)'), ylabel('Gain (dB)') 95 | 96 | 97 | %% done. 98 | -------------------------------------------------------------------------------- /filtering/sigprocMXC_signalLength.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Filtering 4 | % VIDEO: Data length and filter kernel length 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % parameters 10 | dataN = 10000; 11 | filtN = 5001; 12 | 13 | % generate data 14 | signal = randn(dataN,1); 15 | 16 | % create filter kernel 17 | fkern = fir1(filtN,.01,'low'); 18 | 19 | % apply filter kernel to data 20 | fdata = filtfilt(fkern,1,signal); 21 | 22 | %%% -------------- %%% 23 | % Is there an error? % 24 | %%% -------------- %%% 25 | 26 | %% 27 | 28 | % use reflection to increase signal length! 29 | signalRefl = [ signal(end:-1:1) signal signal(end:-1:1) ]; 30 | 31 | 32 | % apply filter kernel to data 33 | fdataR = filtfilt(fkern,1,signalRefl); 34 | 35 | % and cut off edges 36 | fdata = fdataR(dataN+1:end-dataN); 37 | 38 | 39 | %% done. 40 | -------------------------------------------------------------------------------- /filtering/sigprocMXC_windowSinc.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Filtering 4 | % VIDEO: Windowed-sinc filters 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | srate = 1000; 10 | time = -4:1/srate:4; 11 | pnts = length(time); 12 | 13 | f = 8; 14 | sincfilt = sin(2*pi*f*time) ./ time; 15 | 16 | % adjust NaN and normalize filter to unit-gain 17 | sincfilt(~isfinite(sincfilt)) = max(sincfilt); 18 | sincfilt = sincfilt./sum(sincfilt); 19 | 20 | % windowed sinc filter 21 | sincfiltW = sincfilt .* hann(pnts)'; 22 | 23 | 24 | %%% plotting 25 | figure(1), clf 26 | 27 | % sinc filter 28 | subplot(221) 29 | plot(time,sincfilt,'k','linew',1) 30 | xlabel('Time (s)') 31 | title('Non-windowed sinc function') 32 | 33 | subplot(223), hold on 34 | hz = linspace(0,srate/2,floor(pnts/2)+1); 35 | pw = abs(fft(sincfilt)); 36 | plot(hz,pw(1:length(hz)),'k','linew',2) 37 | set(gca,'xlim',[0 f*3],'YScale','lo','ylim',[10e-7 10]) 38 | plot([1 1]*f,get(gca,'ylim'),'r--') 39 | xlabel('Frequency (Hz)'), ylabel('Gain') 40 | 41 | 42 | 43 | 44 | % now plot the windowed sinc filter 45 | subplot(222) 46 | plot(time,sincfiltW,'k','linew',1) 47 | xlabel('Time (s)') 48 | title('Windowed sinc function') 49 | 50 | subplot(224), hold on 51 | hz = linspace(0,srate/2,floor(pnts/2)+1); 52 | pw = abs(fft(sincfiltW)); 53 | plot(hz,pw(1:length(hz)),'k','linew',2) 54 | set(gca,'xlim',[0 f*3],'YScale','lo','ylim',[10e-7 10]) 55 | plot([1 1]*f,get(gca,'ylim'),'r--') 56 | xlabel('Frequency (Hz)'), ylabel('Gain') 57 | 58 | %% apply the filter to noise 59 | 60 | % generate data as integrated noise 61 | data = cumsum( randn(pnts,1) ); 62 | 63 | % reflection 64 | datacat = [data; data(end:-1:1)]; 65 | 66 | % apply filter (zero-phase-shift) 67 | dataf = filter(sincfiltW,1,datacat); 68 | dataf = filter(sincfiltW,1,dataf(end:-1:1)); 69 | 70 | % flip forwards and remove reflected points 71 | dataf = dataf(end:-1:pnts+1); 72 | 73 | 74 | % compute spectra of original and filtered signals 75 | hz = linspace(0,srate/2,floor(pnts/2)+1); 76 | powOrig = abs(fft(data)/pnts).^2; 77 | powFilt = abs(fft(dataf)/pnts).^2; 78 | 79 | 80 | 81 | % plot 82 | figure(2), clf 83 | subplot(211) 84 | plot(time,data, time,dataf,'linew',1) 85 | xlabel('Time (s)'), ylabel('Amplitude') 86 | legend({'Original signal';'Windowed-sinc filtered'}) 87 | zoom on 88 | 89 | 90 | % plot original and filtered spectra 91 | subplot(212) 92 | plot(hz,powOrig(1:length(hz)), hz,powFilt(1:length(hz)), 'linew',1) 93 | set(gca,'xlim',[0 40],'YScale','log') 94 | xlabel('Frequency (Hz)'), ylabel('Power') 95 | legend({'Original signal';'Windowed-sinc filtered'}) 96 | 97 | %% with different windowing functions 98 | 99 | sincfiltW = cell(3,1); 100 | 101 | tapernames = {'Hann';'Hamming';'Gauss'}; 102 | 103 | % with Hann taper 104 | %sincfiltW{1} = sincfilt .* hann(pnts)'; 105 | hannw = .5 - cos(2*pi*linspace(0,1,pnts))./2; 106 | sincfiltW{1} = sincfilt .* hannw; 107 | 108 | 109 | % with Hamming taper 110 | %sincfiltW{2} = sincfilt .* hamming(pnts)'; 111 | hammingw = .54 - .46*cos(2*pi*linspace(0,1,pnts)); 112 | sincfiltW{2} = sincfilt .* hammingw; 113 | 114 | 115 | % with Gaussian taper 116 | sincfiltW{3} = sincfilt .* exp(-time.^2); 117 | 118 | 119 | 120 | figure(3), clf 121 | 122 | for filti=1:length(sincfiltW) 123 | subplot(211), hold on 124 | plot(time,sincfiltW{filti},'linew',2) 125 | xlabel('Time (s)'), title('Time domain') 126 | 127 | subplot(212), hold on 128 | hz = linspace(0,srate/2,floor(pnts/2)+1); 129 | pw = abs(fft(sincfiltW{filti})); 130 | plot(hz,pw(1:length(hz)),'linew',3) 131 | set(gca,'xlim',[f-3 f+10],'YScale','lo','ylim',[10e-7 10]) 132 | xlabel('Frequency (Hz)'), ylabel('Gain') 133 | title('Frequency domain') 134 | 135 | end 136 | 137 | legend(tapernames) 138 | 139 | %% done. 140 | -------------------------------------------------------------------------------- /filtering/sigproc_Filtering.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing and image processing in MATLAB and Python 3 | % SECTION: Filtering 4 | % VIDEO: Code challenge: Filter these signals! 5 | % Instructor: mikexcohen.com 6 | % 7 | %% 8 | 9 | 10 | fs = 1000; 11 | N = 10000; 12 | x = randn(N,1); 13 | 14 | 15 | 16 | %%% lowpass 17 | fcutoff = 30; 18 | transw = .2; 19 | order = round( 5*fs/fcutoff ); 20 | shape = [ 1 1 0 0 ]; 21 | frex = [ 0 fcutoff fcutoff+fcutoff*transw fs/2 ] / (fs/2); 22 | 23 | 24 | % filter 25 | filtkern = firls(order,frex,shape); 26 | y = filtfilt(filtkern,1,x); 27 | 28 | 29 | %%% highpass 30 | fcutoff = 5; 31 | transw = .05; 32 | order = round( 5*fs/fcutoff ); 33 | shape = [ 0 0 1 1 ]; 34 | frex = [ 0 fcutoff fcutoff+fcutoff*transw fs/2 ] / (fs/2); 35 | 36 | % filter 37 | filtkern = firls(order,frex,shape); 38 | y = filtfilt(filtkern,1,y); 39 | 40 | 41 | %%% notch 42 | fcutoff = [ 18 24 ]; 43 | transw = .1; 44 | order = round( 5*fs/fcutoff(1) ); 45 | shape = [ 1 1 0 0 1 1 ]; 46 | frex = [ 0 fcutoff(1)*(1-transw) fcutoff fcutoff(2)+fcutoff(2)*transw fs/2 ] / (fs/2); 47 | 48 | % filter 49 | filtkern = firls(order,frex,shape); 50 | y = filtfilt(filtkern,1,y); 51 | 52 | 53 | 54 | 55 | 56 | 57 | clf 58 | subplot(211), hold on 59 | plot(x,'r') 60 | plot(y,'k') 61 | title('Time domain') 62 | 63 | yX = abs(fft(y)).^2; 64 | xX = abs(fft(x)).^2; 65 | hz = linspace(0,fs,N); 66 | 67 | subplot(212), hold on 68 | plot(hz,xX,'r'); 69 | plot(hz,yX,'k'); 70 | set(gca,'xlim',[0 80]) 71 | title('Frequency domain') 72 | 73 | legend({'X';'Y'}) 74 | 75 | 76 | %% 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /outliers/codeChallenge.m: -------------------------------------------------------------------------------- 1 | 2 | 3 | % window size as percent of total signal length 4 | pct_win = linspace(.1,20,40); % in percent, not proportion! 5 | 6 | rmsz = zeros(length(pct_win),n); 7 | 8 | for p=1:length(pct_win) 9 | 10 | % convert to indices 11 | k = round(n * pct_win(p)/2/100); 12 | 13 | 14 | for ti=1:n 15 | 16 | % boundary points 17 | low_bnd = max(1,ti-k); 18 | upp_bnd = min(n,ti+k); 19 | 20 | % signal segment (and mean-center!) 21 | tmpsig = signal(low_bnd:upp_bnd); 22 | tmpsig = tmpsig - mean(tmpsig); 23 | 24 | % compute RMS in this window 25 | rmsz(p,ti) = sqrt(sum( tmpsig.^2 )); 26 | end 27 | 28 | end 29 | 30 | % 31 | 32 | figure(3), clf 33 | subplot(511) 34 | plot(log(rms_ts),'s-') 35 | 36 | subplot(5,1,2:5) 37 | contourf(1:n,pct_win,log(rmsz),40,'linecolor','none') 38 | ylabel('Window size (%)'), xlabel('Time') 39 | set(gca,'clim',[1 4.5]) 40 | 41 | 42 | -------------------------------------------------------------------------------- /outliers/forex.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/outliers/forex.mat -------------------------------------------------------------------------------- /outliers/sigprocMXC_RMSoutlierWindows.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Outlier detection 4 | % VIDEO: Outlier time windows via sliding RMS 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % generate signal with varying variability 10 | n = 2000; 11 | p = 15; % poles for random interpolation 12 | 13 | % amplitude modulator 14 | signal = interp1(randn(p,1)*3,linspace(1,p,n),'pchip'); 15 | signal = signal + randn(1,n); 16 | 17 | % add some high-amplitude noise 18 | signal(200:220) = signal(200:220) + randn(1,21)*9; 19 | signal(1500:1600) = signal(1500:1600) + randn(1,101)*9; 20 | 21 | 22 | % plot 23 | figure(1), clf, hold on 24 | plot(signal) 25 | 26 | %% detect bad segments using sliding RMS 27 | 28 | % window size as percent of total signal length 29 | pct_win = 2; % in percent, not proportion! 30 | 31 | % convert to indices 32 | k = round(n * pct_win/2/100); 33 | 34 | 35 | % initialize RMS time series vector 36 | rms_ts = zeros(1,n); 37 | 38 | for ti=1:n 39 | 40 | % boundary points 41 | low_bnd = max(1,ti-k); 42 | upp_bnd = min(n,ti+k); 43 | 44 | % signal segment (and mean-center!) 45 | tmpsig = signal(low_bnd:upp_bnd); 46 | tmpsig = tmpsig - mean(tmpsig); 47 | 48 | % compute RMS in this window 49 | rms_ts(ti) = sqrt(sum( tmpsig.^2 )); 50 | end 51 | 52 | 53 | 54 | % plot RMS 55 | figure(2), clf, hold on 56 | plot(rms_ts,'s-') 57 | 58 | 59 | % pick threshold manually based on visual inspection 60 | thresh = 15; 61 | plot(get(gca,'xlim'),[1 1]*thresh,'r') 62 | legend({'Local RMS';'Threshold'}) 63 | 64 | 65 | % mark bad regions in original time series 66 | signalR = signal; 67 | signalR( rms_ts>thresh ) = NaN; 68 | 69 | figure(1), clf, hold on 70 | plot(signal,'b') 71 | plot(signalR,'r') 72 | legend({'Original';'Thresholded'}) 73 | 74 | %% done. 75 | -------------------------------------------------------------------------------- /outliers/sigprocMXC_StandardDevThresh.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Outlier detection 4 | % VIDEO: Outliers via standard deviation threshold 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %%% signal is log-normal noise 10 | N = 10000; 11 | time = (1:N)/N; 12 | signal = exp( .5*randn(N,1) ); 13 | 14 | % add some random outiers 15 | nOutliers = 50; 16 | randpnts = randi(N,[nOutliers,1]); 17 | signal(randpnts) = rand(nOutliers,1) * range(signal)*10; 18 | 19 | % show the signal 20 | figure(1), clf, hold on 21 | plot(time,signal,'ks-','markerfacecolor','k') 22 | 23 | 24 | % auto-threshold based on mean and standard deviation 25 | threshold = mean(signal)+3*std(signal); 26 | plot(time([1 end]),[1 1]*threshold,'k--') 27 | 28 | %% interpolate outlier points 29 | 30 | % remove supra-threshold points 31 | outliers = signal > threshold; 32 | 33 | 34 | % and interpolate missing points 35 | F = griddedInterpolant(time(~outliers),signal(~outliers)); 36 | signalR = signal; 37 | signalR(outliers) = F(time(outliers)); 38 | 39 | % and plot the new results 40 | plot(time,signalR,'ro-') 41 | 42 | %% done. 43 | -------------------------------------------------------------------------------- /outliers/sigprocMXC_localThreshold.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Outlier detection 4 | % VIDEO: Outliers via local threshold exceedance 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % data downloaded from: 10 | % http://www.histdata.com/download-free-forex-historical-data/?/ascii/1-minute-bar-quotes/eurusd/2017 11 | 12 | % import data, etc. 13 | load forex.mat 14 | N = length(forex); 15 | time = (0:N-1)'/N; 16 | 17 | 18 | % plot it 19 | figure(1), clf, hold on 20 | plot(time,forex) 21 | xlabel('Time (year)'), ylabel('EUR/USD') 22 | 23 | % add global thresholds 24 | plot(get(gca,'xlim'),[1 1]*(mean(forex)+3*std(forex)),'r--') 25 | plot(get(gca,'xlim'),[1 1]*(mean(forex)-3*std(forex)),'k--') 26 | legend({'EUR/USD';'M+3std';'M-3std'}) 27 | 28 | %% local threshold 29 | 30 | % window size as percent of total signal length 31 | pct_win = 5; % in percent, not proportion! 32 | 33 | % convert to indices 34 | k = round(length(forex) * pct_win/2/100); 35 | 36 | % initialize statistics time series to be the global stats 37 | mean_ts = ones(size(time)) * mean(forex); 38 | std3_ts = ones(size(time)) * std(forex); 39 | 40 | 41 | for i=1:N 42 | 43 | % boundaries 44 | lo_bnd = max(1,i-k); 45 | hi_bnd = min(i+k,N); 46 | 47 | % compute local mean and std 48 | mean_ts(i) = mean( forex(lo_bnd:hi_bnd) ); 49 | std3_ts(i) = 3*std( forex(lo_bnd:hi_bnd) ); 50 | end 51 | 52 | % plot the uncertainty 53 | figure(2), clf, hold on 54 | h = patch([time; time(end:-1:1)],[mean_ts+std3_ts; mean_ts(end:-1:1)-std3_ts(end:-1:1)],'m'); 55 | 56 | % plot the data 57 | plot(time,forex,'k','linew',2) 58 | 59 | 60 | %%% compute local outliers 61 | outliers = forex > (mean_ts+std3_ts) | forex < (mean_ts-std3_ts); 62 | 63 | % and plot those 64 | plot(time(outliers),forex(outliers),'ro','markerfacecolor','r') 65 | 66 | 67 | %%% finishing touches on the plot 68 | legend({'Mean +/- 3std';'EUR/USD';'Outliers'}) 69 | set(h,'linestyle','none','facealpha',.4) 70 | xlabel('Time (year)'), ylabel('EUR/USD') 71 | title([ 'Using a ' num2str(pct_win) '% window size' ]) 72 | 73 | %% done. 74 | -------------------------------------------------------------------------------- /quickwin/GlassDance.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/quickwin/GlassDance.mp3 -------------------------------------------------------------------------------- /quickwin/glassDance.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/quickwin/glassDance.mat -------------------------------------------------------------------------------- /quickwin/sigprocMXC_filterGlass.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import numpy as np\n", 10 | "import matplotlib.pyplot as plt\n", 11 | "from scipy.io import loadmat\n", 12 | "import scipy.signal as signal\n", 13 | "from IPython.display import Audio" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "# load the file\n", 23 | "matfile = loadmat('glassDance.mat')\n", 24 | "# this is a clip of Philip Glass, Dance VII (https://www.youtube.com/watch?v=LpewOlR-z_4)\n", 25 | "\n", 26 | "glassclip = matfile['glassclip']\n", 27 | "srate = matfile['srate'][0][0]\n", 28 | "\n", 29 | "# play the music!\n", 30 | "Audio(np.array(glassclip[:,0]), rate=srate)\n" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": null, 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "# some variables for convenience\n", 40 | "pnts = len(glassclip)\n", 41 | "timevec = np.arange(0,pnts)/srate\n", 42 | "\n", 43 | "\n", 44 | "# draw the time-domain signals\n", 45 | "plt.plot(timevec,glassclip)\n", 46 | "plt.xlabel('Time (s)')\n", 47 | "plt.show()" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": null, 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [ 56 | "## static power spectrum and pick a frequency range\n", 57 | "\n", 58 | "# inspect the power spectrum\n", 59 | "hz = np.linspace(0,srate/2,int(np.floor(len(glassclip)/2)+1))\n", 60 | "powr = abs(np.fft.fft(glassclip[:,0])/pnts)\n", 61 | "\n", 62 | "plt.plot(hz,powr[:len(hz)])\n", 63 | "plt.xlim([100,2000])\n", 64 | "plt.ylim([0,np.max(powr)])\n", 65 | "plt.xlabel('Frequency (Hz)')\n", 66 | "plt.ylabel('Amplitude')\n", 67 | "plt.show()\n" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": null, 73 | "metadata": {}, 74 | "outputs": [], 75 | "source": [ 76 | "# pick frequencies to filter\n", 77 | "frange = [ 300, 460 ]\n", 78 | "frange = [ 1000, 1100 ]\n", 79 | "frange = [ 1200, 1450 ]\n", 80 | "\n", 81 | "\n", 82 | "# design an FIR1 filter\n", 83 | "fkern = signal.firwin(2001,frange,fs=srate,pass_zero=False)\n", 84 | "\n", 85 | "# apply the filter to the signal\n", 86 | "filtglass = np.zeros(np.shape(glassclip))\n", 87 | "filtglass[:,0] = signal.filtfilt(fkern,1,glassclip[:,0])\n", 88 | "filtglass[:,1] = signal.filtfilt(fkern,1,glassclip[:,1])\n" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": null, 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "## plot the raw and filtered signal power spectra\n", 98 | "powrF = abs(np.fft.fft(filtglass[:,0])/pnts)\n", 99 | "\n", 100 | "plt.plot(hz,powr[:len(hz)])\n", 101 | "plt.plot(hz,powrF[:len(hz)],'r')\n", 102 | "\n", 103 | "plt.xlim([100,2000])\n", 104 | "plt.ylim([0,np.max(powr)])\n", 105 | "plt.xlabel('Frequency (Hz)')\n", 106 | "plt.ylabel('Amplitude')\n", 107 | "plt.show()" 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": null, 113 | "metadata": {}, 114 | "outputs": [], 115 | "source": [ 116 | "# plot the time-frequency response\n", 117 | "\n", 118 | "frex,time,tf = signal.spectrogram(glassclip[:,0],window=('tukey',.25),fs=srate,noverlap=100)\n", 119 | "\n", 120 | "plt.plot([timevec[0],timevec[-1]],[frange[0],frange[0]],'w:')\n", 121 | "plt.plot([timevec[0],timevec[-1]],[frange[1],frange[1]],'w:')\n", 122 | "\n", 123 | "plt.pcolormesh(time,frex,np.log(tf),vmin=-20,vmax=-10)\n", 124 | "plt.ylim([0,2000])\n", 125 | "plt.xlabel('Time (s)')\n", 126 | "plt.ylabel('Frequency (Hz)')\n", 127 | "plt.show()" 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": null, 133 | "metadata": {}, 134 | "outputs": [], 135 | "source": [ 136 | "## Play the filtered signal!\n", 137 | "Audio(np.array(filtglass[:,0]), rate=srate)" 138 | ] 139 | } 140 | ], 141 | "metadata": { 142 | "kernelspec": { 143 | "display_name": "Python 3", 144 | "language": "python", 145 | "name": "python3" 146 | }, 147 | "language_info": { 148 | "codemirror_mode": { 149 | "name": "ipython", 150 | "version": 3 151 | }, 152 | "file_extension": ".py", 153 | "mimetype": "text/x-python", 154 | "name": "python", 155 | "nbconvert_exporter": "python", 156 | "pygments_lexer": "ipython3", 157 | "version": "3.7.4" 158 | } 159 | }, 160 | "nbformat": 4, 161 | "nbformat_minor": 2 162 | } 163 | -------------------------------------------------------------------------------- /quickwin/sigprocMXC_filterGlass.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Introduction 4 | % VIDEO: Having fun with filtered Glass dance 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % load the file 10 | load glassDance.mat 11 | % this is a clip of Philip Glass, Dance VII (https://www.youtube.com/watch?v=LpewOlR-z_4) 12 | 13 | 14 | % play the music! 15 | soundsc(glassclip,srate) 16 | 17 | % some variables for convenience 18 | pnts = length(glassclip); 19 | timevec = (0:pnts-1)/srate; 20 | 21 | % draw the time-domain signals 22 | figure(1), clf 23 | subplot(511) 24 | plot(timevec,glassclip) 25 | xlabel('Time (s)') 26 | 27 | 28 | %% static power spectrum and pick a frequency range 29 | 30 | % inspect the power spectrum 31 | hz = linspace(0,srate/2,floor(length(glassclip)/2)+1); 32 | powr = abs(fft(glassclip(:,1))/pnts); 33 | 34 | subplot(512), cla 35 | plot(hz,powr(1:length(hz))) 36 | set(gca,'xlim',[100 2000],'ylim',[0 max(powr)]) 37 | xlabel('Frequency (Hz)'), ylabel('Amplitude') 38 | 39 | 40 | % pick frequencies to filter 41 | frange = [ 300 460 ]; 42 | frange = [ 1000 1100 ]; 43 | % frange = [ 1200 1450 ]; 44 | 45 | 46 | % design an FIR1 filter 47 | fkern = fir1(2001,frange/(srate/2),'bandpass'); 48 | 49 | % apply the filter to the signal 50 | filtglass(:,1) = filtfilt(fkern,1,glassclip(:,1)); 51 | filtglass(:,2) = filtfilt(fkern,1,glassclip(:,2)); 52 | 53 | % plot the filtered signal power spectrum 54 | hold on 55 | powr = abs(fft(filtglass(:,1))/pnts); 56 | plot(hz,powr(1:length(hz)),'r') 57 | 58 | 59 | % plot the time-frequency response 60 | subplot(5,1,3:5) 61 | spectrogram(glassclip(:,1),hann(round(srate/10)),[],[],srate,'yaxis'); 62 | hold on 63 | plot(timevec([1 1; end end]),frange([1 2; 1 2])/1000,'k:','linew',2) 64 | 65 | % NOTE: use the following line in Octave 66 | %[powspect,frex,time] = specgram(glassclip(:,1),1000,srate,hann(round(srate/10))); 67 | 68 | 69 | set(gca,'ylim',[0 2]) 70 | 71 | %% play the sound 72 | 73 | soundsc(filtglass,srate) 74 | 75 | %% done. 76 | 77 | -------------------------------------------------------------------------------- /quickwin/sigproc_mp3.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Introduction 4 | % VIDEO: Having fun with filtered Glass dance 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % load the file 10 | load glassDance.mat 11 | 12 | 13 | % play the music! 14 | soundsc(glassclip,srate) 15 | 16 | % some variables for convenience 17 | pnts = length(glassclip); 18 | timevec = (0:pnts-1)/srate; 19 | 20 | % draw the time-domain signals 21 | figure(1), clf 22 | subplot(511) 23 | plot(timevec,glassclip) 24 | xlabel('Time (s)') 25 | 26 | 27 | %% static power spectrum and pick a frequency range 28 | 29 | % inspect the power spectrum 30 | hz = linspace(0,srate/2,floor(length(glassclip)/2)+1); 31 | powr = abs(fft(glassclip(:,1))/pnts); 32 | 33 | subplot(512), cla 34 | plot(hz,powr(1:length(hz))) 35 | set(gca,'xlim',[100 2000],'ylim',[0 max(powr)]) 36 | xlabel('Frequency (Hz)'), ylabel('Amplitude') 37 | 38 | 39 | % pick frequencies to filter 40 | frange = [ 300 460 ]; 41 | frange = [ 1000 1100 ]; 42 | % frange = [ 1200 1450 ]; 43 | 44 | 45 | % design an FIR1 filter 46 | fkern = fir1(2001,frange/(srate/2),'bandpass'); 47 | 48 | % apply the filter to the signal 49 | filtglass(:,1) = filtfilt(fkern,1,glassclip(:,1)); 50 | filtglass(:,2) = filtfilt(fkern,1,glassclip(:,2)); 51 | 52 | % plot the filtered signal power spectrum 53 | hold on 54 | powr = abs(fft(filtglass(:,1))/pnts); 55 | plot(hz,powr(1:length(hz)),'r') 56 | 57 | 58 | % plot the time-frequency response 59 | subplot(5,1,3:5) 60 | spectrogram(glassclip(:,1),hann(round(srate/10)),[],[],srate,'yaxis'); 61 | hold on 62 | plot(timevec([1 1; end end]),frange([1 2; 1 2])/1000,'k:','linew',2) 63 | 64 | set(gca,'ylim',[0 2]) 65 | 66 | %% play the sound 67 | 68 | soundsc(filtglass,srate) 69 | 70 | %% done. 71 | 72 | -------------------------------------------------------------------------------- /resample/resample_codeChallenge.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Resampling, interpolating, extrapolating 4 | % VIDEO: Code challenge 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % slowish signal (interp1 + narrowband noise) 10 | % GOAL: 11 | % denoise and downsample to regular 12 | 13 | 14 | 15 | % create signal 16 | srate = 1000; % Hz 17 | [origT,time] = deal( 0:1/srate:3 ); 18 | n = length(time); 19 | p = 20; % poles for random interpolation 20 | 21 | % amplitude modulator and noise level 22 | ampmod = interp1(rand(p,1)*30,linspace(1,p,n),'nearest')'; 23 | 24 | 25 | peakfreq = 7; % Hz 26 | fwhm = 5; % Hz 27 | 28 | % frequencies 29 | hz = linspace(0,srate,n)'; 30 | s = fwhm*(2*pi-1)/(4*pi); % normalized width 31 | x = hz-peakfreq; % shifted frequencies 32 | fg = exp(-.5*(x/s).^2); % gaussian 33 | 34 | 35 | % Fourier coefficients of random spectrum 36 | fc = rand(n,1) .* exp(1i*2*pi*rand(n,1)); 37 | 38 | % taper with Gaussian 39 | fc = fc .* fg; 40 | 41 | % go back to time domain to get signal 42 | greal = 2*real( ifft(fc) )*n; 43 | 44 | 45 | 46 | 47 | [origS,signal] = deal( ampmod + greal ); 48 | 49 | 50 | figure(1), clf 51 | plot(time,signal,'linew',3) 52 | 53 | %% 54 | 55 | % add some NaNs 56 | randnans = randperm(n); 57 | randnans = randnans(1:round(n*.2)); 58 | signal(randnans) = nan; 59 | 60 | 61 | 62 | % add noise bursts 63 | signal(290:310) = signal(290:310) + 70*randn(size(signal(290:310))); 64 | signal(1290:1310) = signal(1290:1310) + 70*randn(size(signal(1290:1310))); 65 | 66 | 67 | % fake irregular time stamps 68 | tokill = []; 69 | for i=1:n 70 | if rand>.8, tokill = [tokill i]; end 71 | end 72 | 73 | time(tokill) = []; 74 | signal(tokill) = []; 75 | 76 | 77 | plot(time,signal,'ks-','linew',3) 78 | hold on 79 | plot(origT,origS,'r','linew',3) 80 | 81 | save resample_codeChallenge.mat time signal origT origS 82 | 83 | 84 | %% 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /resample/resample_codeChallenge.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/resample/resample_codeChallenge.mat -------------------------------------------------------------------------------- /resample/sigprocMXC_downsample.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Resampling, interpolating, extrapolating 4 | % VIDEO: Downsampling 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% Laplace distribution 10 | 11 | % parameters 12 | srate = 100; 13 | tv = -5:1/srate:5; 14 | npnts = length(tv); 15 | 16 | % signal components 17 | laplace = 1-exp(-abs(tv)); 18 | fastsine = .25*sin(2*pi*tv*15); 19 | 20 | % combine into one signal (no noise) 21 | signal = laplace + fastsine; 22 | 23 | % power spectrum (O = original) 24 | hzO = linspace(0,srate/2,floor(npnts/2)+1); 25 | signalO_pow = abs(fft(signal)/npnts).^2; 26 | signalO_pow = signalO_pow(1:length(hzO)); 27 | 28 | 29 | figure(1), clf 30 | subplot(211) 31 | plot(tv,signal,'ko-','linew',2,'markerfacecolor','w','markeredgecolor','k') 32 | xlabel('Time (s)') 33 | 34 | subplot(212) 35 | plot(hzO,signalO_pow,'ko-','linew',2,'markerfacecolor','w','markeredgecolor','k') 36 | set(gca,'xlim',[0 50],'ylim',[0 .05],'yscale','lo') 37 | xlabel('Frequency (Hz)'), ylabel('Power') 38 | 39 | %% downsample by a factor 40 | 41 | dnsampleFactor = 4; 42 | newSrate = srate/dnsampleFactor; 43 | 44 | % new time vector after upsampling 45 | newTv = -5:1/newSrate:5; 46 | newPnts = length(newTv); 47 | 48 | 49 | 50 | %%% downsample WITHOUT low-pass filtering (bad idea!!) 51 | signal_dsB = signal(1:dnsampleFactor:end); 52 | 53 | % power spectrum (B = bad) 54 | hz_ds = linspace(0,newSrate/2,floor(newPnts/2)+1); 55 | signal_dsB_pow = abs(fft(signal_dsB)/newPnts).^2; 56 | signal_dsB_pow = signal_dsB_pow(1:length(hz_ds)); 57 | 58 | 59 | %%% low-pass filter at new Nyquist frequency! (good idea!!) 60 | fkern = fir1(14*newSrate/2,(newSrate/2)/(srate/2)); 61 | fsignal = filtfilt(fkern,1,signal); 62 | 63 | % now downsample 64 | signal_dsG = fsignal(1:dnsampleFactor:end); 65 | 66 | % power spectrum (G = good) 67 | signal_dsG_pow = abs(fft(signal_dsG)/newPnts).^2; 68 | signal_dsG_pow = signal_dsG_pow(1:length(hz_ds)); 69 | 70 | fsignal_pow = abs(fft(fsignal)/npnts).^2; 71 | fsignal_pow = fsignal_pow(1:length(hz_ds)); 72 | 73 | 74 | 75 | 76 | % plot in the time domain 77 | subplot(211), hold on 78 | plot(newTv,.02+signal_dsB,'m^-','markersize',8,'markerfacecolor','m') 79 | plot(newTv,.04+signal_dsG,'gs-','markersize',8,'markerfacecolor','g') 80 | legend({'Original';'DS bad';'DS good'}) 81 | 82 | % plot in the frequency domain 83 | subplot(212), hold on 84 | plot(hz_ds,signal_dsB_pow,'m^-','linew',1,'markerfacecolor','m','markeredgecolor','m') 85 | plot(hz_ds,signal_dsG_pow,'gs-','linew',1,'markerfacecolor','g','markeredgecolor','g') 86 | legend({'Original';'DS bad';'DS good'}) 87 | 88 | %% using MATLAB's resample function (signal processing toolbox) 89 | 90 | % find p and q coefficients 91 | [p,q] = rat( newSrate/srate ); 92 | 93 | % use resample function (sigproc toolbox) 94 | signal_dsM = resample(signal,p,q); 95 | 96 | % power spectrum (M = MATLAB) 97 | signal_dsM_pow = abs(fft(signal_dsM)/newPnts).^2; 98 | signal_dsM_pow = signal_dsM_pow(1:length(hz_ds)); 99 | 100 | 101 | 102 | % and plot it. 103 | subplot(211) 104 | plot(newTv,.06+signal_dsM,'rs-','markersize',8,'markerfacecolor','r') 105 | 106 | subplot(212) 107 | plot(hz_ds,signal_dsM_pow,'rs-','linew',1,'markerfacecolor','r','markeredgecolor','r') 108 | 109 | %% done. 110 | -------------------------------------------------------------------------------- /resample/sigprocMXC_dtw.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Resampling, interpolating, extrapolating 4 | % VIDEO: Dynamic time warping 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% create signals 10 | 11 | % different time vectors 12 | tx = linspace(0,1.5*pi,400); 13 | ty = linspace(0,8*pi,100); 14 | 15 | % different signals 16 | x = sin(tx.^2); % chirp 17 | y = sin(ty); % sine wave 18 | 19 | 20 | % show them 21 | figure(1), clf 22 | subplot(311), hold on 23 | plot(tx,x,'bs-','linew',1,'markerfacecolor','b') 24 | plot(ty,y,'rs-','linew',1,'markerfacecolor','w','markersize',5) 25 | xlabel('Time (rad.)') 26 | title('Original') 27 | 28 | %% distance matrix 29 | 30 | % initialize distance matrix (dm) and set first element to zero 31 | dm = nan(length(x),length(y)); 32 | dm(1) = 0; 33 | 34 | for xi=2:length(x) 35 | for yi=2:length(y) 36 | cost = abs(x(xi)-y(yi)).^1; 37 | dm(xi,yi) = cost + min([ dm(xi-1,yi) dm(xi,yi-1) dm(xi-1,yi-1) ]); 38 | end 39 | end 40 | 41 | figure(2), clf 42 | surf(dm), shading interp 43 | colormap hot 44 | view([90 -90]) 45 | rotate3d on 46 | xlabel('x'), ylabel('y') 47 | 48 | %% 49 | 50 | % find minimum for each y 51 | minpath = zeros(2,length(x)); 52 | for xi=1:size(dm,1) 53 | [minpath(1,xi),minpath(2,xi)] = min(dm(xi,:)); 54 | end 55 | 56 | 57 | figure(1) 58 | subplot(312), hold on 59 | plot(tx,x,'bs-','linew',1,'markerfacecolor','b') 60 | plot(tx,y(minpath(2,:)),'rs','linew',1,'markerfacecolor','w','markersize',5) 61 | xlabel('Time (rad.)') 62 | title('Warped') 63 | 64 | %% using MATLAB's dtw function 65 | 66 | % in the signal-processing toolbox! 67 | [d2,warpx,warpy] = dtw(x,y); 68 | 69 | figure(1) 70 | subplot(313), hold on 71 | plot(tx(warpx),x(warpx),'bs-','linew',1,'markerfacecolor','b') 72 | plot(tx(warpx),y(warpy),'rs','linew',1,'markerfacecolor','w','markersize',5) 73 | xlabel('Time (rad.)') 74 | title('MATLAB dtw function') 75 | 76 | %% 77 | -------------------------------------------------------------------------------- /resample/sigprocMXC_extrap.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Resampling, interpolating, extrapolating 4 | % VIDEO: Extrapolating 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % get the landscape 10 | signal = [1 4 3 6 2 19]; 11 | timevec = 1:length(signal); 12 | 13 | figure(1), clf, hold on 14 | h = plot(timevec,signal,'ks-','markersize',8,'markerfacecolor','y'); 15 | 16 | %% two methods of extrapolation 17 | 18 | times2extrap = -length(signal):2*length(signal); 19 | 20 | % extrapolate using linear and spline methods. 21 | Flin = griddedInterpolant(timevec,signal,'linear'); 22 | Fspl = griddedInterpolant(timevec,signal,'spline'); 23 | 24 | % query data points 25 | extrapLin = Flin(times2extrap); 26 | extrapSpl = Fspl(times2extrap); 27 | 28 | %% plot them 29 | 30 | % linear 31 | plot(times2extrap,extrapLin,'ro-','markersize',10,'markerfacecolor','w') 32 | 33 | % spline 34 | plot(times2extrap,extrapSpl,'bp-','markersize',10,'markerfacecolor','w') 35 | 36 | legend({'original';'linear';'spline'},'location','northwest') 37 | uistack(h,'top') % put original signal on top 38 | zoom on 39 | 40 | %% done. 41 | -------------------------------------------------------------------------------- /resample/sigprocMXC_interp.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Resampling, interpolating, extrapolating 4 | % VIDEO: Interpolating 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% low-sampling-rate data to upsample 10 | 11 | % in Hz 12 | srate = 10; 13 | 14 | % just some numbers... 15 | data = [1 4 3 6 2 19]; 16 | 17 | % other parameters 18 | npnts = length(data); 19 | time = (0:npnts-1)/srate; 20 | 21 | % plot the original data 22 | figure(1), clf 23 | subplot(221) 24 | plot(time,data,'go','markersize',15,'markerfacecolor','m') 25 | 26 | % amplitude spectrum 27 | figure(2), clf 28 | plot(linspace(0,1,npnts),abs(fft(data/npnts)),'go-','markerfacecolor','m','linew',4,'markersize',8) 29 | xlabel('Frequency (a.u.)') 30 | 31 | %% interpolation 32 | 33 | % new time vector for interpolation 34 | N = 47; 35 | newTime = linspace(time(1),time(end),N); 36 | 37 | % different interpolation options 38 | interpOptions = {'linear';'next';'nearest';'spline'}; 39 | interpColors = 'brkm'; 40 | interpShapes = 'sd^p'; 41 | 42 | for methodi=1:length(interpOptions) 43 | 44 | %% using griddedInterpolant 45 | 46 | % define interpolation object 47 | F = griddedInterpolant(time,data,interpOptions{methodi}); 48 | 49 | % query that object at requested time points 50 | newdata = F(newTime); 51 | 52 | %% using interp1 (same as above) 53 | 54 | newdata = interp1(time,data,newTime,interpOptions{methodi}); 55 | 56 | %% plots 57 | 58 | figure(1) 59 | subplot(2,2,methodi), hold on 60 | plot(newTime,newdata,'ks-','markersize',10,'markerfacecolor','w') 61 | plot(time,data,'go','markersize',15,'markerfacecolor','m') 62 | 63 | % make the axis a bit nicer 64 | set(gca,'xlim',[0 max(time(end),newTime(end))]) 65 | title([ '''' interpOptions{methodi} '''' ]) % 4 single quotes here to get a single quote of text! 66 | axis square 67 | 68 | figure(2), hold on 69 | plot(linspace(0,1,N),abs(fft(newdata/N)),[ interpColors(methodi) interpShapes(methodi) '-' ],'markerfacecolor',interpColors(methodi)) 70 | 71 | end 72 | 73 | % adjust spectral plot 74 | figure(2) 75 | set(gca,'xlim',[0 .5]) 76 | legend(cat(1,'original',interpOptions)) 77 | 78 | %% done. 79 | -------------------------------------------------------------------------------- /resample/sigprocMXC_irregular.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Resampling, interpolating, extrapolating 4 | % VIDEO: Resample irregularly sampled data 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % simulation parameters 10 | srate = 1324; % Hz 11 | peakfreq = 7; % Hz 12 | fwhm = 5; % Hz 13 | npnts = srate*2; % time points 14 | timevec = (0:npnts-1)/srate; % seconds 15 | 16 | % frequencies 17 | hz = linspace(0,srate,npnts); 18 | s = fwhm*(2*pi-1)/(4*pi); % normalized width 19 | x = hz-peakfreq; % shifted frequencies 20 | fg = exp(-.5*(x/s).^2); % gaussian 21 | 22 | 23 | % Fourier coefficients of random spectrum 24 | fc = rand(1,npnts) .* exp(1i*2*pi*rand(1,npnts)); 25 | 26 | % taper with Gaussian 27 | fc = fc .* fg; 28 | 29 | % go back to time domain to get signal 30 | signal = 2*real( ifft(fc) )*npnts; 31 | 32 | 33 | %%% plot 34 | figure(1), clf, hold on 35 | plot(timevec,signal,'k','linew',3) 36 | xlabel('Time (s)') 37 | 38 | %% now randomly sample from this "continuous" time series 39 | 40 | % initialize to empty 41 | sampSig = []; 42 | 43 | 44 | % random sampling intervals 45 | sampintervals = cumsum([ 1; ceil( exp(4*rand(npnts,1)) ) ]); 46 | sampintervals(sampintervals>npnts) = []; % remove points beyond the data 47 | 48 | 49 | % loop through sampling points and "measure" data 50 | for i=1:length(sampintervals) 51 | 52 | % "real world" measurement 53 | nextdat = [signal(sampintervals(i)); timevec(sampintervals(i))]; 54 | 55 | % put in data matrix 56 | sampSig = cat(2,sampSig,nextdat); 57 | end 58 | 59 | % more plotting 60 | plot(sampSig(2,:),sampSig(1,:),'ro','markerfacecolor','r','markersize',12) 61 | 62 | %% upsample to original sampling rate 63 | 64 | 65 | % define interpolation object 66 | F = griddedInterpolant(sampSig(2,:),sampSig(1,:),'spline'); 67 | 68 | % query that object at requested time points 69 | newsignal = F(timevec); 70 | 71 | 72 | % and plot 73 | plot(timevec,newsignal,'ms','markersize',10,'markerfacecolor','m') 74 | legend({'"Analog"';'Measured';'Upsampled'}) 75 | 76 | %% done. 77 | -------------------------------------------------------------------------------- /resample/sigprocMXC_multirate.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Resampling, interpolating, extrapolating 4 | % VIDEO: Strategies for multirate signals 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% create multichannel signal with multiple sampling rates 10 | 11 | % initialize signals, time vectors, and sampling rates 12 | % note: hard-coded to three signals 13 | [fs,timez,signals] = deal( cell(3,1) ); 14 | 15 | 16 | % sampling rates in Hz 17 | fs{1} = 10; 18 | fs{2} = 40; 19 | fs{3} = 83; 20 | 21 | % create signals 22 | for si=1:3 23 | 24 | % create signal 25 | signals{si} = cumsum( sign(randn(fs{si},1)) ); 26 | 27 | % create time vector 28 | timez{si} = (0:fs{si}-1)/fs{si}; 29 | end 30 | 31 | 32 | 33 | % plot all signals 34 | figure(1), clf, hold on 35 | 36 | color = 'kbr'; 37 | shape = 'os^'; 38 | for si=1:3 39 | plot(timez{si},signals{si},[ color(si) shape(si) '-' ],'linew',1,'markerfacecolor','w','markersize',6) 40 | end 41 | axlims = axis; 42 | xlabel('Time (s)') 43 | 44 | %% upsample to fastest frequency 45 | 46 | % in Hz 47 | [newSrate,whichIsFastest] = max(cell2mat(fs)); 48 | 49 | % need to round in case it's not exact 50 | newNpnts = round( length(signals{whichIsFastest}) * (newSrate/fs{whichIsFastest}) ); 51 | 52 | % new time vector after upsampling 53 | newTime = (0:newNpnts-1) / newSrate; 54 | 55 | %% continue on to interpolation 56 | 57 | % initialize (as matrix!) 58 | newsignals = zeros(length(fs),length(newTime)); 59 | 60 | for si=1:length(fs) 61 | 62 | % define interpolation object 63 | F = griddedInterpolant(timez{si},signals{si},'spline'); 64 | 65 | % query that object at requested time points 66 | newsignals(si,:) = F(newTime); 67 | end 68 | 69 | %% plot for comparison 70 | 71 | % plot all signals 72 | figure(2), clf, hold onplot for comparison 73 | 74 | % plot all signals 75 | figure(2), clf, hold on 76 | 77 | for si=1:3 78 | plot(newTime,newsignals(si,:),[ color(si) shape(si) '-' ],'linew',1,'markerfacecolor','w','markersize',6) 79 | end 80 | 81 | % set axis limits to match figure 1 82 | axis(axlims) 83 | 84 | for si=1:3 85 | plot(newTime,newsignals(si,:),[ color(si) shape(si) '-' ],'linew',1,'markerfacecolor','w','markersize',6) 86 | end 87 | 88 | % set axis limits to match figure 1 89 | axis(axlims) 90 | 91 | %% done. 92 | -------------------------------------------------------------------------------- /resample/sigprocMXC_spectralInterp.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Resampling, interpolating, extrapolating 4 | % VIDEO: Spectral interpolation 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % number of points in signal 10 | n = 10000; 11 | 12 | % create signal 13 | [origsig,signal] = deal( cumsum( randn(n,1) ) ); 14 | 15 | 16 | % remove a specified window 17 | boundaryPnts = [ 5000 6500 ]; 18 | signal(boundaryPnts(1):boundaryPnts(2)) = 0/0; 19 | 20 | 21 | % FFTs of pre- and post-window data 22 | fftPre = fft(signal( boundaryPnts(1)-diff(boundaryPnts)-1:boundaryPnts(1)-1) ); 23 | fftPst = fft(signal( boundaryPnts(2)+1:boundaryPnts(2)+diff(boundaryPnts)+1) ); 24 | 25 | % interpolated signal is a combination of mixed FFTs and straight line 26 | mixeddata = detrend(ifft( ( fftPre+fftPst )/2 )); 27 | linedata = linspace(0,1,diff(boundaryPnts)+1)'*(signal(boundaryPnts(2)+1)-signal(boundaryPnts(1)-1)) + signal(boundaryPnts(1)-1); 28 | 29 | % sum together for final result 30 | linterp = mixeddata + linedata; 31 | 32 | % put the interpolated piece into the signal 33 | filtsig = signal; 34 | filtsig(boundaryPnts(1):boundaryPnts(2)) = linterp; 35 | 36 | figure(1), clf 37 | plot(1:n,[origsig signal+5 filtsig+10]) 38 | legend({'Original';'With gap';'Filtered'}) 39 | zoom on 40 | 41 | %% done. 42 | -------------------------------------------------------------------------------- /resample/sigprocMXC_upsample.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Resampling, interpolating, extrapolating 4 | % VIDEO: Upsampling 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% low-sampling-rate data to upsample 10 | 11 | % in Hz 12 | srate = 10; 13 | 14 | % just some numbers... 15 | data = [1 4 3 6 2 19]; 16 | 17 | % other parameters 18 | npnts = length(data); 19 | time = (0:npnts-1)/srate; 20 | 21 | % plot the original signal 22 | figure(1), clf, hold on 23 | plot(time,data,'ko-','markersize',15,'markerfacecolor','k','linew',3) 24 | 25 | %% option 1: upsample by a factor 26 | 27 | upsampleFactor = 4; 28 | newNpnts = npnts*upsampleFactor; 29 | 30 | % new time vector after upsampling 31 | newTime = (0:newNpnts-1)/(upsampleFactor*srate); 32 | 33 | %% option 2: upsample to desired frequency, then cut off points if necessary 34 | 35 | % % in Hz 36 | % newSrate = 37; 37 | % 38 | % % need to round in case it's not exact 39 | % newNpnts = round( npnts * (newSrate/srate) ); 40 | % 41 | % % new time vector after upsampling 42 | % newTime = (0:newNpnts-1) / newSrate; 43 | 44 | %% continue on to interpolation 45 | 46 | % cut out extra time points 47 | newTime(newTime>time(end)) = []; 48 | 49 | % the new sampling rate actually implemented 50 | newSrateActual = 1/mean(diff(newTime)) 51 | 52 | 53 | 54 | % define interpolation object 55 | F = griddedInterpolant(time,data,'spline'); 56 | 57 | % query that object at requested time points 58 | updataI = F(newTime); 59 | 60 | 61 | 62 | % plot the upsampled signal 63 | plot(newTime,updataI,'rs-','markersize',14,'markerfacecolor','r') 64 | set(gca,'xlim',[0 max(time(end),newTime(end))]) 65 | 66 | %% using MATLAB's resample function (signal processing toolbox) 67 | 68 | % new sampling rate in Hz 69 | newSrate = 42; 70 | 71 | % find p and q coefficients 72 | [p,q] = rat( newSrate/srate); 73 | 74 | % use resample function (sigproc toolbox) 75 | updataR = resample(data,p,q); 76 | 77 | % the new time vector 78 | newTime = (0:length(updataR)-1)/newSrate; 79 | 80 | 81 | % cut out extra time points 82 | updataR(newTime>time(end)) = []; 83 | newTime(newTime>time(end)) = []; 84 | 85 | 86 | % and plot it 87 | plot(newTime,updataR,'b^-','markersize',14,'markerfacecolor','b') 88 | legend({'Original';'Interpolated';'resample'}) 89 | 90 | %% done. 91 | -------------------------------------------------------------------------------- /spectral/EEGrestingState.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/spectral/EEGrestingState.mat -------------------------------------------------------------------------------- /spectral/XC403881.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/spectral/XC403881.mp3 -------------------------------------------------------------------------------- /spectral/XC403881.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/spectral/XC403881.wav -------------------------------------------------------------------------------- /spectral/sigprocMXC_FourierTransform.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Spectral and rhythmicity analyses 4 | % VIDEO: Fourier transform for spectral analyses 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% Generate a multispectral noisy signal 10 | 11 | % simulation parameters 12 | srate = 1234; % in Hz 13 | npnts = srate*2; % 2 seconds 14 | time = (0:npnts-1)/srate; 15 | 16 | % frequencies to include 17 | frex = [ 12 18 30 ]; 18 | 19 | signal = zeros(size(time)); 20 | 21 | % loop over frequencies to create signal 22 | for fi=1:length(frex) 23 | signal = signal + fi*sin(2*pi*frex(fi)*time); 24 | end 25 | 26 | % add some noise 27 | signal = signal + randn(size(signal)); 28 | 29 | % amplitude spectrum via Fourier transform 30 | signalX = fft(signal); 31 | signalAmp = 2*abs(signalX)/npnts; 32 | 33 | % vector of frequencies in Hz 34 | hz = linspace(0,srate/2,floor(npnts/2)+1); 35 | 36 | 37 | %% plots 38 | 39 | figure(1), clf 40 | subplot(211) 41 | 42 | plot(time,signal) 43 | xlabel('Time (s)'), ylabel('Amplitude') 44 | title('Time domain') 45 | 46 | 47 | subplot(212) 48 | stem(hz,signalAmp(1:length(hz)),'ks','linew',2,'markersize',10) 49 | set(gca,'xlim',[0 max(frex)*3]) 50 | xlabel('Frequency (Hz)'), ylabel('Amplitude') 51 | title('Frequency domain') 52 | 53 | 54 | subplot(211), hold on 55 | plot(time,ifft(signalX),'ro') 56 | legend({'Original';'IFFT reconstructed'}) 57 | 58 | %% example with real data 59 | 60 | % data downloaded from https://trends.google.com/trends/explore?date=today%205-y&geo=US&q=signal%20processing 61 | searchdata = [69 77 87 86 87 71 70 92 83 73 76 78 56 75 68 60 30 44 58 69 82 76 73 60 71 86 72 55 56 65 73 71 71 71 62 65 57 54 54 60 49 59 58 46 50 62 60 65 67 60 70 89 78 94 86 80 81 73 100 95 78 75 64 80 53 81 73 66 26 44 70 85 81 91 85 79 77 80 68 67 51 78 85 76 72 87 65 59 60 64 56 52 71 77 53 53 49 57 61 42 58 65 67 93 88 83 89 60 79 72 79 69 78 85 72 85 51 73 73 52 41 27 44 68 77 71 49 63 72 73 60 68 63 55 50 56 58 74 51 62 52 47 46 38 45 48 44 46 46 51 38 44 39 47 42 55 52 68 56 59 69 61 51 61 65 61 47 59 47 55 57 48 43 35 41 55 50 76 56 60 59 62 56 58 60 58 61 69 65 52 55 64 42 42 54 46 47 52 54 44 31 51 46 42 40 51 60 53 64 58 63 52 53 51 56 65 65 61 61 62 44 51 54 51 42 34 42 33 55 67 57 62 55 52 48 50 48 49 52 53 54 55 48 51 57 46 45 41 55 44 34 40 38 41 31 41 41 40 53 35 31]; 62 | N = length(searchdata); 63 | 64 | % possible normalization 65 | % searchdata = searchdata; 66 | 67 | % power 68 | searchpow = abs( fft( searchdata )/N ).^2; 69 | hz = linspace(0,52,N); 70 | 71 | figure(2), clf 72 | subplot(211) 73 | plot(searchdata,'ko-','markerfacecolor','m','linew',1) 74 | xlabel('Time (weeks)'), ylabel('Search volume') 75 | 76 | subplot(212) 77 | plot(hz,searchpow,'ms-','markerfacecolor','k','linew',1) 78 | xlabel('Frequency (norm.)'), ylabel('Search power') 79 | set(gca,'xlim',[0 12]) 80 | 81 | %% done. 82 | -------------------------------------------------------------------------------- /spectral/sigprocMXC_SpectBirdcall.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Spectral and rhythmicity analyses 4 | % VIDEO: Spectrogram of birdsong 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % load in birdcall (source: https://www.xeno-canto.org/403881) 10 | [bc,fs] = audioread('XC403881.mp3'); % in Octave, import the wav version 11 | 12 | % let's hear it! 13 | soundsc(bc,fs) 14 | 15 | 16 | % create a time vector based on the data sampling rate 17 | n = length(bc); 18 | timevec = (0:n-1)/fs; 19 | 20 | % plot the data from the two channels 21 | figure(1), clf 22 | subplot(211) 23 | plot(timevec,bsxfun(@plus,bc,[.2 0])) 24 | xlabel('Time (sec.)') 25 | title('Time domain') 26 | set(gca,'ytick',[],'xlim',timevec([1 end])) 27 | 28 | % compute the power spectrum 29 | hz = linspace(0,fs/2,floor(n/2)+1); 30 | bcpow = abs(fft( detrend(bc(:,1)) )/n).^2; 31 | 32 | % now plot it 33 | subplot(212) 34 | plot(hz,bcpow(1:length(hz)),'linew',2) 35 | xlabel('Frequency (Hz)') 36 | title('Frequency domain') 37 | set(gca,'xlim',[0 8000]) 38 | 39 | %% now for a time-frequency analysis 40 | 41 | % use MATLAB's spectrogram function (in the signal processing toolbox) 42 | [powspect,frex,time] = spectrogram(detrend(bc(:,2)),hann(1000),100,[],fs); 43 | 44 | % Octave uses the following line instead of the above line 45 | %[powspect,frex,time] = specgram(detrend(bc(:,2)),1000,fs,hann(1000)); 46 | 47 | 48 | % show the time-frequency power plot 49 | figure(3), clf 50 | imagesc(time,frex,abs(powspect).^2) 51 | axis xy 52 | xlabel('Time (sec.)'), ylabel('Frequency (Hz)') 53 | set(gca,'clim',[0 1]*2,'ylim',frex([1 dsearchn(frex(:),15000)]),'xlim',time([1 end])) 54 | colormap hot 55 | 56 | %% done. 57 | -------------------------------------------------------------------------------- /spectral/sigprocMXC_Welch.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Spectral and rhythmicity analyses 4 | % VIDEO: Welch's method 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % load data 10 | load EEGrestingState.mat 11 | N = length(eegdata); 12 | 13 | % time vector 14 | timevec = (0:N-1)/srate; 15 | 16 | % plot the data 17 | figure(1), clf 18 | plot(timevec,eegdata,'k') 19 | xlabel('Time (seconds)'), ylabel('Voltage (\muV)') 20 | 21 | %% one big FFT (not Welch's method) 22 | 23 | % "static" FFT over entire period, for comparison with Welch 24 | eegpow = abs( fft(eegdata)/N ).^2; 25 | hz = linspace(0,srate/2,floor(N/2)+1); 26 | 27 | %% "manual" Welch's method 28 | 29 | % window length in seconds*srate 30 | winlength = 1*srate; 31 | 32 | % number of points of overlap 33 | nOverlap = round(srate/2); 34 | 35 | % window onset times 36 | winonsets = 1:winlength-nOverlap:N-winlength; 37 | 38 | % note: different-length signal needs a different-length Hz vector 39 | hzW = linspace(0,srate/2,floor(winlength/2)+1); 40 | 41 | % Hann window 42 | hannw = .5 - cos(2*pi*linspace(0,1,winlength))./2; 43 | 44 | % initialize the power matrix (windows x frequencies) 45 | eegpowW = zeros(1,length(hzW)); 46 | 47 | % loop over frequencies 48 | for wi=1:length(winonsets) 49 | 50 | % get a chunk of data from this time window 51 | datachunk = eegdata(winonsets(wi):winonsets(wi)+winlength-1); 52 | 53 | % apply Hann taper to data 54 | datachunk = datachunk .* hannw; 55 | 56 | % compute its power 57 | tmppow = abs(fft(datachunk)/winlength).^2; 58 | 59 | % enter into matrix 60 | eegpowW = eegpowW + tmppow(1:length(hzW)); 61 | end 62 | 63 | % divide by N 64 | eegpowW = eegpowW / length(winonsets); 65 | 66 | 67 | %%% plotting 68 | figure(2), clf, hold on 69 | 70 | plot(hz,eegpow(1:length(hz)),'k','linew',2) 71 | plot(hzW,eegpowW/10,'r','linew',2) 72 | set(gca,'xlim',[0 40]) 73 | xlabel('Frequency (Hz)') 74 | legend({'"Static FFT';'Welch''s method'}) 75 | 76 | %% MATLAB pwelch 77 | 78 | figure(3), clf 79 | 80 | % create Hann window 81 | winsize = 2*srate; % 2-second window 82 | hannw = .5 - cos(2*pi*linspace(0,1,winsize))./2; 83 | 84 | % number of FFT points (frequency resolution) 85 | nfft = srate*100; 86 | 87 | pwelch(eegdata,hannw,round(winsize/4),nfft,srate); 88 | set(gca,'xlim',[0 40]) 89 | 90 | %% done. 91 | -------------------------------------------------------------------------------- /spectral/sigprocMXC_spectral.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import numpy as np\n", 10 | "import matplotlib.pyplot as plt\n", 11 | "import scipy.io as sio\n", 12 | "import scipy.fftpack\n", 13 | "import scipy.signal\n", 14 | "import scipy.io.wavfile\n", 15 | "import copy" 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": {}, 21 | "source": [ 22 | "\n", 23 | "---\n", 24 | "VIDEO: Fourier transform for spectral analyses\n", 25 | "---\n" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "## Generate a multispectral noisy signal\n", 35 | "\n", 36 | "# simulation parameters\n", 37 | "srate = 1234 # in Hz\n", 38 | "npnts = srate*2 # 2 seconds\n", 39 | "time = np.arange(0,npnts)/srate\n", 40 | "\n", 41 | "# frequencies to include\n", 42 | "frex = [ 12,18,30 ]\n", 43 | "\n", 44 | "signal = np.zeros(len(time))\n", 45 | "\n", 46 | "# loop over frequencies to create signal\n", 47 | "for fi in range(0,len(frex)):\n", 48 | " signal = signal + (fi+1)*np.sin(2*np.pi*frex[fi]*time)\n", 49 | "\n", 50 | "# add some noise\n", 51 | "signal = signal + np.random.randn(len(signal))\n", 52 | "\n", 53 | "# amplitude spectrum via Fourier transform\n", 54 | "signalX = scipy.fftpack.fft(signal)\n", 55 | "signalAmp = 2*np.abs(signalX)/npnts\n", 56 | "\n", 57 | "# vector of frequencies in Hz\n", 58 | "hz = np.linspace(0,srate/2,int(np.floor(npnts/2)+1))\n" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": null, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "## plots\n", 68 | "\n", 69 | "plt.plot(time,signal,label='Original')\n", 70 | "plt.plot(time,np.real(scipy.fftpack.ifft(signalX)),'ro',label='IFFT reconstructed')\n", 71 | "\n", 72 | "plt.xlabel('Time (s)')\n", 73 | "plt.ylabel('Amplitude')\n", 74 | "plt.title('Time domain')\n", 75 | "plt.legend()\n", 76 | "plt.show()\n", 77 | "\n", 78 | "plt.stem(hz,signalAmp[0:len(hz)],'k')\n", 79 | "plt.xlim([0,np.max(frex)*3])\n", 80 | "plt.xlabel('Frequency (Hz)')\n", 81 | "plt.ylabel('Amplitude')\n", 82 | "plt.title('Frequency domain')\n", 83 | "plt.show()" 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": null, 89 | "metadata": {}, 90 | "outputs": [], 91 | "source": [ 92 | "## example with real data\n", 93 | "\n", 94 | "# data downloaded from https://trends.google.com/trends/explore?date=today%205-y&geo=US&q=signal%20processing\n", 95 | "searchdata = [69,77,87,86,87,71,70,92,83,73,76,78,56,75,68,60,30,44,58,69,82,76,73,60,71,86,72,55,56,65,73,71,71,71,62,65,57,54,54,60,49,59,58,46,50,62,60,65,67,60,70,89,78,94,86,80,81,73,100,95,78,75,64,80,53,81,73,66,26,44,70,85,81,91,85,79,77,80,68,67,51,78,85,76,72,87,65,59,60,64,56,52,71,77,53,53,49,57,61,42,58,65,67,93,88,83,89,60,79,72,79,69,78,85,72,85,51,73,73,52,41,27,44,68,77,71,49,63,72,73,60,68,63,55,50,56,58,74,51,62,52,47,46,38,45,48,44,46,46,51,38,44,39,47,42,55,52,68,56,59,69,61,51,61,65,61,47,59,47,55,57,48,43,35,41,55,50,76,56,60,59,62,56,58,60,58,61,69,65,52,55,64,42,42,54,46,47,52,54,44,31,51,46,42,40,51,60,53,64,58,63,52,53,51,56,65,65,61,61,62,44,51,54,51,42,34,42,33,55,67,57,62,55,52,48,50,48,49,52,53,54,55,48,51,57,46,45,41,55,44,34,40,38,41,31,41,41,40,53,35,31]\n", 96 | "N = len(searchdata)\n", 97 | "\n", 98 | "# possible normalizations...\n", 99 | "searchdata = searchdata - np.mean(searchdata)\n", 100 | "\n", 101 | "# power\n", 102 | "searchpow = np.abs( scipy.fftpack.fft( searchdata )/N )**2\n", 103 | "hz = np.linspace(0,52,N)\n", 104 | "\n", 105 | "plt.plot(searchdata,'ko-')\n", 106 | "plt.xlabel('Time (weeks)')\n", 107 | "plt.ylabel('Search volume')\n", 108 | "plt.show()\n", 109 | "\n", 110 | "plt.plot(hz,searchpow,'ms-')\n", 111 | "plt.xlabel('Frequency (norm.)')\n", 112 | "plt.ylabel('Search power')\n", 113 | "plt.xlim([0,12])\n", 114 | "plt.show()" 115 | ] 116 | }, 117 | { 118 | "cell_type": "markdown", 119 | "metadata": {}, 120 | "source": [ 121 | "\n", 122 | "---\n", 123 | "# VIDEO: Welch's method\n", 124 | "---\n" 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": null, 130 | "metadata": {}, 131 | "outputs": [], 132 | "source": [ 133 | "# load data and extract\n", 134 | "matdat = sio.loadmat('EEGrestingState.mat')\n", 135 | "eegdata = matdat['eegdata'][0]\n", 136 | "srate = matdat['srate'][0]\n", 137 | "\n", 138 | "# time vector\n", 139 | "N = len(eegdata)\n", 140 | "timevec = np.arange(0,N)/srate\n", 141 | "\n", 142 | "# plot the data\n", 143 | "plt.plot(timevec,eegdata,'k')\n", 144 | "plt.xlabel('Time (seconds)')\n", 145 | "plt.ylabel('Voltage (\\muV)')\n", 146 | "plt.show()" 147 | ] 148 | }, 149 | { 150 | "cell_type": "code", 151 | "execution_count": null, 152 | "metadata": {}, 153 | "outputs": [], 154 | "source": [ 155 | "## one big FFT (not Welch's method)\n", 156 | "\n", 157 | "# \"static\" FFT over entire period, for comparison with Welch\n", 158 | "eegpow = np.abs( scipy.fftpack.fft(eegdata)/N )**2\n", 159 | "hz = np.linspace(0,srate/2,int(np.floor(N/2)+1))\n" 160 | ] 161 | }, 162 | { 163 | "cell_type": "code", 164 | "execution_count": null, 165 | "metadata": {}, 166 | "outputs": [], 167 | "source": [ 168 | "## \"manual\" Welch's method\n", 169 | "\n", 170 | "# window length in seconds*srate\n", 171 | "winlength = int( 1*srate )\n", 172 | "\n", 173 | "# number of points of overlap\n", 174 | "nOverlap = np.round(srate/2)\n", 175 | "\n", 176 | "# window onset times\n", 177 | "winonsets = np.arange(0,int(N-winlength),int(winlength-nOverlap))\n", 178 | "\n", 179 | "# note: different-length signal needs a different-length Hz vector\n", 180 | "hzW = np.linspace(0,srate/2,int(np.floor(winlength/2)+1))\n", 181 | "\n", 182 | "# Hann window\n", 183 | "hannw = .5 - np.cos(2*np.pi*np.linspace(0,1,int(winlength)))/2\n", 184 | "\n", 185 | "# initialize the power matrix (windows x frequencies)\n", 186 | "eegpowW = np.zeros(len(hzW))\n", 187 | "\n", 188 | "# loop over frequencies\n", 189 | "for wi in range(0,len(winonsets)):\n", 190 | " \n", 191 | " # get a chunk of data from this time window\n", 192 | " datachunk = eegdata[ winonsets[wi]:winonsets[wi]+winlength ]\n", 193 | " \n", 194 | " # apply Hann taper to data\n", 195 | " datachunk = datachunk * hannw\n", 196 | " \n", 197 | " # compute its power\n", 198 | " tmppow = np.abs(scipy.fftpack.fft(datachunk)/winlength)**2\n", 199 | " \n", 200 | " # enter into matrix\n", 201 | " eegpowW = eegpowW + tmppow[0:len(hzW)]\n", 202 | "\n", 203 | "# divide by N\n", 204 | "eegpowW = eegpowW / len(winonsets)\n", 205 | "\n", 206 | "\n", 207 | "# plotting\n", 208 | "plt.plot(hz,eegpow[0:len(hz)],'k',label='Static FFT')\n", 209 | "plt.plot(hzW,eegpowW/10,'r',label='Welch''s method')\n", 210 | "plt.xlim([0,40])\n", 211 | "plt.xlabel('Frequency (Hz)')\n", 212 | "plt.legend()\n", 213 | "plt.show()" 214 | ] 215 | }, 216 | { 217 | "cell_type": "code", 218 | "execution_count": null, 219 | "metadata": {}, 220 | "outputs": [], 221 | "source": [ 222 | "## Python's welch\n", 223 | "\n", 224 | "# create Hann window\n", 225 | "winsize = int( 2*srate ) # 2-second window\n", 226 | "hannw = .5 - np.cos(2*np.pi*np.linspace(0,1,winsize))/2\n", 227 | "\n", 228 | "# number of FFT points (frequency resolution)\n", 229 | "nfft = srate*100\n", 230 | "\n", 231 | "f, welchpow = scipy.signal.welch(eegdata,fs=srate,window=hannw,nperseg=winsize,noverlap=winsize/4,nfft=nfft)\n", 232 | "\n", 233 | "plt.semilogy(f,welchpow)\n", 234 | "plt.xlim([0,40])\n", 235 | "plt.xlabel('frequency [Hz]')\n", 236 | "plt.ylabel('Power')\n", 237 | "plt.show()" 238 | ] 239 | }, 240 | { 241 | "cell_type": "markdown", 242 | "metadata": {}, 243 | "source": [ 244 | "\n", 245 | "---\n", 246 | "# VIDEO: Spectrogram of birdsong\n", 247 | "---\n" 248 | ] 249 | }, 250 | { 251 | "cell_type": "code", 252 | "execution_count": null, 253 | "metadata": {}, 254 | "outputs": [], 255 | "source": [ 256 | "## load in birdcall (source: https://www.xeno-canto.org/403881)\n", 257 | "\n", 258 | "fs,bc = scipy.io.wavfile.read('XC403881.wav')\n", 259 | "\n", 260 | "\n", 261 | "# create a time vector based on the data sampling rate\n", 262 | "n = len(bc)\n", 263 | "timevec = np.arange(0,n)/fs\n", 264 | "\n", 265 | "# plot the data from the two channels\n", 266 | "plt.plot(timevec,bc)\n", 267 | "plt.xlabel('Time (sec.)')\n", 268 | "plt.title('Time domain')\n", 269 | "plt.show()\n", 270 | "\n", 271 | "# compute the power spectrum\n", 272 | "hz = np.linspace(0,fs/2,int(np.floor(n/2)+1))\n", 273 | "bcpow = np.abs(scipy.fftpack.fft( scipy.signal.detrend(bc[:,0]) )/n)**2\n", 274 | "\n", 275 | "# now plot it\n", 276 | "plt.plot(hz,bcpow[0:len(hz)])\n", 277 | "plt.xlabel('Frequency (Hz)')\n", 278 | "plt.title('Frequency domain')\n", 279 | "plt.xlim([0,8000])\n", 280 | "plt.show()" 281 | ] 282 | }, 283 | { 284 | "cell_type": "code", 285 | "execution_count": null, 286 | "metadata": {}, 287 | "outputs": [], 288 | "source": [ 289 | "## time-frequency analysis via spectrogram\n", 290 | "\n", 291 | "frex,time,pwr = scipy.signal.spectrogram(bc[:,0],fs)\n", 292 | "\n", 293 | "plt.pcolormesh(time,frex,pwr,vmin=0,vmax=9)\n", 294 | "plt.xlabel('Time (s)'), plt.ylabel('Frequency (Hz)')\n", 295 | "plt.show()" 296 | ] 297 | } 298 | ], 299 | "metadata": { 300 | "kernelspec": { 301 | "display_name": "Python 3", 302 | "language": "python", 303 | "name": "python3" 304 | }, 305 | "language_info": { 306 | "codemirror_mode": { 307 | "name": "ipython", 308 | "version": 3 309 | }, 310 | "file_extension": ".py", 311 | "mimetype": "text/x-python", 312 | "name": "python", 313 | "nbconvert_exporter": "python", 314 | "pygments_lexer": "ipython3", 315 | "version": "3.7.4" 316 | } 317 | }, 318 | "nbformat": 4, 319 | "nbformat_minor": 2 320 | } 321 | -------------------------------------------------------------------------------- /spectral/spectral_codeChallenge.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/spectral/spectral_codeChallenge.mat -------------------------------------------------------------------------------- /spectral/spectral_code_challenge.m: -------------------------------------------------------------------------------- 1 | 2 | % create signal 3 | srate = 1000; 4 | time = -3:1/srate:3; 5 | pnts = length(time); 6 | freqmod = exp(-time.^2)*10+10; 7 | freqmod = freqmod + linspace(0,10,pnts); 8 | signal = sin( 2*pi * ((time + cumsum(freqmod))/srate) ); 9 | 10 | 11 | % plot the signal 12 | figure(1), clf 13 | subplot(411) 14 | plot(time,signal,'linew',1) 15 | xlabel('Time (s)') 16 | title('Time-domain signal') 17 | 18 | 19 | n = 500; 20 | hz = linspace(0,srate,n+1); 21 | tf = zeros(floor(pnts/n)-1,length(hz)); 22 | tv = zeros(floor(pnts/n)-1,1); 23 | 24 | for i=1:floor(pnts/n)-1 25 | datasnip = signal(i*n:(i+1)*n); 26 | 27 | pw = abs(fft(datasnip)).^2; 28 | tf(i,1:length(hz)) = pw(1:length(hz)); 29 | tv(i) = mean(time(i*n:(i+1)*n)); 30 | end 31 | 32 | subplot(4,1,2:4) 33 | imagesc(tv,hz,tf') 34 | axis xy 35 | set(gca,'ylim',[0 40]) 36 | colormap hot 37 | xlabel('Time (s)'), ylabel('Frequency (Hz)') 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /variability/SNRdata.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/variability/SNRdata.mat -------------------------------------------------------------------------------- /variability/codechallenge.m: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | frex = linspace(4,30,40); 5 | 6 | SNR = zeros(length(frex),2); 7 | srate = 1000./mean(diff(timevec)); 8 | 9 | for fi=1:length(frex) 10 | 11 | erpf = eegfilt(erp,srate,0,frex(fi)); 12 | 13 | % SNR components 14 | snr_num = erpf(:,dsearchn(timevec',timepoint)); 15 | snr_den = std( erpf(:,dsearchn(timevec',basetime(1)):dsearchn(timevec',basetime(2))) ,[],2); 16 | 17 | SNR(fi,:) = snr_num./snr_den; 18 | end 19 | 20 | clf 21 | plot(frex,SNR,'s-','markerfacecolor','w','linew',2,'markersize',10) 22 | xlabel('Low-pass upper-edge (Hz)'), ylabel('SNR') 23 | legend({'Chan1';'Chan2'}) 24 | set(gca,'xlim',[3.5 30.5]) 25 | -------------------------------------------------------------------------------- /variability/mutualinformationx.m: -------------------------------------------------------------------------------- 1 | function [mi entropy fd_bins] = mutualinformationx(x,y,fd_bins,permtest) 2 | % MUTUALINFORMATIONX Compute mutual information between two vectors 3 | % 4 | % Inputs: 5 | % x,y : data matrices of equal size 6 | % 7 | % Optional inputs: 8 | % bins : number of bins to use for distribution discretization 9 | % permtest : perform permutation test and return mi in standard-Z values 10 | % 11 | % Outputs: 12 | % mi : mutual information in bits 13 | % entropy : entropy of x, y, and joint 14 | % nbins : number of bins used for discretization 15 | % (based on Freedman-Diaconis rule) 16 | % 17 | % Mike X Cohen (mikexcohen@gmail.com) 18 | 19 | if nargin<2, error('Specify two inputs.'); end 20 | if length(x)~=length(y), error('X and Y must have equal length'); end 21 | 22 | %% determine the optimal number of bins for each variable 23 | 24 | % vectorize in the case of matrices 25 | x=x(:); y=y(:); 26 | 27 | if nargin<3 || isempty(fd_bins) 28 | n = length(x); 29 | maxmin_range = max(x)-min(x); 30 | fd_bins1 = ceil(maxmin_range/(2.0*iqr(x)*n^(-1/3))); % Freedman-Diaconis 31 | 32 | n = length(y); 33 | maxmin_range = max(y)-min(y); 34 | fd_bins2 = ceil(maxmin_range/(2.0*iqr(y)*n^(-1/3))); 35 | 36 | % and use the average... 37 | fd_bins = ceil((fd_bins1+fd_bins2)/2); 38 | end 39 | 40 | %% bin data 41 | 42 | edges = linspace(min(x),max(x),fd_bins+1); 43 | [nPerBin1,bins1] = histc(x,edges); 44 | 45 | edges = linspace(min(y),max(y),fd_bins+1); 46 | [nPerBin2,bins2] = histc(y,edges); 47 | 48 | %% compute entropies 49 | 50 | % recompute entropy with optimal bins for comparison 51 | hdat1 = hist(x,fd_bins); 52 | hdat1 = hdat1./sum(hdat1); 53 | hdat2 = hist(y,fd_bins); 54 | hdat2 = hdat2./sum(hdat2); 55 | 56 | % convert histograms to probability values 57 | for i=1:2 58 | eval([ 'entropy(' num2str(i) ') = -sum(hdat' num2str(i) '.*log2(hdat' num2str(i) '+eps));' ]); 59 | end 60 | 61 | %% compute joint probabilities 62 | 63 | jointprobs = zeros(fd_bins); 64 | for i1=1:fd_bins 65 | for i2=1:fd_bins 66 | jointprobs(i1,i2) = sum(bins1==i1 & bins2==i2); 67 | end 68 | end 69 | jointprobs=jointprobs./sum(jointprobs(:)); 70 | 71 | entropy(3) = -sum(jointprobs(:).*log2(jointprobs(:)+eps)); 72 | 73 | %% mutual information 74 | 75 | mi = sum(entropy(1:2)) - entropy(3); 76 | 77 | %% optional permutation testing 78 | 79 | if nargin==4 80 | 81 | npermutes = 500; 82 | n = length(bins2); 83 | 84 | perm_mi = zeros(1,npermutes); 85 | 86 | for permi=1:npermutes 87 | 88 | jointprobs = zeros(fd_bins); 89 | 90 | % shuffle bins 91 | binbreak = randsample(round(n*.8),1,1)+round(n*.1); 92 | switch mod(permi,4) 93 | case 0, bins2 = [ bins2(binbreak:end); bins2(1:binbreak-1) ]; 94 | case 1, bins2 = [ bins2(end:-1:binbreak); bins2(1:binbreak-1) ]; 95 | case 2, bins2 = [ bins2(binbreak:end); bins2(binbreak-1:-1:1) ]; 96 | case 3, bins2 = [ bins2(end:-1:binbreak); bins2(binbreak-1:-1:1) ]; 97 | end 98 | 99 | for i1=1:fd_bins 100 | for i2=1:fd_bins 101 | jointprobs(i1,i2) = sum(bins1==i1 & bins2==i2); 102 | end 103 | end 104 | jointprobs=jointprobs./sum(jointprobs(:)); 105 | 106 | perm_jentropy = -sum(jointprobs(:).*log2(jointprobs(:)+eps)); 107 | 108 | % mutual information 109 | perm_mi(permi) = sum(entropy(1:2)) - perm_jentropy; 110 | end 111 | 112 | mi = (mi-mean(perm_mi))/std(perm_mi); 113 | end 114 | 115 | %% 116 | % simplified replacement for randsample 117 | function y = randsample(x,n,junk) 118 | y=randperm(x); 119 | y=y(1:n); 120 | -------------------------------------------------------------------------------- /variability/sigprocMXC_CV.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Variability 4 | % VIDEO: Coefficient of variation 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % number of data points 10 | nmeans = 100; 11 | nstds = 110; 12 | 13 | % ranges of means (den.) and standard deviations (num.) 14 | means = linspace(.1,5,nmeans); 15 | stds = linspace(.1,10,nstds); 16 | 17 | 18 | % initialize matrix 19 | cv = zeros(nmeans,nstds); 20 | 21 | % loop over all values and populate matrix 22 | for mi=1:nmeans 23 | for si=1:nstds 24 | % coefficient of variation 25 | cv(mi,si) = stds(si) / means(mi); 26 | end 27 | end 28 | 29 | % programming trick! 30 | cv = bsxfun(@rdivide,stds,means'); 31 | 32 | 33 | % show in an image 34 | figure(1), clf 35 | imagesc(stds,means,cv) 36 | set(gca,'clim',[0 30],'ydir','normal') 37 | ylabel('Mean'), xlabel('Standard deviation') 38 | colorbar 39 | 40 | %% done. 41 | -------------------------------------------------------------------------------- /variability/sigprocMXC_SNR.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Variability 4 | % VIDEO: Signal-to-noise ratio (SNR) 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | pnts = 1000; 10 | time = linspace(0,5*pi,pnts); 11 | 12 | % one signal 13 | signal1 = 3+sin(time); 14 | signal1 = signal1 + randn(1,pnts); 15 | 16 | % another signal 17 | signal2 = 5 + randn(1,pnts) .* (2+sin(time)); 18 | 19 | 20 | 21 | % compute SNR in sliding windows 22 | k = round(pnts*.05); % one-sided window is 5% of signal length 23 | 24 | % initialize 25 | [snr_ts1,snr_ts2] = deal( zeros(1,pnts) ); 26 | 27 | % loop over time points 28 | for i=1:pnts 29 | 30 | % time boundaries 31 | bndL = max(1,i-k); 32 | bndU = min(pnts,i+k); 33 | 34 | % extract parts of signals 35 | sigpart1 = signal1(bndL:bndU); 36 | sigpart2 = signal2(bndL:bndU); 37 | 38 | % compute windowed SNR 39 | snr_ts1(i) = mean(sigpart1) / std(sigpart1); 40 | snr_ts2(i) = mean(sigpart2) / std(sigpart2); 41 | end 42 | 43 | 44 | 45 | %%% some plotting 46 | % plot the signals 47 | figure(1), clf 48 | subplot(211), hold on 49 | plot(time,signal1+10,'linew',3) 50 | plot(time,signal2,'linew',3) 51 | set(gca,'ytick',[]) 52 | xlabel('Time (rad.)') 53 | legend({'Signal 1';'Signal 2'}) 54 | 55 | % plot SNRs 56 | subplot(212), hold on 57 | plot(time,snr_ts1,'linew',3) 58 | plot(time,snr_ts2,'linew',3) 59 | legend({'SNR 1';'SNR 2'}) 60 | xlabel('Time (rad.)') 61 | 62 | 63 | 64 | % plot SNRs 65 | figure(2), clf 66 | subplot(211), hold on 67 | plot(time,snr_ts1,'linew',3) 68 | plot(time,snr_ts2,'linew',3) 69 | legend({'SNR 1';'SNR 2'}) 70 | ylabel('"Raw" SNR units') 71 | xlabel('Time (rad.)') 72 | 73 | 74 | subplot(212), hold on 75 | plot(time,10*log10(snr_ts1),'linew',3) 76 | plot(time,10*log10(snr_ts2),'linew',3) 77 | legend({'SNR 1';'SNR 2'}) 78 | ylabel('dB SNR units') 79 | xlabel('Time (rad.)') 80 | 81 | 82 | %% try in a voltage time series 83 | 84 | % import data 85 | load SNRdata.mat 86 | 87 | % plot mean and std data time series 88 | figure(3), clf 89 | subplot(211) 90 | plot(timevec,mean(eegdata,3),'linew',3) 91 | xlabel('Time (ms)'), ylabel('Voltage (\muV)') 92 | legend({'Chan_1';'Chan_2'}) 93 | title('Average time series') 94 | 95 | 96 | subplot(212) 97 | plot(timevec,std(eegdata,[],3),'linew',3) 98 | xlabel('Time (ms)'), ylabel('Voltage (std.)') 99 | legend({'Chan_1';'Chan_2'}) 100 | title('Standard deviation time series') 101 | 102 | 103 | %% SNR 104 | 105 | % compute SNR 106 | snr = mean(eegdata,3) ./ std(eegdata,[],3); 107 | 108 | % plot 109 | figure(4), clf 110 | plot(timevec,10*log10(snr),'linew',3) 111 | xlabel('Time (ms)'), ylabel('SNR 10log_{10}(mean/var)') 112 | legend({'Chan_1';'Chan_2'}) 113 | title('SNR time series') 114 | 115 | %% alternative: SNR at a point 116 | 117 | % pick time point 118 | timepoint = 375; 119 | basetime = [-500 0]; 120 | 121 | % average over repetitions 122 | erp = mean(eegdata,3); 123 | 124 | % SNR components 125 | snr_num = erp(:,dsearchn(timevec',timepoint)); 126 | snr_den = std( erp(:,dsearchn(timevec',basetime(1)):dsearchn(timevec',basetime(2))) ,[],2); 127 | 128 | 129 | 130 | % display SNR in the command window 131 | for i=1:2 132 | disp([ 'SNR at ' num2str(timepoint) 'ms in channel ' num2str(i) ' = ' num2str(snr_num(i)./snr_den(i)) ]) 133 | end 134 | 135 | 136 | %% done. 137 | -------------------------------------------------------------------------------- /variability/sigprocMXC_entropy.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Variability 4 | % VIDEO: Entropy 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% "discrete" entropy 10 | 11 | % generate data 12 | N = 1000; 13 | numbers = ceil( 8*rand(N,1).^2 ); 14 | 15 | 16 | % get counts and probabilities 17 | u = unique(numbers); 18 | probs = zeros(length(u),1); 19 | 20 | for ui=1:length(u) 21 | probs(ui) = sum(numbers==u(ui)) / N; 22 | end 23 | 24 | 25 | % compute entropy 26 | entropee = -sum( probs.*log2(probs+eps) ); 27 | 28 | 29 | % plot 30 | figure(1), clf 31 | bar(u,probs) 32 | title([ 'Entropy = ' num2str(entropee) ]) 33 | ylabel('Probability') 34 | 35 | %% same procedure for spike times 36 | 37 | % generate spike times series 38 | [spikets1,spikets2] = deal( zeros(N,1) ); 39 | 40 | % nonrandom 41 | spikets1( rand(N,1)>.9 ) = 1; 42 | 43 | % equal probability 44 | spikets2( rand(N,1)>.5 ) = 1; 45 | 46 | % probabilities 47 | % (note: this was incorrect in the video; the entropy of the entire time 48 | % series requires the probability of each event type. And the theoretical 49 | % entropy of a random binary sequence is 1.) 50 | probs1 = [ sum(spikets2==0) sum(spikets1==1) ] / N; 51 | probs2 = [ sum(spikets2==0) sum(spikets2==1) ] / N; 52 | 53 | % compute entropy 54 | entropee1 = -sum( probs1.*log2(probs1+eps) ); 55 | entropee2 = -sum( probs2.*log2(probs2+eps) ); 56 | 57 | % bar 58 | figure(2), clf 59 | subplot(211) 60 | plot(1:N,smooth(spikets1,10), 1:N,smooth(spikets2,10)) 61 | legend({'TS1';'TS2'}) 62 | 63 | subplot(212) 64 | bar([ entropee1 entropee2 ]) 65 | set(gca,'xlim',[0 3],'XTickLabel',{'TS1';'TS2'}) 66 | ylabel('Entropy') 67 | 68 | %% extra step for analog time series 69 | 70 | % load data time series 71 | load v1_laminar.mat 72 | 73 | % compute event-related potential (averaging) 74 | erp = mean(csd,3); 75 | 76 | 77 | % crucial parameter -- number of bins! 78 | nbins = 50; 79 | 80 | 81 | % initialize 82 | entro = zeros(size(erp,1),1); 83 | 84 | % compute entropy for each channel 85 | for chani=1:size(erp,1) 86 | 87 | % find boundaries 88 | edges = linspace(min(erp(chani,:)),max(erp(chani,:)),nbins); 89 | 90 | % bin the data 91 | [nPerBin,bins] = histc(erp(chani,:),edges); 92 | 93 | % convert to probability 94 | probs = nPerBin ./ sum(nPerBin); 95 | 96 | % compute entropy 97 | entro(chani) = -sum( probs.*log2(probs+eps) ); 98 | end 99 | 100 | % plot 101 | figure(3), clf 102 | plot(entro,1:16,'ks-','linew',5,'markerfacecolor','k','markersize',15) 103 | set(gca,'xlim',[min(entro)*.9 min(entro)*1.2]) 104 | xlabel('Entropy'), ylabel('Channel') 105 | 106 | %% loop over bin count 107 | 108 | % variable number of bins! 109 | nbins = 4:50; 110 | 111 | 112 | % initialize 113 | entro = zeros(size(erp,1),length(nbins)); 114 | 115 | for bini=1:length(nbins) 116 | 117 | % compute entropy as above 118 | for chani=1:size(erp,1) 119 | edges = linspace(min(erp(chani,:)),max(erp(chani,:)),nbins(bini)); 120 | [nPerBin,bins] = histc(erp(chani,:),edges); 121 | probs = nPerBin ./ sum(nPerBin); 122 | entro(chani,bini) = -sum( probs.*log2(probs+eps) ); 123 | end 124 | end 125 | 126 | 127 | % image of entropy by channel and bin count 128 | figure(4), clLayf 129 | contourf(nbins,1:16,entro,40,'linecolor','none') 130 | xlabel('Number of bins'), ylabel('Channel') 131 | title('Entropy as a function of bin count and channel') 132 | colorbar 133 | 134 | % plot of the same data 135 | figure(5), clf 136 | plot(entro,1:16,'s-','linew',3,'markerfacecolor','w') 137 | xlabel('Entropy'), ylabel('Channel') 138 | 139 | %% done. 140 | -------------------------------------------------------------------------------- /variability/sigprocMXC_variability.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import numpy as np\n", 10 | "import matplotlib.pyplot as plt\n", 11 | "from scipy.interpolate import griddata\n", 12 | "import scipy.io as sio\n", 13 | "from pandas import Series" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": {}, 19 | "source": [ 20 | "\n", 21 | "---\n", 22 | "# VIDEO: Total and windowed variance and RMS\n", 23 | "---\n" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "# generate signal with varying variability\n", 33 | "n = 2000\n", 34 | "p = 20 # poles for random interpolation\n", 35 | "\n", 36 | "# amplitude modulator\n", 37 | "ampmod = griddata(np.arange(0,p), np.random.rand(p)*30, np.linspace(0,p-1,n), method='nearest')\n", 38 | "ampmod = ampmod + np.mean(ampmod)/3*np.sin(np.linspace(0,6*np.pi,n))\n", 39 | "\n", 40 | "# signal and modulated noise plus quadratic\n", 41 | "signal1 = ampmod * np.random.randn(n)\n", 42 | "signal1 = signal1 + np.linspace(-10,20,n)**2\n", 43 | "\n", 44 | "\n", 45 | "# plot the signal\n", 46 | "plt.plot(signal1,'k')\n", 47 | "plt.show()" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": null, 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [ 56 | "## compute windowed variance and RMS\n", 57 | "\n", 58 | "# window size (NOTE: actual window is halfwin*2+1)\n", 59 | "halfwin = 25 # in points\n", 60 | "\n", 61 | "var_ts = np.zeros(n)\n", 62 | "rms_ts = np.zeros(n)\n", 63 | "\n", 64 | "for ti in range(0,n):\n", 65 | " \n", 66 | " # boundary points\n", 67 | " low_bnd = np.max((0,ti-halfwin))\n", 68 | " upp_bnd = np.min((n,ti+halfwin))\n", 69 | " \n", 70 | " # signal segment\n", 71 | " tmpsig = signal1[range(low_bnd,upp_bnd)]\n", 72 | " \n", 73 | " # compute variance and RMS in this window\n", 74 | " var_ts[ti] = np.var(tmpsig)\n", 75 | " rms_ts[ti] = np.sqrt(np.mean( tmpsig**2 ))\n", 76 | "\n", 77 | "\n", 78 | "# and plot\n", 79 | "plt.plot(var_ts,'r',label='Variance')\n", 80 | "plt.plot(rms_ts,'b',label='RMS')\n", 81 | "plt.legend()\n", 82 | "plt.show()" 83 | ] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "metadata": {}, 88 | "source": [ 89 | "\n", 90 | "---\n", 91 | "# VIDEO: Signal-to-noise ratio (SNR)\n", 92 | "---\n" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": null, 98 | "metadata": {}, 99 | "outputs": [], 100 | "source": [ 101 | "\n", 102 | "# simulation params\n", 103 | "pnts = 1000\n", 104 | "time = np.linspace(0,5*np.pi,pnts)\n", 105 | "\n", 106 | "# one signal\n", 107 | "signal1 = 3+np.sin(time)\n", 108 | "signal1 = signal1 + np.random.randn(pnts)\n", 109 | "\n", 110 | "# another signal\n", 111 | "signal2 = 5 + np.random.randn(pnts) * (2+np.sin(time))\n", 112 | "\n", 113 | "\n", 114 | "\n", 115 | "# compute SNR in sliding windows\n", 116 | "k = int(pnts*.05) # one-sided window is 5% of signal length\n", 117 | "\n", 118 | "# initialize\n", 119 | "snr_ts1 = np.zeros(pnts)\n", 120 | "snr_ts2 = np.zeros(pnts)\n", 121 | "\n", 122 | "\n", 123 | "# loop over time points\n", 124 | "for i in range(0,pnts):\n", 125 | " \n", 126 | " # time boundaries\n", 127 | " bndL = max((0,i-k))\n", 128 | " bndU = min((pnts,i+k))\n", 129 | " \n", 130 | " # extract parts of signals\n", 131 | " sigpart1 = signal1[range(bndL,bndU)]\n", 132 | " sigpart2 = signal2[range(bndL,bndU)]\n", 133 | " \n", 134 | " # compute windowed SNR\n", 135 | " snr_ts1[i] = np.mean(sigpart1) / np.std(sigpart1)\n", 136 | " snr_ts2[i] = np.mean(sigpart2) / np.std(sigpart2)\n", 137 | " \n", 138 | "\n", 139 | "\n", 140 | "# plot the signals\n", 141 | "plt.subplot(211)\n", 142 | "plt.plot(time,signal1+10,label='Signal 1')\n", 143 | "plt.plot(time,signal2,label='Signal 2')\n", 144 | "plt.yticks([],[])\n", 145 | "plt.xlabel('Time (rad.)')\n", 146 | "plt.legend()\n", 147 | "\n", 148 | "# plot SNRs\n", 149 | "plt.subplot(212)\n", 150 | "plt.plot(time,snr_ts1,label='SNR 1')\n", 151 | "plt.plot(time,snr_ts2,label='SNR 2')\n", 152 | "plt.legend()\n", 153 | "plt.xlabel('Time (rad.)')\n", 154 | "plt.show()\n", 155 | "\n", 156 | "\n", 157 | "# plot SNRs\n", 158 | "plt.subplot(211)\n", 159 | "plt.plot(time,snr_ts1,label='SNR 1')\n", 160 | "plt.plot(time,snr_ts2,label='SNR 2')\n", 161 | "plt.legend()\n", 162 | "plt.ylabel('\"Raw\" SNR units')\n", 163 | "plt.xlabel('Time (rad.)')\n", 164 | "\n", 165 | "\n", 166 | "plt.subplot(212)\n", 167 | "plt.plot(time,10*np.log10(snr_ts1),label='SNR 1')\n", 168 | "plt.plot(time,10*np.log10(snr_ts2),label='SNR 2')\n", 169 | "plt.legend()\n", 170 | "plt.ylabel('dB SNR units')\n", 171 | "plt.xlabel('Time (rad.)')\n", 172 | "plt.show()" 173 | ] 174 | }, 175 | { 176 | "cell_type": "code", 177 | "execution_count": null, 178 | "metadata": {}, 179 | "outputs": [], 180 | "source": [ 181 | "## try in a voltage time series\n", 182 | "\n", 183 | "# import data\n", 184 | "matdat = sio.loadmat('SNRdata.mat')\n", 185 | "timevec = np.squeeze( matdat['timevec'] )\n", 186 | "eegdata = matdat['eegdata']\n", 187 | "\n", 188 | "\n", 189 | "# plot mean and std data time series\n", 190 | "plt.subplot(211)\n", 191 | "plt.plot(timevec,np.mean(eegdata,2).T)\n", 192 | "plt.xlabel('Time (ms)')\n", 193 | "plt.ylabel('Voltage ($\\mu$V)')\n", 194 | "plt.legend(['Chan 1','Chan 2'])\n", 195 | "plt.title('Average time series')\n", 196 | "\n", 197 | "\n", 198 | "plt.subplot(212)\n", 199 | "plt.plot(timevec,np.std(eegdata,2).T)\n", 200 | "plt.xlabel('Time (ms)')\n", 201 | "plt.ylabel('Voltage (std.)')\n", 202 | "plt.legend(['Chan 1','Chan 2'])\n", 203 | "plt.title('Standard deviation time series')\n", 204 | "\n", 205 | "plt.show()\n", 206 | "\n", 207 | "\n", 208 | "\n", 209 | "\n", 210 | "## compute SNR\n", 211 | "snr = np.mean(eegdata,2) / np.std(eegdata,2)\n", 212 | "\n", 213 | "# plot\n", 214 | "plt.plot(timevec,10*np.log10(snr).T)\n", 215 | "plt.xlabel('Time (ms)')\n", 216 | "plt.ylabel('SNR 10log_{10}(mean/var)')\n", 217 | "plt.legend(['Chan 1','Chan 2'])\n", 218 | "plt.title('SNR time series')\n", 219 | "plt.show()" 220 | ] 221 | }, 222 | { 223 | "cell_type": "code", 224 | "execution_count": null, 225 | "metadata": {}, 226 | "outputs": [], 227 | "source": [ 228 | "## alternative: SNR at a point\n", 229 | "\n", 230 | "# pick time point\n", 231 | "timepoint = 375\n", 232 | "basetime = [-500, 0]\n", 233 | "\n", 234 | "# convert baseline time window to indices\n", 235 | "\n", 236 | "\n", 237 | "# average over repetitions\n", 238 | "erp = np.mean(eegdata,2)\n", 239 | "bidx1 = np.argmin((timevec-basetime[0])**2)\n", 240 | "bidx2 = np.argmin((timevec-basetime[1])**2)\n", 241 | "\n", 242 | "\n", 243 | "# SNR components\n", 244 | "snr_num = erp[:,np.argmin((timevec-timepoint)**2)]\n", 245 | "snr_den = np.std( erp[:,range(bidx1,bidx2)] ,1)\n", 246 | "\n", 247 | "\n", 248 | "\n", 249 | "# display SNR in the command window\n", 250 | "print('SNR at %d ms in channel 1 = %g' %(timepoint,snr_num[0]/snr_den[0]) )\n", 251 | "print('SNR at %d ms in channel 2 = %g' %(timepoint,snr_num[1]/snr_den[1]) )\n" 252 | ] 253 | }, 254 | { 255 | "cell_type": "markdown", 256 | "metadata": {}, 257 | "source": [ 258 | "\n", 259 | "---\n", 260 | "# VIDEO: Coefficient of variation\n", 261 | "---\n" 262 | ] 263 | }, 264 | { 265 | "cell_type": "code", 266 | "execution_count": null, 267 | "metadata": {}, 268 | "outputs": [], 269 | "source": [ 270 | "# number of data points\n", 271 | "nmeans = 100\n", 272 | "nstds = 110\n", 273 | "\n", 274 | "# ranges of means (den.) and standard deviations (num.)\n", 275 | "means = np.linspace(.1,5,nmeans)\n", 276 | "stds = np.linspace(.1,10,nstds)\n", 277 | "\n", 278 | "\n", 279 | "# initialize matrix\n", 280 | "cv = np.zeros((nmeans,nstds))\n", 281 | "\n", 282 | "# loop over all values and populate matrix\n", 283 | "for mi in range(0,nmeans):\n", 284 | " for si in range(0,nstds):\n", 285 | " \n", 286 | " # coefficient of variation\n", 287 | " cv[mi,si] = stds[si] / means[mi]\n", 288 | " \n", 289 | "\n", 290 | "\n", 291 | "# show in an image\n", 292 | "plt.pcolormesh(stds,means,cv,vmin=0,vmax=30)\n", 293 | "plt.ylabel('Mean')\n", 294 | "plt.xlabel('Standard deviation')\n", 295 | "plt.colorbar()\n", 296 | "plt.show()" 297 | ] 298 | }, 299 | { 300 | "cell_type": "markdown", 301 | "metadata": {}, 302 | "source": [ 303 | "\n", 304 | "---\n", 305 | "# VIDEO: Entropy\n", 306 | "---\n" 307 | ] 308 | }, 309 | { 310 | "cell_type": "code", 311 | "execution_count": null, 312 | "metadata": {}, 313 | "outputs": [], 314 | "source": [ 315 | "## \"discrete\" entropy\n", 316 | "\n", 317 | "# generate data\n", 318 | "N = 1000\n", 319 | "numbers = np.ceil( 8*np.random.rand(N)**2 )\n", 320 | "\n", 321 | "\n", 322 | "# get counts and probabilities\n", 323 | "u = np.unique(numbers)\n", 324 | "probs = np.zeros(len(u))\n", 325 | "\n", 326 | "for ui in range(0,len(u)):\n", 327 | " probs[ui] = np.sum(numbers==u[ui]) / N\n", 328 | "\n", 329 | "\n", 330 | "# compute entropy\n", 331 | "entropee = -np.sum( probs*np.log2(probs+np.finfo(float).eps) )\n", 332 | "\n", 333 | "\n", 334 | "# plot\n", 335 | "plt.bar(u,probs)\n", 336 | "plt.title('Entropy = %g' %entropee)\n", 337 | "plt.ylabel('Probability')\n", 338 | "plt.show()" 339 | ] 340 | }, 341 | { 342 | "cell_type": "code", 343 | "execution_count": null, 344 | "metadata": {}, 345 | "outputs": [], 346 | "source": [ 347 | "## same procedure for spike times\n", 348 | "\n", 349 | "# generate spike times series\n", 350 | "spikets1 = np.zeros(N)\n", 351 | "spikets2 = np.zeros(N)\n", 352 | "\n", 353 | "# nonrandom\n", 354 | "spikets1[ np.random.rand(N)>.9 ] = 1\n", 355 | "\n", 356 | "# equal probability\n", 357 | "spikets2[ np.random.rand(N)>.5 ] = 1\n", 358 | "\n", 359 | "# probabilities\n", 360 | "# (note: this was incorrect in the video; the entropy of the entire time\n", 361 | "# series requires the probability of each event type. And the theoretical\n", 362 | "# entropy of a random binary sequence is 1.)\n", 363 | "probs1 = ( np.sum(spikets1==0)/N, np.sum(spikets1==1)/N )\n", 364 | "probs2 = ( np.sum(spikets2==0)/N, np.sum(spikets2==1)/N )\n", 365 | "\n", 366 | "# compute entropy\n", 367 | "entropee1 = -np.sum( probs1*np.log2(probs1+np.finfo(float).eps) )\n", 368 | "entropee2 = -np.sum( probs2*np.log2(probs2+np.finfo(float).eps) )\n", 369 | "\n", 370 | "\n", 371 | "\n", 372 | "# convert data to pandas for smoothing\n", 373 | "spikets1 = Series(spikets1).rolling(window=10)\n", 374 | "spikets2 = Series(spikets2).rolling(window=10)\n", 375 | "\n", 376 | "\n", 377 | "plt.plot(np.arange(0,N),spikets1.mean(),label='Spikes 1')\n", 378 | "plt.plot(np.arange(0,N),spikets2.mean(),label='Spikes 2')\n", 379 | "plt.legend()\n", 380 | "plt.show()\n", 381 | "\n", 382 | "\n", 383 | "plt.bar([1,2],[ entropee1, entropee2 ])\n", 384 | "plt.xlim([0, 3])\n", 385 | "plt.xticks([1,2],('TS1','TS2'))\n", 386 | "plt.ylabel('Entropy')\n", 387 | "plt.show()\n" 388 | ] 389 | }, 390 | { 391 | "cell_type": "code", 392 | "execution_count": null, 393 | "metadata": {}, 394 | "outputs": [], 395 | "source": [ 396 | "## extra step for analog time series\n", 397 | "\n", 398 | "# load data time series\n", 399 | "matdat = sio.loadmat('v1_laminar.mat')\n", 400 | "csd = matdat['csd']\n", 401 | "\n", 402 | "# sizes of the dimensions (chan X time X repetitions)\n", 403 | "csdsize = np.shape(csd)\n", 404 | "\n", 405 | "# compute event-related potential (averaging)\n", 406 | "erp = np.mean(csd,2)\n", 407 | "\n", 408 | "\n", 409 | "# crucial parameter -- number of bins!\n", 410 | "nbins = 50\n", 411 | "\n", 412 | "\n", 413 | "# initialize entropy matrix\n", 414 | "entro = np.zeros(csdsize[0])\n", 415 | "\n", 416 | "# compute entropy for each channel\n", 417 | "for chani in range(0,csdsize[0]):\n", 418 | " \n", 419 | " # find boundaries\n", 420 | " edges = np.linspace(np.min(erp[chani,:]),np.max(erp[chani,:]),nbins)\n", 421 | " \n", 422 | " # bin the data\n", 423 | " nPerBin,bins = np.histogram(erp[chani,:],edges)\n", 424 | " \n", 425 | " # convert to probability\n", 426 | " probs = nPerBin / np.sum(nPerBin)\n", 427 | " \n", 428 | " # compute entropy\n", 429 | " entro[chani] = -np.sum( probs*np.log2(probs+np.finfo(float).eps) )\n", 430 | " \n", 431 | "\n", 432 | "# plot\n", 433 | "plt.plot(entro,np.arange(1,17),'ks-')\n", 434 | "plt.xlim([np.min(entro)*.9, np.min(entro)*1.2])\n", 435 | "plt.xlabel('Entropy')\n", 436 | "plt.ylabel('Channel')\n", 437 | "plt.show()" 438 | ] 439 | }, 440 | { 441 | "cell_type": "code", 442 | "execution_count": null, 443 | "metadata": {}, 444 | "outputs": [], 445 | "source": [ 446 | "## loop over bin count\n", 447 | "\n", 448 | "# variable number of bins!\n", 449 | "nbins = np.arange(4,51)\n", 450 | "\n", 451 | "# initialize\n", 452 | "entro = np.zeros((np.shape(erp)[0],len(nbins)))\n", 453 | "\n", 454 | "for bini in range(len(nbins)):\n", 455 | " \n", 456 | " # compute entropy as above\n", 457 | " for chani in range(np.shape(erp)[0]):\n", 458 | " edges = np.linspace(np.min(erp[chani,:]),np.max(erp[chani,:]),nbins[bini])\n", 459 | " nPerBin,bins = np.histogram(erp[chani,:],edges)\n", 460 | " probs = nPerBin / np.sum(nPerBin)\n", 461 | " entro[chani,bini] = -np.sum( probs*np.log2(probs+np.finfo(float).eps) )\n", 462 | "\n", 463 | " \n", 464 | "plt.imshow(entro, extent=[4,50,16,1])\n", 465 | "plt.xlabel('Number of bins')\n", 466 | "plt.ylabel('Channel')\n", 467 | "plt.title('Entropy as a function of bin count and channel')\n", 468 | "plt.show()\n", 469 | "\n", 470 | "\n", 471 | "plt.plot(entro,np.arange(1,17),'s-')\n", 472 | "plt.xlabel('Entropy')\n", 473 | "plt.ylabel('Channel')\n", 474 | "plt.show()" 475 | ] 476 | } 477 | ], 478 | "metadata": { 479 | "kernelspec": { 480 | "display_name": "Python 3", 481 | "language": "python", 482 | "name": "python3" 483 | }, 484 | "language_info": { 485 | "codemirror_mode": { 486 | "name": "ipython", 487 | "version": 3 488 | }, 489 | "file_extension": ".py", 490 | "mimetype": "text/x-python", 491 | "name": "python", 492 | "nbconvert_exporter": "python", 493 | "pygments_lexer": "ipython3", 494 | "version": "3.7.4" 495 | } 496 | }, 497 | "nbformat": 4, 498 | "nbformat_minor": 2 499 | } 500 | -------------------------------------------------------------------------------- /variability/sigprocMXC_windowedVar.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Variability 4 | % VIDEO: Total and windowed variance and RMS 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % generate signal with varying variability 10 | n = 2000; 11 | p = 20; % poles for random interpolation 12 | 13 | % amplitude modulator 14 | ampmod = interp1(rand(p,1)*30,linspace(1,p,n),'nearest')'; 15 | ampmod = ampmod + mean(ampmod)/3*sin(linspace(0,6*pi,n))'; 16 | 17 | % signal and modulated noise plus quadratic 18 | signal = ampmod .* randn(size(ampmod)); 19 | signal = signal + linspace(-10,20,n)'.^2; 20 | 21 | 22 | % plot the signal 23 | figure(1), clf 24 | plot(signal) 25 | 26 | %% compute windowed variance and RMS 27 | 28 | % window size (NOTE: actual window is halfwin*2+1) 29 | halfwin = 25; % in points 30 | 31 | [var_ts,rms_ts] = deal( zeros(n,1) ); 32 | 33 | for ti=1:n 34 | 35 | % boundary points 36 | low_bnd = max(1,ti-halfwin); 37 | upp_bnd = min(n,ti+halfwin); 38 | 39 | % signal segment 40 | tmpsig = signal(low_bnd:upp_bnd); 41 | 42 | % compute variance and RMS in this window 43 | var_ts(ti) = var(tmpsig); 44 | rms_ts(ti) = sqrt(mean( tmpsig.^2 )); 45 | end 46 | 47 | 48 | % and plot 49 | figure(2), clf, hold on 50 | 51 | plot(var_ts,'r','linew',2) 52 | plot(rms_ts,'b','linew',2) 53 | legend({'Variance';'RMS'}) 54 | 55 | %% done. 56 | -------------------------------------------------------------------------------- /variability/v1_laminar.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/variability/v1_laminar.mat -------------------------------------------------------------------------------- /wavelet/data4TF.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/wavelet/data4TF.mat -------------------------------------------------------------------------------- /wavelet/sigprocMXC_timefreq.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing and image processing in MATLAB and Python 3 | % SECTION: Wavelet analysis 4 | % VIDEO: Time-frequency analysis with complex wavelets 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % create signal 10 | srate = 1000; 11 | time = -3:1/srate:3; 12 | pnts = length(time); 13 | freqmod = exp(-time.^2)*10+10; 14 | freqmod = freqmod + linspace(0,10,pnts); 15 | signal = sin( 2*pi * ((time + cumsum(freqmod))/srate) ); 16 | 17 | 18 | % plot the signal 19 | figure(1), clf 20 | subplot(411) 21 | plot(time,signal,'linew',1) 22 | xlabel('Time (s)') 23 | title('Time-domain signal') 24 | 25 | %% create complex Morlet wavelets 26 | 27 | % wavelet parameters 28 | nfrex = 50; % 50 frequencies 29 | frex = linspace(3,35,nfrex); 30 | fwhm = .2; % full-width at half-maximum in seconds 31 | 32 | % initialize matrices for wavelets 33 | wavelets = zeros(nfrex,pnts); 34 | 35 | % create complex Morlet wavelet family 36 | for wi=1:nfrex 37 | % Gaussian 38 | gaussian = exp( -(4*log(2)*time.^2) / fwhm^2 ); 39 | 40 | % complex Morlet wavelet 41 | wavelets(wi,:) = exp(1i*2*pi*frex(wi)*time) .* gaussian; 42 | end 43 | 44 | % show the wavelets 45 | figure(2), clf 46 | subplot(411) 47 | plot(time,real(wavelets(10,:)), time,imag(wavelets(10,:))) 48 | xlabel('Time') 49 | legend({'Real';'Imaginary'}) 50 | 51 | subplot(4,1,2:4) 52 | contourf(time,frex,real(wavelets),40,'linecolor','none') 53 | xlabel('Time (s)'), ylabel('Frequency (Hz)') 54 | title('Real part of wavelets') 55 | 56 | %% run convolution using FFTs 57 | 58 | % convolution parameters 59 | nconv = pnts*2-1; % M+N-1 60 | halfk = floor(pnts/2)+1; 61 | 62 | % Fourier spectrum of the signal 63 | sigX = fft(signal,nconv); 64 | 65 | % initialize time-frequency matrix 66 | tf = zeros(nfrex,pnts); 67 | 68 | 69 | % convolution per frequency 70 | for fi=1:nfrex 71 | 72 | % FFT of the wavelet 73 | waveX = fft(wavelets(fi,:),nconv); 74 | % amplitude-normalize the wavelet 75 | %%% note: ensure we're taking the magnitude of the peak; 76 | % I didn't explain this in the video but it ensures normalization by 77 | % the magnitude and not the complex value. 78 | waveX = waveX ./ abs(max(waveX)); 79 | 80 | % convolution 81 | convres = ifft( waveX.*sigX ); 82 | % trim the "wings" 83 | convres = convres(halfk:end-halfk+1); 84 | 85 | % extract power from complex signal 86 | tf(fi,:) = abs(convres).^2; 87 | end 88 | 89 | %% plot the results 90 | 91 | figure(1) 92 | subplot(4,1,[2 3 4]) 93 | contourf(time,frex,tf,40,'linecolor','none') 94 | xlabel('Time (s)'), ylabel('Frequency (Hz)') 95 | title('Time-frequency power') 96 | 97 | %% done. 98 | -------------------------------------------------------------------------------- /wavelet/sigprocMXC_timefreqBrain.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Wavelet analysis 4 | % VIDEO: Time-frequency analysis of brain signals 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % load in data 10 | load data4TF.mat 11 | 12 | % plot the signal 13 | figure(1), clf 14 | subplot(411) 15 | plot(timevec,data,'linew',1) 16 | set(gca,'xlim',timevec([1 end])) 17 | xlabel('Time (s)'), ylabel('Voltage (\muV)') 18 | title('Time-domain signal') 19 | 20 | %% create complex Morlet wavelets 21 | 22 | % wavelet parameters 23 | nfrex = 50; % 50 frequencies 24 | frex = linspace(8,70,nfrex); 25 | fwhm = .2; % full-width at half-maximum in seconds 26 | 27 | % time vector for wavelets 28 | wavetime = -2:1/srate:2; 29 | 30 | 31 | % initialize matrices for wavelets 32 | wavelets = zeros(nfrex,length(wavetime)); 33 | 34 | % create complex Morlet wavelet family 35 | for wi=1:nfrex 36 | % Gaussian 37 | gaussian = exp( -(4*log(2)*wavetime.^2) / fwhm^2 ); 38 | 39 | % complex Morlet wavelet 40 | wavelets(wi,:) = exp(1i*2*pi*frex(wi)*wavetime) .* gaussian; 41 | end 42 | 43 | % show the wavelets 44 | figure(2), clf 45 | subplot(411) 46 | plot(wavetime,real(wavelets(10,:)), wavetime,imag(wavelets(10,:))) 47 | xlabel('Time') 48 | legend({'Real';'Imaginary'}) 49 | 50 | subplot(4,1,2:4) 51 | contourf(wavetime,frex,real(wavelets),40,'linecolor','none') 52 | xlabel('Time (s)'), ylabel('Frequency (Hz)') 53 | title('Real part of wavelets') 54 | 55 | %% run convolution using spectral multiplication 56 | 57 | % convolution parameters 58 | nconv = length(timevec) + length(wavetime) - 1; % M+N-1 59 | halfk = floor(length(wavetime)/2); 60 | 61 | % Fourier spectrum of the signal 62 | dataX = fft(data,nconv); 63 | 64 | % initialize time-frequency matrix 65 | tf = zeros(nfrex,length(timevec)); 66 | 67 | 68 | % convolution per frequency 69 | for fi=1:nfrex 70 | 71 | % FFT of the wavelet 72 | waveX = fft(wavelets(fi,:),nconv); 73 | 74 | % amplitude-normalize the wavelet 75 | %%% note: ensure we're taking the magnitude of the peak; 76 | % I didn't explain this in the video but it ensures normalization by 77 | % the magnitude and not the complex value. 78 | waveX = waveX ./ abs(max(waveX)); 79 | 80 | % convolution 81 | convres = ifft( waveX.*dataX ); 82 | % trim the "wings" 83 | convres = convres(halfk:end-halfk); 84 | 85 | % extract power from complex signal 86 | tf(fi,:) = abs(convres).^2; 87 | end 88 | 89 | %% plot the results 90 | 91 | figure(1) 92 | subplot(4,1,[2 3 4]) 93 | contourf(timevec,frex,tf,40,'linecolor','none') 94 | xlabel('Time (s)'), ylabel('Frequency (Hz)') 95 | set(gca,'clim',[0 1e3]) 96 | title('Time-frequency power') 97 | colormap hot 98 | 99 | %% done. 100 | -------------------------------------------------------------------------------- /wavelet/sigprocMXC_waveletConv.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Wavelet analysis 4 | % VIDEO: Convolution with wavelets 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% general simulation parameters 10 | 11 | fs = 1024; 12 | npnts = fs*5; % 5 seconds 13 | 14 | % centered time vector 15 | timevec = (1:npnts)/fs; 16 | timevec = timevec - mean(timevec); 17 | 18 | % for power spectrum 19 | hz = linspace(0,fs/2,floor(npnts/2)+1); 20 | 21 | %% Morlet wavelet 22 | 23 | % parameters 24 | freq = 4; % peak frequency 25 | csw = cos(2*pi*freq*timevec); % cosine wave 26 | fwhm = .5; % full-width at half-maximum in seconds 27 | gaussian = exp( -(4*log(2)*timevec.^2) / fwhm^2 ); % Gaussian 28 | 29 | % Morlet wavelet 30 | MorletWavelet = csw .* gaussian; 31 | 32 | %% Haar wavelet 33 | 34 | HaarWavelet = zeros(npnts,1); 35 | HaarWavelet(dsearchn(timevec',0):dsearchn(timevec',.5)) = 1; 36 | HaarWavelet(dsearchn(timevec',.5):dsearchn(timevec',1-1/fs)) = -1; 37 | 38 | %% Mexican hat wavelet 39 | 40 | s = .4; 41 | MexicanWavelet = (2/(sqrt(3*s)*pi^.25)) .* (1- (timevec.^2)/(s^2) ) .* exp( (-timevec.^2)./(2*s^2) ); 42 | 43 | %% convolve with random signal 44 | 45 | % signal 46 | signal = detrend(cumsum(randn(npnts,1))); 47 | 48 | % convolve signal with different wavelets 49 | morewav = conv(signal,MorletWavelet,'same'); 50 | haarwav = conv(signal,HaarWavelet,'same'); 51 | mexiwav = conv(signal,MexicanWavelet,'same'); 52 | 53 | % amplitude spectra 54 | morewaveAmp = abs(fft(morewav)/npnts); 55 | haarwaveAmp = abs(fft(haarwav)/npnts); 56 | mexiwaveAmp = abs(fft(mexiwav)/npnts); 57 | 58 | 59 | %%% plotting 60 | figure(2), clf 61 | 62 | % the signal 63 | subplot(511) 64 | plot(timevec,signal,'k') 65 | title('Signal') 66 | xlabel('Time (s)'), ylabel('Amplitude') 67 | 68 | 69 | % the convolved signals 70 | subplot(5,1,2:3), hold on 71 | plot(timevec,morewav,'linew',2) 72 | plot(timevec,haarwav,'linew',2) 73 | plot(timevec,mexiwav,'linew',2) 74 | 75 | xlabel('Time (sec.)'), ylabel('Amplitude') 76 | legend({'Morlet';'Haar';'Mexican'}) 77 | 78 | 79 | % spectra of convolved signals 80 | subplot(5,1,4:5), hold on 81 | plot(hz,morewaveAmp(1:length(hz)),'linew',2) 82 | plot(hz,haarwaveAmp(1:length(hz)),'linew',2) 83 | plot(hz,mexiwaveAmp(1:length(hz)),'linew',2) 84 | 85 | set(gca,'xlim',[0 40],'yscale','lo') 86 | xlabel('Frequency (Hz.)'), ylabel('Amplitude') 87 | 88 | %% done. 89 | -------------------------------------------------------------------------------- /wavelet/sigprocMXC_waveletTF.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Wavelet analysis 4 | % VIDEO: Time-frequency analysis with complex wavelets 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % data from http://www.vibrationdata.com/Solomon_Time_History.zip 10 | 11 | equake = load('Solomon_Time_History.txt'); 12 | 13 | % more convenient 14 | times = equake(:,1); 15 | equake = equake(:,2); 16 | 17 | srate = round( 1./mean(diff(times)) ); 18 | 19 | %% plot the signal 20 | 21 | % time domain 22 | figure(1), clf 23 | subplot(211) 24 | plot(times/60/60,equake) 25 | set(gca,'xlim',times([1 end])/60/60) 26 | xlabel('Time (hours)') 27 | 28 | 29 | 30 | %%%% NOTE FOR OCTAVE USERS 31 | % Run the following line before calling the pwelch function 32 | %prev_compat = pwelch('R12+'); 33 | 34 | 35 | 36 | % frequency domain using pwelch 37 | subplot(212) 38 | winsize = srate*60*10; % window size of 10 minutes 39 | pwelch(equake,winsize,winsize/10,[],srate); 40 | set(gca,'ylim',[-100 -70]) 41 | 42 | %% setup time-frequency analysis 43 | 44 | % parameters (in Hz) 45 | numFrex = 40; 46 | minFreq = 2; 47 | maxFreq = srate/2; 48 | npntsTF = 1000; % this one's in points 49 | 50 | % frequencies in Hz 51 | frex = linspace(minFreq,maxFreq,numFrex); 52 | 53 | 54 | % wavelet widths (FWHM in seconds) 55 | fwhms = linspace(5,15,numFrex)'; 56 | 57 | 58 | % time points to save 59 | tidx = round( linspace(1,length(times),npntsTF) ); 60 | 61 | 62 | % setup wavelet and convolution parameters 63 | wavet = (-10:1/srate:10); 64 | halfw = floor(length(wavet)/2); 65 | nConv = length(times) + length(wavet) - 1; 66 | 67 | % create family of Morlet wavelets 68 | cmw = zeros(length(wavet),numFrex); 69 | 70 | % loop over frequencies and create wavelets 71 | for fi=1:numFrex 72 | cmw(:,fi) = exp(2*1i*pi*frex(fi)*wavet) .* exp(-(4*log(2)*wavet.^2)/fwhms(fi).^2); 73 | end 74 | 75 | % plot them 76 | figure(2), clf 77 | imagesc(wavet,frex,abs(cmw)') 78 | xlabel('Time (s)'), ylabel('Frequency (Hz)') 79 | set(gca,'ydir','normal') 80 | 81 | %% run convolution 82 | 83 | % initialize time-frequency matrix 84 | [tf,tfN] = deal( zeros(length(frex),length(tidx)) ); 85 | 86 | % baseline time window for normalization 87 | basetidx = dsearchn(times,[-1000 0]'); 88 | basepow = zeros(numFrex,1); 89 | 90 | % spectrum of data 91 | dataX = fft(equake,nConv); 92 | 93 | % loop over frequencies 94 | for fi=1:numFrex 95 | 96 | % create wavelet 97 | waveX = fft( cmw(:,fi),nConv ); 98 | 99 | %%% note: ensure we're taking the magnitude of the peak; 100 | % I didn't explain this in the video but it ensures normalization by 101 | % the magnitude and not the complex value. 102 | waveX = waveX./abs(max(waveX)); 103 | 104 | % convolve 105 | as = ifft( waveX.*dataX ); 106 | % trim 107 | as = as(halfw:end-halfw); 108 | 109 | % power time course at this frequency 110 | powts = abs(as).^2; 111 | 112 | % baseline (pre-quake) 113 | basepow(fi) = mean(powts(basetidx(1):basetidx(2))); 114 | 115 | tf(fi,:) = 10*log10( powts(tidx) ); 116 | tfN(fi,:) = 10*log10( powts(tidx)/basepow(fi) ); 117 | end 118 | 119 | %% show time-frequency maps 120 | 121 | % "raw" power 122 | figure(3), clf 123 | subplot(211) 124 | contourf(times(tidx),frex,tf,40,'linecolor','none') 125 | xlabel('Time'), ylabel('Frequency (Hz)') 126 | title('Time-frequency plot') 127 | set(gca,'clim',[-150 -70]) 128 | 129 | % pre-quake normalized power 130 | subplot(212) 131 | contourf(times(tidx),frex,tfN,40,'linecolor','none') 132 | xlabel('Time'), ylabel('Frequency (Hz)') 133 | title('Time-frequency plot') 134 | set(gca,'clim',[-1 1]*15) 135 | 136 | %% normalized and non-normalized power 137 | 138 | figure(4), clf 139 | 140 | subplot(211) 141 | plot(frex,mean(tf,2),'ks-','linew',2,'markersize',10,'markerfacecolor','w') 142 | xlabel('Frequency (Hz)'), ylabel('Power (10log_{10})') 143 | title('Raw power') 144 | 145 | 146 | subplot(212) 147 | plot(frex,mean(tfN,2),'ks-','linew',2,'markersize',10,'markerfacecolor','w') 148 | xlabel('Frequency (Hz)'), ylabel('Power (norm.)') 149 | title('Pre-quake normalized power') 150 | 151 | %% done. 152 | 153 | -------------------------------------------------------------------------------- /wavelet/sigprocMXC_wavelets.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Wavelet analysis 4 | % VIDEO: What are wavelets? 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | %% general simulation parameters 10 | 11 | fs = 1024; 12 | npnts = fs*5; % 5 seconds 13 | 14 | % centered time vector 15 | timevec = (1:npnts)/fs; 16 | timevec = timevec - mean(timevec); 17 | 18 | % for power spectrum 19 | hz = linspace(0,fs/2,floor(npnts/2)+1); 20 | 21 | %% Morlet wavelet 22 | 23 | % parameters 24 | freq = 4; % peak frequency 25 | csw = cos(2*pi*freq*timevec); % cosine wave 26 | fwhm = .5; % full-width at half-maximum in seconds 27 | gaussian = exp( -(4*log(2)*timevec.^2) / fwhm^2 ); % Gaussian 28 | 29 | % Morlet wavelet 30 | MorletWavelet = csw .* gaussian; 31 | 32 | % amplitude spectrum 33 | MorletWaveletPow = abs(fft(MorletWavelet)/npnts); 34 | 35 | 36 | 37 | figure(1), clf 38 | 39 | % time-domain plotting 40 | subplot(421) 41 | plot(timevec,MorletWavelet,'k','linew',2) 42 | set(gca,'ylim',[-1 1.1],'xlim',timevec([1 end])) 43 | xlabel('Time (sec.)'), ylabel('Amplitude') 44 | title('Morlet wavelet in time domain') 45 | 46 | % frequency-domain plotting 47 | subplot(422) 48 | plot(hz,MorletWaveletPow(1:length(hz)),'k','linew',2) 49 | set(gca,'xlim',[0 freq*3]) 50 | xlabel('Frequency (Hz)'), ylabel('Amplitude') 51 | title('Morlet wavelet in frequency domain') 52 | 53 | %% Haar wavelet 54 | 55 | % create Haar wavelet 56 | HaarWavelet = zeros(npnts,1); 57 | HaarWavelet(dsearchn(timevec',0):dsearchn(timevec',.5)) = 1; 58 | HaarWavelet(dsearchn(timevec',.5):dsearchn(timevec',1-1/fs)) = -1; 59 | 60 | % amplitude spectrum 61 | HaarWaveletPow = abs(fft(HaarWavelet)/npnts); 62 | 63 | 64 | % time-domain plotting 65 | subplot(423) 66 | plot(timevec,HaarWavelet,'k','linew',2) 67 | set(gca,'ylim',[-1.1 1.1],'xlim',timevec([1 end])) 68 | xlabel('Time (sec.)'), ylabel('Amplitude') 69 | title('Haar wavelet in time domain') 70 | 71 | % frequency-domain plotting 72 | subplot(424) 73 | plot(hz,HaarWaveletPow(1:length(hz)),'k','linew',2) 74 | set(gca,'xlim',[0 freq*3]) 75 | xlabel('Frequency (Hz)'), ylabel('Amplitude') 76 | title('Haar wavelet in frequency domain') 77 | 78 | 79 | %% Mexican hat wavelet 80 | 81 | % the wavelet 82 | s = .4; 83 | MexicanWavelet = (2/(sqrt(3*s)*pi^.25)) .* (1- (timevec.^2)/(s^2) ) .* exp( (-timevec.^2)./(2*s^2) ); 84 | 85 | % amplitude spectrum 86 | MexicanPow = abs(fft(MexicanWavelet)/npnts); 87 | 88 | 89 | % time-domain plotting 90 | subplot(425) 91 | plot(timevec,MexicanWavelet,'k','linew',2) 92 | set(gca,'ylim',[-1 1]*1.5,'xlim',timevec([1 end])) 93 | xlabel('Time (sec.)'), ylabel('Amplitude') 94 | title('Mexican wavelet in time domain') 95 | 96 | % frequency-domain plotting 97 | subplot(426) 98 | plot(hz,MexicanPow(1:length(hz)),'k','linew',2) 99 | set(gca,'xlim',[0 freq*3]) 100 | xlabel('Frequency (Hz)'), ylabel('Amplitude') 101 | title('Mexican wavelet in frequency domain') 102 | 103 | %% Difference of Gaussians (DoG) 104 | % (approximation of Laplacian of Gaussian) 105 | 106 | % define sigmas 107 | sPos = .1; 108 | sNeg = .5; 109 | 110 | % create the two GAussians 111 | gaus1 = exp( (-timevec.^2) / (2*sPos^2) ) / (sPos*sqrt(2*pi)); 112 | gaus2 = exp( (-timevec.^2) / (2*sNeg^2) ) / (sNeg*sqrt(2*pi)); 113 | 114 | % their difference is the DoG 115 | DoG = gaus1 - gaus2; 116 | 117 | 118 | % amplitude spectrum 119 | DoGPow = abs(fft(DoG)/npnts); 120 | 121 | 122 | % time-domain plotting 123 | subplot(427) 124 | plot(timevec,DoG,'k','linew',2) 125 | set(gca,'ylim',[-1.1 4],'xlim',timevec([1 end])) 126 | xlabel('Time (sec.)'), ylabel('Amplitude') 127 | title('DoG wavelet in time domain') 128 | 129 | % frequency-domain plotting 130 | subplot(428) 131 | plot(hz,DoGPow(1:length(hz)),'k','linew',2) 132 | set(gca,'xlim',[0 freq*3]) 133 | xlabel('Frequency (Hz)'), ylabel('Amplitude') 134 | title('DoG wavelet in frequency domain') 135 | 136 | %% done. 137 | -------------------------------------------------------------------------------- /wavelet/sigprocMXC_wavelets4narrowband.m: -------------------------------------------------------------------------------- 1 | %% 2 | % COURSE: Signal processing problems, solved in MATLAB and Python 3 | % SECTION: Wavelet analysis 4 | % VIDEO: Wavelet convolution for narrowband filtering 5 | % Instructor: sincxpress.com 6 | % 7 | %% 8 | 9 | % simulation parameters 10 | srate = 4352; % hz 11 | npnts = 8425; 12 | time = (0:npnts-1)/srate; 13 | hz = linspace(0,srate/2,floor(npnts/2)+1); 14 | 15 | % pure noise signal 16 | signal = exp( .5*randn(1,npnts) ); 17 | 18 | 19 | % let's see what it looks like 20 | figure(1), clf 21 | subplot(211) 22 | plot(time,signal,'k') 23 | xlabel('Time (s)'), ylabel('Amplitude') 24 | 25 | % in the frequency domain 26 | signalX = 2*abs(fft(signal)); 27 | subplot(212) 28 | plot(hz,signalX(1:length(hz)),'k') 29 | set(gca,'xlim',[1 srate/6]) 30 | xlabel('Frequency (Hz)'), ylabel('Amplitude') 31 | 32 | 33 | %% create and inspect the Morlet wavelet 34 | 35 | % wavelet parameters 36 | ffreq = 34; % filter frequency in Hz 37 | fwhm = .12; % full-width at half-maximum in seconds 38 | wavtime = -3:1/srate:3; % wavelet time vector (same sampling rate as signal!) 39 | 40 | % create the wavelet 41 | morwav = cos(2*pi*ffreq*wavtime) .* exp( -(4*log(2)*wavtime.^2) / fwhm.^2 ); 42 | 43 | 44 | % amplitude spectrum of wavelet 45 | % (note that the wavelet needs its own hz because different length) 46 | wavehz = linspace(0,srate/2,floor(length(wavtime)/2)+1); 47 | morwavX = 2*abs(fft(morwav)); 48 | 49 | 50 | % plot it! 51 | figure(2), clf 52 | subplot(211) 53 | plot(wavtime,morwav,'k') 54 | xlabel('Time (sec.)') 55 | 56 | subplot(212) 57 | plot(wavehz,morwavX(1:length(wavehz)),'k') 58 | set(gca,'xlim',[0 ffreq*2]) 59 | xlabel('Frequency (Hz)') 60 | 61 | %% now for convolution 62 | 63 | convres = conv(signal,morwav,'same'); 64 | 65 | % show in the time domain 66 | figure(1) 67 | subplot(211), hold on 68 | plot(time,convres,'r') 69 | 70 | % and in the frequency domain 71 | subplot(212), hold on 72 | convresX = 2*abs(fft(convres)); 73 | plot(hz,convresX(1:length(hz)),'r') 74 | 75 | %%% Time-domain wavelet normalization is... annoying and difficult. 76 | %%% Let's do it in the frequency domain 77 | 78 | %% "manual" convolution 79 | 80 | nConv = npnts + length(wavtime) - 1; 81 | halfw = floor(length(wavtime)/2)+1; 82 | 83 | % spectrum of wavelet 84 | morwavX = fft(morwav,nConv); 85 | 86 | % now normalize in the frequency domain 87 | %%% note: ensure we're taking the magnitude of the peak; 88 | % I didn't explain this in the video but it ensures normalization by 89 | % the magnitude and not the complex value. 90 | morwavX = morwavX ./ abs(max(morwavX)); 91 | % also equivalent: 92 | % morwavX = (abs(morwavX)./max(abs(morwavX))) .* exp(1i*angle(morwavX)); 93 | 94 | % now for the rest of convolution 95 | convres = ifft( morwavX .* fft(signal,nConv) ); 96 | convres = real( convres(halfw:end-halfw+1) ); 97 | 98 | %% 99 | 100 | figure(1), subplot(211) 101 | plot(time,convres,'b') 102 | legend({'Original','Filtered, no norm','Filtered, norm.'}) 103 | 104 | 105 | subplot(212) 106 | convresX = 2*abs(fft(convres)); 107 | plot(hz,convresX(1:length(hz)),'b','linew',3) 108 | set(gca,'ylim',[0 300]) 109 | 110 | %% to preserve DC offset, compute and add back 111 | 112 | convres = convres + mean(signal); 113 | figure(1), subplot(211) 114 | plot(time,convres,'m','linew',3) 115 | 116 | %% done. 117 | -------------------------------------------------------------------------------- /wavelet/wavelet_codeChallenge.m: -------------------------------------------------------------------------------- 1 | clear 2 | 3 | % signal is random noise 4 | srate = 2048; 5 | signal = smooth( randn(srate*6,1),3 ); 6 | 7 | clf 8 | plot(signal) 9 | plot(abs(fft(signal))) 10 | 11 | %% FIR filter 12 | 13 | % define filter parameters 14 | lower_bnd = 10; % Hz 15 | upper_bnd = 15; % Hz 16 | 17 | tw = .1; 18 | 19 | samprate = 2048; % Hz 20 | filtorder = 4*round(samprate/lower_bnd); 21 | 22 | filter_shape = [ 0 0 1 1 0 0 ]; 23 | filter_freqs = [ 0 lower_bnd*(1-tw) lower_bnd ... 24 | upper_bnd upper_bnd+upper_bnd*tw ... 25 | (samprate/2) ] / (samprate/2); 26 | 27 | filterkern = firls(filtorder,filter_freqs,filter_shape); 28 | 29 | signalFIR = filtfilt(filterkern,1,signal); 30 | 31 | %% wavelet 32 | 33 | timevec = -1:1/srate:1; 34 | freq = (lower_bnd+upper_bnd)/2; 35 | 36 | csw = cos(2*pi*freq*timevec); % cosine wave 37 | fwhm = .25; % full-width at half-maximum in seconds 38 | gaussian = exp( -(4*log(2)*timevec.^2) / fwhm^2 ); % Gaussian 39 | 40 | % Morlet wavelet 41 | mw = csw .* gaussian/(2*pi*30); 42 | 43 | signalMW = conv(signal,mw,'same'); 44 | 45 | 46 | % nc = length(mw)+length(signal)-1; 47 | % kh = floor(length(mw)/2)+1; 48 | % signalMW2 = ifft( fft(signal',nc).*fft(miwav,nc) ); 49 | % signalMW2 = signalMW2(kh:end-kh+1); 50 | 51 | 52 | 53 | % plotting 54 | 55 | %% 56 | 57 | N = length(signal); 58 | 59 | tv = (0:N-1)/srate; 60 | 61 | clf 62 | subplot(311) 63 | plot(tv,signal) 64 | xlabel('Time (s)') 65 | title('Original signal') 66 | 67 | subplot(312), cla; hold on 68 | plot(tv,signalFIR) 69 | plot(tv,signalMW) 70 | xlabel('Time (s)') 71 | title('Filtered signals') 72 | legend({'FIR';'MW'}) 73 | 74 | subplot(313), cla, hold on 75 | hz = linspace(0,srate,N); 76 | plot(hz,abs(fft(signalFIR/N))) 77 | plot(hz,abs(fft(signalMW/N))) 78 | set(gca,'xlim',[0 20]) 79 | legend({'FIR';'MW'}) 80 | title('Amplitude spectra') 81 | xlabel('Frequency (Hz)') 82 | 83 | %% 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /wavelet/wavelet_codeChallenge.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/SignalProcessing/31f872180db83874f46fc970d420b046416ddab1/wavelet/wavelet_codeChallenge.mat --------------------------------------------------------------------------------