OSCR

Hippocampal skill memory expansion drives online performance dynamics during skill learning.

Code ↔ Paper

2 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 2 matches
  1. [1] § STAR★METHODS › METHOD DETAILS › Oscillatory bursting analysis ↔ spectralevents.py, lines 293–399 · score 0.79 · spectralEvents, full width, median power, Event duration, FWHM, detection
  2. [2] § STAR★METHODS › METHOD DETAILS › Oscillatory bursting analysis ↔ spectralevents_find.m, lines 282–404 · score 0.79 · spectralEvents, full width, median power, Event duration, FWHM, overlap

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 601 lines · 23 KB · BSD-3-Clause · 1 match

  1. '''Spectral Event Analysis functions'''
  2. # Authors: Tim Bardouille <[email hidden]>
  3. # Ryan Thorpe <[email hidden]>
  4. import numpy as np
  5. import scipy.signal as signal
  6. import scipy.ndimage as ndimage
  7. import scipy.ndimage.filters as filters
  8. import matplotlib.pyplot as plt
  9. def tfr(timeseries, freqs, samp_freq, width=7.):
  10. '''Calculate the time-frequency response by convolving with Morlet wavelets
  11. Parameters
  12. ----------
  13. timeseries : array-like, shape ([n_epochs,] n_times)
  14. The timeseries signal, one for each epoch of data.
  15. freqs : array-like, shape (n_freqs,)
  16. Frequency domain of the TFR (Hz).
  17. samp_freq : float
  18. Sampling frequency (Hz).
  19. width : int
  20. Number of cycles in each Morlet wavelet (>5 advisable).
  21. Returns
  22. -------
  23. tfr : array, shape (n_epochs, n_freqs, n_times)
  24. A collection of time-frequency responses (TFRs), one for each epoch.
  25. Notes
  26. -----
  27. Adapted from Ole Jensen's traces2tfr in the 4Dtools toolbox.
  28. '''
  29. ts = np.atleast_2d(timeseries)
  30. n_epochs = ts.shape[0]
  31. n_samps = ts.shape[1]
  32. n_freqs = len(freqs)
  33. # Validate freqs input
  34. f_nyquist = samp_freq / 2 # Nyquist frequency
  35. dt = 1 / samp_freq # Sampling time interval
  36. min_freq = 1 / (n_samps * dt) # Minimum resolvable frequency
  37. if freqs[0] < min_freq:
  38. raise ValueError('Frequency vector includes values outside the '
  39. 'resolvable/alias-free range.')
  40. elif freqs[-1] > f_nyquist:
  41. raise ValueError('Frequency vector includes values outside the '
  42. 'resolvable/alias-free range.')
  43. elif np.abs(freqs[1] - freqs[0]) < min_freq:
  44. raise ValueError('Frequency vector includes values outside the '
  45. 'resolvable/alias-free range.')
  46. tfr = np.zeros((n_epochs, n_freqs, n_samps))
  47. # Trial Loop
  48. for trial_idx in np.arange(n_epochs):
  49. ts_detrended = signal.detrend(ts[trial_idx, :])
  50. # Frequency loop
  51. for freq_idx in np.arange(n_freqs):
  52. tfr[trial_idx, freq_idx, :] = _energyvec(freqs[freq_idx],
  53. ts_detrended, samp_freq,
  54. width)
  55. return tfr
  56. def _get_power_thresholds(tfr, FOM_threshold=6.):
  57. '''Get the power threshold for each frequency band of a TFR'''
  58. med_powers = np.median(tfr, axis=1)
  59. return med_powers * FOM_threshold, med_powers
  60. def tfr_normalize(tfr):
  61. '''Normalize the power in each frequency band of a TFR
  62. Parameters
  63. ----------
  64. tfr : array, shape ([n_epochs,] n_freqs, n_times)
  65. The time-frequency response (TFR) to be normalized.
  66. Returns
  67. -------
  68. tfr_norm : array
  69. The normalized TFR calculated by dividing the power values in each
  70. frequency bin by the median power across all trials and time samples.
  71. '''
  72. if len(tfr.shape) == 3:
  73. n_epochs, _, n_times = tfr.shape
  74. med_powers = np.median(tfr, axis=(0, 2))
  75. med_powers_tiled = np.tile(med_powers, (n_epochs, n_times, 1))
  76. med_powers = np.transpose(med_powers_tiled, axes=(0, 2, 1))
  77. elif len(tfr.shape) == 2:
  78. _, n_times = tfr.shape
  79. med_powers = np.median(tfr, axis=1)
  80. med_powers_tiled = np.tile(med_powers, (n_times, 1))
  81. med_powers = np.transpose(med_powers_tiled, axes=(1, 0))
  82. else:
  83. raise ValueError(f'TFR must be an array of at least 2 dimensions. Got '
  84. f'{tfr.shape}.')
  85. return tfr / med_powers
  86. def find_events(tfr, times, freqs, event_band, thresholds=None,
  87. threshold_FOM=6.):
  88. '''Locate spectral events in a time-frequency response.
  89. Parameters
  90. ----------
  91. tfr : array, shape ([n_epochs,] n_freqs, n_times)
  92. The time-frequency response (TFR) in which to search for high power
  93. spectral events.
  94. times : array-like, shape (n_times,)
  95. Time domain of the TFR in seconds.
  96. freqs : array-like, shape (n_freqs,)
  97. Frequency domain of the TFR in Hertz.
  98. event_band : list
  99. Lower and upper bounds (inclusive, Hz) of the frequency band-of-
  100. interest in which to search for spectral events.
  101. thresholds : None | array-like, shape (n_freqs,)
  102. If not None, these frequency-specific threshold values (in units of
  103. spectral power) will be used to identify suprathreshold spectral
  104. events.
  105. threshold_FOM : float | int
  106. Factor-of-the-median threshold (a.u.) with which to identify
  107. suprathreshold spectral events (default: 6). This threshold value is
  108. applied across frequencies of the TFR when thresholds=None. See Shin et
  109. al. eLife 2017 for more details concerning this value.
  110. Returns
  111. -------
  112. events : list of list of dict
  113. A nested list with n_epochs elements in the outer list and n_events in
  114. the inner list. Each inner element comprises an event that is
  115. characterized by dictionary items including it's location in time,
  116. location in frequency, duration, and frequency span, and more.
  117. Notes
  118. -----
  119. This version only supports find-method #1 at the moment. As outlined in
  120. Shin et al. eLife (2017), it isolates spectral events by first retrieving
  121. all local maxima in un-normalized TFR using imregionalmax, then selecting
  122. suprathreshold peaks within the frequency band of interest. This method
  123. allows for multiple, overlapping events to occur in a given suprathreshold
  124. region and does not guarantee that the presence of within-band,
  125. suprathreshold activity in any given trial will render an event.
  126. '''
  127. # ensure tfr has 3 dimensions (epochs x freqs x time)
  128. if len(tfr.shape) < 3:
  129. tfr = tfr[np.newaxis, ...]
  130. n_epochs = tfr.shape[0]
  131. n_freqs = tfr.shape[1]
  132. n_times = tfr.shape[2]
  133. # some time steps might be slightly different due to rounding error, etc.
  134. samp_freq = 1 / np.unique(np.diff(times).round(10))
  135. if len(samp_freq) > 1:
  136. raise ValueError('Sampling rate is not consistent across time '
  137. 'samples.')
  138. samp_freq = samp_freq[0]
  139. # concatenate trials together to make one big spectrogram, then find thresh
  140. tfr_permute = np.transpose(tfr, [1, 2, 0]) # freq x time x trial
  141. tfr_concat_trials = np.reshape(tfr_permute, (n_freqs, n_times * n_epochs))
  142. thresholds, med_powers = _get_power_thresholds(tfr_concat_trials,
  143. FOM_threshold=threshold_FOM)
  144. # Validate consistency of parameter dimensions
  145. if n_freqs != len(freqs):
  146. raise ValueError('Mismatch in frequency dimensions!')
  147. if n_times != len(times):
  148. raise ValueError('Mismatch in time dimensions!')
  149. # Find spectral events using appropriate method
  150. # Implementing find_method=1 for now
  151. events = _find_localmax_method_1(tfr, freqs, times, event_band,
  152. thresholds, med_powers, samp_freq)
  153. return events
  154. def _energyvec(f, s, Fs, width=7.):
  155. '''Spectral energy as a function of frequency using Morlet wavelets.'''
  156. dt = 1 / Fs
  157. sf = f / width
  158. st = 1 / (2 * np.pi * sf)
  159. t_single_side = np.arange(0., 3.5 * st, dt)
  160. # mirror about 0; always an odd # of elements
  161. times = np.r_[-t_single_side[-1:0:-1], t_single_side]
  162. wavelet = _morlet(f, times, width)
  163. fourier_coeffs = np.convolve(s, wavelet)
  164. spec_energy = 2 * (dt * np.abs(fourier_coeffs)) ** 2
  165. lower_idx = int(len(wavelet) // 2)
  166. upper_idx = int(len(spec_energy) - (len(wavelet) // 2))
  167. return spec_energy[lower_idx:upper_idx]
  168. def _morlet(f, t, width):
  169. '''
  170. Morlet's wavelet for frequency f and time t. The wavelet will be normalized
  171. so the total energy is 1. width defines the ``width'' of the wavelet. A
  172. value >= 5 is suggested.
  173. Ref: Tallon-Baudry et al., J. Neurosci. 15, 722-734 (1997)
  174. '''
  175. sf = f / width
  176. st = 1 / (2 * np.pi * sf)
  177. A = 1 / (st * np.sqrt(2 * np.pi))
  178. y = A * np.exp(-t ** 2 / (2 * st ** 2)) * np.exp(1j * 2 * np.pi * f * t)
  179. return y
  180. def _fwhm_lower_upper_bound1(vec, peakInd, peakValue):
  181. '''
  182. Function to find the lower and upper indices within which the vector is
  183. less than the FWHM with some rather complicated boundary rules
  184. (Shin, eLife, 2017).
  185. '''
  186. halfMax = peakValue/2
  187. # Extract data before the peak only (data should be rising at the end of
  188. # the new array)
  189. vec1 = vec[0:peakInd]
  190. # Find indices less than half the max
  191. vec1_underThreshold = np.where(vec1 < halfMax)[0]
  192. if len(vec1_underThreshold) == 0:
  193. # There are no indices less than half the max, so we have to estimate
  194. # the lower edge
  195. estimateLowerEdge = True
  196. else:
  197. # There are indices less than half the max, take the last one under
  198. # halfMax as the lower edge
  199. estimateLowerEdge = False
  200. lowerEdgeIndex = vec1_underThreshold[-1]
  201. # Extract data following the peak only (data should be falling at the start
  202. # of the new array)
  203. vec2 = vec[peakInd:]
  204. # Find indices less than half the max
  205. vec2_underThreshold = np.where(vec2 < halfMax)[0]
  206. if len(vec2_underThreshold) == 0:
  207. # There are no indices less than half the max, so we have to estimate
  208. # the upper edge
  209. estimateUpperEdge = True
  210. else:
  211. # There are indices less than half the max, take the first one under
  212. # halfMax as the upper edge
  213. estimateUpperEdge = False
  214. upperEdgeIndex = vec2_underThreshold[0] + len(vec1)
  215. if not estimateLowerEdge:
  216. if not estimateUpperEdge:
  217. # FWHM fits in the range, so pick off the edges of the FWHM
  218. lowerInd = lowerEdgeIndex
  219. upperInd = upperEdgeIndex
  220. FWHM = upperInd - lowerInd
  221. if estimateUpperEdge:
  222. # FWHM fits in on the low end, but hits the edge on the high end
  223. lowerInd = lowerEdgeIndex
  224. upperInd = len(vec)-1
  225. FWHM = 2 * (peakInd - lowerInd + 1)
  226. else:
  227. if not estimateUpperEdge:
  228. # FWHM hits the edge on the low end, but fits on the high end
  229. lowerInd = 0
  230. upperInd = upperEdgeIndex
  231. FWHM = 2 * (upperInd - peakInd + 1)
  232. if estimateUpperEdge:
  233. # FWHM hits the edge on the low end and the high end
  234. lowerInd = 0
  235. upperInd = len(vec)-1
  236. FWHM = 2*len(vec)
  237. return lowerInd, upperInd, FWHM
  238. def _find_localmax_method_1(tfr, freqs, times, event_band,
  239. eventThresholdByFrequency,
  240. medianPower, Fs):
  241. '''
  242. 1st event-finding method (primary event detection method in Shin et
  243. al. eLife 2017): Find spectral events by first retrieving all local
  244. maxima in un-normalized TFR using imregionalmax, then selecting
  245. suprathreshold peaks within the frequency band of interest. This
  246. method allows for multiple, overlapping events to occur in a given
  247. suprathreshold region and does not guarantee the presence of
  248. within-band, suprathreshold activity in any given trial will render
  249. an event.
  250. spectralEvents: 12 column matrix for storing local max event metrics:
  251. hit/miss, maxima frequency,
  252. lowerbound frequency, upperbound frequency,
  253. frequency span, maxima timing, event onset timing,
  254. event offset timing, event duration, maxima power,
  255. maxima/median power
  256. '''
  257. n_epochs = tfr.shape[0]
  258. all_epochs_events = list()
  259. # Retrieve all local maxima in tfr using python equivalent of imregionalmax
  260. for trial_idx in range(n_epochs):
  261. epoch_events = list()
  262. # Get tfr data for this trial [frequency x time]
  263. thistfr = tfr[trial_idx, :, :]
  264. # Find local maxima in the tfr data
  265. data = thistfr
  266. # Find maximum amoung adjacent pixels (3x3 footprint) for each pixel
  267. data_max = filters.maximum_filter(data, size=(3, 3))
  268. maxima = (data == data_max)
  269. data_min = filters.minimum_filter(data, size=(3, 3))
  270. # Rule out pixels with footprints that have flatlined
  271. maxima[data_max == data_min] = False
  272. labeled, num_objects = ndimage.label(maxima)
  273. yx = ndimage.center_of_mass(data, labels=labeled,
  274. index=range(1, num_objects + 1))
  275. peakF = list()
  276. peakT = list()
  277. peakPower = list()
  278. for f_idx, t_idx in yx:
  279. f_idx = int(round(f_idx))
  280. t_idx = int(round(t_idx))
  281. event_freq = freqs[f_idx]
  282. if (event_freq >= event_band[0] and event_freq <= event_band[1] and
  283. thistfr[f_idx, t_idx] > eventThresholdByFrequency[f_idx]):
  284. peakF.append(f_idx)
  285. peakT.append(t_idx)
  286. peakPower.append(thistfr[f_idx, t_idx])
  287. numPeaks = len(peakF)
  288. # Find local maxima lowerbound, upperbound, and full width at half max
  289. # for both frequency and time
  290. for lmi in range(numPeaks):
  291. thisPeakF = peakF[lmi]
  292. thisPeakT = peakT[lmi]
  293. thisPeakPower = peakPower[lmi]
  294. # Indices of tfr frequencies < half max power at the time of a
  295. # given local peak
  296. tfrFrequencies = thistfr[:, thisPeakT]
  297. lowerInd, upperInd, FWHM = _fwhm_lower_upper_bound1(tfrFrequencies,
  298. thisPeakF,
  299. thisPeakPower)
  300. lowerEdgeFreq = freqs[lowerInd]
  301. upperEdgeFreq = freqs[upperInd]
  302. FWHMFreq = FWHM * (freqs[1] - freqs[0])
  303. # Indices of tfr times < half max power at the frequency of a given
  304. # local peak
  305. tfrTimes = thistfr[thisPeakF, :]
  306. lowerInd, upperInd, FWHM = _fwhm_lower_upper_bound1(tfrTimes,
  307. thisPeakT,
  308. thisPeakPower)
  309. lowerEdgeTime = times[lowerInd]
  310. upperEdgeTime = times[upperInd]
  311. FWHMTime = FWHM / Fs
  312. # Put peak characteristics to a dictionary
  313. peakParameters = {
  314. 'Peak Frequency': freqs[thisPeakF],
  315. 'Lower Frequency Bound': lowerEdgeFreq,
  316. 'Upper Frequency Bound': upperEdgeFreq,
  317. 'Frequency Span': FWHMFreq,
  318. 'Peak Time': times[thisPeakT],
  319. 'Event Onset Time': lowerEdgeTime,
  320. 'Event Offset Time': upperEdgeTime,
  321. 'Event Duration': FWHMTime,
  322. 'Peak Power': thisPeakPower,
  323. 'Normalized Peak Power': thisPeakPower / medianPower[thisPeakF]
  324. }
  325. # Build a list of dictionaries
  326. epoch_events.append(peakParameters)
  327. all_epochs_events.append(epoch_events)
  328. return all_epochs_events
  329. def plot_events(tfr, times, freqs, event_band, spec_events=None,
  330. timeseries=None, ax=None, vlim=None, ylim_ts=None, label=None):
  331. '''Plot a single TFR spectrogram overlayed with spectral events.
  332. Parameters
  333. ----------
  334. tfr : array, shape (n_freqs, n_times)
  335. A time-frequency response (TFR).
  336. times : array-like, shape (n_times,)
  337. Time domain of the TFR in seconds.
  338. freqs : array-like, shape (n_freqs,)
  339. Frequency domain of the TFR in Hertz.
  340. event_band : list
  341. Lower and upper bounds (inclusive, Hz) of the frequency band-of-
  342. interest in which spectral events were detected.
  343. spec_events : list of dict
  344. A single-level list where each element comprises an event that is
  345. characterized by dictionary items including it's location in time,
  346. location in frequency, duration, and frequency span, and more. If None,
  347. no events are plotted.
  348. timeseries : array, shape (n_times,) | None
  349. The timeseries associated with the TFR.
  350. ax : None | Matplotlib.axes.Axes instance
  351. The Axes instance with wich to plot the spectrogram on. If None, a new
  352. Axes object is created.
  353. vlim : list, shape (2,) | None
  354. If not None, sets the colorbar lower and upper bounds for the
  355. spectrogram across all axes of the returned figure. If None, each
  356. spectrogram (including example epochs) sets its vlim independently.
  357. ylim_ts : list | None
  358. The lower and upper limits of the timeseries (if applicable) overlayed
  359. on the spectrogram. If None, these values are automatically assigned.
  360. label : str | None
  361. A label to overlay in the upper left corner of this plot.
  362. Return
  363. ------
  364. fig : Matplotlib.figure.Figure instance
  365. The Figure instance containing the spectrogram.
  366. '''
  367. if tfr.shape != (len(freqs), len(times)):
  368. raise ValueError(f'tfr must be an array of shape (n_freqs, n_times), '
  369. f'got tfr: {tfr.shape}, freqs: ({len(freqs)},), '
  370. f'times: ({len(times)},)')
  371. if vlim is None:
  372. vlim = [None, None]
  373. # convert to numpy array if not already
  374. freqs = np.array(freqs)
  375. # frequencies within the band of interest
  376. # band_mask = np.logical_and(freqs >= event_band[0],
  377. # freqs <= event_band[1])
  378. if ax is None:
  379. fig, ax = plt.subplots(1, 1)
  380. else:
  381. fig = ax.get_figure()
  382. # plot tfr
  383. im = ax.pcolormesh(times, freqs, tfr, cmap='jet', vmin=vlim[0],
  384. vmax=vlim[1], shading='nearest')
  385. fig.colorbar(im, ax=ax)
  386. ax.axhline(y=event_band[0], c='w', linewidth=2., linestyle=':', alpha=.7)
  387. ax.axhline(y=event_band[1], c='w', linewidth=2., linestyle=':', alpha=.7)
  388. ax.set_yticks([freqs[0], event_band[0], event_band[1], freqs[-1]])
  389. ax.set_xlim(times[0], times[-1])
  390. # overlay with timecourse
  391. # twin axis needs to be created and yticks set regardless of if a
  392. # timeseries is provided to ensure consistency with seaborn tick formatting
  393. ax_twin = ax.twinx()
  394. ax_twin.set_yticks([])
  395. if timeseries is not None:
  396. ax_twin.plot(times, timeseries, 'w', linewidth=1., alpha=0.8)
  397. if ylim_ts is not None:
  398. ax_twin.set_ylim(ylim_ts[0], ylim_ts[1])
  399. # plot event locations
  400. if spec_events is not None:
  401. event_times = [event['Peak Time'] for event in spec_events]
  402. event_freqs = [event['Peak Frequency'] for event in spec_events]
  403. # reverse sigmoid: make scatter markers more transparent when there are
  404. # more of them
  405. alpha = lambda x : (0.6 + 0.4 * np.exp(-0.2 * (x - 30)) # noqa
  406. / (1 + np.exp(-0.2 * (x - 30)))) # noqa
  407. ax.scatter(event_times, event_freqs, s=20, c='w', marker='x',
  408. alpha=alpha(len(spec_events)))
  409. # add label on top of spectrogram plot
  410. if label is not None:
  411. ax.annotate(label, xy=(0.01, 0.98), xycoords='axes fraction',
  412. va='top', ha='left', color='w', size=10, fontweight='bold')
  413. return fig
  414. def plot_avg_spectrogram(tfr, times, freqs, event_band, spec_events=None,
  415. timeseries=None, example_epochs=None, vlim=None,
  416. show_events=False):
  417. '''Plot the average spectrogram over all TFR epochs/trials.
  418. Parameters
  419. ----------
  420. tfr : array, shape (n_epochs, n_freqs, n_times)
  421. A stack of time-frequency response (TFR) epochs/trials.
  422. times : array-like, shape (n_times,)
  423. Time domain of each TFR in seconds.
  424. freqs : array-like, shape (n_freqs,)
  425. Frequency domain of each TFR in Hertz.
  426. event_band : list
  427. Lower and upper bounds (inclusive, Hz) of the frequency band-of-
  428. interest in which spectral events were detected.
  429. spec_events : list of list of dict
  430. A nested list with n_epochs elements in the outer list and n_events in
  431. the inner list. Each inner element comprises an event that is
  432. characterized by dictionary items including it's location in time,
  433. location in frequency, duration, and frequency span, and more. If None,
  434. no events are plotted.
  435. timeseries : array, shape (n_epochs, n_times) | None
  436. The stack of timeseries associated with the TFR epochs/trials.
  437. example_epochs : list | None
  438. The epoch/trial indices that will be used to plot example spectrogram
  439. trials alonside the average spectrogram.
  440. vlim : list, shape (2,) | None
  441. If not None, sets the colorbar lower and upper bounds for the
  442. spectrogram across all axes of the returned figure. If None, each
  443. spectrogram (including example epochs) sets its vlim independently.
  444. show_events : boolean
  445. Whether or not to overlay spectral event locations on the average
  446. spectrogram.
  447. Return
  448. ------
  449. fig : Matplotlib.figure.Figure instance
  450. The Figure instance containing the average spectrogram.
  451. '''
  452. if len(tfr.shape) != 3:
  453. raise ValueError(f'tfr should be a 3D array of shape (n_epochs, '
  454. f'n_freqs, n_times), got {tfr.shape}.')
  455. if example_epochs is not None:
  456. trial_idx_set = set(range(tfr.shape[0]))
  457. trial_idx_subset = set(example_epochs)
  458. if trial_idx_subset.intersection(trial_idx_set) != trial_idx_subset:
  459. raise ValueError('One or more of the specified example trial '
  460. 'indices does not exist in the provided tfr '
  461. 'array.')
  462. else:
  463. # set to empty list
  464. example_epochs = list()
  465. if show_events:
  466. spec_events_agg = sum(spec_events, [])
  467. else:
  468. spec_events_agg = None
  469. fig, axs = plt.subplots(nrows=len(example_epochs) + 1, ncols=1,
  470. sharex=True)
  471. # if example_epochs is None, ensure that axs is still subscriptable
  472. axs = np.atleast_1d(axs)
  473. # plot trial-average tfr
  474. tfr_avg = np.mean(tfr, axis=0).squeeze()
  475. plot_events(tfr=tfr_avg, times=times, freqs=freqs,
  476. event_band=event_band, spec_events=spec_events_agg,
  477. ax=axs[0], vlim=vlim, label='epoch avg.')
  478. # plot tfr + events for example trials
  479. if timeseries is not None and example_epochs is not None:
  480. max_ts_amplitude = np.max(timeseries[example_epochs])
  481. min_ts_amplitude = np.min(timeseries[example_epochs])
  482. ylim_ts = [max_ts_amplitude, min_ts_amplitude]
  483. for plot_idx, trial_idx in enumerate(example_epochs):
  484. # get spectral events for the current trial
  485. if spec_events is not None:
  486. trial_events = spec_events[trial_idx]
  487. else:
  488. trial_events = None
  489. # plot trial tfr
  490. tfr_trial = tfr[trial_idx, :, :].squeeze()
  491. timeseries_trial = timeseries[trial_idx, :]
  492. plot_events(tfr=tfr_trial, times=times, freqs=freqs,
  493. event_band=event_band, spec_events=trial_events,
  494. timeseries=timeseries_trial, ax=axs[plot_idx + 1],
  495. vlim=vlim, ylim_ts=ylim_ts, label=f'epoch {trial_idx}')
  496. axs[-1].set_xlabel('time (s)')
  497. axs[0].set_ylabel('freq. (Hz)')
  498. fig.tight_layout()
  499. return fig

spectralevents.py at commit cd0c83d, under BSD-3-Clause · at the source

Overview

Authors: Fumiaki Iwane1, William Hayward1, I M Dushyanthi Karunathilake1, Ethan R Buch1, Leonardo G Cohen1,2
  1. Human Cortical Physiology and Neurorehabilitation Section, National Institute of Neurological Disorders and Stroke, NIH, 9000 Rockville Pike, Bethesda, MD 20817, USA
  2. Lead contact
Journal: Cell reports, volume 45, issue 8, article 117845
Dates: published online 14 August 2026; in print August 2026
Type: Research article · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1016/j.celrep.2026.117845 · PMID 42599800 · PMCID PMC13573638 · OpenAlex W7203483517
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: MEG (modality), cognitive (subfield)
Methods: Spectral & time-frequency, Statistics, Smoothing, state filtering, decompositions, Machine learning, Preprocessing, fMRI & imaging, Physiology & signal measures, Connectivity
Keywords: Memory, Hippocampus, Magnetoencephalography, MEG, Fatigue, Consolidation, Skill Learning, Motor Learning, Reactive Inhibition, Theta-gamma Phase-amplitude Coupling, Beta Oscillatory Activity, Cp: Neuroscience
Topic: Motor Control and Adaptation (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: Intramural NIH HHS (Z01 NS003030, Z01 NS002978, ZIA NS002978, ZIA NS003030); National Institutes of Health
Citations: not cited yet (Europe PMC); 115 references in the paper

Abstract

The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.

Repository

Its files are read in the Code ↔ Paper reader above, with 2 matches between paragraphs and lines of code.

jonescompneurolab/SpectralEvents

License: BSD-3-Clause
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: cd0c83d2492446e03f1e58b79ed31bc73c024f22, 22 July 2024
Languages: MATLAB (6), Python (4), Jupyter (1)
Size: 69 files, 11 scripts
Software Heritage: not archived
Found in: the text, “MEG data analysis”
Holds: README, license file, environment (requirements.txt, setup.cfg), tests, continuous integration, 1 notebook
Not found: CITATION.cff, documentation
Tools: NumPy (3 files), SciPy (3 files), Matplotlib (2 files), Image Processing Toolbox (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
13 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 11 scripts, each with its path and the digest of its content;
  • 2 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

Datasets cited

Code and data availability statement

The paper has a code and data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

Read it in the paper: doi.org/10.1016/j.celrep.2026.117845.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 2, 28 September 2026

  • Publisher: n/a → Cell Press
  • Authors: added Leonardo G Cohen (0000-0002-1705-8773); removed Leonardo G Cohen

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 5 authors, 12 keywords, 2 funders, 113 references.

Cite

This paper

Iwane, F., Hayward, W., Karunathilake, I. M. D., Buch, E. R., & Cohen, L. G. (2026). Hippocampal skill memory expansion drives online performance dynamics during skill learning. Cell reports, 45(8), 117845. https://doi.org/10.1016/j.celrep.2026.117845

BibTeX

@article{iwane2026hippocampal,
author = {Iwane, Fumiaki and Hayward, William and Karunathilake, I M Dushyanthi and Buch, Ethan R and Cohen, Leonardo G},
title = {{Hippocampal skill memory expansion drives online performance dynamics during skill learning}},
journal = {Cell reports},
year = {2026},
month = aug,
volume = {45},
number = {8},
pages = {117845},
publisher = {Cell Press},
issn = {2211-1247},
doi = {10.1016/j.celrep.2026.117845},
url = {https://doi.org/10.1016/j.celrep.2026.117845},
pmid = {42599800},
pmcid = {PMC13573638}
}

RIS

TY - JOUR
AU - Iwane, Fumiaki
AU - Hayward, William
AU - Karunathilake, I M Dushyanthi
AU - Buch, Ethan R
AU - Cohen, Leonardo G
TI - Hippocampal skill memory expansion drives online performance dynamics during skill learning
T2 - Cell reports
J2 - Cell Rep
PY - 2026
DA - 2026/08/14
VL - 45
IS - 8
SP - 117845
SN - 2211-1247
PB - Cell Press
DO - 10.1016/j.celrep.2026.117845
UR - https://doi.org/10.1016/j.celrep.2026.117845
LA - en
ER -

CSL-JSON

{
"id": "10.1016/j.celrep.2026.117845",
"type": "article-journal",
"title": "Hippocampal skill memory expansion drives online performance dynamics during skill learning",
"container-title": "Cell reports",
"author": [
{
"family": "Iwane",
"given": "Fumiaki"
},
{
"family": "Hayward",
"given": "William"
},
{
"family": "Karunathilake",
"given": "I M Dushyanthi"
},
{
"family": "Buch",
"given": "Ethan R"
},
{
"family": "Cohen",
"given": "Leonardo G"
}
],
"container-title-short": "Cell Rep",
"volume": "45",
"issue": "8",
"page": "117845",
"DOI": "10.1016/j.celrep.2026.117845",
"PMID": "42599800",
"PMCID": "PMC13573638",
"ISSN": "2211-1247",
"publisher": "Cell Press",
"URL": "https://doi.org/10.1016/j.celrep.2026.117845",
"language": "en",
"issued": {
"date-parts": [
[
2026,
8,
14
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1038/s41467-026-75345-6 [code]
Hippocampal ripples initiate cortical dimensionality expansion for memory retrieval.
Journal: Nature communications
In common: Image Processing Toolbox, cognitive, 9 references
[2] doi:10.1016/j.isci.2026.116586 [code]
Condition-specific neural signatures of reactivation during post-retrieval rest: An EEG study.
Journal: iScience
In common: Image Processing Toolbox, SciPy, Matplotlib, 1 other tool, cognitive, 6 references
[3] doi:10.1002/hbm.70562 [code]
Distinct Physiological Mechanisms Drive Grey Matter Plasticity in Complex Versus Simple Sequence Learning.
Journal: Human brain mapping
In common: SciPy, Matplotlib, NumPy, 5 references
[4] doi:10.1038/s41593-026-02362-5 [code]
Replay of procedural memory is independent of the hippocampus.
Journal: Nature neuroscience
In common: Image Processing Toolbox, SciPy, Matplotlib, 1 other tool, cognitive, 4 references
[5] doi:10.1038/s41593-026-02285-1 [code]
Fixation duration on natural scenes is explained by memory encoding not processing demand.
Journal: Nature neuroscience
In common: SciPy, Matplotlib, NumPy, MEG, cognitive, 5 references
[6] doi:10.1162/imag.a.1203 [code]
Motor cortical areas facilitate schema-mediated integration of new motor information into memory.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: cognitive, 6 references
[7] doi:10.1038/s42003-026-10427-1 [code]
Phase-tuned modulation during reward expectancy in human anterior insular cortex.
Journal: Communications biology
In common: SciPy, NumPy, 6 references
[8] doi:10.1038/s41467-026-73818-2 [code]
Prefrontal parvalbumin neurons mediate working memory in a task demand-dependent manner.
Journal: Nature communications
In common: SciPy, Matplotlib, NumPy, cognitive, 4 references
[9] doi:10.1162/imag.a.1359 [code]
Distinct roles of brain network flexibility in motor learning across age.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: 6 references
[10] doi:10.1002/hbm.70579
Neural Correlates of Motor Sequence Learning and Enhanced Offline Consolidation in 7-11-Year-Old Children.
Journal: Human brain mapping
In common: cognitive, 5 references

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.