├── Cohen_MorletWavelets_betterdef.pdf ├── MorletWaveletDefinition_data.mat ├── MorletWaveletDefinition_demo.ipynb ├── MorletWaveletDefinition_demo.m ├── README.md └── bluewhitered.m /Cohen_MorletWavelets_betterdef.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/MorletWavelets/c964a6efa70121f20edea878b8c1de8d2acc76b2/Cohen_MorletWavelets_betterdef.pdf -------------------------------------------------------------------------------- /MorletWaveletDefinition_data.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikexcohen/MorletWavelets/c964a6efa70121f20edea878b8c1de8d2acc76b2/MorletWaveletDefinition_data.mat -------------------------------------------------------------------------------- /MorletWaveletDefinition_demo.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "name": "MorletWaveletDefinition_demo.ipynb", 7 | "provenance": [], 8 | "collapsed_sections": [] 9 | }, 10 | "kernelspec": { 11 | "name": "python3", 12 | "display_name": "Python 3" 13 | } 14 | }, 15 | "cells": [ 16 | { 17 | "cell_type": "markdown", 18 | "metadata": { 19 | "id": "BM9BThYr2vOg" 20 | }, 21 | "source": [ 22 | "# **Morelet wavelet FWHM definition**\r\n", 23 | "\r\n", 24 | "## This code accompanies the paper:\r\n", 25 | "### \"A better way to define and describe Morlet wavelets for time-frequency analysis\"\r\n", 26 | "#### by MX Cohen (NeuroImage, 2019)\r\n", 27 | "\r\n", 28 | "\r\n", 29 | "Questions? -> mikexcohen@gmail.com" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "metadata": { 35 | "id": "eUv3SCHV3Cdr" 36 | }, 37 | "source": [ 38 | "# import libraries\r\n", 39 | "import numpy as np\r\n", 40 | "import matplotlib.pyplot as plt\r\n", 41 | "import matplotlib.gridspec as gridspec # for subplots\r\n", 42 | "import scipy.io as sio # for importing matlab .mat data" 43 | ], 44 | "execution_count": null, 45 | "outputs": [] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "metadata": { 50 | "id": "Gcj5tWGr3TNy" 51 | }, 52 | "source": [ 53 | "# **Figure 1: The happy time-frequency man**" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "metadata": { 59 | "id": "pmwc0plw3Gou" 60 | }, 61 | "source": [ 62 | "### generate signal\r\n", 63 | "\r\n", 64 | "# parameters\r\n", 65 | "srate = 1000\r\n", 66 | "time = np.arange(0,3*srate)/srate\r\n", 67 | "pnts = len(time)\r\n", 68 | "\r\n", 69 | "# frequency ranges\r\n", 70 | "f1 = [3, 20]\r\n", 71 | "f2 = [20, 3]\r\n", 72 | "\r\n", 73 | "# transient gaussian\r\n", 74 | "transgaus = np.exp( -(time-np.mean(time))**2 / .2 )\r\n", 75 | "\r\n", 76 | "# the signal\r\n", 77 | "signal = np.sin(2*np.pi*time * np.linspace(f1[0],np.mean(f1),pnts)) + \\\r\n", 78 | " np.sin(2*np.pi*time * np.linspace(f2[0],np.mean(f2),pnts)) + \\\r\n", 79 | " np.sin(2*np.pi*time*20) * transgaus" 80 | ], 81 | "execution_count": null, 82 | "outputs": [] 83 | }, 84 | { 85 | "cell_type": "code", 86 | "metadata": { 87 | "id": "7GaTXkuk3t41" 88 | }, 89 | "source": [ 90 | "### static power spectrum\r\n", 91 | "\r\n", 92 | "hz = np.linspace(0,srate/2,int(pnts/2)+1)\r\n", 93 | "powr = (2*abs(np.fft.fft(signal)/pnts))**2" 94 | ], 95 | "execution_count": null, 96 | "outputs": [] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "metadata": { 101 | "id": "7sup_RQD355U" 102 | }, 103 | "source": [ 104 | "### time-frequency analysis\r\n", 105 | "\r\n", 106 | "# tf params\r\n", 107 | "nfrex = 40\r\n", 108 | "frex = np.linspace(2,25,nfrex)\r\n", 109 | "fwhm = np.linspace(.8,.7,nfrex)\r\n", 110 | "\r\n", 111 | "# setup wavelet and convolution parameters\r\n", 112 | "wavet = np.arange(-2,2,1/srate)\r\n", 113 | "halfw = int(len(wavet)/2)\r\n", 114 | "nConv = pnts + len(wavet) - 1\r\n", 115 | "\r\n", 116 | "# initialize time-frequency matrix\r\n", 117 | "tf = np.zeros((len(frex),pnts))\r\n", 118 | "\r\n", 119 | "# spectrum of data\r\n", 120 | "dataX = np.fft.fft(signal,nConv)\r\n", 121 | "\r\n", 122 | "# loop over frequencies\r\n", 123 | "for fi,f in enumerate(frex):\r\n", 124 | " \r\n", 125 | " # create wavelet\r\n", 126 | " waveX = np.fft.fft( np.exp(2*1j*np.pi*f*wavet) * np.exp(-4*np.log(2)*wavet**2/fwhm[fi]**2),nConv )\r\n", 127 | " waveX = waveX / np.abs(max(waveX)) # normalize\r\n", 128 | " \r\n", 129 | " # convolve\r\n", 130 | " ast = np.fft.ifft( waveX*dataX )\r\n", 131 | " # trim and reshape\r\n", 132 | " ast = ast[halfw-1:-halfw]\r\n", 133 | " \r\n", 134 | " # power\r\n", 135 | " tf[fi,:] = np.abs(ast)**2\r\n" 136 | ], 137 | "execution_count": null, 138 | "outputs": [] 139 | }, 140 | { 141 | "cell_type": "code", 142 | "metadata": { 143 | "id": "6oIHvtGQ5VmH" 144 | }, 145 | "source": [ 146 | "### plotting\r\n", 147 | "\r\n", 148 | "fig = plt.figure(figsize=(8,7),tight_layout=True)\r\n", 149 | "gs = gridspec.GridSpec(2,2)\r\n", 150 | "\r\n", 151 | "ax = fig.add_subplot(gs[0,:])\r\n", 152 | "ax.plot(time,signal,'k',linewidth=2)\r\n", 153 | "ax.set_xlabel('Time (s)')\r\n", 154 | "ax.set_ylabel('Amplitude')\r\n", 155 | "ax.set_title('A) Time domain')\r\n", 156 | "\r\n", 157 | "\r\n", 158 | "ax = fig.add_subplot(gs[1,0])\r\n", 159 | "ax.plot(hz,powr[:len(hz)],'k',linewidth=2)\r\n", 160 | "ax.set_xlabel('Frequency (Hz)')\r\n", 161 | "ax.set_ylabel('Power')\r\n", 162 | "ax.set_xlim([0,30])\r\n", 163 | "ax.set_title('B) Frequency domain')\r\n", 164 | "\r\n", 165 | "\r\n", 166 | "ax = fig.add_subplot(gs[1,1])\r\n", 167 | "ax.imshow(tf,origin='top',aspect='auto',extent=[time[0],time[-1],frex[0],frex[-1]])\r\n", 168 | "ax.set_xlabel('Time (sec.)')\r\n", 169 | "ax.set_ylabel('Frequency (Hz)')\r\n", 170 | "ax.set_title('C) Time-frequency domain')\r\n", 171 | "\r\n", 172 | "plt.show()" 173 | ], 174 | "execution_count": null, 175 | "outputs": [] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "metadata": { 180 | "id": "2lywxBqHTM4u" 181 | }, 182 | "source": [ 183 | "" 184 | ], 185 | "execution_count": null, 186 | "outputs": [] 187 | }, 188 | { 189 | "cell_type": "markdown", 190 | "metadata": { 191 | "id": "tCheLm2uG-Fw" 192 | }, 193 | "source": [ 194 | "# **Figure 2: Big confusion from little wavelets**" 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "metadata": { 200 | "id": "GdX0MrVsGy5M" 201 | }, 202 | "source": [ 203 | "### Create the three wavelets\r\n", 204 | "\r\n", 205 | "# simulation parameters\r\n", 206 | "freq1 = 7 # Hz\r\n", 207 | "freq2 = 12\r\n", 208 | "srate = 1000\r\n", 209 | "time = np.arange(-2000,2001)/srate\r\n", 210 | "pnts = len(time)\r\n", 211 | "\r\n", 212 | "# define number of cycles\r\n", 213 | "numcycles = [ 3, 8 ]\r\n", 214 | "\r\n", 215 | "# create the sine waves and Gaussian windows\r\n", 216 | "sinwave1 = np.cos(2*np.pi*freq1*time)\r\n", 217 | "sinwave2 = np.cos(2*np.pi*freq2*time)\r\n", 218 | "gauswin1 = np.exp( -time**2 / (2* (numcycles[0]/(2*np.pi*freq1))**2 ) )\r\n", 219 | "gauswin2 = np.exp( -time**2 / (2* (numcycles[1]/(2*np.pi*freq1))**2 ) )\r\n", 220 | "gauswin3 = np.exp( -time**2 / (2* (numcycles[1]/(2*np.pi*freq2))**2 ) )\r\n", 221 | "\r\n", 222 | "\r\n", 223 | "# create the three wavelets\r\n", 224 | "morletwave1 = sinwave1 * gauswin1\r\n", 225 | "morletwave2 = sinwave1 * gauswin2\r\n", 226 | "morletwave3 = sinwave2 * gauswin3" 227 | ], 228 | "execution_count": null, 229 | "outputs": [] 230 | }, 231 | { 232 | "cell_type": "code", 233 | "metadata": { 234 | "id": "BzsRq24AJ50e" 235 | }, 236 | "source": [ 237 | "### normalized power spectra of the wavelets\r\n", 238 | "\r\n", 239 | "powspect1 = np.abs(np.fft.fft(morletwave1)/pnts)**2\r\n", 240 | "powspect1 = powspect1 / np.max(np.abs(powspect1))\r\n", 241 | "\r\n", 242 | "powspect2 = np.abs(np.fft.fft(morletwave2)/pnts)**2\r\n", 243 | "powspect2 = powspect2 / np.max(np.abs(powspect2))\r\n", 244 | "\r\n", 245 | "powspect3 = np.abs(np.fft.fft(morletwave3)/pnts)**2\r\n", 246 | "powspect3 = powspect3 / np.max(np.abs(powspect3))\r\n", 247 | "\r\n", 248 | "# vector of frequencies\r\n", 249 | "hz = np.linspace(0,srate/2,int(pnts/2)+1)" 250 | ], 251 | "execution_count": null, 252 | "outputs": [] 253 | }, 254 | { 255 | "cell_type": "code", 256 | "metadata": { 257 | "id": "nltPvAJVJxx2" 258 | }, 259 | "source": [ 260 | "### compute the empirical temporal FWHM in seconds (later converted to ms)\r\n", 261 | "\r\n", 262 | "# midpoint\r\n", 263 | "midp = np.argmin(np.abs(time))\r\n", 264 | "\r\n", 265 | "fwhmT = [0,0,0]\r\n", 266 | "fwhmT[0] = time[ midp+np.argmin(np.abs(gauswin1[midp:]-.5)) ] - time[ np.argmin(np.abs(gauswin1[:midp]-.5)) ]\r\n", 267 | "fwhmT[1] = time[ midp+np.argmin(np.abs(gauswin2[midp:]-.5)) ] - time[ np.argmin(np.abs(gauswin2[:midp]-.5)) ]\r\n", 268 | "fwhmT[2] = time[ midp+np.argmin(np.abs(gauswin3[midp:]-.5)) ] - time[ np.argmin(np.abs(gauswin3[:midp]-.5)) ]\r\n", 269 | "\r\n", 270 | "idx1 = np.argmin(np.abs(hz-freq1))\r\n", 271 | "idx2 = np.argmin(np.abs(hz-freq2))\r\n", 272 | "fwhmF = [0,0,0]\r\n", 273 | "fwhmF[0] = hz[idx1+np.argmin(np.abs(powspect1[idx1:]-.5))] - hz[np.argmin(np.abs(powspect1[:idx1]-.5))]\r\n", 274 | "fwhmF[1] = hz[idx1+np.argmin(np.abs(powspect2[idx1:idx1+100]-.5))] - hz[np.argmin(np.abs(powspect2[:idx1]-.5))]\r\n", 275 | "fwhmF[2] = hz[idx2+np.argmin(np.abs(powspect3[idx2:]-.5))] - hz[np.argmin(np.abs(powspect3[:idx2]-.5))]" 276 | ], 277 | "execution_count": null, 278 | "outputs": [] 279 | }, 280 | { 281 | "cell_type": "code", 282 | "metadata": { 283 | "id": "KtfMb9sUKBrN" 284 | }, 285 | "source": [ 286 | "### plotting\r\n", 287 | "\r\n", 288 | "fig,ax = plt.subplots(2,3,figsize=(10,7),tight_layout=True)\r\n", 289 | "\r\n", 290 | "# time domain\r\n", 291 | "ax[0,0].plot(time,morletwave1,'k')\r\n", 292 | "ax[0,0].set_xlabel('Time (s)')\r\n", 293 | "ax[0,0].set_title('%g cycles, FWHM: %g ms' %(numcycles[0],fwhmT[0]*1000))\r\n", 294 | "ax[0,0].set_xlim([-1,1])\r\n", 295 | "\r\n", 296 | "ax[0,1].plot(time,morletwave2,'k')\r\n", 297 | "ax[0,1].set_xlabel('Time (s)')\r\n", 298 | "ax[0,1].set_title('%g cycles, FWHM: %g ms' %(numcycles[1],fwhmT[1]*1000))\r\n", 299 | "ax[0,1].set_xlim([-1,1])\r\n", 300 | "\r\n", 301 | "ax[0,2].plot(time,morletwave3,'k')\r\n", 302 | "ax[0,2].set_xlabel('Time (s)')\r\n", 303 | "ax[0,2].set_title('%g cycles, FWHM: %g ms' %(numcycles[1],fwhmT[2]*1000))\r\n", 304 | "ax[0,2].set_xlim([-1,1])\r\n", 305 | "\r\n", 306 | "\r\n", 307 | "\r\n", 308 | "# frequency domain\r\n", 309 | "ax[1,0].plot(hz,powspect1[:len(hz)],'k')\r\n", 310 | "ax[1,0].set_xlabel('Frequency (Hz)')\r\n", 311 | "ax[1,0].set_title('%g cycles, FWHM: %g Hz' %(numcycles[0],fwhmF[0]))\r\n", 312 | "ax[1,0].set_xlim([0,freq2*2])\r\n", 313 | "\r\n", 314 | "ax[1,1].plot(hz,powspect2[:len(hz)],'k')\r\n", 315 | "ax[1,1].set_xlabel('Frequency (Hz)')\r\n", 316 | "ax[1,1].set_title('%g cycles, FWHM: %g Hz' %(numcycles[1],fwhmF[1]))\r\n", 317 | "ax[1,1].set_xlim([0,freq2*2])\r\n", 318 | "\r\n", 319 | "ax[1,2].plot(hz,powspect3[:len(hz)],'k')\r\n", 320 | "ax[1,2].set_xlabel('Frequency (Hz)')\r\n", 321 | "ax[1,2].set_title('%g cycles, FWHM: %g Hz' %(numcycles[1],fwhmF[2]))\r\n", 322 | "ax[1,2].set_xlim([0,freq2*2])\r\n", 323 | "\r\n", 324 | "plt.show()" 325 | ], 326 | "execution_count": null, 327 | "outputs": [] 328 | }, 329 | { 330 | "cell_type": "code", 331 | "metadata": { 332 | "id": "WLYwx2cvTO3t" 333 | }, 334 | "source": [ 335 | "" 336 | ], 337 | "execution_count": null, 338 | "outputs": [] 339 | }, 340 | { 341 | "cell_type": "markdown", 342 | "metadata": { 343 | "id": "LJx3czt-TKrf" 344 | }, 345 | "source": [ 346 | "## **Figure 3: FWHM vs. number-of-cycles**" 347 | ] 348 | }, 349 | { 350 | "cell_type": "code", 351 | "metadata": { 352 | "id": "8aMd4L8bTTVs" 353 | }, 354 | "source": [ 355 | "# specify frequencies\r\n", 356 | "frex = np.linspace(3,60,50)\r\n", 357 | "fwhm = np.linspace(.4,.1,len(frex)) # in ms (use this or the following)\r\n", 358 | "fwhm = np.logspace(np.log10(.4),np.log10(.1),len(frex)) # in ms (use this or the previous)\r\n", 359 | "ncyc = np.linspace(3.2,16,len(frex))\r\n", 360 | "\r\n", 361 | "\r\n", 362 | "\r\n", 363 | "# parameters for complex Morlet wavelets\r\n", 364 | "srate = 1024\r\n", 365 | "wavtime = np.arange(-srate*2,srate*2+1)/srate\r\n", 366 | "midp = np.argmin(np.abs(wavtime))\r\n", 367 | "\r\n", 368 | "\r\n", 369 | "# outputs\r\n", 370 | "empfwhm = np.zeros((len(frex),2))\r\n", 371 | "\r\n", 372 | "# loop over frequencies\r\n", 373 | "for fi in range(len(frex)):\r\n", 374 | " \r\n", 375 | " ### create the Gaussian using the FWHM formula (equation 3)\r\n", 376 | " gwin = np.exp( (-4*np.log(2)*wavtime**2) / fwhm[fi]**2 )\r\n", 377 | " \r\n", 378 | " # measure the empirical fwhm\r\n", 379 | " empfwhm[fi,0] = wavtime[ midp+np.argmin(np.abs(gwin[midp:]-.5)) ] \\\r\n", 380 | " - wavtime[ np.argmin(np.abs(gwin[:midp]-.5)) ]\r\n", 381 | " \r\n", 382 | "\r\n", 383 | " ### create the Gaussian using the n-cycles formula (equations 1-2)\r\n", 384 | " s = ncyc[fi] / (2*np.pi*frex[fi])\r\n", 385 | " gwin = np.exp( -wavtime**2 / (2*s**2) )\r\n", 386 | " \r\n", 387 | " # empirical FWHM\r\n", 388 | " empfwhm[fi,1] = wavtime[ midp+np.argmin(np.abs(gwin[midp:]-.5)) ] \\\r\n", 389 | " - wavtime[ np.argmin(np.abs(gwin[:midp]-.5)) ]\r\n", 390 | "\r\n", 391 | "\r\n", 392 | "fig = plt.figure(figsize=(8,5))\r\n", 393 | "\r\n", 394 | "plt.plot(frex,empfwhm*1000,'o-',markersize=8,markerfacecolor='w')\r\n", 395 | "plt.xlabel('Wavelet frequency (Hz)')\r\n", 396 | "plt.ylabel('FWHM (ms)')\r\n", 397 | "plt.legend(['Using FWHM','Using n-cycles'])\r\n", 398 | "plt.show()" 399 | ], 400 | "execution_count": null, 401 | "outputs": [] 402 | }, 403 | { 404 | "cell_type": "code", 405 | "metadata": { 406 | "id": "RjD4sRFQVSFP" 407 | }, 408 | "source": [ 409 | "" 410 | ], 411 | "execution_count": null, 412 | "outputs": [] 413 | }, 414 | { 415 | "cell_type": "code", 416 | "metadata": { 417 | "id": "7YYNMCjHWjFn" 418 | }, 419 | "source": [ 420 | "" 421 | ], 422 | "execution_count": null, 423 | "outputs": [] 424 | }, 425 | { 426 | "cell_type": "markdown", 427 | "metadata": { 428 | "id": "xnezWlvYWh96" 429 | }, 430 | "source": [ 431 | "## **Figure 4: Defining wavelets in the frequency domain**" 432 | ] 433 | }, 434 | { 435 | "cell_type": "code", 436 | "metadata": { 437 | "id": "wkIWP8yjWl5u" 438 | }, 439 | "source": [ 440 | "# specify wavelet parameters\r\n", 441 | "peakf = 11\r\n", 442 | "fwhm = 5.2 # integers are boring\r\n", 443 | "\r\n", 444 | "# specify simulation details\r\n", 445 | "npnts = 8001\r\n", 446 | "srate = 1000\r\n", 447 | "\r\n", 448 | "\r\n", 449 | "# vector of frequencies\r\n", 450 | "hz = np.linspace(0,srate,npnts)\r\n", 451 | "\r\n", 452 | "# frequency-domain Gaussian\r\n", 453 | "s = fwhm*(2*np.pi-1)/(4*np.pi) # normalized width\r\n", 454 | "x = hz-peakf; # shifted frequencies\r\n", 455 | "fx = np.exp(-.5*(x/s)**2) # gaussian\r\n", 456 | "\r\n", 457 | "# empirical FWHM\r\n", 458 | "idx = np.argmin(np.abs(hz-peakf))\r\n", 459 | "empfwhmF = hz[idx+np.argmin(np.abs(fx[idx:]-.5))] - hz[np.argmin(np.abs(fx[:idx]-.5))]\r\n", 460 | "\r\n", 461 | "\r\n", 462 | "# time-domain wavelet\r\n", 463 | "morletwavelet = np.fft.fftshift( np.fft.ifft(fx) )\r\n", 464 | "time = np.arange(-np.floor(npnts/2),np.floor(npnts/2)+1)/srate\r\n", 465 | "\r\n", 466 | "# FWHM of wavelet in the time domain\r\n", 467 | "midp = np.argmin(np.abs(time))\r\n", 468 | "mw_amp = np.abs(morletwavelet)\r\n", 469 | "mw_amp = mw_amp / np.max(mw_amp)\r\n", 470 | "empfwhmT = time[ midp+np.argmin(np.abs(mw_amp[midp:]-.5)) ] - time[ np.argmin(np.abs(mw_amp[:midp]-.5)) ]" 471 | ], 472 | "execution_count": null, 473 | "outputs": [] 474 | }, 475 | { 476 | "cell_type": "code", 477 | "metadata": { 478 | "id": "-1L6UL2UX91U" 479 | }, 480 | "source": [ 481 | "### plotting\r\n", 482 | "\r\n", 483 | "fig,ax = plt.subplots(2,1,figsize=(6,6),tight_layout=True)\r\n", 484 | "\r\n", 485 | "# frequency domain\r\n", 486 | "ax[0].plot(hz,fx,'k')\r\n", 487 | "ax[0].set_xlim([0,peakf*3])\r\n", 488 | "ax[0].set_xlabel('Frequency (Hz)')\r\n", 489 | "ax[0].set_ylabel('Amplitude (gain)')\r\n", 490 | "ax[0].set_title('FWHM specified: %g, obtained: %g Hz' %(fwhm,empfwhmF))\r\n", 491 | "\r\n", 492 | "\r\n", 493 | "# time domain\r\n", 494 | "ax[1].plot(time,np.real(morletwavelet),linewidth=2)\r\n", 495 | "ax[1].plot(time,np.imag(morletwavelet),'--',linewidth=2)\r\n", 496 | "ax[1].plot(time,np.abs(morletwavelet),linewidth=2,color=[.8,.8,.8])\r\n", 497 | "ax[1].set_xlim([-1,1])\r\n", 498 | "ax[1].legend(['Real part','Imag part','Envelope'])\r\n", 499 | "ax[1].set_xlabel('Time (sec.)')\r\n", 500 | "ax[1].set_title('FWHM: %g ms' %(empfwhmT*1000))\r\n", 501 | "\r\n", 502 | "plt.show()" 503 | ], 504 | "execution_count": null, 505 | "outputs": [] 506 | }, 507 | { 508 | "cell_type": "code", 509 | "metadata": { 510 | "id": "6MFTGjCKcRyy" 511 | }, 512 | "source": [ 513 | "" 514 | ], 515 | "execution_count": null, 516 | "outputs": [] 517 | }, 518 | { 519 | "cell_type": "markdown", 520 | "metadata": { 521 | "id": "YlchvlMrfQEh" 522 | }, 523 | "source": [ 524 | "## **Figure 5a: The real deal (fixed FWHM)**" 525 | ] 526 | }, 527 | { 528 | "cell_type": "code", 529 | "metadata": { 530 | "id": "x1-xiddOcR1j" 531 | }, 532 | "source": [ 533 | "# note: this is one channel from one dataset from \n", 534 | "# Cohen, M.X., 2015. Comparison of different spatial transformations applied to EEG data: A case study of error processing. Int. J. Psychophysiol. 97, 245–257\n", 535 | "\n", 536 | "matdat = sio.loadmat('MorletWaveletDefinition_data.mat')\n", 537 | "\n", 538 | "# extract data from EEG structure\n", 539 | "EEGtimes = matdat['EEG'][0][0][0][0]\n", 540 | "EEGsrate = int(matdat['EEG'][0][0][1][0][0])\n", 541 | "EEGpnts = int(matdat['EEG'][0][0][2][0][0])\n", 542 | "EEGtrials = int(matdat['EEG'][0][0][3][0][0])\n", 543 | "EEGdata = matdat['EEG'][0][0][4]" 544 | ], 545 | "execution_count": null, 546 | "outputs": [] 547 | }, 548 | { 549 | "cell_type": "code", 550 | "metadata": { 551 | "id": "2su3BeqOm0Tz" 552 | }, 553 | "source": [ 554 | "" 555 | ], 556 | "execution_count": null, 557 | "outputs": [] 558 | }, 559 | { 560 | "cell_type": "code", 561 | "metadata": { 562 | "id": "o46Mn6_Ggk0n" 563 | }, 564 | "source": [ 565 | "### setup time-frequency analysis\n", 566 | "\n", 567 | "# parameters\n", 568 | "nfrex = 80\n", 569 | "frex = np.linspace(2,40,nfrex)\n", 570 | "fwhm = [.1, .5, 2]\n", 571 | "\n", 572 | "# timimg parameters\n", 573 | "bidx = [ np.argmin(np.abs(EEGtimes--500)), np.argmin(np.abs(EEGtimes--200)) ]\n", 574 | "tidx = np.arange( np.argmin(np.abs(EEGtimes--500)), np.argmin(np.abs(EEGtimes-1300))+1 )\n", 575 | "\n", 576 | "# setup wavelet and convolution parameters\n", 577 | "wavet = np.arange(-5*EEGsrate,5*EEGsrate+1)/EEGsrate\n", 578 | "halfw = int(len(wavet)/2)+1\n", 579 | "nConv = EEGpnts*EEGtrials + len(wavet) - 1\n", 580 | "\n", 581 | "\n", 582 | "# initialize time-frequency matrix\n", 583 | "tf = np.zeros((len(frex),len(tidx),len(fwhm)+1))\n", 584 | "empfwhm = np.zeros((len(fwhm)+1,len(frex)))\n" 585 | ], 586 | "execution_count": null, 587 | "outputs": [] 588 | }, 589 | { 590 | "cell_type": "code", 591 | "metadata": { 592 | "id": "78wGJ3s9jmp-" 593 | }, 594 | "source": [ 595 | "### run TF analysis\n", 596 | "\n", 597 | "# setup plot\n", 598 | "fig,ax = plt.subplots(1,3,figsize=(10,4))\n", 599 | "\n", 600 | "\n", 601 | "# spectrum of data\n", 602 | "dataX = np.fft.fft(np.reshape(EEGdata,(1,-1),order='F'),nConv)\n", 603 | "\n", 604 | "\n", 605 | "# loop over FWHM parameter settings\n", 606 | "for fwhmi in range(len(fwhm)):\n", 607 | " \n", 608 | " # loop over frequencies\n", 609 | " for fi in range(len(frex)):\n", 610 | " \n", 611 | " # create wavelet\n", 612 | " waveX = np.fft.fft( np.exp(2*1j*np.pi*frex[fi]*wavet) \\\n", 613 | " * np.exp(-4*np.log(2)*wavet**2/fwhm[fwhmi]**2),nConv )\n", 614 | " waveX = waveX / np.max(np.abs(waveX)) # normalize\n", 615 | " \n", 616 | " # convolve\n", 617 | " ast = np.fft.ifft( waveX*dataX )[0]\n", 618 | " # trim and reshape\n", 619 | " ast = np.reshape(ast[halfw-1:-halfw+1],(EEGpnts,EEGtrials),order='F')\n", 620 | " \n", 621 | " # power\n", 622 | " p = np.mean( np.abs(ast)**2 ,axis=1)\n", 623 | " tf[fi,:,fwhmi] = 10*np.log10( p[tidx]/np.mean(p[bidx[0]:bidx[1]]) )\n", 624 | " \n", 625 | " # empirical FWHM\n", 626 | " hz = np.linspace(0,EEGsrate,nConv)\n", 627 | " idx = np.argmin(np.abs(hz-frex[fi]))\n", 628 | " fx = np.abs(waveX)\n", 629 | " empfwhm[fwhmi,fi] = hz[idx+np.argmin(np.abs(fx[idx:]-.5))] - hz[np.argmin(np.abs(fx[:idx]-.5))]\n", 630 | "\n", 631 | " # plots\n", 632 | " ax[fwhmi].imshow(np.squeeze(tf[:,:,fwhmi]),aspect='auto',\\\n", 633 | " extent=[EEGtimes[tidx[0]],EEGtimes[tidx[-1]],frex[0],frex[-1] ],origin='bottom',\\\n", 634 | " vmin=-2,vmax=2)\n", 635 | " ax[fwhmi].set_title('%g sec' %fwhm[fwhmi])\n", 636 | " \n", 637 | "\n", 638 | "ax[0].set_xlabel('Time (ms)')\n", 639 | "ax[0].set_ylabel('Frequency (Hz)')\n", 640 | "plt.show()" 641 | ], 642 | "execution_count": null, 643 | "outputs": [] 644 | }, 645 | { 646 | "cell_type": "code", 647 | "metadata": { 648 | "id": "JB54goNNlZwQ" 649 | }, 650 | "source": [ 651 | "### Figure 5a: The real deal (variable FWHM)\n", 652 | "\n", 653 | "# range of FWHMs\n", 654 | "fwhm = np.linspace(1,.2,len(frex))\n", 655 | "\n", 656 | "# loop over frequencies\n", 657 | "for fi in range(len(frex)):\n", 658 | " \n", 659 | " # create wavelet\n", 660 | " waveX = np.fft.fft( np.exp(2*1j*np.pi*frex[fi]*wavet) \\\n", 661 | " * np.exp(-4*np.log(2)*wavet**2/fwhm[fi]**2),nConv )\n", 662 | " waveX = waveX / np.max(np.abs(waveX)) # normalize\n", 663 | " \n", 664 | " # convolve\n", 665 | " ast = np.fft.ifft( waveX*dataX )[0]\n", 666 | " # trim and reshape\n", 667 | " ast = np.reshape(ast[halfw-1:-halfw+1],(EEGpnts,EEGtrials),order='F')\n", 668 | " \n", 669 | " # power\n", 670 | " p = np.mean( np.abs(ast)**2 ,axis=1)\n", 671 | " tf[fi,:,3] = 10*np.log10( p[tidx]/np.mean(p[bidx[0]:bidx[1]]) )\n", 672 | " \n", 673 | " # empirical FWHM\n", 674 | " hz = np.linspace(0,EEGsrate,nConv)\n", 675 | " idx = np.argmin(np.abs(hz-frex[fi]))\n", 676 | " fx = np.abs(waveX)\n", 677 | " empfwhm[3,fi] = hz[idx+np.argmin(np.abs(fx[idx:]-.5))] - hz[np.argmin(np.abs(fx[:idx]-.5))]\n", 678 | "\n", 679 | "\n", 680 | "# plot\n", 681 | "plt.imshow(np.squeeze(tf[:,:,3]),aspect='auto',\\\n", 682 | " extent=[EEGtimes[tidx[0]],EEGtimes[tidx[-1]],frex[0],frex[-1] ],origin='bottom',\\\n", 683 | " vmin=-2,vmax=2)\n", 684 | "plt.title('%g-%g s' %(fwhm[0],fwhm[-1]))\n", 685 | "plt.xlabel('Time (ms)')\n", 686 | "plt.ylabel('Frequency (Hz)')\n", 687 | "plt.show()" 688 | ], 689 | "execution_count": null, 690 | "outputs": [] 691 | }, 692 | { 693 | "cell_type": "code", 694 | "metadata": { 695 | "id": "bHlg0xzHcR4Q", 696 | "collapsed": true 697 | }, 698 | "source": [ 699 | "### Figure 5b: the real deal, part deux\r\n", 700 | "\r\n", 701 | "empfwhm[empfwhm>100] = np.nan\r\n", 702 | "\r\n", 703 | "fig = plt.figure(figsize=(6,6))\r\n", 704 | "\r\n", 705 | "plt.plot(frex,empfwhm.T,'s',markerfacecolor='w',linewidth=2,markersize=8)\r\n", 706 | "plt.legend(['100','500','2000','var'])\r\n", 707 | "plt.ylim([0,18])\r\n", 708 | "plt.xlim([frex[0]-1, frex[-1]+1])\r\n", 709 | "plt.xlabel('Peak frequency (Hz)')\r\n", 710 | "plt.ylabel('FWHM (Hz)')\r\n", 711 | "plt.show()" 712 | ], 713 | "execution_count": null, 714 | "outputs": [] 715 | } 716 | ] 717 | } -------------------------------------------------------------------------------- /MorletWaveletDefinition_demo.m: -------------------------------------------------------------------------------- 1 | %%% 2 | % Morelet wavelet FWHM definition 3 | % 4 | % This code accompanies the paper: 5 | % "A better way to define and describe Morlet wavelets for time-frequency analysis" 6 | % by MX Cohen (NeuroImage, 2019) 7 | % 8 | % Questions? -> mikexcohen@gmail.com 9 | %%% 10 | 11 | %% Figure 1: The happy time-frequency man 12 | 13 | clear 14 | 15 | %%% generate signal 16 | srate = 1000; 17 | time = (0:3*srate)/srate; 18 | pnts = length(time); 19 | 20 | f1 = [3 20]; 21 | f2 = [20 3]; 22 | 23 | transgaus = exp( -(time-mean(time)).^2 / .2 ); 24 | 25 | signal = sin(2*pi*time.*linspace(f1(1),mean(f1),pnts)) + ... 26 | sin(2*pi*time.*linspace(f2(1),mean(f2),pnts)) + ... 27 | sin(2*pi*time*20).*transgaus; 28 | 29 | 30 | %%% static power spectrum 31 | hz = linspace(0,srate/2,floor(pnts/2)+1); 32 | powr = (2*abs(fft(signal)/pnts)).^2; 33 | 34 | 35 | %%% time-frequency analysis 36 | nfrex = 40; 37 | frex = linspace(2,25,nfrex); 38 | fwhm = linspace(.8,.7,nfrex); 39 | 40 | % setup wavelet and convolution parameters 41 | wavet = -2:1/srate:2; 42 | halfw = floor(length(wavet)/2)+1; 43 | nConv = pnts + length(wavet) - 1; 44 | 45 | % initialize time-frequency matrix 46 | tf = zeros(length(frex),pnts); 47 | 48 | % spectrum of data 49 | dataX = fft(signal,nConv); 50 | 51 | % loop over frequencies 52 | for fi=1:length(frex) 53 | 54 | % create wavelet 55 | waveX = fft( exp(2*1i*pi*frex(fi)*wavet).*exp(-4*log(2)*wavet.^2/fwhm(fi).^2),nConv ); 56 | waveX = waveX./max(waveX); % normalize 57 | 58 | % convolve 59 | as = ifft( waveX.*dataX ); 60 | % trim and reshape 61 | as = as(halfw:end-halfw+1); 62 | 63 | % power 64 | tf(fi,:) = abs(as).^2; 65 | end 66 | 67 | 68 | %%% plotting 69 | 70 | figure(1), clf 71 | subplot(211) 72 | plot(time,signal,'k','linew',2) 73 | xlabel('Time (s)'), ylabel('Amplitude') 74 | set(gca,'xlim',time([1 end])) 75 | title('A) Time domain') 76 | 77 | 78 | subplot(223) 79 | plot(hz,powr(1:length(hz)),'k','linew',2) 80 | set(gca,'xlim',[0 30]) 81 | xlabel('Frequency (Hz)'), ylabel('Power') 82 | title('B) Frequency domain') 83 | 84 | 85 | subplot(224) 86 | contourf(time,frex,tf,40,'linecolor','none') 87 | xlabel('Time (sec.)'), ylabel('Frequency (Hz)') 88 | title('C) Time-frequency domain') 89 | 90 | colormap(bluewhitered(64)) 91 | 92 | 93 | %% Figure 2: Big confusion from little wavelets 94 | 95 | clear 96 | 97 | % simulation parameters 98 | freq1 = 7; % of the wavelet 99 | freq2 = 12; 100 | srate = 1000; 101 | time = -2:1/srate:2; 102 | pnts = length(time); 103 | 104 | % define number of cycles 105 | numcycles = [ 3 8 ]; 106 | 107 | % create the sine wave and two Gaussian windows 108 | sinwave1 = cos(2*pi*freq1*time); 109 | sinwave2 = cos(2*pi*freq2*time); 110 | gauswin1 = exp( -time.^2 / (2* (numcycles(1)/(2*pi*freq1))^2 ) ); 111 | gauswin2 = exp( -time.^2 / (2* (numcycles(2)/(2*pi*freq1))^2 ) ); 112 | gauswin3 = exp( -time.^2 / (2* (numcycles(2)/(2*pi*freq2))^2 ) ); 113 | 114 | 115 | % create the three wavelets 116 | morletwave1 = sinwave1 .* gauswin1; 117 | morletwave2 = sinwave1 .* gauswin2; 118 | morletwave3 = sinwave2 .* gauswin3; 119 | 120 | 121 | % normalized power spectra of the wavelets 122 | powspect1 = abs(fft(morletwave1)/pnts).^2; 123 | powspect1 = powspect1 ./ max(powspect1); 124 | 125 | powspect2 = abs(fft(morletwave2)/pnts).^2; 126 | powspect2 = powspect2 ./ max(powspect2); 127 | 128 | powspect3 = abs(fft(morletwave3)/pnts).^2; 129 | powspect3 = powspect3 ./ max(powspect3); 130 | 131 | 132 | hz = linspace(0,srate/2,floor(pnts/2)+1); 133 | 134 | % compute the empirical temporal FWHM in seconds (later converted to ms) 135 | midp = dsearchn(time',0); 136 | fwhmT(1) = time(midp-1+dsearchn(gauswin1(midp:end)',.5)) - time(dsearchn(gauswin1(1:midp)',.5)); 137 | fwhmT(2) = time(midp-1+dsearchn(gauswin2(midp:end)',.5)) - time(dsearchn(gauswin2(1:midp)',.5)); 138 | fwhmT(3) = time(midp-1+dsearchn(gauswin3(midp:end)',.5)) - time(dsearchn(gauswin3(1:midp)',.5)); 139 | 140 | 141 | idx1 = dsearchn(hz',freq1); 142 | idx2 = dsearchn(hz',freq2); 143 | fwhmF(1) = hz(idx1-1+dsearchn(powspect1(idx1:end)',.5)) - hz(dsearchn(powspect1(1:idx1)',.5)); 144 | fwhmF(2) = hz(idx1-1+dsearchn(powspect2(idx1:end)',.5)) - hz(dsearchn(powspect2(1:idx1)',.5)); 145 | fwhmF(3) = hz(idx2-1+dsearchn(powspect3(idx2:end)',.5)) - hz(dsearchn(powspect3(1:idx2)',.5)); 146 | 147 | 148 | 149 | 150 | 151 | 152 | %%% plotting 153 | figure(2), clf 154 | subplot(231) 155 | plot(time,morletwave1,'k') 156 | xlabel('Time (s)'), axis square 157 | title([ num2str(numcycles(1)) ' cycles, FWHM: ' num2str(fwhmT(1)*1000) ' ms' ]) 158 | set(gca,'xlim',[-1 1]) 159 | 160 | subplot(232) 161 | plot(time,morletwave2,'k') 162 | xlabel('Time (s)'), axis square 163 | title([ num2str(numcycles(2)) ' cycles, FWHM: ' num2str(fwhmT(2)*1000) ' ms' ]) 164 | set(gca,'xlim',[-1 1]) 165 | 166 | subplot(233) 167 | plot(time,morletwave3,'k') 168 | xlabel('Time (s)'), axis square 169 | title([ num2str(numcycles(2)) ' cycles, FWHM: ' num2str(fwhmT(3)*1000) ' ms' ]) 170 | set(gca,'xlim',[-1 1]) 171 | 172 | 173 | 174 | 175 | subplot(234) 176 | plot(hz,powspect1(1:length(hz)),'k','linew',2) 177 | set(gca,'xlim',[0 freq2*2]) 178 | xlabel('Frequency (Hz)'), axis square 179 | title([ num2str(numcycles(1)) ' cycles, FWHM: ' num2str(fwhmF(1)) ' Hz' ]) 180 | 181 | subplot(235) 182 | plot(hz,powspect2(1:length(hz)),'k','linew',2) 183 | set(gca,'xlim',[0 freq2*2]) 184 | xlabel('Frequency (Hz)'), axis square 185 | title([ num2str(numcycles(2)) ' cycles, FWHM: ' num2str(fwhmF(2)) ' Hz' ]) 186 | 187 | subplot(236) 188 | plot(hz,powspect3(1:length(hz)),'k','linew',2) 189 | set(gca,'xlim',[0 freq2*2]) 190 | xlabel('Frequency (Hz)'), axis square 191 | title([ num2str(numcycles(2)) ' cycles, FWHM: ' num2str(fwhmF(3)) ' Hz' ]) 192 | 193 | 194 | %% Figure 3: FWHM vs. number-of-cycles 195 | 196 | clear 197 | 198 | 199 | % specify frequencies 200 | frex = linspace(3,60,50); 201 | fwhm = linspace(.4,.1,length(frex)); % in ms 202 | fwhm = logspace(log10(.4),log10(.1),length(frex)); % in ms 203 | ncyc = linspace(3.2,16,length(frex)); 204 | 205 | 206 | 207 | % parameters for complex Morlet wavelets 208 | srate = 1024; 209 | wavtime = -2:1/srate:2; 210 | midp = dsearchn(wavtime',0); 211 | 212 | 213 | % outputs 214 | empfwhm = zeros(length(frex),2); 215 | 216 | % loop over frequencies 217 | for fi=1:length(frex) 218 | 219 | % create the Gaussian using the FWHM formula (equation 3) 220 | gwin = exp( (-4*log(2)*wavtime.^2) ./ fwhm(fi)^2 ); 221 | 222 | % measure the empirical fwhm 223 | empfwhm(fi,1) = wavtime(midp-1+dsearchn(gwin(midp:end)',.5)) - wavtime(dsearchn(gwin(1:midp)',.5)); 224 | 225 | 226 | 227 | % create the Gaussian using the n-cycles formula (equations 1-2) 228 | s = ncyc(fi) / (2*pi*frex(fi)); 229 | gwin = exp( -wavtime.^2 ./ (2*s^2) ); 230 | 231 | % empirical FWHM 232 | empfwhm(fi,2) = wavtime(midp-1+dsearchn(gwin(midp:end)',.5)) - wavtime(dsearchn(gwin(1:midp)',.5)); 233 | end 234 | 235 | figure(3), clf 236 | 237 | plot(frex,empfwhm*1000,'o-','markersize',8,'markerfacecolor','w','linew',2) 238 | xlabel('Wavelet frequency (Hz)'), ylabel('FWHM (ms)') 239 | legend({'Using FWHM';'Using n-cycles'}) 240 | set(gca,'xlim',[frex(1)-1 frex(end)+1]) 241 | 242 | %% Figure 4: Defining wavelets in the frequency domain 243 | 244 | clear 245 | 246 | 247 | % specify wavelet parameters 248 | peakf = 11; 249 | fwhm = 5.2; 250 | 251 | % specify simulation details 252 | npnts = 8001; 253 | srate = 1000; 254 | 255 | 256 | % vector of frequencies 257 | hz = linspace(0,srate,npnts); 258 | 259 | % frequency-domain Gaussian 260 | s = fwhm*(2*pi-1)/(4*pi); % normalized width 261 | x = hz-peakf; % shifted frequencies 262 | fx = exp(-.5*(x/s).^2); % gaussian 263 | 264 | % empirical FWHM 265 | idx = dsearchn(hz',peakf); 266 | empfwhmF = hz(idx-1+dsearchn(fx(idx:end)',.5)) - hz(dsearchn(fx(1:idx)',.5)); 267 | 268 | 269 | 270 | % time-domain wavelet 271 | morletwavelet = fftshift( ifft(fx) ); 272 | time = (-floor(npnts/2):floor(npnts/2))/srate; 273 | 274 | % FWHM of wavelet in the time domain 275 | midp = dsearchn(time',0); 276 | mw_amp = abs(morletwavelet); 277 | mw_amp = mw_amp./max(mw_amp); 278 | empfwhmT = time(midp-1+dsearchn(mw_amp(midp:end)',.5)) - time(dsearchn(mw_amp(1:midp)',.5)); 279 | 280 | 281 | 282 | %%% plotting 283 | figure(5), clf 284 | subplot(211) 285 | plot(hz,fx,'k','linew',2) 286 | set(gca,'xlim',[0 peakf*3]) 287 | xlabel('Frequency (Hz)'), ylabel('Amplitude (gain)') 288 | title([ 'FWHM specified: ' num2str(fwhm) ', obtained: ' num2str(empfwhmF) ' Hz' ]) 289 | 290 | subplot(212), hold on 291 | plot(time,real(morletwavelet),'linew',2) 292 | plot(time,imag(morletwavelet),'--','linew',2) 293 | h = plot(time,abs(morletwavelet),'linew',2); 294 | set(h,'color',[1 1 1]*.8) 295 | set(gca,'xlim',[-1 1]) 296 | legend({'Real part';'Imag part';'Envelope'}) 297 | xlabel('Time (sec.)') 298 | title([ 'FWHM: ' num2str(empfwhmT*1000) 'ms' ]) 299 | 300 | %% Figure 5a: The real deal (fixed FWHM) 301 | 302 | % note: this is one channel from one dataset from 303 | % Cohen, M.X., 2015. Comparison of different spatial transformations applied to EEG data: A case study of error processing. Int. J. Psychophysiol. 97, 245–257 304 | 305 | load MorletWaveletDefinition_data.mat 306 | 307 | %%% time-frequency parameters 308 | nfrex = 80; 309 | frex = linspace(2,40,nfrex); 310 | fwhm = [.1 .5 2]; 311 | 312 | % timimg parameters 313 | bidx = dsearchn(EEG.times',[-500 -200]'); 314 | tidx = dsearchn(EEG.times',-500):dsearchn(EEG.times',1300); 315 | 316 | 317 | % setup wavelet and convolution parameters 318 | wavet = -5:1/EEG.srate:5; 319 | halfw = floor(length(wavet)/2)+1; 320 | nConv = EEG.pnts*EEG.trials + length(wavet) - 1; 321 | 322 | 323 | % initialize time-frequency matrix 324 | tf = zeros(length(frex),length(tidx),length(fwhm)+1); 325 | empfwhm = zeros(length(fwhm),length(frex)+1); 326 | 327 | 328 | % spectrum of data 329 | dataX = fft(reshape(EEG.data,1,[]),nConv); 330 | 331 | 332 | figure(5), clf 333 | clim = [-2 2]; 334 | 335 | 336 | %%% loop over FWHM parameter settings 337 | for fwhmi=1:length(fwhm) 338 | 339 | % loop over frequencies 340 | for fi=1:length(frex) 341 | 342 | % create wavelet 343 | waveX = fft( exp(2*1i*pi*frex(fi)*wavet).*exp(-4*log(2)*wavet.^2/fwhm(fwhmi).^2),nConv ); 344 | waveX = waveX./max(waveX); % normalize 345 | 346 | % convolve 347 | as = ifft( waveX.*dataX ); 348 | % trim and reshape 349 | as = reshape(as(halfw:end-halfw+1),EEG.pnts,EEG.trials); 350 | 351 | % power 352 | p = mean( abs(as).^2 ,2); 353 | tf(fi,:,fwhmi) = 10*log10( p(tidx)/mean(p(bidx(1):bidx(2))) ); 354 | 355 | % empirical FWHM 356 | hz = linspace(0,EEG.srate,nConv); 357 | idx = dsearchn(hz',frex(fi)); 358 | fx = abs(waveX); 359 | empfwhm(fwhmi,fi) = hz(idx-1+dsearchn(fx(idx:end)',.5)) - hz(dsearchn(fx(1:idx)',.5)); 360 | 361 | end 362 | 363 | % plots. 364 | subplot(2,2,fwhmi) 365 | contourf(EEG.times(tidx),frex,squeeze(tf(:,:,fwhmi)),60,'linecolor','none') 366 | set(gca,'clim',clim,'YScale','log','YTick',logspace(log10(frex(1)),log10(frex(end)),10),'YTickLabel',round(logspace(log10(frex(1)),log10(frex(end)),10),2)) 367 | axis square 368 | title([ num2str(fwhm(fwhmi)) ' sec' ]) 369 | end 370 | 371 | colormap(bluewhitered(64)) 372 | 373 | %% Figure 5a: The real deal (variable FWHM) 374 | 375 | fwhm = linspace(1,.2,length(frex)); 376 | 377 | % loop over frequencies 378 | for fi=1:length(frex) 379 | 380 | % create wavelet 381 | waveX = fft( exp(2*1i*pi*frex(fi)*wavet).*exp(-4*log(2)*wavet.^2/fwhm(fi).^2),nConv ); 382 | waveX = waveX./max(waveX); % normalize 383 | 384 | % convolve 385 | as = ifft( waveX.*dataX ); 386 | % trim and reshape2 to 40 Hz in 387 | as = reshape(as(halfw:end-halfw+1),EEG.pnts,EEG.trials); 388 | 389 | % power 390 | p = mean( abs(as).^2 ,2); 391 | tf(fi,:,4) = 10*log10( p(tidx)/mean(p(bidx(1):bidx(2))) ); 392 | 393 | 394 | % empirical FWHM 395 | hz = linspace(0,EEG.srate,nConv); 396 | idx = dsearchn(hz',frex(fi)); 397 | fx = abs(waveX); 398 | empfwhm(4,fi) = hz(idx-1+dsearchn(fx(idx:end)',.5)) - hz(dsearchn(fx(1:idx)',.5)); 399 | 400 | end 401 | 402 | % plots. 403 | subplot(2,2,4) 404 | contourf(EEG.times(tidx),frex,squeeze(tf(:,:,4)),60,'linecolor','none') 405 | set(gca,'clim',clim,'YScale','log','YTick',logspace(log10(frex(1)),log10(frex(end)),10),'YTickLabel',round(logspace(log10(frex(1)),log10(frex(end)),10),2)) 406 | axis square 407 | title([ num2str(fwhm(1)) '-' num2str(fwhm(end)) ]) 408 | 409 | %% Figure 5b: the real deal, part deux 410 | 411 | figure(6), clf 412 | 413 | empfwhm(empfwhm>100) = nan; 414 | plot(frex,empfwhm,'s','markerfacecolor','w','linew',2,'markersize',8) 415 | legend({'100','500','2000','var'}) 416 | set(gca,'ylim',[0 18],'xlim',[frex(1)-1 frex(end)+1]) 417 | xlabel('Peak frequency (Hz)'), ylabel('FWHM (Hz)') 418 | axis square 419 | 420 | %% done. 421 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MorletWavelets 2 | 3 | This is the code the accompanies the paper "A better way to define and describe Morlet wavelets for time-frequency analysis" (Cohen, NeuroImage, 2019) 4 | 5 | Preprint available here: https://www.biorxiv.org/content/10.1101/397182v1.full.pdf+html 6 | and as pdf in this repository. 7 | -------------------------------------------------------------------------------- /bluewhitered.m: -------------------------------------------------------------------------------- 1 | function newmap = bluewhitered(m) 2 | %BLUEWHITERED Blue, white, and red color map. 3 | % BLUEWHITERED(M) returns an M-by-3 matrix containing a blue to white 4 | % to red colormap, with white corresponding to the CAXIS value closest 5 | % to zero. This colormap is most useful for images and surface plots 6 | % with positive and negative values. BLUEWHITERED, by itself, is the 7 | % same length as the current colormap. 8 | % 9 | % Examples: 10 | % ------------------------------ 11 | % figure 12 | % imagesc(peaks(250)); 13 | % colormap(bluewhitered(256)), colorbar 14 | % 15 | % figure 16 | % imagesc(peaks(250), [0 8]) 17 | % colormap(bluewhitered), colorbar 18 | % 19 | % figure 20 | % imagesc(peaks(250), [-6 0]) 21 | % colormap(bluewhitered), colorbar 22 | % 23 | % figure 24 | % surf(peaks) 25 | % colormap(bluewhitered) 26 | % axis tight 27 | % 28 | % See also HSV, HOT, COOL, BONE, COPPER, PINK, FLAG, 29 | % COLORMAP, RGBPLOT. 30 | 31 | % https://www.mathworks.com/matlabcentral/fileexchange/4058-bluewhitered 32 | 33 | 34 | if nargin < 1 35 | m = size(get(gcf,'colormap'),1); 36 | end 37 | 38 | 39 | bottom = [0 0 0.5]; 40 | botmiddle = [0 0.5 1]; 41 | middle = [1 1 1]; 42 | topmiddle = [1 0 0]; 43 | top = [0.5 0 0]; 44 | 45 | % Find middle 46 | lims = get(gca, 'CLim'); 47 | 48 | % Find ratio of negative to positive 49 | if (lims(1) < 0) & (lims(2) > 0) 50 | % It has both negative and positive 51 | % Find ratio of negative to positive 52 | ratio = abs(lims(1)) / (abs(lims(1)) + lims(2)); 53 | neglen = round(m*ratio); 54 | poslen = m - neglen; 55 | 56 | % Just negative 57 | new = [bottom; botmiddle; middle]; 58 | len = length(new); 59 | oldsteps = linspace(0, 1, len); 60 | newsteps = linspace(0, 1, neglen); 61 | newmap1 = zeros(neglen, 3); 62 | 63 | for i=1:3 64 | % Interpolate over RGB spaces of colormap 65 | newmap1(:,i) = min(max(interp1(oldsteps, new(:,i), newsteps)', 0), 1); 66 | end 67 | 68 | % Just positive 69 | new = [middle; topmiddle; top]; 70 | len = length(new); 71 | oldsteps = linspace(0, 1, len); 72 | newsteps = linspace(0, 1, poslen); 73 | newmap = zeros(poslen, 3); 74 | 75 | for i=1:3 76 | % Interpolate over RGB spaces of colormap 77 | newmap(:,i) = min(max(interp1(oldsteps, new(:,i), newsteps)', 0), 1); 78 | end 79 | 80 | % And put 'em together 81 | newmap = [newmap1; newmap]; 82 | 83 | elseif lims(1) >= 0 84 | % Just positive 85 | new = [middle; topmiddle; top]; 86 | len = length(new); 87 | oldsteps = linspace(0, 1, len); 88 | newsteps = linspace(0, 1, m); 89 | newmap = zeros(m, 3); 90 | 91 | for i=1:3 92 | % Interpolate over RGB spaces of colormap 93 | newmap(:,i) = min(max(interp1(oldsteps, new(:,i), newsteps)', 0), 1); 94 | end 95 | 96 | else 97 | % Just negative 98 | new = [bottom; botmiddle; middle]; 99 | len = length(new); 100 | oldsteps = linspace(0, 1, len); 101 | newsteps = linspace(0, 1, m); 102 | newmap = zeros(m, 3); 103 | 104 | for i=1:3 105 | % Interpolate over RGB spaces of colormap 106 | newmap(:,i) = min(max(interp1(oldsteps, new(:,i), newsteps)', 0), 1); 107 | end 108 | 109 | end 110 | % 111 | % m = 64; 112 | % new = [bottom; botmiddle; middle; topmiddle; top]; 113 | % % x = 1:m; 114 | % 115 | % oldsteps = linspace(0, 1, 5); 116 | % newsteps = linspace(0, 1, m); 117 | % newmap = zeros(m, 3); 118 | % 119 | % for i=1:3 120 | % % Interpolate over RGB spaces of colormap 121 | % newmap(:,i) = min(max(interp1(oldsteps, new(:,i), newsteps)', 0), 1); 122 | % end 123 | % 124 | % % set(gcf, 'colormap', newmap), colorbar 125 | --------------------------------------------------------------------------------