OSCR

High-speed whole-brain imaging in Drosophila.

Code ↔ Paper

10 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 10 matches
  1. [1] § Methods › Data analysis › Fourier analyses ↔ analysis_scripts/functions.py, lines 551–685 · score 0.74 · Fourier spectrum, absolute power, stimulus block, fft, scipy, subtracted
  2. [2] § Results › Resolving neural activity underlying individual song pulses ↔ analysis_scripts/functions.py, lines 786–868 · score 0.74 · 27–28 Hz, Fourier spectrum, absolute power, frame rate, IPI, peak
  3. [3] § Results › Capturing fast dynamics across the entire brain ↔ analysis_scripts/functions.py, lines 1108–1163 · score 0.64 · Correlation coefficients, highest correlation, ROIs extracted, Auditory stimulus, cutoff, Activity
  4. [4] § Results › Resolving neural activity underlying individual song pulses ↔ analysis_scripts/functions.py, lines 786–868 · score 0.63 · 27–28 Hz, Fourier spectrum, absolute power, IPI, peak, trace
  5. [5] § Methods › Data analysis › Identification of stimulus-modulated ROIs ↔ analysis_scripts/functions.py, lines 114–216 · score 0.61 · correlation coefficient, correlated ROIs, auditory stimulus, subset
  6. [6] § Methods › Data analysis › Identification of stimulus-modulated ROIs ↔ analysis_scripts/Suppl_figs.py, lines 101–167 · score 0.61 · correlation coefficient, convolving, kernel, rise, decay, scored
  7. [7] § Methods › Image processing and signal extraction ↔ dellaserver_processing/brainviz/roi.py, lines 26–55 · score 0.57 · agglomerative clustering, Ward, linkage, voxel, slice, brain
  8. [8] § Methods › Animal auditory stimulation and behavior recording ↔ flyvr/fictrac/fictrac_driver.py, lines 92–211 · score 0.57 · fly vr, Fictrac, tracked, setup
  9. [9] § Methods › Animal auditory stimulation and behavior recording ↔ matlab/audio_calibration/CalibrateSound_WhiteNoise_flyVR.m, lines 128–229 · score 0.55 · fly vr, Auditory stimuli, MATLAB, sound, Audio
  10. [10] § Results › Capturing fast dynamics across the entire brain ↔ analysis_scripts/Suppl_figs.py, lines 101–167 · score 0.54 · convolved stimulus, Correlation coefficients, kernel, duration, scored, blocks

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 · 1,483 lines · 55 KB · no license · 5 matches

  1. # -*- coding: utf-8 -*-
  2. import numpy as np
  3. import matplotlib.pyplot as plt
  4. from scipy.stats import zscore
  5. from scipy.stats import sem
  6. from scipy.fft import rfftfreq
  7. import scipy.fft
  8. from scipy.signal import find_peaks
  9. import matplotlib
  10. from scipy.interpolate import interp1d
  11. from typing import Optional, Tuple
  12. matplotlib.rcParams['pdf.fonttype'] = 42
  13. matplotlib.rcParams['ps.fonttype'] = 42
  14. def create_stim(dffs, start_block_seconds,end_block_seconds ,frame_rate, t_i2c=0):
  15. """
  16. Description
  17. ----------
  18. This function creates a continuous version of the stimulus (array of 0 or 1) with the same shape as the activity
  19. ----------
  20. Parameters
  21. ----------
  22. dffs (np.ndarray)
  23. Array containing the calcium activity over time, each row is an ROI.
  24. start_block_seconds (np.ndarray)
  25. array containing the start of each block of stimulus in seconds
  26. end_block_seconds (np.ndarray)
  27. array containing the end of each block of stimulus in seconds
  28. t_i2c (float)
  29. The first time point received by I2C. Set to 0 if data is already aligned
  30. frame_rate (float)
  31. frame rate of the scope
  32. ----------
  33. Returns
  34. ----------
  35. continuous_stim
  36. An array of 0 and 1s when stimulus is off and on respectively
  37. ----------
  38. """
  39. # Get index of start and end
  40. s = (start_block_seconds)*frame_rate
  41. e = (end_block_seconds )*frame_rate
  42. continuous_stim = []
  43. for ii in range(int((t_i2c)*frame_rate)):
  44. continuous_stim.append(0)
  45. for ii in range(dffs.shape[1]-int((t_i2c)*frame_rate)):
  46. if (s[0]<ii<e[0]) or (s[1]<ii<e[1]) or (s[2]<ii<e[2]) or (s[3]<ii<e[3]) or (s[4]<ii<e[4]) or (s[5]<ii<e[5]) or (s[6]<ii<e[6]) or (s[7]<ii<e[7]) or (s[8]<ii<e[8]) or (s[9]<ii<e[9]) or (s[10]<ii<e[10]) or (s[11]<ii<e[11]) or (s[12]<ii<e[12]):
  47. continuous_stim.append(1)
  48. else:
  49. continuous_stim.append(0)
  50. return (continuous_stim)
  51. def create_stim_train(dffs, start_block_seconds,end_block_seconds ,frame_rate, t_i2c=0):
  52. """
  53. Description
  54. ----------
  55. This function creates a continuous version of the stimulus (array of 0 or 1) with the same shape as the activity
  56. ----------
  57. Parameters
  58. ----------
  59. dffs (np.ndarray)
  60. Array containing the calcium activity over time, each row is an ROI.
  61. start_block_seconds (np.ndarray)
  62. array containing the start of each block in seconds
  63. end_block_seconds (np.ndarray)
  64. array containing the end of each block in seconds
  65. t_i2c (float)
  66. The first time point received by I2C. Set to 0 is data is already aligned
  67. frame_rate (float)
  68. frame rate of the scope
  69. ----------
  70. Returns
  71. ----------
  72. continuous_stim
  73. An array of 0 and 1s when stimulus is off and on respectively
  74. ----------
  75. """
  76. # Get index of start and end
  77. s = (start_block_seconds)*frame_rate
  78. e = (end_block_seconds )*frame_rate
  79. continuous_stim = []
  80. for ii in range(int((t_i2c)*frame_rate)):
  81. continuous_stim.append(0)
  82. for ii in range(dffs.shape[1]-int((t_i2c)*frame_rate)):
  83. if (s[0]<ii<e[0]) or (s[1]<ii<e[1]) or (s[2]<ii<e[2]) or (s[3]<ii<e[3]) or (s[4]<ii<e[4]) or (s[5]<ii<e[5]) or (s[6]<ii<e[6]) or (s[7]<ii<e[7]) or (s[8]<ii<e[8]) or (s[9]<ii<e[9]) or (s[10]<ii<e[10]) or (s[11]<ii<e[11]) or (s[12]<ii<e[12]) or (s[13]<ii<e[13]):
  84. continuous_stim.append(0.15)
  85. else:
  86. continuous_stim.append(0)
  87. return (continuous_stim)
  88. def crosscorr_sort(dffs, stimulus,cutoff,frame_rate,max_lag=0):
  89. """
  90. Description
  91. ----------
  92. This function computes the cross correlation between the calcium activity and the auditory stimulus and extract the top cutoff % of ROIs based on the correlation coefficient.
  93. ----------
  94. Parameters
  95. ----------
  96. dffs (np.ndarray)
  97. Array containing the calcium activity over time, each row is an ROI.
  98. stimulus (np.ndarray)
  99. array containing the auditory stimulus
  100. cutoff (float)
  101. threshold in percentage to use when extracting the top X% based on correlation
  102. max_lag (float)
  103. the lag over which to compute the cross correlation
  104. frame_rate (float)
  105. frame rate of the scope
  106. ----------
  107. Returns
  108. ----------
  109. audio_correlated, corr_coeff, correlations, sorted_indices
  110. array containing the index of the top 'cutoff'% ROIs with the highest correlation coefficient with the stimulus
  111. corr_coeff
  112. array containing the correlation coefficient of the extracted ROIs in audio_correlated
  113. correlations
  114. array containing the correlation coefficients of all ROIs in dffs
  115. ----------
  116. """
  117. # Normalize the activity and the stimulus
  118. dffs_mean = dffs.mean(axis=1, keepdims=True)
  119. dffs_std = dffs.std(axis=1, keepdims=True)
  120. dffs_normalized = (dffs - dffs_mean) / dffs_std
  121. stimulus_normalized = (stimulus - stimulus.mean()) / stimulus.std()
  122. # define index cutoff
  123. index_cutoff = int(dffs.shape[0] * (cutoff/100))
  124. # If we don't want any lag
  125. if max_lag == 0:
  126. # Compute correlations
  127. correlations = np.dot(dffs_normalized, stimulus_normalized.T) / dffs_normalized.shape[1]
  128. correlations = correlations.flatten()
  129. rois = np.arange(0,dffs.shape[0])
  130. rois = rois[~np.isnan(correlations)]
  131. correlations = correlations[~np.isnan(correlations)]
  132. #sort the correlation coefficients and rois
  133. sorted_indices = np.argsort(correlations)
  134. sorted_corr = correlations[sorted_indices]
  135. sorted_rois = rois[sorted_indices]
  136. # Extract the top X%
  137. audio_correlated = sorted_rois[-index_cutoff:]
  138. corr_coeff = sorted_corr[-index_cutoff:]
  139. # Result: correlations is a 1D array of shape (number of ROIs,)
  140. else:
  141. # Compute correlations for different lags
  142. num_rois, time_points = dffs.shape
  143. lags = np.arange(int(-max_lag*frame_rate), int(max_lag*frame_rate) + 1,int(0.5*frame_rate))
  144. correlation_matrix = np.zeros((num_rois, len(lags)))
  145. for i, lag in enumerate(lags):
  146. if lag < 0:
  147. # Shift auditory stimulus forward
  148. shifted_stimulus = stimulus_normalized[-lag:]
  149. activity_subset = dffs_normalized[:, :len(shifted_stimulus)]
  150. elif lag > 0:
  151. # Shift auditory stimulus backward
  152. shifted_stimulus = stimulus_normalized[:-lag]
  153. activity_subset = dffs_normalized[:, lag:]
  154. # Compute correlations for the current lag
  155. correlation_matrix[:, i] = (
  156. np.dot(activity_subset, shifted_stimulus) / len(shifted_stimulus)
  157. )
  158. # Find the best correlation across all lags for each ROI
  159. correlations = np.max(np.abs(correlation_matrix), axis=1)
  160. #best_lags = lags[np.argmax(np.abs(correlation_matrix), axis=1)]
  161. rois = np.arange(0,dffs.shape[0])
  162. rois = rois[~np.isnan(correlations)]
  163. correlations = correlations[~np.isnan(correlations)]
  164. #sort the correlation and rois
  165. sorted_indices = np.argsort(correlations)
  166. sorted_corr = correlations[sorted_indices]
  167. sorted_rois = rois[sorted_indices]
  168. audio_correlated = sorted_rois[-index_cutoff:]
  169. corr_coeff = sorted_corr[-index_cutoff:]
  170. print('number of audio correlated ROIs: {}'.format(len(audio_correlated)))
  171. return(audio_correlated, corr_coeff, correlations, sorted_indices[-index_cutoff:])
  172. def truncate_colormap(cmap, minval=0.0, maxval=1.0, n=100):
  173. if isinstance(cmap, str):
  174. cmap = plt.get_cmap(cmap)
  175. new_cmap = matplotlib.colors.LinearSegmentedColormap.from_list(
  176. 'trunc({n},{a:.2f},{b:.2f})'.format(n=cmap.name, a=minval, b=maxval),
  177. cmap(np.linspace(minval, maxval, n)))
  178. return new_cmap
  179. def compute_mean_time_series_per_block_pair(dffs, time_activity, frame_rate, start_block_seconds, end_block_seconds, t_added, scope):
  180. """
  181. Description
  182. ----------
  183. This function computes the mean activity across all ROIs during the two presentation of stimulus block with the same frequency.
  184. ----------
  185. Parameters
  186. ----------
  187. dffs (np.ndarray)
  188. Array containing the calcium activity over time, each row is an ROI.
  189. time_activity (np.ndarray)
  190. array containing the time for each calcium trace
  191. frame_rate (float)
  192. frame rate of the scope
  193. start_block_seconds (np.ndarray)
  194. array containing the start of each block in seconds
  195. end_block_seconds (np.ndarray)
  196. array containing the end of each block in seconds
  197. t_added (float)
  198. Time to add around each block for visualization purposes
  199. scope (str)
  200. 'LB' or '2p' to specify which scope was used to aquire the data
  201. ----------
  202. Returns
  203. ----------
  204. block_pair_traces (shape: (n_block_pairs, samples_per_block))
  205. array containing the mean activity across all ROIs during each block of stimulus with different frequencies
  206. block_pair_sem (shape: (n_block_pairs, samples_per_block))
  207. array containing the sem of the activity across all ROIs during each block of stimulus with different frequencies
  208. ----------
  209. """
  210. n_blocks = len(start_block_seconds)
  211. assert n_blocks % 2 == 0, "Number of blocks must be even"
  212. block_pair_traces = []
  213. block_pair_sem=[]
  214. for i in range(0, n_blocks, 2):
  215. pair_traces = []
  216. pair_sem = []
  217. for j in [i, i + 1]: # handle each of the two blocks in the pair
  218. start_time = start_block_seconds[j]-t_added
  219. end_time = end_block_seconds[j]+t_added
  220. samples_per_block = int((end_time-start_time) * frame_rate)
  221. # Get mask for time points in this block
  222. mask = (time_activity >= start_time) & (time_activity <= end_time)
  223. if scope == '2p':
  224. samples_per_block+=1
  225. if (i==10) and (j==11):
  226. mask = np.hstack(( np.array(np.where(mask)[0][0]-1) , np.where(mask)[0] ))
  227. block_data = dffs[:, mask]
  228. if block_data.shape[1] != samples_per_block:
  229. raise ValueError(f"Block {j+1} does not contain {samples_per_block} samples. Got {block_data.shape[1]}.")
  230. # Average across ROIs
  231. mean_trace = np.mean(block_data, axis=0)
  232. pair_traces.append(mean_trace)
  233. sem_trace = sem(block_data, axis=0)
  234. pair_sem.append(sem_trace)
  235. # Average the two blocks
  236. mean_pair_trace = np.mean(pair_traces, axis=0)
  237. block_pair_traces.append(mean_pair_trace)
  238. sem_pair_trace = np.mean(sem_trace, axis=0)
  239. block_pair_sem.append(sem_pair_trace)
  240. return np.stack(block_pair_traces, axis=0),np.stack(block_pair_sem, axis=0)
  241. def extract_single_stimulus_per_block_pair(stimulus, time_audio, frame_rate, start_block_seconds,end_block_seconds,t_added):
  242. """
  243. Description
  244. ----------
  245. This function extract the auditory stimulus during each block presentation for plotting purposes in other functions.
  246. ----------
  247. Parameters
  248. ----------
  249. stimulus (np.ndarray)
  250. array containing the auditory stimulus
  251. time_audio (np.ndarray)
  252. array containing the time for the auditory stimulus
  253. frame_rate (float)
  254. frame rate of the scope
  255. start_block_seconds (np.ndarray)
  256. array containing the start of each block in seconds
  257. end_block_seconds (np.ndarray)
  258. array containing the end of each block in seconds
  259. t_added (float)
  260. Time to add around each block for visualization purposes
  261. ----------
  262. Returns
  263. ----------
  264. stim_traces (shape: (n_block_pairs, samples_per_block))
  265. array containing the stimulus during each block of stimulus of a given frequency
  266. ----------
  267. """
  268. samples_per_block = int((10+t_added+t_added) * frame_rate)
  269. n_blocks = len(start_block_seconds)
  270. assert n_blocks % 2 == 0, "Number of blocks must be even"
  271. stim_traces = []
  272. for i in range(0, n_blocks, 2): # take the first block in each pair
  273. start_time = start_block_seconds[i]-t_added
  274. end_time = end_block_seconds[i] +t_added # 10s block
  275. mask = (time_audio >= start_time) & (time_audio < end_time)
  276. stim_block = stimulus[mask]
  277. if stim_block.shape[0] != samples_per_block:
  278. raise ValueError(f"Stimulus block {i+1} has {stim_block.shape[0]} samples; expected {samples_per_block}.")
  279. stim_traces.append(stim_block)
  280. return np.stack(stim_traces, axis=0)
  281. def plot_calcium_with_stimulus_overlay(mean_traces,sem_traces, stim_traces, frame_rate,path, scope):
  282. """
  283. Description
  284. ----------
  285. This function plots the mean activity across all ROIs during the two presentation of stimulus block of a given frequency overlayed with the auditory stimulus.
  286. ----------
  287. Parameters
  288. ----------
  289. mean_traces (nd.array)
  290. array containing the mean activity across all ROIs during each block of stimulus with different frequencies
  291. sem_traces (nd.array)
  292. array containing the sem of the activity across all ROIs during each block of stimulus with different frequencies
  293. stim_traces (np.ndarray)
  294. array containing the stimulus during each block of stimulus of a given frequency
  295. time_activity (np.ndarray)
  296. array containing the time for each calcium trace
  297. frame_rate (float)
  298. frame rate of the scope
  299. path (str)
  300. Path to folder where to save the plots. If set to None, plots won't be saved.
  301. scope (str)
  302. 'LB' or '2p' to specify which scope was used to aquire the data
  303. ----------
  304. """
  305. if scope == 'LB':
  306. col = 'g'
  307. else:
  308. col = 'm'
  309. fr = 1/frame_rate
  310. n_pairs, samples_per_block = mean_traces.shape
  311. time_axis = np.arange(0,(samples_per_block)*fr,fr)
  312. fr_stim = 1/100
  313. samples_per_block_stim = np.shape(stim_traces[0])[0]
  314. t_stim = np.arange(0,(float(samples_per_block_stim))*fr_stim,fr_stim)
  315. for i in range(n_pairs):
  316. max_trace = np.max(mean_traces[i] + sem_traces[i])
  317. #min_trace = np.min(mean_traces[i] + sem_traces[i])
  318. height1 = 0.006
  319. height2 = 0.085
  320. plt.figure(figsize = (8,5))
  321. plt.plot(time_axis, mean_traces[i],color= col,alpha = 1, lw = 2.5)
  322. plt.fill_between(t_stim,y1=(max_trace*stim_traces[i])+ height1,y2=(max_trace*stim_traces[i])+height2,where =stim_traces[i]>0,color='r',alpha=1)
  323. plt.fill_between(time_axis,y1=(mean_traces[i] + sem_traces[i]),y2=( mean_traces[i] - sem_traces[i]),color=col,alpha=0.4)
  324. plt.xlabel('Time (s)', fontsize = 36)
  325. plt.ylabel('Z(DF/F)', fontsize = 36)
  326. plt.locator_params(axis='y', nbins=5)
  327. plt.xlim(0,time_axis[-1])
  328. plt.xticks(fontsize = 34)
  329. plt.yticks(fontsize = 34)
  330. plt.locator_params(axis='y', nbins=3)
  331. plt.tight_layout()
  332. if path != None:
  333. plt.savefig(path + 'mean_activity_cluster_block_' + str(i) + '_2p' + '.pdf', transparent = True)
  334. def fourier_mean_activity_interpolate(dffs,start_block_seconds,end_block_seconds,t_added,frame_rate,time_activity,scope, target_frame_rate,N_target, path,xlim, col):
  335. """
  336. Description
  337. ----------
  338. This function computes and plots the fourier spectrum of the mean activity for each block pair of a given frequency
  339. ----------
  340. Parameters
  341. ----------
  342. dffs (np.ndarray)
  343. Array containing the calcium activity over time, each row is an ROI.
  344. start_block_seconds (np.ndarray)
  345. array containing the start of each block in seconds
  346. end_block_seconds (np.ndarray)
  347. array containing the end of each block in seconds
  348. t_added (float)
  349. Time to add around each block for visualization purposes
  350. frame_rate (float)
  351. frame rate of the scope
  352. time_activity (np.ndarray)
  353. array containing the time for each calcium trace
  354. scope (str)
  355. Either 'LB' or '2p'
  356. Hz_target (float)
  357. Target frame rate when interpolating 2p
  358. N_target (float)
  359. Target number of samples when interpolating 2p
  360. path (float)
  361. Path to folder where to save the plots. If set to None, plots won't be saved.
  362. xlim (float)
  363. maximum of x axis for plotting
  364. col (float)
  365. color for plotting
  366. ----------
  367. """
  368. b = 1 # Index that keeps track of the stimulus blocks
  369. # Loop through each pair of stimulus blocks
  370. for k in range(6):
  371. # Grab first block
  372. t_sart = start_block_seconds[b]
  373. t_end = end_block_seconds[b] +t_added
  374. N = int((t_end-t_sart)*frame_rate)
  375. t_act = np.linspace(t_sart,t_end,N)
  376. start_act = np.argwhere((t_sart-time_activity)<0.001)[0][0]
  377. end_act = np.argwhere((t_end-time_activity)<0.001)[0][0]
  378. if (end_act-start_act)>(len(t_act)):
  379. end_act = end_act - ((end_act-start_act)-len(t_act))
  380. if len(dffs.shape)>1:
  381. activity_block1 = dffs[:,start_act:end_act]
  382. else:
  383. activity_block1 = dffs[start_act:end_act]
  384. # Grab second block
  385. t_sart = start_block_seconds[b+1]
  386. t_end = end_block_seconds[b+1] + t_added
  387. N = int((t_end-t_sart)*frame_rate)
  388. t_act = np.linspace(t_sart,t_end,N)
  389. start_act = np.argwhere((t_sart-time_activity)<0.001)[0][0]
  390. end_act = np.argwhere((t_end-time_activity)<0.001)[0][0]
  391. if (end_act-start_act)>(len(t_act)):
  392. end_act = end_act - ((end_act-start_act)-len(t_act))
  393. if len(dffs.shape)>1:
  394. activity_block2 = dffs[:,start_act:end_act]
  395. else:
  396. activity_block2 = dffs[start_act:end_act]
  397. # append both blocks and take the mean
  398. act_both_block = np.vstack((activity_block1,activity_block2))
  399. # Compute the mean
  400. mean_block = np.mean(act_both_block,axis = 0)
  401. # subtract the mean
  402. activity = mean_block-np.mean(mean_block)
  403. normalize = int(N/2)+1
  404. #### compute fourier
  405. fourier = scipy.fft.fft(activity)
  406. fourier = np.abs(fourier)**2
  407. ff = scipy.fft.fft(activity)
  408. ff = np.abs(ff)**2
  409. # Interpolate 2-photon spectrum to match light bead frequency axis
  410. if scope == '2p':
  411. time_inter = rfftfreq(N_target, d = 1/frame_rate)
  412. freq_2p = rfftfreq(N, d = 1/frame_rate)
  413. interp_func = interp1d(freq_2p, ff[:normalize], kind='linear')
  414. fourier_interp = interp_func(time_inter)
  415. # plot the Fourier spectrums
  416. N = len(activity)
  417. plt.figure()
  418. if scope == '2p':
  419. plt.plot(rfftfreq(N_target, d = 1/frame_rate),fourier_interp[:int(N_target/2)+1], color = col,lw = 2.5 )
  420. if scope == 'LB':
  421. plt.plot(rfftfreq(N, d = 1/target_frame_rate), fourier[:normalize], color = col,lw = 2.5)
  422. plt.xlabel('Frequency (Hz)',fontsize =36)
  423. plt.ylabel('Amplitude',fontsize =36)
  424. plt.xlim(0,xlim)
  425. plt.xticks(fontsize =34)
  426. plt.yticks(fontsize =34,)
  427. plt.locator_params(axis='y', nbins=4)
  428. #plt.title('Spectrum block {0}Hz'.format(HZ[k]))
  429. plt.tight_layout()
  430. if path != None:
  431. plt.savefig(path + 'spectrum_block_' + str(k) + '_'+ scope + '.pdf', transparent = True)
  432. b=b+2 # Move to the next block pair
  433. plt.tight_layout()
  434. def power_ROI(dffs,start_block_seconds,end_block_seconds,t_added,frame_rate,time_activity,scope,N_target):
  435. """
  436. Description
  437. ----------
  438. This function computes the absolute and fraction of power at each frequencies for each ROIs.
  439. ----------
  440. Parameters
  441. ----------
  442. dffs (np.ndarray)
  443. Array containing the calcium activity over time, each row is an ROI.
  444. start_block_seconds (np.ndarray)
  445. array containing the start of each block in seconds
  446. end_block_seconds (np.ndarray)
  447. array containing the end of each block in seconds
  448. t_added (float)
  449. Time to add around each block for visualization purposes
  450. frame_rate (float)
  451. frame rate of the scope
  452. time_activity (np.ndarray)
  453. array containing the time for each calcium trace
  454. scope (str)
  455. Either 'LB' or '2p'
  456. N_target (float)
  457. Target number of samples when interpolating 2p
  458. ----------
  459. Returns
  460. ----------
  461. ps (nd.array)
  462. array containing the absolute power at each frequency for all ROIs
  463. fracps (nd.array)
  464. array containing the fraction of power at each frequency for all ROIs
  465. """
  466. mean_blocks = []
  467. ### First we grab the mean activity during of each stimulus block pair to subtract later
  468. b = 1 # Index that keeps track of the stimulus blocks
  469. # Loop through each block pairs
  470. for k in range(6):
  471. # extract first block in the pair
  472. t_sart = start_block_seconds[b]
  473. t_end = end_block_seconds[b] +t_added
  474. N = int((t_end-t_sart)*frame_rate)
  475. t_act = np.linspace(t_sart,t_end,N)
  476. start_act = np.argwhere((t_sart-time_activity)<0.001)[0][0]
  477. end_act = np.argwhere((t_end-time_activity)<0.001)[0][0]
  478. if (end_act-start_act)>(len(t_act)):
  479. end_act = end_act - ((end_act-start_act)-len(t_act))
  480. activity_block1 = dffs[:,start_act:end_act]
  481. # Grab second block in the pair
  482. t_sart = start_block_seconds[b+1]
  483. t_end = end_block_seconds[b+1] + t_added
  484. N = int((t_end-t_sart)*frame_rate)
  485. t_act = np.linspace(t_sart,t_end,N)
  486. start_act = np.argwhere((t_sart-time_activity)<0.001)[0][0]
  487. end_act = np.argwhere((t_end-time_activity)<0.001)[0][0]
  488. if (end_act-start_act)>(len(t_act)):
  489. end_act = end_act - ((end_act-start_act)-len(t_act))
  490. activity_block2 = dffs[:,start_act:end_act]
  491. # Combine both blocks
  492. activity_both_block = np.vstack((activity_block1,activity_block2))
  493. # store the mean
  494. mean_blocks.append(np.mean(activity_both_block))
  495. b +=2 # Move to the next block pair
  496. HZ = [0.25,0.5,1,2,3,5]
  497. ps = np.zeros((6,dffs.shape[0])) # will contain the absolute power
  498. fracps = np.zeros((6,dffs.shape[0])) # will contain the fraction of power
  499. for roi, i in enumerate(dffs):
  500. b=1 # Index that keeps track of the stimulus blocks
  501. # Loop through each block pairs
  502. for k in range(6):
  503. # extract first block in the pair
  504. t_sart = start_block_seconds[b]
  505. t_end = end_block_seconds[b] +t_added
  506. N = int((t_end-t_sart)*frame_rate)
  507. t_act = np.linspace(t_sart,t_end,N)
  508. start_act = np.argwhere((t_sart-time_activity)<0.001)[0][0]
  509. end_act = np.argwhere((t_end-time_activity)<0.001)[0][0]
  510. if (end_act-start_act)>(len(t_act)):
  511. end_act = end_act - ((end_act-start_act)-len(t_act))
  512. activity_block1 = dffs[:,start_act:end_act][roi,:]
  513. # Grab second block in the pair
  514. t_sart = start_block_seconds[b+1]
  515. t_end = end_block_seconds[b+1] + t_added
  516. N = int((t_end-t_sart)*frame_rate)
  517. t_act = np.linspace(t_sart,t_end,N)
  518. start_act = np.argwhere((t_sart-time_activity)<0.001)[0][0]
  519. end_act = np.argwhere((t_end-time_activity)<0.001)[0][0]
  520. if (end_act-start_act)>(len(t_act)):
  521. end_act = end_act - ((end_act-start_act)-len(t_act))
  522. activity_block2 = dffs[:,start_act:end_act][roi,:]
  523. # Combine both blocks
  524. activity_both_block = np.vstack((activity_block1,activity_block2))
  525. # Compute the mean
  526. mean_both_block = np.mean(activity_both_block,axis = 0)
  527. #### Compute fourier spectrum
  528. # subtract the overall mean during these two blocks
  529. ff_mean = scipy.fft.fft(mean_both_block - mean_blocks[k], norm = 'ortho' )
  530. ff_mean = np.abs(ff_mean)**2
  531. N_mean = len(mean_both_block)
  532. normalize = int(N_mean/2)+1
  533. ### slice frequency range
  534. f_range = [HZ[k]-0.1, HZ[k]+0.1]
  535. freq_axis = rfftfreq(N, d = 1/frame_rate)
  536. f0 = np.argmin(np.abs(freq_axis- f_range[0]))
  537. f1 = np.argmin(np.abs(freq_axis- f_range[1]))
  538. f_cutoff = np.argmin(np.abs(freq_axis- 1.1))
  539. if scope == 'LB':
  540. p = np.sum(ff_mean[f0:f1])
  541. if HZ[k]<2:
  542. totp = np.sum(ff_mean[1:f_cutoff])
  543. else:
  544. totp = np.sum(ff_mean[1:normalize])
  545. if scope == '2p':
  546. if (HZ[k]-freq_axis[f0])>0.8:
  547. p = 0
  548. else:
  549. p = np.sum(ff_mean[f0:f1])
  550. totp = np.sum(ff_mean[1:normalize])
  551. ps[k,roi] = p
  552. fracp = (p/totp)*100
  553. fracps[k,roi] = fracp
  554. return(ps,fracps)
  555. def plot_power_ROIs(ps,fracps,ps_2p, fracps_2p,path):
  556. """
  557. Description
  558. ----------
  559. This function plot the fraction of power at each frequency across all ROIs for both 2p and LB
  560. ----------
  561. Parameters
  562. ----------
  563. ps (nd.array)
  564. array containing the absolute power at each frequency for all ROIs aquired with LB
  565. fracps (nd.array)
  566. array containing the fraction of power at each frequency for all ROIs aquired with LB
  567. ps_2p (nd.array)
  568. array containing the absolute power at each frequency for all ROIs aquired with 2p
  569. fracps_2p (nd.array)
  570. array containing the fraction of power at each frequency for all ROIs aquired with 2p
  571. path (str)
  572. Path to folder where to save the plots. If set to None, plots won't be saved.
  573. ----------
  574. """
  575. HZ = [0.25,0.5,1,2,3,5]
  576. # Loop through each frequencies
  577. for k in range(len(ps)):
  578. plt.figure(figsize=(3.5, 6))
  579. plt.plot(np.random.normal(0,0.03, size = len(fracps[k])),fracps[k],'g.',alpha = 0.1)
  580. plt.plot(np.random.normal(0.3,0.03, size = len(fracps_2p[k])),fracps_2p[k],'m.',alpha = 0.1)
  581. plt.bar([0],np.mean(fracps[k]),yerr = np.std(fracps[k]),capsize = 5, color = 'white',edgecolor = 'g', width = 0.2)
  582. plt.bar([0.3],np.mean(fracps_2p[k]),yerr = np.std(fracps_2p[k]),capsize = 5, color = 'white', edgecolor = 'm', width = 0.2)
  583. plt.xticks([])
  584. plt.ylabel('Fraction of power at {} Hz'.format(HZ[k]), fontsize = 32)
  585. plt.yticks(fontsize = 32)
  586. plt.locator_params(axis='y', nbins=4)
  587. plt.tight_layout()
  588. if path != None:
  589. plt.savefig(path + 'frac_power' + str(HZ[k]) + '_' + '.pdf', transparent = True)
  590. def shuffle_within_blocks(dffs, start_block_seconds, end_block_seconds, frame_rate, seed=None):
  591. """
  592. This function returns a copy of the activity where, for each stimulus block,
  593. the time‐points within that block are independently shuffled for each ROI.
  594. Parameters
  595. ----------
  596. dffs (np.ndarray)
  597. Array containing the calcium activity over time, each row is an ROI.
  598. start_block_seconds (np.ndarray)
  599. array containing the start of each block in seconds
  600. end_block_seconds (np.ndarray)
  601. array containing the end of each block in seconds
  602. frame_rate (float)
  603. frame rate of the scope
  604. seed : int or None
  605. If given, seeds the RNG
  606. Returns
  607. -------
  608. shuffled : np.ndarray, shape (n_rois, n_timepoints)
  609. A copy of `dffs` with time‐points shuffled within each block for each ROI.
  610. """
  611. if seed is not None:
  612. np.random.seed(seed)
  613. n_rois, n_time = dffs.shape
  614. shuffled = dffs.copy()
  615. for start_sec, end_sec in zip(start_block_seconds, end_block_seconds):
  616. # Convert seconds to integer frame indices
  617. start_idx = int(np.floor(start_sec * frame_rate))
  618. end_idx = int(np.ceil (end_sec * frame_rate))
  619. # Clip to valid range
  620. start_idx = max(start_idx, 0)
  621. end_idx = min(end_idx, n_time)
  622. block_len = end_idx - start_idx
  623. if block_len <= 1:
  624. continue
  625. # For each ROI, shuffle the values within [start_idx:end_idx]
  626. for r in range(n_rois):
  627. block_vals = dffs[r, start_idx:end_idx]
  628. permuted = block_vals[np.random.permutation(block_len)]
  629. shuffled[r, start_idx:end_idx] = permuted
  630. return shuffled
  631. def fourier_and_peaks_mean(dffs,start_block_seconds,end_block_seconds,frame_rate,time_activity, path):
  632. """
  633. Description
  634. ----------
  635. This function computes the absolute power at 27-28Hz and the fourier spectrum for each ROIs
  636. ----------
  637. Parameters
  638. ----------
  639. dffs (np.ndarray)
  640. Array containing the calcium activity over time, each row is an ROI.
  641. start_block_seconds (np.ndarray)
  642. array containing the start of each block in seconds
  643. end_block_seconds (np.ndarray)
  644. array containing the end of each block in seconds
  645. frame_rate (float)
  646. frame rate of the scope
  647. time_activity (np.ndarray)
  648. array containing the time for each calcium trace
  649. path (str)
  650. Path to folder where to save the plots. If set to None, plots won't be saved.
  651. ----------
  652. Returns
  653. ----------
  654. ps (nd.array)
  655. array containing the absolute power at each frequency for all ROIs
  656. ff_all_roi (nd.array)
  657. array containing the mean Fourier spectrum for each ROI
  658. """
  659. HZ = 27.77 # frequency of pulses within the stimulus (36ms IPI)
  660. ps = []
  661. mean_roi = np.zeros((dffs.shape[0],2*int(frame_rate) ))
  662. ff_all_roi = np.zeros((dffs.shape[0],2*int(frame_rate) )) # Contains the mean ff spectrum of each ROI
  663. # First we compute the mean activity over all blocks for each ROIs to subtract later
  664. for roi in range(dffs.shape[0]):
  665. ## Loop through the blocks
  666. for k in range(2,14):
  667. # Grab block
  668. t_sart = start_block_seconds[k]
  669. t_end = end_block_seconds[k]
  670. N = int((t_end-t_sart)*frame_rate)
  671. t_act = np.linspace(t_sart,t_end,N)
  672. start_act = (np.abs(t_sart-time_activity)).argmin()
  673. end_act = (np.abs(t_end-time_activity)).argmin()
  674. if (end_act-start_act)>(len(t_act)):
  675. end_act = end_act - ((end_act-start_act)-len(t_act))
  676. # store the block
  677. if k == 2:
  678. activity_block = dffs[roi, start_act:end_act]
  679. else:
  680. activity_block = np.vstack((activity_block,dffs[roi, start_act:end_act]))
  681. ## take the mean across all block for each roi
  682. mean_across_blocks = np.mean(activity_block, axis = 0)
  683. mean_roi[roi,:] = mean_across_blocks
  684. # compute the mean of all these means
  685. overall_mean = np.mean(mean_roi)
  686. # compute fourier
  687. for roi in range(mean_roi.shape[0]):
  688. # extract activity of each roi and subtract the overall mean
  689. activity_fourier = mean_roi[roi,:] - overall_mean
  690. N = len(activity_fourier)
  691. #Slice frequency range
  692. f_range = [HZ -0.77, HZ +0.33] # Frequency range of interesest (27-28Hz)
  693. freq_axis = rfftfreq(N, d = 1/frame_rate)
  694. f0 = np.argmin(np.abs(freq_axis- f_range[0]))
  695. f1 = np.argmin(np.abs(freq_axis- f_range[1]))
  696. #Compute fourier
  697. ff = np.abs(scipy.fft.fft(activity_fourier, norm = 'ortho' ))**2
  698. if roi == 0:
  699. ff_all_roi = ff
  700. else:
  701. ff_all_roi = np.vstack((ff_all_roi,ff))
  702. #compute abstolute power
  703. p = np.sum(ff[f0:f1])
  704. ps.append(p)
  705. return ps , ff_all_roi
  706. def plot_power_ROIs_all(ps,ps_shuffled,ps_off, path,title):
  707. """
  708. Description
  709. ----------
  710. This function plots the absolute at 27-28Hz for the three groups (stim on, stim off and shuffled activity)
  711. ----------
  712. Parameters
  713. ----------
  714. ps (nd.array)
  715. array containing the absolute power at 27-28Hz for all ROIs when stimulus is on
  716. ps_shuffled (nd.array)
  717. array containing the absolute power at 27-28Hz for all ROIs when stimulus is on and the activity is shuffled
  718. ps_off (nd.array)
  719. array containing the absolute power at 27-28Hz for all ROIs when stimulus is off
  720. path (str)
  721. Path to folder where to save the plots. If set to None, plots won't be saved.
  722. ----------
  723. """
  724. plt.figure(figsize=(11, 6))
  725. plt.plot(np.random.normal(0.1,0.025, size = len(ps)),ps,'g.',alpha = 0.3)#+'.'
  726. plt.plot(np.random.normal(0.9,0.025, size = len(ps_shuffled)),ps_shuffled,'m.',alpha = 0.3)#+'.'
  727. plt.plot(np.random.normal(0.5,0.025, size = len(ps_off)),ps_off,'k.',alpha = 0.3)#+'.'
  728. plt.bar([0.1],np.mean(ps),yerr = np.std(ps),capsize = 5, color = 'white',edgecolor = 'g', width = 0.1)
  729. plt.bar([0.9],np.mean(ps_shuffled),yerr = np.std(ps_shuffled),capsize = 5, color = 'white',edgecolor = 'm', width = 0.1)
  730. plt.bar([0.5],np.mean(ps_off),yerr = np.std(ps_off),capsize = 5, color = 'white',edgecolor = 'k', width = 0.1)
  731. plt.xticks([])
  732. plt.yticks(fontsize = 22)
  733. plt.ylabel('Absolute power at [27-28] Hz', fontsize = 20)
  734. plt.locator_params(axis='y', nbins=3)
  735. plt.ylim(0,0.9)
  736. plt.tight_layout()
  737. if path != None:
  738. plt.savefig(path + 'absolute_power'+ title +'.pdf', transparent = True)
  739. def plot_power_ROIs_all_no_shuffle(ps,ps_off, path,title):
  740. """
  741. Description
  742. ----------
  743. This function plots the absolute at 27-28Hz for the three groups (stim on, stim off)
  744. ----------
  745. Parameters
  746. ----------
  747. ps (nd.array)
  748. array containing the absolute power at 27-28Hz for all ROIs when stimulus is on
  749. ps_off (nd.array)
  750. array containing the absolute power at 27-28Hz for all ROIs when stimulus is off
  751. path (str)
  752. Path to folder where to save the plots. If set to None, plots won't be saved.
  753. ----------
  754. """
  755. plt.figure(figsize=(11, 6))
  756. plt.plot(np.random.normal(0.25,0.025, size = len(ps)),ps,'g.',alpha = 0.3)#+'.'
  757. plt.plot(np.random.normal(0.6,0.025, size = len(ps_off)),ps_off,'k.',alpha = 0.3)#+'.'
  758. plt.bar([0.25],np.mean(ps),yerr = np.std(ps),capsize = 5, color = 'white',edgecolor = 'g', width = 0.1)
  759. plt.bar([0.6],np.mean(ps_off),yerr = np.std(ps_off),capsize = 5, color = 'white',edgecolor = 'k', width = 0.1)
  760. plt.xticks([])
  761. plt.yticks(fontsize = 22)
  762. plt.ylabel('Absolute power at [27-28] Hz', fontsize = 20)
  763. plt.locator_params(axis='y', nbins=3)
  764. plt.ylim(0,0.9)
  765. plt.tight_layout()
  766. if path != None:
  767. plt.savefig(path + 'absolute_power'+ title +'.pdf', transparent = True)
  768. def peaks_fourier_ROI_combine(dffs,start_block_seconds,end_block_seconds,t_added,Hz,time_activity,scope,N_target,top_peaks,path_fig):
  769. """
  770. Description
  771. ----------
  772. This function computes the fourier of each roi for each block and extract the peak frequency of the spectrum.
  773. It also plots the fourier spectrum for each block of a representative ROI.
  774. ----------
  775. Parameters
  776. ----------
  777. dffs (np.ndarray)
  778. Array containing the calcium activity over time, each row is an ROI.
  779. start_block_seconds (np.ndarray)
  780. array containing the start of each block in seconds
  781. end_block_seconds (np.ndarray)
  782. array containing the end of each block in seconds
  783. t_added (float)
  784. Time to add around each block for visualization purposes
  785. frame_rate (float)
  786. frame rate of the scope
  787. time_activity (np.ndarray)
  788. array containing the time for each calcium trace
  789. scope (str)
  790. 'LB' or '2p' to specify which scope was used to aquire the data
  791. N_target (float)
  792. Target number of samples when interpolating 2p
  793. top_peaks (int)
  794. the number of peaks to extract per spectrum
  795. path_fig (float)
  796. Path to folder where to save the plots. If set to None, plots won't be saved.
  797. ----------
  798. Returns
  799. ----------
  800. peak_freq (np.ndarray)
  801. contains the absolute peak frequency of the Fourier for each block of each ROI.
  802. ----------
  803. """
  804. if scope == 'LB':
  805. roi_to_plot = 1596
  806. col = 'g'
  807. else:
  808. roi_to_plot = 890
  809. col = 'm'
  810. mean_blocks = []
  811. # First we grab mean of each block to subtract later
  812. b = 1 # Index that keeps track of the stimulus blocks
  813. for k in range(6):
  814. # Grab first block
  815. t_sart = start_block_seconds[b]
  816. t_end = end_block_seconds[b] +t_added
  817. N = int((t_end-t_sart)*Hz)
  818. t_act = np.linspace(t_sart,t_end,N)
  819. start_act = np.argwhere((t_sart-time_activity)<0.001)[0][0]
  820. end_act = np.argwhere((t_end-time_activity)<0.001)[0][0]
  821. if (end_act-start_act)>(len(t_act)):
  822. end_act = end_act - ((end_act-start_act)-len(t_act))
  823. activity_block1 = zscore(dffs[:,start_act:end_act],axis = 1)
  824. # Grab second block
  825. t_sart = start_block_seconds[b+1]
  826. t_end = end_block_seconds[b+1] + t_added
  827. N = int((t_end-t_sart)*Hz)
  828. t_act = np.linspace(t_sart,t_end,N)
  829. start_act = np.argwhere((t_sart-time_activity)<0.001)[0][0]
  830. end_act = np.argwhere((t_end-time_activity)<0.001)[0][0]
  831. if (end_act-start_act)>(len(t_act)):
  832. end_act = end_act - ((end_act-start_act)-len(t_act))
  833. activity_block2 = zscore(dffs[:,start_act:end_act],axis = 1)
  834. # Combine both blocks
  835. activity_both_block = np.vstack((activity_block1,activity_block2))
  836. mean_blocks.append(np.mean(activity_both_block))
  837. b +=2 # move to next pair
  838. HZ = [0.25,0.5,1,2,3,5]
  839. peak_freq = np.zeros((6,top_peaks*dffs.shape[0]))
  840. for roi, i in enumerate(dffs):
  841. b=1 # Index that keeps track of the stimulus blocks
  842. for k in range(6):
  843. #extract first block
  844. t_sart = start_block_seconds[b]
  845. t_end = end_block_seconds[b] +t_added
  846. N = int((t_end-t_sart)*Hz)
  847. t_act = np.linspace(t_sart,t_end,N)
  848. start_act = np.argwhere((t_sart-time_activity)<0.001)[0][0]
  849. end_act = np.argwhere((t_end-time_activity)<0.001)[0][0]
  850. if (end_act-start_act)>(len(t_act)):
  851. end_act = end_act - ((end_act-start_act)-len(t_act))
  852. activity_block1 = zscore(dffs[:,start_act:end_act][roi,:])
  853. # Grab second block
  854. t_sart = start_block_seconds[b+1]
  855. t_end = end_block_seconds[b+1] + t_added
  856. N = int((t_end-t_sart)*Hz)
  857. t_act = np.linspace(t_sart,t_end,N)
  858. start_act = np.argwhere((t_sart-time_activity)<0.001)[0][0]
  859. end_act = np.argwhere((t_end-time_activity)<0.001)[0][0]
  860. if (end_act-start_act)>(len(t_act)):
  861. end_act = end_act - ((end_act-start_act)-len(t_act))
  862. activity_block2 = zscore(dffs[:,start_act:end_act][roi,:])
  863. #Combine both blocks
  864. activity_both_block = np.vstack((activity_block1,activity_block2))
  865. mean_both_block = np.mean(activity_both_block,axis = 0)
  866. # subtract the overall mean during these two blocks
  867. ff_mean = np.abs(scipy.fft.fft(mean_both_block - mean_blocks[k] ))**2 #
  868. N_mean = len(mean_both_block)
  869. normalize = int(N_mean/2)+1
  870. # Interpolate 2-photon spectrum to match light bead frequency axis
  871. if scope == '2p':
  872. # Frequency axes
  873. time_inter = rfftfreq(N_target, d = 1/Hz)
  874. freq_2p = rfftfreq(N, d = 1/Hz)
  875. interp_func = interp1d(freq_2p, ff_mean[:normalize], kind='linear')
  876. ff_mean = interp_func(time_inter)
  877. normalize = int(N_target/2)+1
  878. if roi == roi_to_plot:
  879. plt.figure()
  880. plt.plot(rfftfreq(N_target, d = 1/Hz), ff_mean[:normalize], color = col)
  881. plt.xlabel('Frequency[Hz]',fontsize = 24)
  882. plt.ylabel('Amplitude',fontsize = 24)
  883. plt.xticks(fontsize =22)
  884. plt.yticks(fontsize =22)
  885. plt.tight_layout()
  886. if path_fig != None:
  887. plt.savefig(path_fig + 'spectrum_ROI_' + str(roi) + '_' + str(HZ[k]) + '_LB.pdf', transparent = True)
  888. # extract peaks
  889. if scope == 'LB':
  890. peaks = find_peaks(ff_mean[:normalize],prominence = 0.7)
  891. else:
  892. peaks = find_peaks(ff_mean,prominence = 0.7)
  893. if len(peaks[0])>0:
  894. # Find the index from the maximum peak
  895. i_max_peak = peaks[0][np.argmax(ff_mean[peaks[0]])]
  896. #second_highest_peak_index = peaks[0][np.argpartition(ff_mean[peaks[0]],-2)[-2]]
  897. # Find the x value from that index
  898. x_max = rfftfreq(N_target, d = 1/Hz)[i_max_peak]
  899. peak_freq[k,roi] = x_max
  900. #if top_peaks == 2:
  901. # peak_freq[k,roi+dffs.shape[0]] = rfftfreq(N_target, d = 1/Hz) [second_highest_peak_index]
  902. b=b+2 # move to next pair
  903. return(peak_freq)
  904. def crosscorr_sort_corr(dffs,stimulus,threshold_test,cutoff,frame_rate):
  905. """
  906. Description
  907. ----------
  908. This function computes extract the number of ROIs for each correlation coefficient between activity and the auditory stimulus
  909. ----------
  910. Parameters
  911. ----------
  912. dffs (np.ndarray)
  913. Array containing the calcium activity over time, each row is an ROI.
  914. stimulus (np.ndarray)
  915. array containing the auditory stimulus
  916. threshold_test (np.ndarray)
  917. Contains all of the coefficient values over which to extract the number of ROIs
  918. cutoff (float)
  919. threshold in percentage to use when extracting the top X% based on correlation
  920. max_lag (float)
  921. the lag over which to compute the cross correlation
  922. frame_rate (float)
  923. frame rate of the scope
  924. ----------
  925. Returns
  926. ----------
  927. n_roi
  928. the number of ROIs extracted for each correlation coefficient
  929. corr_coeff
  930. array containing the correlation coefficient of the top 'cutoff'% of ROIs with the highest correlation with the stimulus
  931. ----------
  932. """
  933. # Normalize dffs and the stimulus
  934. dffs_mean = dffs.mean(axis=1, keepdims=True)
  935. dffs_std = dffs.std(axis=1, keepdims=True)
  936. dffs_normalized = (dffs - dffs_mean) / dffs_std
  937. stimulus_normalized = (stimulus - stimulus.mean()) / stimulus.std()
  938. # Compute correlations
  939. correlations = np.dot(dffs_normalized, stimulus_normalized.T) / dffs_normalized.shape[1]
  940. correlations = correlations.flatten()
  941. rois = np.arange(0,dffs.shape[0])
  942. rois = rois[~np.isnan(correlations)]
  943. correlations = correlations[~np.isnan(correlations)]
  944. #sort the correlation and roi index
  945. sorted_indices = np.argsort(correlations)
  946. sorted_corr = correlations[sorted_indices]
  947. index_cutoff = int(dffs.shape[0] * (cutoff/100))
  948. corr_coeff = sorted_corr[-index_cutoff:]
  949. n_roi = []
  950. for t in threshold_test:
  951. n_roi.append( np.where(sorted_corr>t)[0].shape[0] )
  952. return(n_roi, corr_coeff)
  953. def assign_depths(n_rois: int, slice_depths: np.ndarray):
  954. """
  955. Assigns depths to each ROI given the total number of ROIs and slice depths.
  956. Parameters
  957. ----------
  958. n_rois : int
  959. Total number of ROIs (number of rows in your 2D array).
  960. slice_depths : np.ndarray
  961. 1D array of shape (n_slices,) containing the depth for each slice.
  962. Returns
  963. -------
  964. roi_depths : np.ndarray
  965. 1D array of shape (n_rois,) where each entry is the depth of the slice
  966. corresponding to that ROI.
  967. """
  968. n_slices = len(slice_depths)
  969. rois_per_slice = n_rois // n_slices # assumes equal number of ROIs per slice
  970. if n_rois % n_slices != 0:
  971. raise ValueError("Number of ROIs is not evenly divisible by number of slices.")
  972. # Repeat each slice depth rois_per_slice times
  973. roi_depths = np.repeat(slice_depths, rois_per_slice)
  974. return roi_depths
  975. def plot_distribution_peaks_fourier_ROIs(freq_block,scope, color, path):
  976. HZ = [0.25,0.5,1,2,3,5]
  977. # define bins
  978. if scope == 'LB':
  979. bins=np.arange(0,14.040,0.40)
  980. xlim = 14
  981. if scope == '2p':
  982. bins = np.arange(0,1.1,0.08)
  983. xlim = 1.1
  984. # Plot histogram
  985. for k in range(len(freq_block)):
  986. plt.figure()
  987. plt.hist(freq_block[k], bins=bins, color = color)
  988. plt.xticks(fontsize = 22)
  989. plt.yticks(fontsize = 22)
  990. plt.xlabel('Frequency (Hz)', fontsize = 24)
  991. plt.ylabel('Count', fontsize = 24)
  992. plt.xlim(0,xlim)
  993. plt.tight_layout()
  994. if path != None:
  995. plt.savefig(path + 'hist_fourier_peaks_' + str(HZ[k]) + '_' + scope + '.pdf', transparent = True)
  996. def circular_shift_null_corr_prestandardized(
  997. dffs_z: np.ndarray, # (n_rois, T) mean-zero (z-scored) ROI traces
  998. stim_z: np.ndarray, # (T,) mean-zero (z-scored) stimulus
  999. n_shuffles: int,
  1000. exclude_lags: int = 0, # ignored when 'shifts' is provided
  1001. seed: Optional[int] = None,
  1002. batch_size: int = 256, # shifts per batch (tune for RAM/BLAS)
  1003. dtype: np.dtype = np.float32,
  1004. shifts: Optional[np.ndarray] = None, # (n_shuffles,) specific circular shifts to use
  1005. ) -> Tuple[np.ndarray, np.ndarray]:
  1006. """
  1007. Compute Pearson correlations between each ROI and circularly-shifted stimulus versions.
  1008. If 'shifts' is provided (ints in [0, T)), it is used directly and 'exclude_lags' is ignored.
  1009. Otherwise, draws 'n_shuffles' random shifts, optionally excluding small lags.
  1010. Returns
  1011. -------
  1012. r_null : (n_rois, n_shuffles) correlations (one column per shift)
  1013. shifts : (n_shuffles,) the shift used for each column (np.int64)
  1014. """
  1015. X = np.asarray(dffs_z, dtype=dtype, order="C")
  1016. s = np.asarray(stim_z, dtype=dtype).reshape(-1)
  1017. n_rois, T = X.shape
  1018. if s.shape[0] != T:
  1019. raise ValueError("stim_z length must equal number of columns in dffs_z")
  1020. Xnorm = np.linalg.norm(X, axis=1, keepdims=True).astype(dtype)
  1021. Xnorm[Xnorm == 0] = 1.0
  1022. Xu = X / Xnorm
  1023. s_norm = float(np.linalg.norm(s))
  1024. if s_norm == 0:
  1025. return np.zeros((n_rois, n_shuffles), dtype=dtype), np.zeros(n_shuffles, dtype=np.int64)
  1026. s_u = s / s_norm
  1027. if shifts is not None:
  1028. shifts = np.asarray(shifts, dtype=np.int64).ravel()
  1029. if shifts.size != n_shuffles:
  1030. raise ValueError("len(shifts) must equal n_shuffles.")
  1031. shifts %= T
  1032. else:
  1033. rng = np.random.default_rng(seed)
  1034. if exclude_lags <= 0:
  1035. shifts = rng.integers(0, T, size=n_shuffles, endpoint=False, dtype=np.int64)
  1036. else:
  1037. mask = np.ones(T, dtype=bool)
  1038. mask[:exclude_lags+1] = False
  1039. if exclude_lags > 0:
  1040. mask[T-exclude_lags:] = False
  1041. allowed = np.nonzero(mask)[0].astype(np.int64)
  1042. if allowed.size == 0:
  1043. raise ValueError("Exclusion window too large: no shifts remain.")
  1044. shifts = rng.choice(allowed, size=n_shuffles, replace=True)
  1045. r_null = np.empty((n_rois, n_shuffles), dtype=dtype)
  1046. t = np.arange(T, dtype=np.int64)[:, None]
  1047. for start in range(0, n_shuffles, batch_size):
  1048. end = min(start + batch_size, n_shuffles)
  1049. k = shifts[start:end]
  1050. idx = (t - k[None, :]) % T
  1051. S_batch = s_u[idx]
  1052. r_null[:, start:end] = Xu @ S_batch
  1053. return r_null, shifts
  1054. def allowed_circ_shifts(T, fs, period_sec, E_sec):
  1055. period = int(round(period_sec * fs))
  1056. E = int(round(E_sec * fs))
  1057. allowed = np.ones(T, dtype=bool)
  1058. if period > 0:
  1059. n_mult = int(np.ceil(T / period)) + 1
  1060. for k in range(n_mult):
  1061. center = (k * period) % T
  1062. lo = (center - E) % T
  1063. hi = (center + E) % T
  1064. if lo <= hi:
  1065. allowed[lo:hi+1] = False
  1066. else:
  1067. allowed[:hi+1] = False
  1068. allowed[lo:] = False
  1069. if not np.any(allowed):
  1070. raise ValueError("Exclusion too wide—no shifts left.")
  1071. return np.flatnonzero(allowed)
  1072. def block_permute_null_corr_prestandardized(
  1073. dffs_z: np.ndarray, # (n_rois, T), mean-zero (z-scored)
  1074. stim_z: np.ndarray, # (T,), mean-zero (z-scored)
  1075. fs: float, # Hz
  1076. n_shuffles: int,
  1077. block_sec: float = 10.0, # seconds
  1078. jitter_within_block: int = 0,# ±samples to circularly roll inside each block
  1079. seed: Optional[int] = None,
  1080. batch_size: int = 128,
  1081. dtype: np.dtype = np.float32,
  1082. forbid_identity_perm: bool = True,
  1083. ) -> Tuple[np.ndarray, np.ndarray]:
  1084. """
  1085. Permute stimulus in contiguous blocks (optionally with within-block jitter) and
  1086. compute Pearson r for each ROI vs each permuted stimulus.
  1087. Returns:
  1088. r_null : (n_rois, n_shuffles)
  1089. perms : (n_shuffles, n_blocks)
  1090. """
  1091. X = np.asarray(dffs_z, dtype=dtype, order="C")
  1092. s = np.asarray(stim_z, dtype=dtype).reshape(-1)
  1093. n_rois, T = X.shape
  1094. if s.shape[0] != T:
  1095. raise ValueError("stim_z length must equal number of columns in dffs_z")
  1096. Xnorm = np.linalg.norm(X, axis=1, keepdims=True).astype(dtype)
  1097. Xnorm[Xnorm == 0] = 1.0
  1098. Xu = X / Xnorm
  1099. s_norm = float(np.linalg.norm(s))
  1100. if s_norm == 0:
  1101. return np.zeros((n_rois, n_shuffles), dtype=dtype), np.empty((n_shuffles, 0), dtype=np.int64)
  1102. s_u = s / s_norm
  1103. block_len = int(round(block_sec * fs))
  1104. if block_len <= 0:
  1105. raise ValueError("block_sec too small for the given fs")
  1106. BI = _make_block_indices(T, block_len) # (block_len, n_blocks)
  1107. block_len_eff, n_blocks = BI.shape
  1108. rng = np.random.default_rng(seed)
  1109. r_null = np.empty((n_rois, n_shuffles), dtype=dtype)
  1110. perms_used = np.empty((n_shuffles, n_blocks), dtype=np.int64)
  1111. for start in range(0, n_shuffles, batch_size):
  1112. end = min(start + batch_size, n_shuffles)
  1113. B = end - start
  1114. perms = np.empty((B, n_blocks), dtype=np.int64)
  1115. for b in range(B):
  1116. if forbid_identity_perm and n_blocks == 1:
  1117. perms[b] = np.array([0], dtype=np.int64)
  1118. else:
  1119. while True:
  1120. p = rng.permutation(n_blocks)
  1121. if not forbid_identity_perm or np.any(p != np.arange(n_blocks)):
  1122. break
  1123. perms[b] = p
  1124. if jitter_within_block > 0:
  1125. J = int(jitter_within_block)
  1126. jit = rng.integers(-J, J + 1, size=(B, n_blocks), endpoint=True, dtype=np.int64)
  1127. else:
  1128. jit = np.zeros((B, n_blocks), dtype=np.int64)
  1129. idx_batch = np.empty((T, B), dtype=np.int64)
  1130. for b in range(B):
  1131. cols = []
  1132. for j in range(n_blocks):
  1133. col = BI[:, perms[b, j]]
  1134. if jitter_within_block != 0:
  1135. col = np.roll(col, jit[b, j], axis=0)
  1136. cols.append(col)
  1137. idx_b = np.concatenate(cols, axis=0)[:T]
  1138. idx_batch[:, b] = idx_b
  1139. S_batch = s_u[idx_batch]
  1140. r_null[:, start:end] = Xu @ S_batch
  1141. perms_used[start:end] = perms
  1142. return r_null, perms_used
  1143. def _make_block_indices(T: int, block_len: int) -> np.ndarray:
  1144. """
  1145. Return BI of shape (block_len, n_blocks) with absolute indices per block.
  1146. Pads the final block by repeating its last index so each column has block_len rows.
  1147. """
  1148. if block_len <= 0:
  1149. raise ValueError("block_len must be positive")
  1150. n_blocks = int(np.ceil(T / block_len))
  1151. pad = n_blocks * block_len - T
  1152. idx = np.arange(T, dtype=np.int64)
  1153. if pad > 0:
  1154. idx = np.concatenate([idx, np.full(pad, idx[-1], dtype=np.int64)])
  1155. return idx.reshape(n_blocks, block_len).T # (block_len, n_blocks)
  1156. def permutation_pvals_one_sided(r_obs, r_null, alternative="greater"):
  1157. """
  1158. One-sided permutation p-values with finite-sample correction.
  1159. p_i = (1 + #{ r_null >= r_obs_i }) / (N_i + 1) if alternative == "greater"
  1160. p_i = (1 + #{ r_null <= r_obs_i }) / (N_i + 1) if alternative == "less"
  1161. Parameters
  1162. ----------
  1163. r_obs : array-like, shape (n_rois,)
  1164. Observed statistics (e.g., correlations) per ROI.
  1165. r_null : array-like, shape (n_rois, n_perm) or (n_perm,)
  1166. Null statistics from permutations. If 2D, each row is that ROI's null.
  1167. If 1D, a pooled null used for all ROIs.
  1168. alternative : {"greater","less"}, default "greater"
  1169. Direction of the one-sided test.
  1170. Returns
  1171. -------
  1172. pvals : ndarray, shape (n_rois,)
  1173. One-sided permutation p-values.
  1174. """
  1175. r_obs = np.asarray(r_obs, dtype=np.float64).reshape(-1)
  1176. r_null = np.asarray(r_null, dtype=np.float64)
  1177. if alternative not in ("greater", "less"):
  1178. raise ValueError("alternative must be 'greater' or 'less'.")
  1179. if r_null.ndim == 1:
  1180. valid = ~np.isnan(r_null)
  1181. N = int(valid.sum())
  1182. if N == 0:
  1183. return np.ones_like(r_obs)
  1184. rn = r_null[valid][None, :]
  1185. if alternative == "greater":
  1186. counts = (rn >= r_obs[:, None]).sum(axis=1)
  1187. else:
  1188. counts = (rn <= r_obs[:, None]).sum(axis=1)
  1189. pvals = (1.0 + counts) / (N + 1.0)
  1190. return pvals
  1191. elif r_null.ndim == 2:
  1192. if r_null.shape[0] != r_obs.shape[0]:
  1193. raise ValueError("For per-ROI nulls, r_null must have shape (n_rois, n_perm).")
  1194. valid = ~np.isnan(r_null)
  1195. N = valid.sum(axis=1).astype(np.int64)
  1196. if alternative == "greater":
  1197. ge = (r_null >= r_obs[:, None]) & valid
  1198. else:
  1199. ge = (r_null <= r_obs[:, None]) & valid
  1200. counts = ge.sum(axis=1)
  1201. denom = N + 1.0
  1202. denom[denom == 0] = np.inf
  1203. pvals = (1.0 + counts) / denom
  1204. pvals[np.isinf(denom)] = 1.0
  1205. return pvals
  1206. else:
  1207. raise ValueError("r_null must be 1D (pooled) or 2D (per-ROI).")

functions.py at commit 55570f4, no license · at the source

Overview

Authors: Wayan Gauthey1, Albert Lin1,2, Osama M Ahmed3, Andrew M Leifer1,2,4, Mala Murthy1,2,5, Stephan Y Thiberge1,5
  1. Princeton Neuroscience Institute, Princeton University, Princeton, NJ USA
  2. Center for the Physics of Biological Function, Princeton University, Princeton, NJ USA
  3. Department of Psychology, University of Washington, Seattle, WA USA
  4. Department of Physics, Princeton University, Princeton, NJ USA
  5. Bezos Center for Neural Circuit Dynamics, Princeton University, Princeton, NJ USA
Institutions: Princeton University (United States); University of Washington (United States)
Journal: Nature communications, volume 17, issue 1, article 5810
Dates: received 8 July 2025; accepted 16 April 2026; published online 28 April 2026
Type: Research article · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1038/s41467-026-72437-1 · PMID 42050341 · PMCID PMC13328740 · OpenAlex W7157194336
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: drosophila (organism), systems (subfield)
Methods: Spectral & time-frequency, Statistics, Machine learning, Preprocessing, Graphs, fMRI & imaging
Keywords: Neuroscience, Biological techniques
MeSH: Brain*, Drosophila melanogaster*, Animals, Calcium, Calcium Signaling, Courtship, Female, Male, Neurons (* major topic)
Topic: Neurobiology and Insect Physiology Research (Cellular and Molecular Neuroscience, Neuroscience), according to OpenAlex
Citations: cited by 2 papers (Europe PMC); 56 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.

Repositories

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

murthylab/fly-vr

License: none: the authors keep all their rights
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: 1a8705e9d9dd9b94d25addee484f2d966ccefa69, 12 February 2024
Languages: Python (59), MATLAB (17), C (1)
Size: 549 files, 77 scripts
Software Heritage: archived
Found in: “Code availability”
Holds: README, environment (requirements.txt, setup.cfg, setup.py), tests
Not found: license file, CITATION.cff, continuous integration, documentation
Tools: NumPy (25 files), h5py (10 files), Matplotlib (4 files), PsychoPy (3 files), SciPy (3 files), Signal Processing Toolbox (2 files), pandas (2 files), OpenCV (1 file), Pillow (1 file)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
78 files

murthylab/lightbead-analysis

License: none: the authors keep all their rights
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: 55570f4ad028bfd19ab63d5b9b13430803cb277c, 6 April 2026
Languages: Python (43), Shell (8), MATLAB (3), Jupyter (1)
Size: 103 files, 55 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, 1 notebook
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (31 files), SciPy (17 files), Matplotlib (12 files), pandas (8 files), ANTs (7 files), h5py (6 files), scikit-image (2 files), scikit-learn (2 files), Image Processing Toolbox (1 file), OpenCV (1 file), Plotly (1 file), SimpleITK (1 file), statsmodels (1 file)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
56 files

Code availability statement

The paper has a code 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.1038/s41467-026-72437-1.

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:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 132 scripts, each with its path and the digest of its content;
  • 10 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.1038/s41467-026-72437-1.

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 1, 30 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 6 authors, 2 keywords, 9 MeSH terms, 52 references.

Cite

This paper

Gauthey, W., Lin, A., Ahmed, O. M., Leifer, A. M., Murthy, M., & Thiberge, S. Y. (2026). High-speed whole-brain imaging in Drosophila. Nature communications, 17(1), 5810. https://doi.org/10.1038/s41467-026-72437-1

BibTeX

@article{gauthey2026high,
author = {Gauthey, Wayan and Lin, Albert and Ahmed, Osama M and Leifer, Andrew M and Murthy, Mala and Thiberge, Stephan Y},
title = {{High-speed whole-brain imaging in Drosophila}},
journal = {Nature communications},
year = {2026},
month = apr,
volume = {17},
number = {1},
pages = {5810},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/s41467-026-72437-1},
url = {https://doi.org/10.1038/s41467-026-72437-1},
pmid = {42050341},
pmcid = {PMC13328740}
}

RIS

TY - JOUR
AU - Gauthey, Wayan
AU - Lin, Albert
AU - Ahmed, Osama M
AU - Leifer, Andrew M
AU - Murthy, Mala
AU - Thiberge, Stephan Y
TI - High-speed whole-brain imaging in Drosophila
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/04/28
VL - 17
IS - 1
SP - 5810
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/s41467-026-72437-1
UR - https://doi.org/10.1038/s41467-026-72437-1
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41467-026-72437-1",
"type": "article-journal",
"title": "High-speed whole-brain imaging in Drosophila",
"container-title": "Nature communications",
"author": [
{
"family": "Gauthey",
"given": "Wayan"
},
{
"family": "Lin",
"given": "Albert"
},
{
"family": "Ahmed",
"given": "Osama M"
},
{
"family": "Leifer",
"given": "Andrew M"
},
{
"family": "Murthy",
"given": "Mala"
},
{
"family": "Thiberge",
"given": "Stephan Y"
}
],
"container-title-short": "Nat Commun",
"volume": "17",
"issue": "1",
"page": "5810",
"DOI": "10.1038/s41467-026-72437-1",
"PMID": "42050341",
"PMCID": "PMC13328740",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41467-026-72437-1",
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
28
]
]
}
}

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/s41586-026-10735-w [code]
Distributed control circuits across a brain-and-cord connectome.
Journal: Nature
In common: Plotly, scikit-image, scikit-learn, 4 other tools, drosophila, 4 references, author Mala Murthy
[2] doi:10.1038/s41467-026-72057-9 [code]
Sex-specific behavioral feedback modulates sensorimotor processing and drives flexible social behavior.
Journal: Nature communications
In common: OpenCV, scikit-image, h5py, 7 other tools, drosophila, systems, 4 references
[3] doi:10.1016/j.xpro.2026.104659 [code]
Protocol for simultaneous in vivo two-photon imaging and locomotion quantification during olfactory stimulation and pharmacology in walking Drosophila.
Journal: STAR protocols
In common: OpenCV, h5py, Pillow, 4 other tools, drosophila, 6 references
[4] doi:10.1038/s41467-026-72710-3 [code]
A modular multi-color fluorescence microscope for simultaneous tracking of cellular activity and behavior.
Journal: Nature communications
In common: OpenCV, scikit-image, Pillow, 6 other tools, drosophila, 5 references
[5] doi:10.3389/fnsys.2026.1822122 [code]
Convergence-divergence circuits for multimodal integration of innate and learned opponent valences.
Journal: Frontiers in systems neuroscience
In common: Plotly, scikit-image, h5py, 6 other tools, systems, 4 references
[6] doi:10.1038/s41592-026-03179-7 [code]
Voltage imaging of neurons distributed across entire brains of larval zebrafish.
Journal: Nature methods
In common: OpenCV, scikit-image, h5py, 3 other tools, systems, 6 references
[7] doi:10.1111/ejn.70582 [code]
Multifiber Array-Based Photometry System for Multiregional Functional Mapping in the Mouse Brain.
Journal: The European journal of neuroscience
In common: SimpleITK, OpenCV, scikit-image, 5 other tools, systems, 4 references
[8] doi:10.1002/alz.71649 [code]
Postmortem brain MRI reveals differential associations of subcortical and limbic volumes with cortical thinning and neurodegenerative pathologies.
Journal: Alzheimer's & dementia : the journal of the Alzheimer's Association
In common: SimpleITK, ANTs, Plotly, 9 other tools
[9] doi:10.1038/s41593-026-02232-0 [code]
Entorhinal cortex represents task-relevant remote locations independently of CA1.
Journal: Nature neuroscience
In common: OpenCV, scikit-image, h5py, 9 other tools, systems
[10] doi:10.21203/rs.3.rs-9326213/v1 [code]
Multi-task fMRI outperforms resting-state fMRI for revealing task-invariant organization of the human brain
Journal: Research Square (preprint)
In common: PsychoPy, ANTs, Plotly, 8 other tools

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.