├── LICENSE ├── morlet.m ├── morse.m └── cwt.m /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Colin M McCrimmon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /morlet.m: -------------------------------------------------------------------------------- 1 | function [ psihat, psi ] = morlet(s,w,wc) 2 | %-INPUT-------------------------------------------------------------------- 3 | % s: scales used (related to frequency of generated morse wave) 4 | % w: frequencies for conducting analysis in FFT order starting with 0 (size equals number of points in frequency/time domain) 5 | % wc: center frequency for morlet wavelet (usually set to 6) 6 | %-OUTPUT------------------------------------------------------------------- 7 | % psihat: morlet wavelet in frequency domain 8 | % psi: morlet wavelet in time domain 9 | %========================================================================== 10 | % Author: Colin M McCrimmon 11 | % E-mail: cmccrimm@uci.edu 12 | %========================================================================== 13 | 14 | nw = numel(w); 15 | ns = numel(s); 16 | s = s(:).'; 17 | w = w(:); 18 | 19 | psihat = 2 * (exp(-(1/2)*(abs(w)*s - wc).^2) - exp(-(1/2)*wc^2)); 20 | psihat = psihat .* (complex(exp(1i * pi * linspace(0,nw-1,nw).')) * ones(1,ns)); 21 | psihat(2:end,s<0) = psihat(end:-1:2,s<0); %conj in time domain = conj reversal in freq domain 22 | psihat(:,s<0) = conj(psihat(:,s<0)); 23 | 24 | if nargout>1 25 | psi = ifft(psihat); 26 | end 27 | 28 | end 29 | -------------------------------------------------------------------------------- /morse.m: -------------------------------------------------------------------------------- 1 | function [psihat,psi] = morse(s,w,wpk,g,b,k) 2 | %-INPUT-------------------------------------------------------------------- 3 | % s: scales used (related to frequency of generated morse wave) 4 | % w: frequencies for conducting analysis in FFT order starting with 0 (size equals number of points in frequency/time domain) 5 | % wc: center frequency for morse wavelet (related to g and b below) 6 | % g: gamma - use 3 for Airy wavelets 7 | % b: beta - increase for high frequency precision but low temporal precision 8 | % k: order of wavelets - typically only need k = 0 9 | %-OUTPUT------------------------------------------------------------------- 10 | % psihat: morse wavelet in frequency domain 11 | % psi: morse wavelet in time domain 12 | %========================================================================== 13 | % Adapted from SC Olhede and AT Walden in "Generalized Morse 14 | % Wavelets", 2002 IEEE TSP and Lilly, J. M. (2015), jLab: A data analysis 15 | % package for Matlab 16 | % 17 | % Author: Colin M McCrimmon 18 | % E-mail: cmccrimm@uci.edu 19 | %========================================================================== 20 | 21 | nw = numel(w); 22 | ns = numel(s); 23 | s = s(:).'; 24 | w = w(:); 25 | 26 | r = (2*b+1)/g; 27 | c = r - 1; 28 | i = 1:(fix(nw/2)+1); 29 | WS = w(i) * s; 30 | if b~=0 31 | a = 2 * sqrt(exp(gammaln(r) + gammaln(k+1) - gammaln(k+r))) * exp(-b*log(wpk) + wpk^g + b*log(WS) - WS.^g); 32 | else 33 | a = 2 * exp(-WS.^g); 34 | end 35 | a(1,:) = (1/2) * a(1,:); 36 | a(isnan(a)) = 0; 37 | 38 | psihat = zeros(nw,ns); 39 | psihat(i,:) = a .* laguerre(2*WS.^g,k,c); 40 | psihat(isinf(psihat)) = 0; 41 | 42 | psihat = psihat .* (complex(exp(1i * pi * linspace(0,nw-1,nw).')) * ones(1,ns)); 43 | psihat(2:end,s<0) = psihat(end:-1:2,s<0); %conj in time domain = conj reversal in freq domain 44 | psihat(:,s<0) = conj(psihat(:,s<0)); 45 | 46 | if nargout>1 47 | psi = ifft(psihat); 48 | end 49 | 50 | end 51 | 52 | 53 | function Y = laguerre(X,k,c) 54 | % compute generalized Laguerre poly L_k^c(x) - much faster than laguerreL 55 | Y = zeros(size(X)); 56 | sn = -1; 57 | for m=0:k 58 | sn = -sn; 59 | Y = Y + sn * exp(gammaln(k+c+1) - gammaln(k-m+1) - gammaln(c+m+1) - gammaln(m+1)) * X.^m; 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /cwt.m: -------------------------------------------------------------------------------- 1 | function [y,f] = cwt(x,fs,varargin) 2 | %-INPUT-------------------------------------------------------------------- 3 | % x: input signal (must be real vector for now) 4 | % fs: sampling frequency in Hz 5 | % varargins: pair of parameter name and value 6 | % 'wavetype': 'morse'(default) or 'morlet' 7 | % 'g': morse gamma parameter (default=3) 8 | % 'b': morse beta parameter (default=20) 9 | % 'k': order of morse waves (default=0) 10 | % 's0': smallest scale (determined automatically by default) 11 | % 'no': number of octaves (determined automatically by default) 12 | % 'nv': number of voices per octave (default=10) 13 | % 'pad': whether to pad input signal (default=true) 14 | %-OUTPUT------------------------------------------------------------------- 15 | % y: output time-frequency spectrum (complex matrix) 16 | % f: frequency bins (in Hz) of spectrum 17 | %========================================================================== 18 | % Author: Colin M McCrimmon 19 | % E-mail: cmccrimm@uci.edu 20 | %========================================================================== 21 | 22 | params.wavetype = 'morse'; 23 | params.g = 3; 24 | params.b = 20; 25 | params.k = 0; 26 | params.s0 = []; 27 | params.no = []; 28 | params.nv = 10; 29 | params.pad = true; 30 | for i = 1:2:numel(varargin) 31 | params.(varargin{i}) = varargin{i+1}; 32 | end 33 | params.b = max(params.b,ceil(3/params.g)); % >=3 34 | params.b = min(params.b,fix(120/params.g)); % <= 120 35 | params.nv = 2*ceil(params.nv/2); % even; 36 | params.nv = max(params.nv,4); % >=4 37 | params.nv = min(params.nv,48); % <= 48 38 | 39 | x = x(:); 40 | x = x(end:-1:1); 41 | x = detrend(x,0); 42 | norig = numel(x); 43 | 44 | npad = 0; 45 | if params.pad 46 | npad = floor(norig/2); 47 | x =[conj(x(npad:-1:1)); x; conj(x(end:-1:end-npad+1))]; 48 | end 49 | n = numel(x); 50 | 51 | [s,~,params.no] = getScales(params.wavetype,norig,params.s0,params.no,params.nv,params.g,params.b); 52 | w = getOmega(n); 53 | 54 | switch params.wavetype 55 | case 'morse' 56 | wc = getMorseCenterFreq(params.g,params.b); 57 | psihat = morse(s,w,wc,params.g,params.b,params.k); 58 | f = wc ./ (2 * pi * s); 59 | [~,sigma] = getMorseSigma(params.g,params.b); 60 | coival = 2 * pi / (sigma * wc); 61 | case 'morlet' 62 | wc = 6; 63 | psihat = morlet(s,w,wc); 64 | f = wc ./ (2 * pi * s); 65 | sigma = 1 / sqrt(2); 66 | coival = 2 * pi / (sigma * wc); 67 | end 68 | 69 | xhat = fft(x); 70 | y = ifft((xhat * ones(1,size(psihat,2))) .* psihat); 71 | y = y(1+npad:norig+npad,:).'; 72 | 73 | f = fs * f.'; 74 | 75 | %[1E-5,1:((norig+1)/2-1),fliplr((1:(norig/2-1))),1E-5]; 76 | if mod(norig,2)==1 %odd 77 | coiInd = 1:ceil(norig/2); 78 | coiInd = [coiInd, fliplr(coiInd(1:end-1))]; 79 | else 80 | coiInd = 1:norig/2; 81 | coiInd = [coiInd, fliplr(coiInd)]; 82 | end 83 | coi = (coival * coiInd).'; 84 | 85 | if nargout<1 86 | t = linspace(0,(size(y,2)-1)/fs,size(y,2)); 87 | plotspectrum(t,f,y,coi); 88 | end 89 | 90 | end 91 | 92 | 93 | function [scales,s0,no] = getScales(wavetype,n,s0,no,nv,g,b) 94 | switch wavetype 95 | case 'morse' 96 | %smallest scale 97 | if ~exist('s0','var') || isempty(s0) 98 | a = 1; 99 | testomegas = linspace(0,12*pi,1001); 100 | omega = testomegas(find( log(testomegas.^b) + (-testomegas.^g) - log(a/2) + (b/g)*(1+(log(g)-log(b))) > 0, 1, 'last')); 101 | s0 = min(2,omega/pi); 102 | end 103 | [~,sigma] = getMorseSigma(g,b); 104 | case 'morlet' 105 | %smallest scale 106 | if ~exist('s0','var') || isempty(s0) 107 | a = 0.1; omega0 = 6; 108 | omega = sqrt(-2*log(a)) + omega0; 109 | s0 = min(2,omega/pi); 110 | end 111 | sigma = sqrt(2)/2; 112 | end 113 | %largest scale 114 | hi = floor(n/(2*sigma*s0)); 115 | if hi <= 1, hi = floor(n/2); end 116 | hi = floor(nv * log2(hi)); 117 | if exist('no','var') && ~isempty(no) 118 | hi = min(nv*no,hi); 119 | else 120 | no = hi/nv; 121 | end 122 | octaves = (1/nv) * (0:hi); 123 | scales = s0 * 2 .^ octaves; 124 | end 125 | 126 | 127 | function omega = getOmega(n) 128 | % Frequency vector sampling the Fourier transform of the wavelet 129 | omega = (2*pi/n) * (1:fix(n/2)); 130 | omega = [0, omega, -omega(fix((n-1)/2):-1:1)].'; 131 | end 132 | 133 | 134 | function wc = getMorseCenterFreq(g,b) 135 | %Center frequency (in radians/sec) of morse wavelet with parameters gamma=g and beta=b 136 | if b~=0 137 | wc = exp((1/g)*(log(b)-log(g))); 138 | else 139 | wc = log(2)^(1/g); 140 | end 141 | end 142 | 143 | 144 | function [sigmafreq,sigmatime] = getMorseSigma(g,b) 145 | a = (2/g) * (log(g) - log(2*b)); 146 | sigmafreq = real(sqrt(exp(a + gammaln((2*b+3)/g) - gammaln((2*b+1)/g)) - exp(a + 2*gammaln((2*b+2)/g) - 2*gammaln((2*b+1)/g)))); 147 | 148 | c1 = (2/g)*log(2*b/g) + 2*log(b) + gammaln((2*(b-1)+1)/g) - gammaln((2*b+1)/g); 149 | c2 = ((2-2*g)/g)*log(2) + (2/g)*log(b/g) + 2*log(g) + gammaln((2*(b-1+g)+1)/g) - gammaln((2*b+1)/g); 150 | c3 = ((2-g)/g)*log(2) + (1+2/g)*log(b) + (1-2/g)*log(g) + log(2) + gammaln((2*(b-1+g./2)+1)/g) - gammaln((2*b+1)/g); 151 | sigmatime = real(sqrt(exp(c1) + exp(c2) - exp(c3))); 152 | end 153 | 154 | function plotspectrum(t,f,y,coi) 155 | %-INPUT-------------------------------------------------------------------- 156 | % t: time (in seconds) of the spectrum and original signal 157 | % f: frequency bins (in Hz) of the spectrum 158 | % y: output time-frequency spectrum (complex matrix) 159 | %-OUTPUT------------------------------------------------------------------- 160 | % none: creates new figure and plots the time-frequency spectrum 161 | 162 | [t,coiweightt,ut] = engunits(t,'unicode','time'); 163 | xlbl = ['Time (',ut,')']; 164 | [f,coiweightf,uf] = engunits(f,'unicode'); 165 | %coiweightf = 1; uf = ''; %6/24/2018 166 | ylbl = ['Frequency (',uf,'Hz)']; 167 | coi = (coi * mean(diff(t))) ./ (coiweightt * coiweightf); 168 | invcoi = 1 ./ coi; 169 | invcoi(invcoi>max(f)) = max(f); 170 | 171 | hf = figure; 172 | hf.NextPlot = 'replace'; 173 | ax = axes('parent',hf); 174 | imagesc(ax,t,log2(f),abs(y)); 175 | 176 | cmap = jet(1000);cmap = cmap([round(linspace(1,375,250)),376:875],:); %jet is convention, but let's adjust 177 | %cmap = parula(750); 178 | colormap(cmap) 179 | 180 | logyticks = round(log2(min(f))):round(log2(max(f))); 181 | ax.YLim = log2([min(f), max(f)]); 182 | ax.YTick = logyticks; 183 | ax.YDir = 'normal'; 184 | set(ax,'YLim',log2([min(f),max(f)]), ... 185 | 'layer','top', ... 186 | 'YTick',logyticks(:), ... 187 | 'YTickLabel',num2str(sprintf('%g\n',2.^logyticks)), ... 188 | 'layer','top') 189 | title('Magnitude Scalogram'); 190 | xlabel(xlbl); 191 | ylabel(ylbl); 192 | hcol = colorbar; 193 | hcol.Label.String = 'Magnitude'; 194 | hold(ax,'on'); 195 | 196 | %shade out complement of coi 197 | plot(ax,t,log2(invcoi),'w--','linewidth',2); 198 | A1 = area(ax,t,log2(invcoi),min([min(ax.YLim) min(invcoi)])); 199 | A1.EdgeColor = 'none'; 200 | A1.FaceColor = [0.5 0.5 0.5]; 201 | alpha(A1,0.8); 202 | hold(ax,'off'); 203 | hf.NextPlot = 'replace'; 204 | 205 | end 206 | --------------------------------------------------------------------------------