Time-resolved Frequency Analysis

Time-resolved Frequency Analysis

Authors
Dr. Atle Rimehaug | Dr. Nicholas Del Grosso

Sometimes, it’s not enough to know that an oscillation at a specific frequency is present in your signal, you also need to know when the oscillation occurred. For that, we have to do a time-resolved frequency analysis of the signal. That means that you compute a power spectrum that shows how the power of different frequencies in your signal varies over time. In this notebook, you will learn about methods for computing time-resolved frequency spectra of signals.

Setup

Import Libraries

import pywt
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path
import xarray as xr

Utility Functions

Run the cell below, but feel free to ignore this code — it’s some utility functions we’ll use later for convenience.

def calc_wavelet_spectrum(sig, sampling_frequency, number_of_freqs, bandwidth, center_frequency):
    '''
    Calculates the time-resolved power spectrum of a signal using Morlet wavelets

    Args:
        sig: signal that the wavelet spectrum is calculated for
        sampling_frequency: sampling frequency of signal
        number_of_freqs: number of frequencies the wavelet spectrum is calculated for
        bandwidth: the bandwidth of the Morlet wavelet
        center_frequency: the central frequency of the Morlet wavelet
    Returns:
        freqs: frequencies for which the spectral values are calculated
        spectrum_amp: spectral amplitudes
    '''
    # the bandwidth and the central frequency sets which Morlet wavelet is used
    wavelet = "cmor"+str(bandwidth)+"-"+str(center_frequency)

    # widths of the Morlet wavelet determines which frequencies the wavelet spectrum is calculated for
    widths = np.logspace(start=3, stop=10, num=number_of_freqs, base=2)

    sampling_period = 1/sampling_frequency
    spectrum, freqs = pywt.cwt(sig, widths, wavelet, sampling_period=sampling_period)

    # take absolute value to get real part and therefore amplitude of spectrum
    spectrum_amp = np.abs(spectrum)

    return freqs, spectrum_amp

def plot_spectrum_with_coi(time, freqs, spectrum, t_coi, apply_shading=True) -> None:
    '''
    Plots wavelet spectrum together with cone of influence

    Args:
        time: time points of spectrum
        freqs: frequencies the spectrum is calculated for
        spectrum: wavelet spectrum
        t_coi: time-points of cone of influence
        apply_shading: whether to apply shading to spectrum plot
    '''

    # plot wavelet spectrum
    if apply_shading:
        plt.pcolormesh(time, freqs, spectrum, cmap='plasma', shading='gouraud')
    else:
        plt.pcolormesh(time, freqs, spectrum, cmap='plasma')
    plt.ylim([0,100])

    # plot cone of influence
    plt.plot(time[0]+t_coi, np.arange(freqs.size)[::-1], 'g-', label = 'Cone of influence')
    plt.plot(time[-1]-t_coi, np.arange(freqs.size)[::-1],'g-')

    plt.xlabel('Time from stimulus onset (ms)')
    plt.ylabel('Frequency (Hz)')
    plt.title('Time-resolved frequency spectrum with\ncone of influence')
    plt.legend();

class utils:
    calc_wavelet_spectrum = calc_wavelet_spectrum
    plot_spectrum_with_coi = plot_spectrum_with_coi

Download dataset

import owncloud

owncloud.Client.from_public_link('https://uni-bonn.sciebo.de/s/rY4YCPXmkpespKG').get_file('/', 'dataset_session_754312389.nc')
True

Section 1: Time-resolved frequency analysis with Morlet wavelets

One of the most important and frequently used methods for computing time-resolved frequency spectra is to convolve the signal with Morlet wavelets. Morlet wavelets are wavelets with specific frequencies, and the process of convolving them with the signal can be seen as a matching process. You check how well the frequency of the signal matches with the frequency of the Morlet wavelet at a given time point. The better the match is, the higher the power will be for that frequency at that time point in the power spectrum. The plot below illustrates a Morlet wavelet whose frequency matches the frequency of the signal.

Code Description
freqs, spectrum_amp = utils.calc_wavelet_spectrum(sig, sampling_frequency) Calculates the wavelet spectrum spectrum for a signal sig with a given sampling_frequency. number_of_freqs sets the number of frequencies the spectrum is calculated for. bandwidth and center_frequency sets the parameters for the Morlet wavelet.
plt.pcolormesh(x, y, C, cmap = 'plasma', shading = 'gouraud') Make a 2D colormap of values in a 2D array (C) against x and y values. The optional parameters cmap and shading defines the colormap and the interpolation applied to the plot, respectively.

Load dataset

Run the cell below to load the dataset.

Exercises

loadpath = 'dataset_session_754312389.nc'

dataset_path = Path(loadpath)

dataset = xr.load_dataset(dataset_path)
dataset
<xarray.Dataset> Size: 225MB
Dimensions:                 (channel_depth: 23, trial_nr: 75, time_lfp: 1875,
time_csd: 1875, time_whole_rec_lfp: 373124,
time_whole_rec_ecp: 373124, unit_id_LGN: 27,
time_spikes: 1500, unit_id_V1: 91, unit_id_LM: 13,
stimulus_start_times: 75, stimulus_stop_times: 75,
channel_id: 23)
Coordinates: (12/13)

  • channel_depth (channel_depth) int64 184B 0 -40 -80 … -840 -880

  • trial_nr (trial_nr) int32 300B 0 1 2 3 4 5 … 70 71 72 73 74

  • time_lfp (time_lfp) float64 15kB -1e+03 -999.2 … 499.2

  • time_csd (time_csd) float64 15kB -1e+03 -999.2 … 499.2

  • time_whole_rec_lfp (time_whole_rec_lfp) float64 3MB 1.286e+06 … 1….

  • time_whole_rec_ecp (time_whole_rec_ecp) float64 3MB 1.286e+06 … 1…. … …

  • time_spikes (time_spikes) float64 12kB -999.5 -998.5 … 499.5

  • unit_id_V1 (unit_id_V1) int32 364B 951795075 … 951798053

  • unit_id_LM (unit_id_LM) int32 52B 951791074 … 951792163

  • stimulus_start_times (stimulus_start_times) float64 600B 1.29e+06 … …

  • stimulus_stop_times (stimulus_stop_times) float64 600B 1.29e+06 … 1…

  • channel_id (channel_id) int32 92B 850144538 … 850144362 Data variables: lfp_V1 (channel_depth, trial_nr, time_lfp) float64 26MB … csd_V1 (channel_depth, trial_nr, time_csd) float64 26MB … lfp_whole_recording_V1 (channel_depth, time_whole_rec_lfp) float64 69MB … ecp_whole_recording_V1 (channel_depth, time_whole_rec_ecp) float64 69MB … spike_counts_LGN (unit_id_LGN, trial_nr, time_spikes) int16 6MB 0 … spike_counts_V1 (unit_id_V1, trial_nr, time_spikes) int16 20MB 0 … spike_counts_LM (unit_id_LM, trial_nr, time_spikes) int16 3MB 0 …. pupil_width (trial_nr) float64 600B 39.12 40.53 … 46.57 44.31 run_speed (trial_nr) float64 600B 1.155 1.597 … 1.251 1.711 Attributes: time_unit: millisecond lfp_unit: Volt channel_depth_unit: micrometer note_channel_depth: Measured in distance from electrode closest t… sampling_frequency_lfp: 1250 sampling_frequency_spikes: 1000 sampling_frequency_unit: Hz stimulus_onset: 1000 stimulus_offset: 1250

xarray.Dataset
    • channel_depth: 23
    • trial_nr: 75
    • time_lfp: 1875
    • time_csd: 1875
    • time_whole_rec_lfp: 373124
    • time_whole_rec_ecp: 373124
    • unit_id_LGN: 27
    • time_spikes: 1500
    • unit_id_V1: 91
    • unit_id_LM: 13
    • stimulus_start_times: 75
    • stimulus_stop_times: 75
    • channel_id: 23
    • channel_depth
      (channel_depth)
      int64
      0 -40 -80 -120 … -800 -840 -880
      array([   0,  -40,  -80, -120, -160, -200, -240, -280, -320, -360, -400, -440,
      -480, -520, -560, -600, -640, -680, -720, -760, -800, -840, -880])
    • trial_nr
      (trial_nr)
      int32
      0 1 2 3 4 5 6 … 69 70 71 72 73 74
      array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17,
      18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
      36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
      54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
      72, 73, 74], dtype=int32)
    • time_lfp
      (time_lfp)
      float64
      -1e+03 -999.2 … 498.4 499.2
      array([-1000. ,  -999.2,  -998.4, …,   497.6,   498.4,   499.2])
    • time_csd
      (time_csd)
      float64
      -1e+03 -999.2 … 498.4 499.2
      array([-1000. ,  -999.2,  -998.4, …,   497.6,   498.4,   499.2])
    • time_whole_rec_lfp
      (time_whole_rec_lfp)
      float64
      1.286e+06 1.286e+06 … 1.584e+06
      array([1285609.738645, 1285610.538645, 1285611.338645, …, 1584106.565384,
      1584107.365384, 1584108.165384])
    • time_whole_rec_ecp
      (time_whole_rec_ecp)
      float64
      1.286e+06 1.286e+06 … 1.584e+06
      array([1285609.738645, 1285610.538645, 1285611.338645, …, 1584106.565384,
      1584107.365384, 1584108.165384])
    • unit_id_LGN
      (unit_id_LGN)
      int32
      951782498 951782516 … 951798528
      array([951782498, 951782516, 951782530, 951782631, 951782683, 951782699,
      951782744, 951782795, 951782832, 951782895, 951787033, 951792341,
      951792375, 951792398, 951792418, 951792441, 951792504, 951792544,
      951797077, 951798318, 951798391, 951798404, 951798553, 951798681,
      951801317, 951798434, 951798528], dtype=int32)
    • time_spikes
      (time_spikes)
      float64
      -999.5 -998.5 … 498.5 499.5
      array([-999.5, -998.5, -997.5, …,  497.5,  498.5,  499.5])
    • unit_id_V1
      (unit_id_V1)
      int32
      951795075 951795086 … 951798053
      array([951795075, 951795086, 951795098, 951795140, 951795163, 951795222,
      951795238, 951795256, 951795269, 951795290, 951795309, 951795317,
      951795403, 951797419, 951795458, 951795474, 951795487, 951795495,
      951795554, 951795680, 951795563, 951795611, 951795663, 951795688,
      951795706, 951795713, 951795721, 951797465, 951795729, 951795737,
      951795747, 951795873, 951795753, 951795782, 951795792, 951795760,
      951795768, 951795775, 951795807, 951795824, 951795832, 951795839,
      951795858, 951795880, 951795886, 951795896, 951795918, 951797520,
      951797539, 951797553, 951795936, 951795943, 951795967, 951795976,
      951797567, 951797489, 951796016, 951796073, 951796125, 951796165,
      951796176, 951796229, 951797633, 951796333, 951797647, 951797664,
      951796352, 951796494, 951797718, 951796542, 951796556, 951797730,
      951796585, 951797742, 951796629, 951796639, 951796660, 951796696,
      951796791, 951797776, 951796866, 951796906, 951796928, 951796949,
      951797595, 951797613, 951797709, 951797764, 951797811, 951797995,
      951798053], dtype=int32)
    • unit_id_LM
      (unit_id_LM)
      int32
      951791074 951791093 … 951792163
      array([951791074, 951791093, 951791202, 951791214, 951791273, 951791305,
      951791297, 951791401, 951791422, 951791467, 951791442, 951791453,
      951792163], dtype=int32)
    • stimulus_start_times
      (stimulus_start_times)
      float64
      1.29e+06 1.294e+06 … 1.584e+06
      array([1289613.25125, 1293616.60125, 1299621.62125, 1301623.29125,
      1303624.97125, 1307628.33125, 1309629.95125, 1319638.32125,
      1329646.71125, 1333650.07125, 1337653.40125, 1341656.74125,
      1351665.05125, 1357670.12125, 1361673.43125, 1371681.84125,
      1373683.47125, 1375685.17125, 1383691.83125, 1387695.15125,
      1397703.53125, 1401706.88125, 1403708.58125, 1407711.93125,
      1411715.25125, 1413716.94125, 1415718.63125, 1417720.26125,
      1419721.95125, 1421723.62125, 1423725.25125, 1427728.62125,
      1429730.28125, 1431731.94125, 1433733.62125, 1435735.32125,
      1441740.33125, 1447745.31125, 1451748.67125, 1459755.38125,
      1461757.06125, 1467762.05125, 1473767.09125, 1481773.75125,
      1485777.10125, 1489780.46125, 1491782.12125, 1493783.81125,
      1497787.16125, 1501790.48125, 1503792.14125, 1505793.80125,
      1511798.87125, 1515802.17125, 1517803.89125, 1519805.55125,
      1521807.23125, 1529813.91125, 1531815.57125, 1533817.23125,
      1537820.57125, 1541823.93125, 1543825.60125, 1545827.30125,
      1547828.93125, 1551832.29125, 1553833.96125, 1563842.35125,
      1565843.99125, 1569847.35125, 1571849.02125, 1573850.69125,
      1575852.34125, 1577854.07125, 1583859.05125])
    • stimulus_stop_times
      (stimulus_stop_times)
      float64
      1.29e+06 1.294e+06 … 1.584e+06
      array([1289863.450591, 1293866.800591, 1299871.820591, 1301873.490591,
      1303875.173091, 1307878.525591, 1309880.158091, 1319888.525591,
      1329896.908091, 1333900.265591, 1337903.598091, 1341906.940591,
      1351915.258091, 1357920.320591, 1361923.638091, 1371932.033091,
      1373933.675591, 1375935.370591, 1383942.035591, 1387945.363091,
      1397953.738091, 1401957.083091, 1403958.778091, 1407962.128091,
      1411965.450591, 1413967.138091, 1415968.823091, 1417970.465591,
      1419972.150591, 1421973.823091, 1423975.460591, 1427978.823091,
      1429980.490591, 1431982.153091, 1433983.830591, 1435985.523091,
      1441990.535591, 1447995.523091, 1451998.875591, 1460005.585591,
      1462007.260591, 1468012.260591, 1474017.293091, 1482023.960591,
      1486027.308091, 1490030.665591, 1492032.325591, 1494034.013091,
      1498037.360591, 1502040.685591, 1504042.350591, 1506044.010591,
      1512049.070591, 1516052.383091, 1518054.088091, 1520055.753091,
      1522057.428091, 1530064.110591, 1532065.775591, 1534067.438091,
      1538070.778091, 1542074.135591, 1544075.808091, 1546077.498091,
      1548079.138091, 1552082.495591, 1554084.165591, 1564092.550591,
      1566094.193091, 1570097.553091, 1572099.225591, 1574100.895591,
      1576102.553091, 1578104.268091, 1584109.255591])
    • channel_id
      (channel_id)
      int32
      850144538 850144530 … 850144362
      array([850144538, 850144530, 850144522, 850144514, 850144506, 850144498,
      850144490, 850144482, 850144474, 850144466, 850144458, 850144450,
      850144442, 850144434, 850144426, 850144418, 850144410, 850144402,
      850144394, 850144386, 850144378, 850144370, 850144362], dtype=int32)
    • lfp_V1
      (channel_depth, trial_nr, time_lfp)
      float64
      -8.516e-06 -1.156e-05 … 6.131e-06
      array([[[-8.51569421e-06, -1.15631923e-05, -3.76924291e-07, …,
      -2.17654367e-06,  1.68119712e-07, -6.94200500e-06],
      [ 2.81771330e-08,  6.29403676e-06,  9.98029865e-06, …,
      1.01304291e-05,  5.93295341e-06,  1.72969475e-05],
      [-1.27271427e-06, -3.68783112e-06, -4.46340366e-06, …,
      3.65666426e-06,  2.96670978e-06,  1.24568597e-06],
      …,
      [ 4.70565253e-06,  1.94317598e-06,  1.20455675e-06, …,
      4.07503496e-06,  7.65054254e-06,  3.91673831e-06],
      [ 8.38144661e-07,  2.28123812e-06,  3.70896321e-06, …,
      1.34013793e-05,  1.15462596e-05,  8.42411093e-06],
      [ 7.32479652e-06,  1.07711181e-06,  7.00885884e-06, …,
      1.92065863e-05,  1.92065863e-05,  1.92065863e-05]],

      [[ 3.91952787e-06, 1.70056905e-06, 5.97275690e-06, …, 4.06974274e-06, 4.04094749e-06, -4.68055730e-06], [-4.10311367e-06, 5.00856269e-06, 6.20500275e-06, …, 8.75868311e-06, 5.69604080e-06, 8.57744575e-06], [ 1.09646506e-06, 1.62070262e-06, 1.10953040e-07, …, 1.01990671e-05, -8.36754580e-06, -1.43387447e-05], … [-6.46738552e-06, -1.19701456e-05, -2.41882630e-05, …, -2.31175915e-05, -2.37374262e-05, -7.53926446e-06], [ 2.91850049e-05, 2.81713398e-05, 5.17618757e-05, …, 4.44764295e-05, 4.08778688e-05, 3.83233192e-05], [-1.80642354e-05, -1.06068608e-05, -3.71177368e-06, …, 1.48063260e-05, 1.48063260e-05, 1.48063260e-05]],

      [[ 4.42062699e-05, 5.99208645e-05, 7.60618853e-05, …, 9.85508013e-05, 8.87923195e-05, 8.02998878e-05], [-6.30930699e-06, 2.14882295e-05, 5.40961203e-06, …, -6.18517313e-05, -8.49961217e-05, -7.07025406e-05], [ 1.32347041e-04, 9.66830285e-05, 9.77152893e-05, …, -3.68393584e-05, -2.08663734e-05, -1.27901811e-05], …, [ 7.59530363e-07, -9.00525049e-06, -1.95382678e-05, …, -2.78150664e-05, -1.46349189e-05, 3.65395950e-06], [ 3.22381374e-05, 3.90163375e-05, 6.39709157e-05, …, 5.17422904e-05, 4.94353600e-05, 3.90770304e-05], [-2.50190016e-05, -1.82145705e-05, -1.97245214e-05, …, 6.13148062e-06, 6.13148062e-06, 6.13148062e-06]]])

    • csd_V1
      (channel_depth, trial_nr, time_csd)
      float64
      -0.04729 -0.04843 … 0.004271
      array([[[-0.04729251, -0.04843248, -0.03745331, …, -0.0506883 ,
      -0.05553956, -0.06003479],
      [-0.01093619,  0.01574628,  0.00914164, …,  0.00598613,
      0.01101591,  0.03123014],
      [-0.03696462, -0.04099173, -0.03852095, …,  0.00098069,
      -0.0050393 , -0.01264635],
      …,
      [-0.01247878,  0.00877942,  0.02265446, …,  0.00331111,
      0.03482657,  0.01182155],
      [-0.00745365, -0.01184343, -0.0143719 , …, -0.00701411,
      -0.00677321,  0.00801698],
      [-0.00185681,  0.00029347,  0.01247577, …,  0.04692436,
      0.04692436,  0.04692436]],

      [[-0.05243487, -0.04187987, -0.01951768, …, 0.00767212, 0.02122529, -0.00091668], [-0.03800667, -0.0267893 , 0.00151346, …, -0.06356118, -0.05255179, -0.02253284], [-0.13918267, -0.15069373, -0.14869443, …, -0.08287076, -0.10533221, -0.11878146], … [-0.0317182 , -0.0291328 , -0.04440809, …, 0.0128667 , -0.00803555, 0.00140582], [-0.00313798, -0.02211998, 0.00973861, …, 0.01834821, 0.02898202, 0.03053275], [ 0.01935376, 0.02458448, 0.02770717, …, 0.0335107 , 0.0335107 , 0.0335107 ]],

      [[ 0.05527245, 0.06647928, 0.08365367, …, 0.03946655, -0.00638597, 0.01617121], [ 0.00484864, 0.04955322, 0.00056809, …, -0.02495747, -0.04720462, -0.05647099], [ 0.14248465, 0.09267001, 0.12532577, …, -0.02102215, 0.01070787, -0.00105385], …, [ 0.02074605, 0.01291679, -0.00810403, …, -0.00440359, 0.01890968, 0.03686672], [-0.03145411, -0.01193072, 0.05258549, …, 0.06270049, 0.05033156, 0.03718263], [-0.01626375, -0.00653823, -0.01209703, …, 0.00427089, 0.00427089, 0.00427089]]])

    • lfp_whole_recording_V1
      (channel_depth, time_whole_rec_lfp)
      float64
      1.131e-05 2.316e-05 … 6.131e-06
      array([[ 1.13123117e-05,  2.31601759e-05,  2.56464473e-05, …,
      2.90957718e-05,  2.80686400e-05,  1.92065863e-05],
      [ 1.25864221e-05,  2.61936713e-05,  2.05721887e-05, …,
      2.51329249e-05,  1.10562717e-05,  1.14064589e-05],
      [ 1.06294434e-05,  1.20898434e-05,  1.05466595e-05, …,
      4.39547441e-06,  2.49921901e-06, -3.03491628e-06],
      …,
      [-1.51105564e-05, -2.86651554e-05, -2.43583384e-05, …,
      1.55104740e-05,  1.08842192e-05,  7.88508374e-06],
      [-2.60411104e-05, -3.63399664e-05, -2.41173628e-05, …,
      4.57287775e-05,  2.16324211e-05,  1.48063260e-05],
      [-1.92918607e-05, -3.23216874e-05, -2.71540780e-05, …,
      5.95832945e-05,  2.76861759e-05,  6.13148062e-06]])
    • ecp_whole_recording_V1
      (channel_depth, time_whole_rec_ecp)
      float64
      1.131e-05 2.316e-05 … 6.131e-06
      array([[ 1.13123117e-05,  2.31601759e-05,  2.56464473e-05, …,
      2.90957718e-05,  2.80686400e-05,  1.92065863e-05],
      [ 1.25864221e-05,  2.61936713e-05,  2.05721887e-05, …,
      2.51329249e-05,  1.10562717e-05,  1.14064589e-05],
      [ 1.06294434e-05,  1.20898434e-05,  1.05466595e-05, …,
      4.39547441e-06,  2.49921901e-06, -3.03491628e-06],
      …,
      [-1.51105564e-05, -2.86651554e-05, -2.43583384e-05, …,
      1.55104740e-05,  1.08842192e-05,  7.88508374e-06],
      [-2.60411104e-05, -3.63399664e-05, -2.41173628e-05, …,
      4.57287775e-05,  2.16324211e-05,  1.48063260e-05],
      [-1.92918607e-05, -3.23216874e-05, -2.71540780e-05, …,
      5.95832945e-05,  2.76861759e-05,  6.13148062e-06]])
    • spike_counts_LGN
      (unit_id_LGN, trial_nr, time_spikes)
      int16
      0 0 0 0 0 0 0 0 … 1 0 0 0 0 0 0 0
      array([[[0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0],
      …,
      [0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], …, [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], …, … …, [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], …, [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], …, [0, 0, 0, …, 0, 0, 0], [0, 1, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]]], dtype=int16)

    • spike_counts_V1
      (unit_id_V1, trial_nr, time_spikes)
      int16
      0 0 0 0 0 0 0 0 … 0 0 0 0 0 0 0 0
      array([[[0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0],
      …,
      [0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], …, [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 1, …, 0, 0, 0], …, … …, [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], …, [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], …, [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]]], dtype=int16)

    • spike_counts_LM
      (unit_id_LM, trial_nr, time_spikes)
      int16
      0 0 0 0 0 0 0 0 … 0 0 0 0 0 0 0 0
      array([[[0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0],
      …,
      [0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], …, [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], …, … …, [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 1], …, [0, 0, 0, …, 0, 1, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], …, [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]]], dtype=int16)

    • pupil_width
      (trial_nr)
      float64
      39.12 40.53 53.87 … 46.57 44.31
      array([39.11652012, 40.53014519, 53.86896327, 50.82396045, 52.16879212,
      57.96043078, 54.92497335, 55.54967205, 43.90274746, 44.79854897,
      38.38702834, 40.71644189, 37.98256784, 35.66463223, 33.77803219,
      37.33170246, 34.40298455, 33.56487003, 34.8366242 , 34.23566353,
      37.82023369, 36.64031488, 34.90016986, 34.01127022, 34.01676223,
      33.07691675, 36.24898149, 34.31897366, 32.37205121, 32.29131936,
      31.22026173, 32.11667831, 35.9407955 , 33.83439488, 40.32179372,
      40.2773362 , 36.8416765 , 38.28154173, 32.92621992, 34.10611125,
      32.64213782, 35.34716687, 38.83240189, 41.4527341 , 40.80453453,
      34.4518716 , 33.54378434, 34.76110284, 39.39896604, 38.12986388,
      35.01399576, 36.1764032 , 36.91026669, 38.09285533, 37.47092614,
      35.08431869, 34.10750204, 33.98908271, 33.49633874, 31.95082717,
      36.40762283, 33.54301125, 30.16956358, 34.97767163, 35.73862834,
      37.5021579 , 46.90415398, 64.55582159, 65.24024526, 62.84832274,
      63.79648979, 64.1050437 , 54.12537803, 46.57182176, 44.30600894])
    • run_speed
      (trial_nr)
      float64
      1.155 1.597 6.057 … 1.251 1.711
      array([ 1.15461545,  1.59668837,  6.056658  ,  5.15987544,  8.52760497,
      11.22847943,  4.57915849,  3.55552612,  1.21703267,  1.14123532,
      1.22313026,  1.45219009,  1.27881682,  1.28545771,  1.30252225,
      1.45938978,  1.23683681,  1.35499306,  1.24373061,  1.72814998,
      1.10057218,  1.12037113,  1.0171456 ,  1.12698213,  1.23746278,
      1.0051122 ,  1.22995347,  1.18035543,  1.33973747,  1.31655126,
      1.42316228,  1.16454631,  1.1175898 ,  1.33965127,  1.44008401,
      1.27856107,  1.08369774,  1.2852668 ,  1.22042713,  0.93635542,
      1.10224883,  1.3656531 ,  1.14346642,  1.34443297,  1.27934364,
      1.22477016,  0.89182778,  1.08752668,  1.29883034,  1.38104512,
      0.96683534,  1.19810534,  0.99052094,  0.9343001 ,  1.71404147,
      1.51968473,  1.23845685,  0.84848258,  1.59605601,  1.25635954,
      1.22987165,  1.64782276,  1.75731669,  1.67665638,  1.42473997,
      1.02886151,  5.65275275,  8.34342969,  9.03230058, 10.64468513,
      8.17371765,  1.18762197,  1.36400808,  1.25093511,  1.71061779])
    • channel_depth
      PandasIndex
      PandasIndex(Index([   0,  -40,  -80, -120, -160, -200, -240, -280, -320, -360, -400, -440,
      -480, -520, -560, -600, -640, -680, -720, -760, -800, -840, -880],
      dtype='int64', name='channel_depth'))
    • trial_nr
      PandasIndex
      PandasIndex(Index([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17,
      18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
      36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
      54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
      72, 73, 74],
      dtype='int32', name='trial_nr'))
    • time_lfp
      PandasIndex
      PandasIndex(Index([           -1000.0, -999.1999999999999,             -998.4,
      -997.5999999999999,             -996.8, -995.9999999999999,
      -995.1999999999998, -994.3999999999999, -993.5999999999998,
      -992.7999999999998,
      …
      492.00000000004263, 492.80000000004276,  493.6000000000429,
      494.4000000000428, 495.20000000004273, 496.00000000004286,
      496.800000000043,  497.6000000000429, 498.40000000004284,
      499.20000000004296],
      dtype='float64', name='time_lfp', length=1875))
    • time_csd
      PandasIndex
      PandasIndex(Index([           -1000.0, -999.1999999999999,             -998.4,
      -997.5999999999999,             -996.8, -995.9999999999999,
      -995.1999999999998, -994.3999999999999, -993.5999999999998,
      -992.7999999999998,
      …
      492.00000000004263, 492.80000000004276,  493.6000000000429,
      494.4000000000428, 495.20000000004273, 496.00000000004286,
      496.800000000043,  497.6000000000429, 498.40000000004284,
      499.20000000004296],
      dtype='float64', name='time_csd', length=1875))
    • time_whole_rec_lfp
      PandasIndex
      PandasIndex(Index([1285609.7386445338, 1285610.5386446053,  1285611.338644677,
      1285612.1386447486, 1285612.9386448204, 1285613.7386448921,
      1285614.5386449639, 1285615.3386450356, 1285616.1386451072,
      1285616.938645179,
      …
      1584100.9653832503, 1584101.7653833218, 1584102.5653833936,
      1584103.365383465, 1584104.1653835368, 1584104.9653836086,
      1584105.7653836801, 1584106.5653837519, 1584107.3653838234,
      1584108.1653838952],
      dtype='float64', name='time_whole_rec_lfp', length=373124))
    • time_whole_rec_ecp
      PandasIndex
      PandasIndex(Index([1285609.7386445338, 1285610.5386446053,  1285611.338644677,
      1285612.1386447486, 1285612.9386448204, 1285613.7386448921,
      1285614.5386449639, 1285615.3386450356, 1285616.1386451072,
      1285616.938645179,
      …
      1584100.9653832503, 1584101.7653833218, 1584102.5653833936,
      1584103.365383465, 1584104.1653835368, 1584104.9653836086,
      1584105.7653836801, 1584106.5653837519, 1584107.3653838234,
      1584108.1653838952],
      dtype='float64', name='time_whole_rec_ecp', length=373124))
    • unit_id_LGN
      PandasIndex
      PandasIndex(Index([951782498, 951782516, 951782530, 951782631, 951782683, 951782699,
      951782744, 951782795, 951782832, 951782895, 951787033, 951792341,
      951792375, 951792398, 951792418, 951792441, 951792504, 951792544,
      951797077, 951798318, 951798391, 951798404, 951798553, 951798681,
      951801317, 951798434, 951798528],
      dtype='int32', name='unit_id_LGN'))
    • time_spikes
      PandasIndex
      PandasIndex(Index([            -999.5,             -998.5,             -997.5,
      -996.4999999999999,             -995.5, -994.4999999999999,
      -993.5, -992.4999999999999,             -991.5,
      -990.4999999999999,
      …
      490.50000000000006, 491.50000000000006, 492.50000000000006,
      493.50000000000006, 494.50000000000006, 495.50000000000006,
      496.50000000000006, 497.50000000000006, 498.50000000000006,
      499.50000000000006],
      dtype='float64', name='time_spikes', length=1500))
    • unit_id_V1
      PandasIndex
      PandasIndex(Index([951795075, 951795086, 951795098, 951795140, 951795163, 951795222,
      951795238, 951795256, 951795269, 951795290, 951795309, 951795317,
      951795403, 951797419, 951795458, 951795474, 951795487, 951795495,
      951795554, 951795680, 951795563, 951795611, 951795663, 951795688,
      951795706, 951795713, 951795721, 951797465, 951795729, 951795737,
      951795747, 951795873, 951795753, 951795782, 951795792, 951795760,
      951795768, 951795775, 951795807, 951795824, 951795832, 951795839,
      951795858, 951795880, 951795886, 951795896, 951795918, 951797520,
      951797539, 951797553, 951795936, 951795943, 951795967, 951795976,
      951797567, 951797489, 951796016, 951796073, 951796125, 951796165,
      951796176, 951796229, 951797633, 951796333, 951797647, 951797664,
      951796352, 951796494, 951797718, 951796542, 951796556, 951797730,
      951796585, 951797742, 951796629, 951796639, 951796660, 951796696,
      951796791, 951797776, 951796866, 951796906, 951796928, 951796949,
      951797595, 951797613, 951797709, 951797764, 951797811, 951797995,
      951798053],
      dtype='int32', name='unit_id_V1'))
    • unit_id_LM
      PandasIndex
      PandasIndex(Index([951791074, 951791093, 951791202, 951791214, 951791273, 951791305,
      951791297, 951791401, 951791422, 951791467, 951791442, 951791453,
      951792163],
      dtype='int32', name='unit_id_LM'))
    • stimulus_start_times
      PandasIndex
      PandasIndex(Index([1289613.2512503476, 1293616.6012503474, 1299621.6212503475,
      1301623.2912503474, 1303624.9712503476, 1307628.3312503474,
      1309629.9512503473, 1319638.3212503474, 1329646.7112503473,
      1333650.0712503474, 1337653.4012503475, 1341656.7412503476,
      1351665.0512503474, 1357670.1212503475, 1361673.4312503475,
      1371681.8412503474, 1373683.4712503473, 1375685.1712503475,
      1383691.8312503477, 1387695.1512503477, 1397703.5312503476,
      1401706.8812503475, 1403708.5812503477, 1407711.9312503475,
      1411715.2512503476, 1413716.9412503475, 1415718.6312503477,
      1417720.2612503476, 1419721.9512503475, 1421723.6212503477,
      1423725.2512503476, 1427728.6212503475, 1429730.2812503476,
      1431731.9412503475, 1433733.6212503477, 1435735.3212503477,
      1441740.3312503477, 1447745.3112503476, 1451748.6712503475,
      1459755.3812503477, 1461757.0612503476, 1467762.0512503476,
      1473767.0912503477, 1481773.7512503476, 1485777.1012503477,
      1489780.4612503476, 1491782.1212503477, 1493783.8112503474,
      1497787.1612503477, 1501790.4812503476, 1503792.1412503477,
      1505793.8012503476, 1511798.8712503477, 1515802.1712503475,
      1517803.8912503475, 1519805.5512503476, 1521807.2312503476,
      1529813.9112503477, 1531815.5712503477, 1533817.2312503476,
      1537820.5712503474, 1541823.9312503478, 1543825.6012503474,
      1545827.3012503476, 1547828.9312503475, 1551832.2912503476,
      1553833.9612503476, 1563842.3512503474, 1565843.9912503478,
      1569847.3512503477, 1571849.0212503476, 1573850.6912503475,
      1575852.3412503474, 1577854.0712503474, 1583859.0512503476],
      dtype='float64', name='stimulus_start_times'))
    • stimulus_stop_times
      PandasIndex
      PandasIndex(Index([1289863.4505906447, 1293866.8005906448, 1299871.8205906446,
      1301873.490590645, 1303875.1730906446, 1307878.5255906447,
      1309880.1580906447, 1319888.5255906447, 1329896.9080906447,
      1333900.2655906447, 1337903.5980906447, 1341906.9405906445,
      1351915.2580906446, 1357920.3205906448, 1361923.6380906447,
      1371932.0330906445, 1373933.6755906448,  1375935.370590645,
      1383942.0355906447, 1387945.3630906448,  1397953.738090645,
      1401957.0830906448, 1403958.7780906446,  1407962.128090645,
      1411965.450590645, 1413967.1380906447,  1415968.823090645,
      1417970.4655906449, 1419972.1505906447, 1421973.8230906448,
      1423975.4605906447,  1427978.823090645, 1429980.4905906448,
      1431982.1530906449, 1433983.8305906449, 1435985.5230906447,
      1441990.5355906447, 1447995.5230906447, 1451998.8755906448,
      1460005.5855906447, 1462007.2605906448, 1468012.2605906453,
      1474017.2930906452,  1482023.960590645, 1486027.3080906447,
      1490030.6655906448, 1492032.3255906447, 1494034.0130906447,
      1498037.3605906449, 1502040.6855906448,  1504042.350590645,
      1506044.0105906448, 1512049.0705906448, 1516052.3830906448,
      1518054.088090645,  1520055.753090645, 1522057.4280906445,
      1530064.1105906446, 1532065.7755906447, 1534067.4380906445,
      1538070.7780906446, 1542074.1355906448, 1544075.8080906447,
      1546077.4980906448, 1548079.1380906447,  1552082.495590645,
      1554084.165590645, 1564092.5505906448,  1566094.193090645,
      1570097.5530906448, 1572099.2255906449, 1574100.8955906448,
      1576102.5530906448, 1578104.2680906446,  1584109.255590645],
      dtype='float64', name='stimulus_stop_times'))
    • channel_id
      PandasIndex
      PandasIndex(Index([850144538, 850144530, 850144522, 850144514, 850144506, 850144498,
      850144490, 850144482, 850144474, 850144466, 850144458, 850144450,
      850144442, 850144434, 850144426, 850144418, 850144410, 850144402,
      850144394, 850144386, 850144378, 850144370, 850144362],
      dtype='int32', name='channel_id'))
  • time_unit :
    millisecond
    lfp_unit :
    Volt
    channel_depth_unit :
    micrometer
    note_channel_depth :
    Measured in distance from electrode closest to scalp
    sampling_frequency_lfp :
    1250
    sampling_frequency_spikes :
    1000
    sampling_frequency_unit :
    Hz
    stimulus_onset :
    1000
    stimulus_offset :
    1250

Example: Calculate the time-resolved power spectrum for the constructed signal below using a Morlet wavelet. Plot the resulting spectrum using the pcolormesh function.

# provided

# Generate signal
sampling_frequency = 1000
durationA = 0.5
time_vecA = np.linspace(0,durationA,int(sampling_frequency*durationA)) # seconds
frequencyA = 5 # Hz
sineA = np.sin(2*np.pi*frequencyA*time_vecA)

durationB = 0.5
time_vecB = np.linspace(durationA, durationA+durationB, int(sampling_frequency*durationB)) # seconds
frequencyB = 15 # Hz
sineB = np.sin(2*np.pi*frequencyB*time_vecB)

time_vec = np.linspace(0,durationA+durationB,int(sampling_frequency*(durationA+durationB)))
sig = np.concatenate([sineA,sineB])

plt.figure()
plt.plot(time_vec,sig)
plt.title(f'Sinusoid signal that changes\nfrequency from {frequencyA} Hz to {frequencyB} Hz');

Solution
# solution

number_of_freqs = 10
bandwidth = 1.5
center_frequency = 1.0
freqs, spectrum = utils.calc_wavelet_spectrum(sig, sampling_frequency, number_of_freqs, bandwidth, center_frequency)

plt.pcolormesh(time_vec, freqs, spectrum, cmap = 'plasma', shading='gouraud')
plt.xlabel('Time (s)')
plt.ylabel('Frequencies (Hz)')
plt.title('Wavelet spectrum of signal');

Exercise: Calculate the time-resolved power spectrum for the same constructed sinusoid below using a Morlet wavelet. But this time change the number_of_freqs to 100. This parameter specifices how many frequencies to calculate the spectrum for. Plot the result using the pcolormesh function.

What happens to the frequency resolution when you compare to the plot above?

Solution
number_of_freqs = 100
bandwidth = 1.5
center_frequency = 1.0
freqs, spectrum = utils.calc_wavelet_spectrum(sig, sampling_frequency, number_of_freqs, bandwidth, center_frequency)

plt.pcolormesh(time_vec, freqs, spectrum, cmap = 'plasma', shading='gouraud')
plt.xlabel('Time (s)')
plt.ylabel('Frequencies (Hz)')
plt.title('Wavelet spectrum of signal');

The Morlet wavelet comes in many different shapes and sizes. There are two parameters that determine which kind of Morlet wavelet is used in the calculation of the spectrum: the bandwidth and the center frequency of the wavelet. The bandwidth determines how broad the wavelet is and the center frequency determines how many oscillations it has. Wavelets with different bandwidths and center frequencies are illustrated in the figure below. These two parameters determine the frequency- and time-resolution of the resulting power spectrum.

Exercise: Calculate the time-resolved power spectrum for the same constructed sinusoid below using a Morlet wavelet. Keep the number_of_frequencies parameter at 100, but change the center_frequency parameter to 0.5.

What happens to the frequency resolution now?

Solution
number_of_freqs = 100
bandwidth = 1.5
center_frequency = 0.5
freqs, spectrum = utils.calc_wavelet_spectrum(sig, sampling_frequency, number_of_freqs, bandwidth, center_frequency)

plt.pcolormesh(time_vec, freqs, spectrum, cmap = 'plasma', shading='gouraud')
plt.xlabel('Time (s)')
plt.ylabel('Frequencies (Hz)')
plt.title('Wavelet spectrum of signal');

Exercise: Change the center_frequency parameter to 2.0. Calculate and plot the time-resolved spectrum for the same constructed sinusoid below using a Morlet wavelet.

What happens to the frequency resolution? What happens to the time resolution?

Solution
number_of_freqs = 100
bandwidth = 1.5
center_frequency = 2.0
freqs, spectrum = utils.calc_wavelet_spectrum(sig, sampling_frequency, number_of_freqs, bandwidth, center_frequency)

plt.pcolormesh(time_vec, freqs, spectrum, cmap = 'plasma', shading='gouraud')
plt.xlabel('Time (s)')
plt.ylabel('Frequencies (Hz)')
plt.title('Wavelet spectrum of signal');

Exercise: Set the center_frequency parameter back to 1.0, but change the bandwidth parameter to 0.5. Calculate and plot the time-resolved spectrum for the same constructed sinusoid again.

What happens to the frequency resolution this time?

Solution
number_of_freqs = 100
bandwidth = 0.5
center_frequency = 1.0
freqs, spectrum = utils.calc_wavelet_spectrum(sig, sampling_frequency, number_of_freqs, bandwidth, center_frequency)

plt.pcolormesh(time_vec, freqs, spectrum, cmap = 'plasma', shading='gouraud')
plt.xlabel('Time (s)')
plt.ylabel('Frequencies (Hz)')
plt.title('Wavelet spectrum of signal');

Exercise: Change the bandwidth of the wavelet to 2.5. Calculate and plot the spectrum using pcolormesh.

What happens to the frequency resolution compared to the last plot?

Solution
number_of_freqs = 100
bandwidth = 2.5
center_frequency = 1.0
freqs, spectrum = utils.calc_wavelet_spectrum(sig, sampling_frequency, number_of_freqs, bandwidth, center_frequency)

plt.pcolormesh(time_vec, freqs, spectrum, cmap = 'plasma', shading='gouraud')
plt.xlabel('Time (s)')
plt.ylabel('Frequencies (Hz)')
plt.title('Wavelet spectrum of signal');

Comment: The exercises and plots above should demonstrate that if you increase the time resolution, you decrease the frequency resolution in your spectrum. And vice versa, if you increase the frequency resolution, you decrease the time resolution of your spectrum. There’s a fundamental trade-off between resolution in frequency and resolution in time.

The different parameters used for the Morlet wavelet determine where you strike the balance between frequency and time resolution. The frequency resolution increases and the time resolution goes down with greater bandwidth or greater center frequency.

Section 2: Time-resolved frequency analysis with morlet wavelets on spikes and LFP data

In this section, you will use Morlet wavelets to calculate the time-resolved spectra for spikes and LFP data.

Code Description
freqs, spectrum_amp = utils.calc_wavelet_spectrum(sig, sampling_frequency) Calculates the wavelet spectrum spectrum for a signal sig with a given sampling_frequency. number_of_freqs sets the number of frequencies the spectrum is calculated for. bandwidth and center_frequency sets the parameters for the Morlet wavelet.
np.mean(data, axis = (dim_num)) or data.mean(axis = dim_num) Calculate the average of the data across the dim_num dimension of the array.
plt.pcolormesh(x, y, C, cmap = 'plasma', shading = 'gouraud') Make a 2D colormap of values in a 2D array (C) against x and y values. The optional parameters cmap and shading defines the colormap and the interpolation applied to the plot, respectively.

Run the cell below to extract the LFP data from the dataset.

Exercises

lfp_V1 = dataset['lfp_V1']
lfp_whole_rec = dataset['lfp_whole_recording_V1']
channels_depth = dataset['channel_depth']

sampling_frequency_lfp = dataset.sampling_frequency_lfp # Hz
time_lfp = lfp_V1.time_lfp.values # ms

We will stick to the parameters for the Morlet wavelet used in the first example in this section. That is, bandwidth = 1.5, and center_frequency = 1.0. Example: Calculate the time-resolved power spectrum of an LFP trace from the channel on index 8 in trial 0 using a morlet wavelet. Plot the result using pcolormesh.

ichan = 8
itrial = 0

number_of_freqs = 100
bandwidth = 1.5
center_frequency = 1.0
freqs, spectrum = utils.calc_wavelet_spectrum(lfp_V1[ichan, itrial], sampling_frequency_lfp, number_of_freqs, bandwidth, center_frequency)
# plot result using matplotlib's pcolormesh
plt.pcolormesh(time_lfp, freqs, spectrum, cmap = 'plasma', shading='gouraud')
plt.xlabel('Time from stimulus onset (ms)')
plt.ylabel('Frequencies (Hz)')
plt.title('Power spectrum')

Exercise: Calculate the time-resolved power spectrum of an LFP trace from the channel on index 2 in trial 0 using a Morlet wavelet. Plot the result using pcolormesh.

Is there a noticable increase in power for some frequencies at any time during the trial?

Solution
ichan = 2
itrial = 0

number_of_freqs = 100
bandwidth = 1.5
center_frequency = 1.0
freqs, spectrum = utils.calc_wavelet_spectrum(lfp_V1[ichan, itrial], sampling_frequency_lfp, number_of_freqs, bandwidth, center_frequency)

plt.pcolormesh(time_lfp, freqs, spectrum, cmap = 'plasma', shading='gouraud')
plt.xlabel('Time from stimulus onset (ms)')
plt.ylabel('Frequencies (Hz)')

Exercise: Calculate the spectrum of an LFP from the channel on index 8 in all trials (you don’t need to use a for loop). Calculate the trial-averaged spectrum and plot the result.

Tip: You might want to decrease the number_of_freqs parameter if you feel that it takes too long to calculate the spectrum.

Solution
ichan = 8

number_of_freqs = 50
bandwidth = 1.5
center_frequency = 1.0
freqs, spectrum = utils.calc_wavelet_spectrum(lfp_V1[ichan], sampling_frequency_lfp, number_of_freqs, bandwidth, center_frequency)

spectrum_trial_avg = np.mean(spectrum, axis = 1)

plt.pcolormesh(time_lfp, freqs, spectrum_trial_avg, cmap = 'plasma', shading='gouraud')
plt.xlabel('Time from stimulus onset (ms)')
plt.ylabel('Frequencies (Hz)')

Run the cell below to extract the spike data.

spike_counts_LGN = dataset['spike_counts_LGN']
spike_counts_V1 = dataset['spike_counts_V1']
spike_counts_LM = dataset['spike_counts_LM']

time_spikes = spike_counts_LM.time_spikes
sampling_frequency_spikes = dataset.sampling_frequency_spikes

Exercise: Calculate the time-resolved power spectrum for the trial-averaged firing rate of the LM cell at index 6. Plot the result using pcolormesh.

Is there a notable increase in power at any time point during the trial?

NB: You have to change the sampling frequency parameter from sampling_frequency_lfp to sampling_frequency_spikes in the calc_wavelet_spectrum function. And the time in the pcolormesh function also needs to be changed to time_spikes.

Solution
icell = 6

number_of_freqs = 50
bandwidth = 1.5
center_frequency = 1.0
freqs, spectrum = utils.calc_wavelet_spectrum(spike_counts_LM[icell].mean('trial_nr'), sampling_frequency_spikes, number_of_freqs, bandwidth, center_frequency)

plt.pcolormesh(time_spikes, freqs, spectrum, cmap = 'plasma', shading='gouraud')
plt.xlabel('Time from stimulus onset (ms)')
plt.ylabel('Frequencies (Hz)')

Comment: For time-resolved spectra of spikes, you will probably get more useful spectra if you average across trials (or cells) to get the firing rate first and then calculate the spectrum, like in the exercise above. However, the fact that the spike trains won’t all be in phase will mean that the spectral estimate isn’t entirely accurate.

Section 3: Cone of influence

When the signal is convolved with a wavelet, the wavelet will partly be outside of the signal window close to the edges (the start and end point of your signal). This will lead to inaccurate spectral estimates in these regions. Which parts of your spectral estimate may be influenced by such edge-effect artifacts can be calculated with the formula for a “cone of influence”. Regions of your spectrum outside this cone can’t be trusted and should be ignored when examining your time-resolved power spectrum. The formula for the cone of influence is given as:

tCOI=σ2π2ft_{COI} = \frac{\sigma}{2\pi}\frac{\sqrt{2}}{f}

where σ\sigma is the bandwidth of the Morlet wavelet and ff is frequencies for which the spectrum is calculated.

Code Description
freqs, spectrum_amp = utils.calc_wavelet_spectrum(sig, sampling_frequency) Utility function to calculate the wavelet spectrum spectrum for a signal sig with a given sampling_frequency. number_of_freqs sets the number of frequencies the spectrum is calculated for. bandwidth and center_frequency sets the parameters for the Morlet wavelet.
t_coi = (bandwidth) / (2*np.pi) * np.sqrt(2) / freqs Calculate the cone of influence (COI) for a spectrum computed with a Morlet wavelet with a given bandwidth.
utils.plot_spectrum_with_coi(time_lfp, freqs, spectrum, t_coi, apply_shading = True) Makes a plot of the wavelet spectrum with the cone of influence superimposed.

Exercises

Example: Calculate the cone of influence (COI) for the Morlet wavelet spectrum computed for a single channel, single trial LFP trace (ichan = 2, itrial = 10). Plot the COI on top of the the wavelet spectrum. You can use the utility functions to calculate the spectrum and to make the plot.

ichan = 2
itrial = 10

number_of_freqs = 50
bandwidth = 1.5
center_frequency = 1.0
freqs, spectrum = utils.calc_wavelet_spectrum(lfp_V1[ichan, itrial], sampling_frequency_lfp, number_of_freqs, bandwidth, center_frequency)
t_coi = (bandwidth) / (2*np.pi) * np.sqrt(2) / freqs * 1E3

# plot spectrum and cone of influence
utils.plot_spectrum_with_coi(time_lfp, freqs, spectrum, t_coi)

Exercise: Change the bandwidth parameter to 3.5 and calculate of the spectrum for the same LFP trace. Calculate and plot the COI on top of the spectrum below. What happens to the COI?

Solution
ichan = 2
itrial = 10


number_of_freqs = 50
bandwidth = 3.5
center_frequency = 1.0
freqs, spectrum = utils.calc_wavelet_spectrum(lfp_V1[ichan, itrial], sampling_frequency_lfp, number_of_freqs, bandwidth, center_frequency)

t_coi = (bandwidth) / (2*np.pi) * np.sqrt(2) / freqs * 1E3

# plot spectrum and cone of influence
utils.plot_spectrum_with_coi(time_lfp, freqs, spectrum, t_coi, apply_shading = True)

One way to avoid having to disregard a significant part of the spectrum because it’s outside of the COI is to calculate it for a larger time window.

Exercise: Below, the spectrum is calculated with bandwidth=3.5 for an LFP trace for a quarter of the whole recording period instead of just a single trial. Calculate and plot the COI for this spectrum. What happens to the COI in this case? Is it a large part of the spectrum that you have to disregard because it’s outside of the COI?

Hint: It’s probably best to set the apply_shading parameter to False in the plot_spectrum_with_coi function to make the plotting go faster.

ichan = 2
lfp_trace = lfp_whole_rec[ichan, :int(lfp_whole_rec.shape[1]/4)]
time_lfp_part_of_rec = lfp_whole_rec.time_whole_rec_lfp.values[:len(lfp_trace)]

number_of_freqs = 20
bandwidth = 3.5
center_frequency = 1.0
freqs, spectrum = utils.calc_wavelet_spectrum(lfp_trace, sampling_frequency_lfp, number_of_freqs, bandwidth, center_frequency)
Solution
# solution 

t_coi = (bandwidth) / (2*np.pi) * np.sqrt(2) / freqs * 1E3

# plot spectrum and cone of influence
utils.plot_spectrum_with_coi(time_lfp_part_of_rec, freqs, spectrum, t_coi, apply_shading = False)

Section 4: Reorganizing spectrum calculated for whole period into trials.

If you want to look at the spectrum in individual trials or the trial average, but you have calculated the spectrum for a whole recording period to limit the influence of edge effects, you have to reorganize the calculated spectrum into trials again. To do that, you can use information about the time points at which the stimuli are presented and logical indexing.

Code Description
mask = np.logical_and(array > start, array < stop) Create logical mask where both conditions are met. The array, e.g. a time vector, is bigger than the starting point and smaller than the stopping point.

Run cell below to load meta data on stimulus presentation start times and LFP from whole stimulus presentation period

Exercises

stim_start_times = dataset['stimulus_start_times'].values

ichan = 2
lfp_trace = lfp_whole_rec[ichan]
time_lfp_whole_rec = lfp_whole_rec.time_whole_rec_lfp.values

Example: Get LFP from 100 ms before the first stimulus presentation to 250 ms after onset using logical indexing. Plot the LFP in this selected window.

time_before_onset = 100
time_after_onset = 250

# get time of first stimulus presentation
idx_stim_start = 0
time_stim_onset = stim_start_times[idx_stim_start]

# create boolean mask for time window around stimulus onset
mask_stim_window = np.logical_and(time_lfp_whole_rec > (time_stim_onset-time_before_onset), 
                                  time_lfp_whole_rec < (time_stim_onset+time_after_onset))

# get time points in window
time_lfp_window = time_lfp_whole_rec[mask_stim_window]
time_lfp_window -= (time_lfp_window.min()+time_before_onset)

# get lfp in time window
lfp_window = lfp_whole_rec[:, mask_stim_window]

# plot lfp in selected time window
plt.pcolormesh(time_lfp_window, channels_depth, lfp_window, cmap='bwr', shading='gouraud')
plt.xlabel('Time from stimulus onset (ms)')
plt.ylabel(r'Depth ($\mu$m)');

Exercise: Get LFP from 500 ms before the first stimulus presentation to 500 ms after onset of the 5th stimulus presentation using logical indexing. Plot the LFP in this selected window.

Solution
time_before_onset = 500
time_after_onset = 500

# get time of selected stimulus presentation
idx_stim_start = 4
time_stim_onset = stim_start_times[idx_stim_start]

# create boolean mask for time window around stimulus onset
mask_stim_window = np.logical_and(time_lfp_whole_rec > (time_stim_onset-time_before_onset), 
                                  time_lfp_whole_rec < (time_stim_onset+time_after_onset))

# get time points in window
time_lfp_window = time_lfp_whole_rec[mask_stim_window]
time_lfp_window -= (time_lfp_window.min()+time_before_onset)

# get lfp in time window
lfp_window = lfp_whole_rec[:, mask_stim_window]

# plot lfp in selected time window
plt.pcolormesh(time_lfp_window, channels_depth, lfp_window, cmap='bwr', shading='gouraud')
plt.xlabel('Time from stimulus onset (ms)')
plt.ylabel(r'Depth ($\mu$m)');

Exercise: Reorganize the array containing LFP from the whole recording into an array organized by trials with 250 ms before and after stimulus onset by using the stimulus start time information.

Hint: Loop through all trials, get the lfp 250 ms before and after stimulus onset in each trial, and store it in a variable that contains the LFP in this window for all stimulus presentations. Plot the trial averaged LFP in this window.

Solution
time_before_onset = 250
time_after_onset = 250

n_trials = lfp_V1.shape[1]
lfp_windows = []
for idx_stim_start in range(n_trials-1): # 1 is subtracted just because the data was cut too early in the last trial
    # get time of selected stimulus presentation
    time_stim_onset = stim_start_times[idx_stim_start]

    # create boolean mask for time window around stimulus onset
    mask_stim_window = np.logical_and(time_lfp_whole_rec > time_stim_onset-time_before_onset, 
                                      time_lfp_whole_rec < time_stim_onset+time_after_onset)

    # get lfp in time window and store in list
    lfp_windows.append(lfp_whole_rec[:, mask_stim_window])

# get time points in window
time_lfp_window = time_lfp_whole_rec[mask_stim_window]
time_lfp_window -= (time_lfp_window.min()+time_before_onset)

# calculate trial averaged lfp
lfp_window_trial_avg = np.mean(lfp_windows, axis = 0)

plt.pcolormesh(time_lfp_window, channels_depth, lfp_window_trial_avg, cmap='bwr', shading='gouraud')
plt.xlabel('Time from stimulus onset (ms)')
plt.ylabel(r'Depth ($\mu$m)');

Exercise: Reorganize the spectrum calculated for the whole recording below into trials by using the stimulus start time information. Recipe: Loop through all trials, get the lfp 250 ms before and after stimulus onset in each trial, and store it in a variable that contains the spectrum in this window for all stimulus presentations. Plot the trial averaged spectrum in this window.

Note: Calculating the spectrum for the whole recording period will take some time.

# provided
number_of_freqs = 20
bandwidth = 3.5
center_frequency = 1.0
freqs, spectrum = utils.calc_wavelet_spectrum(lfp_trace, sampling_frequency_lfp, number_of_freqs, bandwidth, center_frequency)
Solution
# solution

n_trials = lfp_V1.shape[1]

# initialize empty list to store lfp from each trial
spectrum_windows = []
for idx_stim_start in range(n_trials-1): # 1 is subtracted because the data was cut too early in the last trial
    
    # get time of selected stimulus presentation
    time_stim_onset = stim_start_times[idx_stim_start]

    # create boolean mask for time window around stimulus onset
    mask_stim_window = np.logical_and(time_lfp_whole_rec > time_stim_onset-250, time_lfp_whole_rec < time_stim_onset+250)

    # get spectrum in time window and store in list
    spectrum_windows.append(spectrum[:, mask_stim_window])

# get time points in window
time_lfp_window = time_lfp_whole_rec[mask_stim_window]
time_lfp_window -= (time_lfp_window.min()+250)

# calculate trial averaged spectrum
spectrum_window_trial_avg = np.mean(spectrum_windows, axis = 0)

plt.pcolormesh(time_lfp_window, freqs, spectrum_window_trial_avg, cmap='inferno', shading='gouraud')
plt.xlabel('Time from stimulus onset (ms)')
plt.ylabel(r'Depth ($\mu$m)');