OSCR

Distinct sensorimotor encoding in tuft dendrites and somata associated with action, correction, and learning.

Code ↔ Paper

5 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 5 matches
  1. [1] § Methods › Behavioral paradigm ↔ Figures/Fig1.ipynb, lines 1037–1153 · score 0.70 · post shift early, post shift late, pre shift early, pre shift late, trajectory, phases
  2. [2] § Results › Contingency-dependent motor skill learning ↔ Figures/Fig1.ipynb, lines 254–291 · score 0.54 · Motor Error Right, Decision Error Right, post shift, pre shift, lickport, cameras
  3. [3] § Results › Contingency-dependent motor skill learning ↔ Figures/Fig1.ipynb, lines 254–291 · score 0.53 · motor error, decision errors, post shift, pre shift, came, lickports
  4. [4] § Methods › Trial-alignment and ROI grouping ↔ Figures/Fig5.ipynb, lines 2016–2161 · score 0.52 · trial traces, GO cue, CA, contact, CL, lick
  5. [5] § Methods › Behavioral paradigm ↔ Figures/js_manuscript_final.py, lines 720–851 · score 0.51 · BPOD State, delay, day, position, behavior, animals

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

Jupyter notebook · 1,316 lines · 47 KB · MIT · 3 matches

  1. # %% [markdown]
  2. # # init
  3. # %%
  4. import os
  5. import sys
  6. import copy
  7. import glob
  8. import numpy as np
  9. import matplotlib.pyplot as plt
  10. from tqdm.auto import tqdm
  11. import pickle
  12. from scipy import stats
  13. import importlib
  14. import time
  15. import tifffile as tf
  16. import shutil
  17. from matplotlib.backends.backend_pdf import PdfPages
  18. import json
  19. import seaborn as sns
  20. #Some Figure Preferences
  21. plt.rcParams['font.family'] = 'sans-serif'
  22. plt.rcParams['font.sans-serif'] = ['Arial']
  23. plt.rcParams['pdf.fonttype'] = 42 # embed fonts in PDF (Type 42)
  24. plt.rcParams['axes.unicode_minus'] = False # use ASCII minus sign
  25. plt.rcParams['mathtext.default'] = 'regular'
  26. #IMPORTANT two options for setting paths here:
  27. #OPTION 1: Automatically determine the parent directory of the notebook's directory
  28. try:
  29. import ipynbname
  30. NOTEBOOK_PATH = ipynbname.path()
  31. PARENT_DIR = NOTEBOOK_PATH.parent.parent # parent of the notebook's directory
  32. DATA_DIR = os.path.join(PARENT_DIR, "Data")
  33. print("NOTEBOOK_PATH: " + str(NOTEBOOK_PATH))
  34. except:
  35. print("UNABLE TO DETERMINE CURRENT SCRIPT LOCAITON PLEASE PROVIDE PATHS MANUALLY")
  36. #OPTION 2: Manually set PARENT_DIR and DATA_DIR if needed
  37. # PARENT_DIR = ".../Scheib_2026/"
  38. # DATA_DIR = ".../Scheib_2026/Data/"
  39. # DATA_DIR = os.path.join(PARENT_DIR,'data')
  40. FIG_EXPORT_DIR = os.path.join(PARENT_DIR, "figurePanels", "Fig1")
  41. PDF_export_active = False
  42. if PDF_export_active:
  43. os.makedirs(FIG_EXPORT_DIR, exist_ok=True)
  44. print("PARENT_DIR: " + str(PARENT_DIR))
  45. print("DATA_DIR: " + str(DATA_DIR))
  46. print("FIG_EXPORT_DIR: " + str(FIG_EXPORT_DIR))
  47. #IMPORTANT: The following class and function are used to conditionally save figures to PDF based on the PDF_export_active flag.
  48. # If PDF_export_active is True, figures will be saved to the specified path; if False, the savefig calls will be ignored.
  49. class _NoPdf:
  50. """Null stand-in for PdfPages: accepts savefig calls and writes nothing."""
  51. def __enter__(self): return self
  52. def __exit__(self, *exc): return False
  53. def savefig(self, *args, **kwargs): pass
  54. def maybe_pdf(path, active=None):
  55. active = PDF_export_active if active is None else active
  56. return PdfPages(path) if active else _NoPdf()
  57. #OPTIONAL Load from specific directory if needed (uncomment the following lines)
  58. if os.path.exists(os.path.join(NOTEBOOK_PATH, "js_manuscript_final.py")):
  59. print('FOUND js_manuscript_final.py')
  60. path = os.path.join(NOTEBOOK_PATH, "js_manuscript_final.py")
  61. spec = importlib.util.spec_from_file_location("jsm", path)
  62. jsm = importlib.util.module_from_spec(spec)
  63. spec.loader.exec_module(jsm)
  64. print(jsm.__file__)
  65. else:
  66. #IMPORTANT Load the manuscript functions from PYTHONPATH
  67. import js_manuscript_final as jsm
  68. #OPTIONAL Reload the module to ensure the latest version is used
  69. %load_ext autoreload
  70. %autoreload 2
  71. # %%
  72. #IMPORTANT Load the masterData dictionary, preferably from the data repo reduced file size versions
  73. masterData = jsm.masterData_loading(DATA_DIR)
  74. # mdDir = '.../mainDS'
  75. # mdTitle = 'allSlim3'
  76. # masterData = pickle.load(open(fr'{mdDir}/{mdTitle}.p', 'rb'))
  77. anms = list(masterData.keys())
  78. #General params
  79. trialStart = 0.15
  80. sampleEnd = trialStart+1.2
  81. delayEnd = sampleEnd+1.25
  82. anatKeys = ['dendrites', 'somas']
  83. frs = [44.6, 22.8]
  84. # %%
  85. dffKeys = []
  86. factors = [4,2]
  87. Z = 'std_only'
  88. phases2 = ['pre', 'preShift_early', 'preShift_late', 'post', 'late', 'day-1_beg', 'day-1_mid', 'day-1_end', 'shiftDay_post', 'shiftDay_pre', 'early']
  89. windowSize = []
  90. allAnmParams = jsm.get_allAnmParams(masterData, anms, frs, factors, Z, phases2, dffKeys)
  91. # %% [markdown]
  92. # # quick stats
  93. # %%
  94. anm_nSess = []
  95. anm_nSessPre = []
  96. anm_nSessPost = []
  97. anm_nTrials = []
  98. anm_nTrialsPre = []
  99. anm_nTrialsPost = []
  100. for a, anm in enumerate(anms):
  101. anmData = allAnmParams[anm]
  102. tracker = anmData['tracker']
  103. days = np.unique(tracker[:,0])
  104. anm_nSess.append(len(days))
  105. anm_nSessPre.append(np.sum(days<0))
  106. anm_nSessPost.append(np.sum(days>=0))
  107. anmNT = [np.sum(tracker[:,0]==day) for day in days]
  108. anmNTPre = [np.sum(tracker[:,0]==day) for day in days if day <0]
  109. anmNTPost = [np.sum(tracker[:,0]==day) for day in days if day >= 0]
  110. anm_nTrials.append(anmNT)
  111. anm_nTrialsPre.append(anmNTPre)
  112. anm_nTrialsPost.append(anmNTPost)
  113. print('median nSessions: ', f'{np.median(anm_nSess)}')
  114. print('median nSessions Pre: ', f'{np.median(anm_nSessPre)}', f'{np.std(anm_nSessPre):.03f}')
  115. print('median nSessions Post: ', f'{np.median(anm_nSessPost)}')
  116. print('')
  117. print('median nTrials: ', f'{np.median(np.hstack(anm_nTrials))}')
  118. print('median nTrials Pre: ', f'{np.median(np.hstack(anm_nTrialsPre))}')
  119. print('median nTrials Post: ', f'{np.median(np.hstack(anm_nTrialsPost))}')
  120. # %%
  121. print('nAnms:', len(anm_nSessPost))
  122. print('nAnms with postShift:', np.sum(np.hstack(anm_nSessPost)!=0))
  123. # %%
  124. nanimals = []
  125. nsessions = []
  126. nTrials = []
  127. for a, anm in enumerate(anms):
  128. print("=============================")
  129. print(f'{anm}')
  130. anmData = allAnmParams[anm]
  131. tracker = anmData['tracker']
  132. anm_sessions = []
  133. anm_trials = []
  134. for p, phase in enumerate(['preShift_early', 'preShift_late', 'early', 'late']):
  135. mask = anmData['shiftMask'][phase]
  136. if 'early' in phase:
  137. print('----')
  138. print(f' {phase} mask/total {np.sum(mask==True)}/{len(mask)}')
  139. if np.sum(mask)>0:
  140. anm_sessions.append(len(np.unique(tracker[mask,0])))
  141. anm_trials.append(np.sum(mask))
  142. else:
  143. anm_trials.append(np.nan)
  144. anm_sessions.append(np.nan)
  145. nsessions.append(anm_sessions)
  146. nTrials.append(anm_trials)
  147. nsessions = np.vstack(nsessions)
  148. nTrials = np.vstack(nTrials)
  149. print("=============================")
  150. print('sessions:', np.nanmedian(nsessions, axis = 0), '+/-', np.nanstd(nsessions, axis = 0))
  151. print('trials', np.nanmedian(nTrials, axis = 0), '+/-', np.nanstd(nTrials, axis = 0))
  152. # %%
  153. print('median nSessions: ', f'{np.nanmedian(nsessions, axis = 0)}')
  154. print('median nSessions Pre: ', f'{np.median(anm_nSessPre)}', f'{np.std(anm_nSessPre):.03f}')
  155. print('median nSessions Post: ', f'{np.median(anm_nSessPost)}')
  156. print('')
  157. print('median nTrials: ', f'{np.median(np.hstack(anm_nTrials))}')
  158. print('median nTrials Pre: ', f'{np.median(np.hstack(anm_nTrialsPre))}')
  159. print('median nTrials Post: ', f'{np.median(np.hstack(anm_nTrialsPost))}')
  160. # %% [markdown]
  161. # # example 1 images and angle calc descriptions
  162. # %%
  163. anm = 'B00002213999'
  164. days = ['240320', '240321', '240322', '240323', '240324', '240325', '240326', '240327', '240328']
  165. # %%
  166. anmData = masterData[anm]['behavior']
  167. licks = jsm.get_contacts(anmData['trajs'])
  168. cTimes = jsm.get_cTimes2(anmData)
  169. tracker = jsm.get_tracker(anmData)
  170. azi,ele = jsm.get_aziEle(anmData, licks)
  171. azi = azi+90
  172. cDists = jsm.get_relDistances(azi, ele, tracker)
  173. me, de, CB = jsm.get_errors_LDA(azi, ele, tracker, returnLDA = True)
  174. recDays = np.hstack([anmData['mats'][m]['Info']['SessionDate'] for m in range(len(anmData['mats']))])
  175. uniqueDays = np.unique(tracker[:,0])
  176. for i in range(len(recDays)):
  177. print(uniqueDays[i], recDays[i])
  178. rp = anmData['port_locs']['pre_shift']['right']
  179. print(f'Right {rp}')
  180. lp = anmData['port_locs']['pre_shift']['left']
  181. print(f'Left {lp}')
  182. # %%
  183. dayIdx = 4
  184. idx1 = 1532 # CR
  185. idx2 = 1531 # MER
  186. idx3 = 1546 # DER
  187. t1 = tracker[idx1,1]
  188. t2 = tracker[idx2,1]
  189. t3 = tracker[idx3,1]
  190. trials = [t1,t2,t3]
  191. camIDs = ['20160684', '20302479']
  192. # %%
  193. try:
  194. os.mkdir(os.path.join(DATA_DIR,'examples','fig1_behav_examples'))
  195. except FileExistsError:
  196. pass
  197. try:
  198. os.mkdir(os.path.join(DATA_DIR,'examples','fig1_behav_examples',anm))
  199. except FileExistsError:
  200. pass
  201. try:
  202. os.mkdir(os.path.join(DATA_DIR,'examples','fig1_behav_examples',anm,days[dayIdx]))
  203. except FileExistsError:
  204. pass
  205. try:
  206. os.mkdir(os.path.join(DATA_DIR,'examples','fig1_behav_examples',anm,days[dayIdx],'run1'))
  207. except FileExistsError:
  208. pass
  209. exampleDir = os.path.join(DATA_DIR,'examples','fig1_behav_examples',anm,days[dayIdx],'run1')
  210. print(f'exampleDir: {exampleDir}')
  211. dayRaw = jsm.unpickler(exampleDir,'rawBehavior')
  212. if os.path.exists(f'{exampleDir}/{camIDs[1]}_trial_{t1+1}_0.tiff'):
  213. print('Loading files from exampleDir')
  214. c1 = tf.TiffFile(f'{exampleDir}/{camIDs[1]}_trial_{t1+1}_0.tiff').asarray()
  215. c2 = tf.TiffFile(f'{exampleDir}/{camIDs[1]}_trial_{t2+1}_0.tiff').asarray()
  216. c3 = tf.TiffFile(f'{exampleDir}/{camIDs[1]}_trial_{t3+1}_0.tiff').asarray()
  217. bottomCam = tf.TiffFile(f'{exampleDir}/{camIDs[1]}_trial_{t1+1}_0.tiff').asarray()
  218. sideCam = tf.TiffFile(f'{exampleDir}/{camIDs[0]}_trial_{t1+1}_0-1.tiff').asarray()
  219. # %%
  220. fontsize = 12
  221. figName = 'Fig1_ErrorType_lickports.pdf'
  222. bottCont = [0,200]
  223. fig, axs = plt.subplots(1,3,tight_layout = True, figsize = [5,2])
  224. imgs = [c1,c2,c3]
  225. trackings = [dayRaw['side'], dayRaw['bottom']]
  226. titles = ['Correct Right', 'Motor Error Right', 'Decision Error Right']
  227. trials = [t1,t2,t3]
  228. frames = [132,355,315]
  229. for a, ax in enumerate(axs):
  230. ax.imshow(imgs[a][frames[a],:,:], vmin = bottCont[0],vmax=bottCont[1], cmap = 'Greys_r',interpolation='none')
  231. ax.spines[['top','right','left','bottom']].set_visible(False)
  232. ax.set_xticks([])
  233. ax.set_yticks([])
  234. pColors = [(0,0,1),(1,0,0)]
  235. for s, side in enumerate(['right', 'left']):
  236. preLoc = anmData['port_locs']['pre_shift'][side][:2]/anmData['cameraCal']['bottomCam']
  237. print(f'{s} {side} {preLoc}')
  238. postLoc = anmData['port_locs']['post_shift'][side][:2]/anmData['cameraCal']['bottomCam']
  239. locs = [preLoc, postLoc]
  240. linestyles = [':','-']
  241. alphas = [0.3,0.9]
  242. for l, loc in enumerate(locs):
  243. ax.plot([loc[0]]*2, [-loc[1],-loc[1]+80], color = pColors[s], alpha = alphas[l], linestyle = linestyles[l], linewidth = 1)
  244. ax.set_title(titles[a],fontsize=fontsize)
  245. ax.set_ylim([100,300])
  246. ax.set_xlim([0,200])
  247. ax.invert_xaxis()
  248. jsm.set_dynamic_suptitle(fig,figName+"\n"+str(FIG_EXPORT_DIR),margin = 0.1,\
  249. fontsize=fontsize,color=(0,0,0),va='bottom')
  250. plt.show()
  251. with maybe_pdf(os.path.join(FIG_EXPORT_DIR,figName)) as pdf:
  252. pdf.savefig(fig,bbox_inches='tight',pad_inches=0.05,dpi=600) # saves the current figure into a pdf page
  253. # %%
  254. fig, axs = plt.subplots(2,1,tight_layout = True, figsize = [3,4])
  255. imgs = [sideCam, bottomCam]
  256. trackings = [dayRaw['side'], dayRaw['bottom']]
  257. sideCont = [0,70]
  258. bottCont = [0,200]
  259. trials = [t1]*2
  260. frames = [132]*2
  261. backFrames = 45
  262. for a, ax in enumerate(axs):
  263. print(imgs[a].shape)
  264. if a == 0:
  265. ax.imshow(imgs[a][frames[a],:,:], vmin = sideCont[0],vmax = sideCont[1], cmap = 'Greys_r',interpolation='none')
  266. else:
  267. ax.imshow(imgs[a][frames[a],:,:], vmin = bottCont[0],vmax = bottCont[1], cmap = 'Greys_r',interpolation='none')
  268. pColors = [(0,0,1),(1,0,0)]
  269. for s, side in enumerate(['right', 'left']):
  270. loc = anmData['port_locs']['post_shift'][side][:2]/anmData['cameraCal']['bottomCam']
  271. print(f'{s} {side} {loc}')
  272. ax.plot([loc[0]]*2, [-loc[1],-loc[1]+80], color = pColors[s], alpha = 0.9, linestyle = '-', linewidth = 1)
  273. ax.scatter(trackings[a][trials[a]][0,frames[a]],trackings[a][trials[a]][1,frames[a]], c = 'orange')
  274. ax.scatter(trackings[a][trials[a]][0,frames[a]-backFrames:frames[a]],trackings[a][trials[a]][1,frames[a]-backFrames:frames[a]],
  275. c = np.arange(45), cmap = 'rainbow', alpha = 0.8, s = 5)
  276. ax.spines[['top','right','left','bottom']].set_visible(False)
  277. ax.set_xticks([])
  278. ax.set_yticks([])
  279. if a == 1:
  280. ax.set_ylim([100,300])
  281. ax.set_xlim([200,0])
  282. else:
  283. ax.set_xlim([150,350])
  284. ax.set_ylim([300,100])
  285. fontsize = 12
  286. figName = 'Fig1_lick tracking.pdf'
  287. jsm.set_dynamic_suptitle(fig,figName+"\n"+str(FIG_EXPORT_DIR),margin = 0.1,\
  288. fontsize=fontsize,color=(0,0,0),va='bottom')
  289. plt.show()
  290. with maybe_pdf(os.path.join(FIG_EXPORT_DIR,figName)) as pdf:
  291. pdf.savefig(fig,bbox_inches='tight',pad_inches=0.05,dpi=600) # saves the current figure into a pdf page
  292. # %% [markdown]
  293. # # 3D lick projections
  294. # %%
  295. anm = 'B00002213889'
  296. anmData = masterData[anm]['behavior']
  297. tracker = jsm.get_tracker(anmData)
  298. licks = jsm.get_contacts(anmData['trajs'])
  299. cTimes = jsm.get_cTimes2(anmData)
  300. azi,ele = jsm.get_aziEle(anmData, licks)
  301. azi = azi+90
  302. cDists = jsm.get_relDistances(azi, ele, tracker)
  303. lickAngles = jsm.get_lickAngle(azi, ele, CB)
  304. preMask = allAnmParams[anm]['shiftMask']['pre']
  305. postMask = jsm.get_behaviorShiftMask(tracker, 'post')
  306. me, de, CB = jsm.get_errors_LDA(azi, ele, tracker, returnLDA = True)
  307. recDays = np.hstack([anmData['mats'][m]['Info']['SessionDate'] for m in range(len(anmData['mats']))])
  308. uniqueDays = np.unique(tracker[:,0])
  309. for i in range(len(recDays)):
  310. print(uniqueDays[i], recDays[i])
  311. # %%
  312. figName = 'Fig1_Alt_lick_surface_all_traj.pdf'
  313. colors = [(0,0,1),(1,0,0), (0,0,0)]
  314. labels = ['Right', 'Left']
  315. # U, V, W components for 3 axes: [x_dir, y_dir, z_dir]
  316. u_dirs = [2, 0, 0]
  317. v_dirs = [0, 2, 0]
  318. w_dirs = [0, 0, 2]
  319. axshift_x = 0
  320. axshift_y = 3
  321. axshift_z = 2
  322. exportSides = [0,1]
  323. fontsize = 12
  324. figsize = [10,3]
  325. fig = plt.figure(figsize = figsize)
  326. ax1 = fig.add_subplot(131, projection = '3d')
  327. ax2 = fig.add_subplot(132, projection = '3d')
  328. ax3 = fig.add_subplot(133, projection = '3d')
  329. plt.subplots_adjust(wspace=0.001, hspace=0.001)
  330. mouth = anmData['mouth']
  331. rp = anmData['port_locs']['pre_shift']['right']
  332. lp = anmData['port_locs']['pre_shift']['left']
  333. rad = jsm.identify_rad_3d(anmData, fraction = 1) #set a different rad with 'fraction'
  334. radI = jsm.get_radIdxs_3d(anmData, licks, rad, count_if_noCross = False)
  335. sm = tracker[:,0] < 0
  336. drad = radI[sm]
  337. dc = licks[sm,:,:]
  338. dt = tracker[sm,:]
  339. elevs = [90,30,0]
  340. azims = [-90,150,0]
  341. for a, ax in enumerate([ax1,ax2,ax3]):
  342. ax.view_init(elev = elevs[a], azim = azims[a])
  343. ax.scatter(mouth[0], mouth[1], mouth[2], color = (0,0,0), s = 50, edgecolor = (0.75,0.75,0.75), alpha = 0.6,label="Mouth",zorder=10)
  344. ax.scatter(rp[0], rp[1], rp[2], color = colors[0], s = 50, edgecolor = (0.75,0.75,0.75), marker = '^',label=labels[0]+" Port",zorder=10)
  345. ax.scatter(lp[0], lp[1], lp[2], color = colors[1], s = 50, edgecolor = (0.75,0.75,0.75), marker = '^',label=labels[1]+" Port",zorder=10)
  346. counts = np.zeros((len(exportSides)),dtype=int)
  347. for t in range(dt.shape[0]):
  348. for s, sideCode in enumerate(exportSides):
  349. if dt[t,2]==sideCode:
  350. if np.isfinite(drad[t]):
  351. counts[s] +=1
  352. i = int(drad[t])
  353. ax.plot(dc[t,:i,0], dc[t,:i,1], dc[t,:i,2], alpha = 0.02, color = colors[s])
  354. for s, sideCode in enumerate(exportSides):
  355. print(f'{labels[s]} {counts[s]}')
  356. r = rad
  357. u = np.linspace(-np.pi, -2*np.pi, 100)
  358. v = np.linspace(-np.pi, -np.pi/2, 100)
  359. x = (r * np.outer(np.cos(u), np.sin(v)))
  360. y = (r * np.outer(np.sin(u), np.sin(v)))
  361. z = (r * np.outer(np.ones(np.size(u)), np.cos(v)))
  362. ax.plot_surface(x+mouth[0], y+mouth[1], z+mouth[2], color=(0,0,0), alpha=0.1)
  363. jsm.set_axes_equal(ax)
  364. ax.set_axis_off()
  365. ax.set_facecolor('none')
  366. # Plot the arrows
  367. # ax.quiver(X, Y, Z, U, V, W, ...)
  368. ax.quiver(mouth[0]+axshift_x, mouth[1]+axshift_y, mouth[2]+axshift_z, u_dirs[0], v_dirs[0], w_dirs[0], \
  369. color=(0.5,0.5,0.5), label='L/R',linewidth=1,arrow_length_ratio=0)
  370. # ax.quiver(mouth[0]+axshift_x, mouth[1]+axshift_y, mouth[2]+axshift_z, -1*u_dirs[0], v_dirs[0], w_dirs[0], \
  371. # color=colors[0], label='Right',linewidth=1,arrow_length_ratio=0)
  372. ax.quiver(mouth[0]+axshift_x, mouth[1]+axshift_y, mouth[2]+axshift_z, u_dirs[1], -1*v_dirs[1], w_dirs[1], \
  373. color=(0.5,0.5,0.5), label='A/P',linewidth=1,arrow_length_ratio=0)
  374. ax.quiver(mouth[0]+axshift_x, mouth[1]+axshift_y, mouth[2]+axshift_z, u_dirs[2], v_dirs[2], w_dirs[2], \
  375. color=(0.5,0.5,0.5), label='D/V',linewidth=1,arrow_length_ratio=0)
  376. ax.plot(mouth[0]+axshift_x, mouth[1]+axshift_y, mouth[2]+axshift_z,'.',color=(0,0,0),markersize=8)
  377. if a == 2:
  378. ax.legend(frameon=False,fontsize=fontsize,bbox_to_anchor=(1.01, 1), loc='upper left')
  379. ax1.invert_xaxis()
  380. ax1.invert_yaxis()
  381. ax3.invert_yaxis()
  382. jsm.set_dynamic_suptitle(fig,figName+"\n"+str(FIG_EXPORT_DIR),margin = 0.1,\
  383. fontsize=fontsize,color=(0,0,0),va='bottom')
  384. plt.show()
  385. with maybe_pdf(os.path.join(FIG_EXPORT_DIR,figName)) as pdf:
  386. pdf.savefig(fig,bbox_inches='tight',pad_inches=0.05,dpi=600) # saves the current figure into a pdf page
  387. # %%
  388. %matplotlib inline
  389. figName = 'Fig1_Alt_lick_locations_pre.pdf'
  390. gridspec_kw=dict(
  391. left=0.05,
  392. right=0.95,
  393. bottom=0.05,
  394. top=0.95,
  395. wspace=0.2,
  396. hspace=0.2
  397. )
  398. fontsize = 8
  399. ms = 4
  400. figSize = [2.2,3]
  401. xlim1 = [-155,-25]
  402. xlim1 = [-50,50]
  403. xlim2 = [-50,50]
  404. xticks1 = [-150,-125,-100,-75,-50,-25]
  405. xticks1 = [-50,-25,0,25,50]
  406. xticks2 = [-50,-25,0,25,50]
  407. ylim2 = [0,0.1]
  408. ylim2 = [0,0.4]
  409. yticks1 = [-10,-20,-30,-40,-50,-60]
  410. ylim1 = [-60,-10]
  411. #titles = ['Pre-Shift', 'Post-Shift']
  412. sColors = [(0,0,1),(1,0,0)]
  413. labels = ['Right', 'Left']
  414. azi_shift = 0#90
  415. histMeth = 'kde'#'pdf'# 'kde' #pdf
  416. binWidth = 5
  417. binRange = [-65,65]
  418. loc='lower left'
  419. sm = tracker[:,0] < 0
  420. rTs = np.where(tracker[sm,2]==0)[0]
  421. rT = rTs[10]#10
  422. lTs = np.where(tracker[sm,2]==1)[0]
  423. lT = lTs[10]#10
  424. fig, axs = jsm.clean_subplots(3,1,figsize=figSize,gridspec_kw=gridspec_kw)
  425. axs = axs.flatten()
  426. w = CB.coef_[0]
  427. C = CB.intercept_[0]
  428. A, B = w[0], w[1]
  429. exampleTrials = [rT, lT]
  430. xOffsets = [-10,-20]
  431. ax = axs[0]
  432. for t in range(2):
  433. mask = np.logical_and(tracker[:,0]<0, tracker[:,2]==t)
  434. print(f'{labels[t]} n = {np.sum(mask==True)}')
  435. for ii,i in enumerate(list(np.argwhere(mask==True))):
  436. if ii == 0:
  437. ax.plot(azi[i]+azi_shift, ele[i], '.', ms = ms, color = sColors[t], alpha=0.1,label=labels[t], linestyle='None', markeredgewidth=0, markeredgecolor='none')
  438. else:
  439. ax.plot(azi[i]+azi_shift, ele[i], '.', ms = ms, color = sColors[t], alpha=0.1, linestyle='None', markeredgewidth=0, markeredgecolor='none')
  440. ax.set_xlim(xlim1)
  441. ax.set_xticks(xticks1)
  442. ax.set_yticks(yticks1)
  443. ax.tick_params(axis='both', which='major', labelsize=14)
  444. ax.set_ylim(ylim1)
  445. ax.set_ylabel('Elevation (°)',fontsize=fontsize,labelpad=0)
  446. ax.set_xlabel('Azimuth (°)',fontsize=fontsize,labelpad=0)
  447. xrng = np.linspace(ax.get_xlim()[0], ax.get_xlim()[1], 200)
  448. yrng = (-A/B) * xrng - (C/B)
  449. ymin, ymax = ax.get_ylim()
  450. if abs(B) < 1e-12:
  451. xline = -C / A
  452. ax.plot([xline+azi_shift, xline+azi_shift], [ymin+5, ymax-5], ls = ':',color=(0,0,0), linewidth = 2,label='Choice\nBoundary')
  453. else:
  454. x1 = (ymin + (C/B)) / (-A/B)
  455. x2 = (ymax + (C/B)) / (-A/B)
  456. ax.plot([x1+azi_shift, x2+azi_shift], [ymin+5, ymax-5], ls = ':',color=(0,0,0), linewidth = 1,label = 'Choice\nBoundary')
  457. ax.legend(frameon=False,fontsize=fontsize,loc=loc,handlelength=0.75, markerscale=0.75, handletextpad=0.2, labelspacing=0.1, borderpad=0.3)
  458. ax = axs[1]
  459. if histMeth == 'kde':
  460. sns.kdeplot(lickAngles[np.logical_and(tracker[:,0]<0, tracker[:,2]==0)]*-1, alpha = 0.3, color = (0,0,1), ax = axs[1], fill = True)
  461. sns.kdeplot(lickAngles[np.logical_and(tracker[:,0]<0, tracker[:,2]==1)]*-1, alpha = 0.3, color = (1,0,0), ax = axs[1], fill = True)
  462. elif histMeth == 'pdf':
  463. tempData = lickAngles[np.logical_and(tracker[:,0]<0, tracker[:,2]==0)]*-1
  464. tempStat = jsm.summary_stats(tempData,nBins=0,binWidth=binWidth,temp_minVal=binRange[0],temp_maxVal=binRange[1],\
  465. calcHist=True, verbose=False, allow_overages = False, splitZeroBin = False, \
  466. calc_hist_log10 = False, calc_cumulative_hist = False, calc_norm_hist = False, calc_cumulative_freq = False, calc_pdf = True,
  467. save_input = False, force_dType=False, dType='float32', exclude_NaNs = True, warningsOn = True)
  468. ax.fill_between(tempStat['binCenters'], np.zeros_like(tempStat['binCenters']), tempStat['pdf'], color = (0,0,1), alpha = 0.3,edgecolor = 'none')
  469. ax.plot(tempStat['binCenters'],tempStat['pdf'], color = (0,0,1), lw = 1,ls='-',label = 'Pre-Shift R')
  470. tempData = lickAngles[np.logical_and(tracker[:,0]<0, tracker[:,2]==1)]*-1
  471. tempStat = jsm.summary_stats(tempData,nBins=0,binWidth=binWidth,temp_minVal=binRange[0],temp_maxVal=binRange[1],\
  472. calcHist=True, verbose=False, allow_overages = False, splitZeroBin = False, \
  473. calc_hist_log10 = False, calc_cumulative_hist = False, calc_norm_hist = False, calc_cumulative_freq = False, calc_pdf = True,
  474. save_input = False, force_dType=False, dType='float32', exclude_NaNs = True, warningsOn = True)
  475. ax.fill_between(tempStat['binCenters'], np.zeros_like(tempStat['binCenters']), tempStat['pdf'], color = (1,0,0), alpha = 0.3,edgecolor = 'none')
  476. ax.plot(tempStat['binCenters'],tempStat['pdf'], color = (1,0,0), lw = 1,ls='-',label = 'Pre-Shift L')
  477. ax.legend(frameon=False,fontsize=fontsize,loc=loc,handlelength=0.75, markerscale=0.75, handletextpad=0.2, labelspacing=0.1, borderpad=0.3)
  478. ax.set_xlabel('Lick Angle (°; relative to CB)',fontsize=fontsize,labelpad=0)
  479. ax.set_ylabel('Density',fontsize=fontsize)
  480. ax.set_xlim(xlim2)
  481. ax.set_xticks(xticks2)
  482. for ax in axs:
  483. ax.spines[['top','right']].set_visible(False)
  484. ax.tick_params(axis='both', which='major', labelsize=fontsize)
  485. ax.tick_params(axis='x', which='major', labelsize=fontsize,pad= 2) #
  486. ax.tick_params(axis='y', which='major', labelsize=fontsize,pad= 1) #
  487. axs[2].cla()
  488. axs[2].axis('off')
  489. plt.show()
  490. with maybe_pdf(os.path.join(FIG_EXPORT_DIR,figName)) as pdf:
  491. pdf.savefig(fig,bbox_inches='tight',pad_inches=0.05,dpi=600) # saves the current figure into a pdf page
  492. # %%
  493. figName = 'Fig1_Alt_postShift_errorDistributions_splitError.pdf'
  494. gridspec_kw=dict(
  495. left=0.05,
  496. right=0.95,
  497. bottom=0.05,
  498. top=0.95,
  499. wspace=0.2,
  500. hspace=0.2
  501. )
  502. fontsize = 8
  503. ms1 = 3
  504. ms2 = 5
  505. figSize = [2.2,3]
  506. xlim1 = [-155,-25]
  507. xlim1 = [-50,50]
  508. xlim2 = [-50,50]
  509. xticks1 = [-150,-125,-100,-75,-50,-25]
  510. xticks1 = [-50,-25,0,25,50]
  511. xticks2 = [-50,-25,0,25,50]
  512. yticks1 = [-10,-20,-30,-40,-50,-60]
  513. ylim1 = [-60,-10]
  514. ylim2 = [0,0.6]
  515. #sColors = [(0,0,1),(1,0,0)]
  516. sColors = [(0,0,1), (0,0,0.5), (1,0,0), (0.5,0.5,0.5)]
  517. labels = ['CR', 'MER', 'CL', 'DER']
  518. azi_shift = 0
  519. histMeth = 'pdf'# 'kde' #pdf
  520. binWidth = 5
  521. binRange = [-65,65]
  522. loc='lower left'
  523. linewidth = 1
  524. fig, axs = jsm.clean_subplots(3,1,figsize=figSize,gridspec_kw=gridspec_kw)
  525. axs = axs.flatten()
  526. w = CB.coef_[0]
  527. C = CB.intercept_[0]
  528. A, B = w[0], w[1]
  529. xOffsets = [0,-10]
  530. meMask = np.logical_and(np.logical_and(postMask, me), tracker[:,2]==5)
  531. deMask = np.logical_and(np.logical_and(postMask, de), tracker[:,2]==5)
  532. clMask = np.logical_and(postMask, tracker[:,2]==1)
  533. crMask = np.logical_and(postMask, tracker[:,2]==0)
  534. masks = [crMask, meMask, clMask, deMask]
  535. markers = ['.','x','.','x']
  536. alphas = [.1,.5,.1,.5]
  537. sizes = [4,6,4,6]
  538. handels = []
  539. ax = axs[0]
  540. indicies = [0,2]
  541. for t in indicies:
  542. mask = masks[t]
  543. if markers[t] == 'x':
  544. for ii,i in enumerate(list(np.argwhere(mask==True))):
  545. if ii == 0:
  546. ax.plot(azi[i]+azi_shift, ele[i], markers[t], ms = sizes[t], color = sColors[t], alpha=alphas[t],label=labels[t], linestyle='None', markeredgewidth=0, markeredgecolor='none')
  547. else:
  548. ax.plot(azi[i]+azi_shift, ele[i], markers[t], ms = sizes[t], color = sColors[t], alpha=alphas[t], linestyle='None', markeredgewidth=0, markeredgecolor='none')
  549. else:
  550. for ii,i in enumerate(list(np.argwhere(mask==True))):
  551. if ii == 0:
  552. ax.plot(azi[i]+azi_shift, ele[i], markers[t], ms = sizes[t], color = sColors[t], alpha=alphas[t],label=labels[t], linestyle='None', markeredgewidth=0, markeredgecolor='none')
  553. else:
  554. ax.plot(azi[i]+azi_shift, ele[i], markers[t], ms = sizes[t], color = sColors[t], alpha=alphas[t], linestyle='None', markeredgewidth=0, markeredgecolor='none')
  555. # handels.append(sct)
  556. print(f'{labels[t]} {np.sum(mask==True)}')
  557. ax.set_xlim(xlim1)
  558. ax.set_xticks(xticks1)
  559. ax.set_yticks(yticks1)
  560. ax.tick_params(axis='both', which='major', labelsize=fontsize)
  561. ax.set_ylim(ylim1)
  562. ax.set_ylabel('Elevation (°)',fontsize=fontsize,labelpad=0)
  563. ax.set_xlabel('Azimuth (°)',fontsize=fontsize,labelpad=0)
  564. xrng = np.linspace(ax.get_xlim()[0], ax.get_xlim()[1], 200)
  565. yrng = (-A/B) * xrng - (C/B)
  566. ymin, ymax = ax.get_ylim()
  567. if abs(B) < 1e-12:
  568. xline = -C / A
  569. ax.plot([xline+azi_shift, xline+azi_shift], [ymin+5, ymax-5], ls = ':',color=(0,0,0), linewidth = 2,label='CB')
  570. else:
  571. x1 = (ymin + (C/B)) / (-A/B)
  572. x2 = (ymax + (C/B)) / (-A/B)
  573. LDALine, = ax.plot([x1+azi_shift, x2+azi_shift], [ymin+5, ymax-5], ls = ':',color=(0,0,0), linewidth = 1,label = 'CB')
  574. ax.text(xlim1[0],ylim1[1],'Post-Shift',color = (0,0,0),fontsize=fontsize,ha='left',va='top')
  575. ax.legend(frameon=False,fontsize=fontsize,loc=loc,handlelength=0.75, markerscale=0.75, handletextpad=0.2, labelspacing=0.1, borderpad=0.3)
  576. ax = axs[1]
  577. indicies = [1,3]
  578. for t in indicies:
  579. mask = masks[t]
  580. if markers[t] == 'x':
  581. sct = ax.scatter(azi[mask]+azi_shift, ele[mask], s=sizes[t], c=sColors[t], alpha=alphas[t], marker = markers[t], label = labels[t])
  582. else:
  583. sct = ax.scatter(azi[mask]+azi_shift, ele[mask], s=sizes[t], c=sColors[t], alpha=alphas[t], marker = markers[t], label = labels[t],edgecolor=None)
  584. handels.append(sct)
  585. print(f'{labels[t]} {np.sum(mask==True)}')
  586. ax.set_xlim(xlim1)
  587. ax.set_xticks(xticks1)
  588. ax.set_yticks(yticks1)
  589. ax.tick_params(axis='both', which='major', labelsize=fontsize)
  590. ax.set_ylim(ylim1)
  591. ax.set_ylabel('Elevation (°)',fontsize=fontsize,labelpad=0)
  592. ax.set_xlabel('Azimuth (°)',fontsize=fontsize,labelpad=0)
  593. xrng = np.linspace(ax.get_xlim()[0], ax.get_xlim()[1], 200)
  594. yrng = (-A/B) * xrng - (C/B)
  595. ymin, ymax = ax.get_ylim()
  596. if abs(B) < 1e-12:
  597. xline = -C / A
  598. ax.plot([xline+azi_shift, xline+azi_shift], [ymin+5, ymax-5], ls = ':',color=(0,0,0), linewidth = 2,label='CB')
  599. else:
  600. x1 = (ymin + (C/B)) / (-A/B)
  601. x2 = (ymax + (C/B)) / (-A/B)
  602. LDALine, = ax.plot([x1+azi_shift, x2+azi_shift], [ymin+5, ymax-5], ls = ':',color=(0,0,0), linewidth = 1,label = 'CB')
  603. ax.text(xlim1[0],ylim1[1],'Post-Shift',color = (0,0,0),fontsize=fontsize,ha='left',va='top')
  604. ax.legend(frameon=False,fontsize=fontsize,loc=loc,handlelength=0.75, markerscale=0.75, handletextpad=0.2, labelspacing=0.1, borderpad=0.3)
  605. ax = axs[2]
  606. loc='upper right'
  607. masks = [crMask, clMask, meMask, deMask]
  608. labels = ['CR','CL','MER','DER']
  609. linestyles = ['-','-','-','-']
  610. colors = [(0,0,1),(1,0,0),(0,0,.5),(.1,.1,.1)]
  611. alphas = [.9,.9,.3,.3]
  612. fills = [False,False,True,True]
  613. for m, mask in enumerate(masks):
  614. if histMeth == 'kde':
  615. sns.kdeplot(cDists[mask]*-1, alpha = alphas[m], color = colors[m], ax = axs[1], fill = fills[m], lw = linewidth)
  616. ax.plot(-100,-100,'-',lw=linewidth,color=colors[m],label=labels[m])
  617. elif histMeth == 'pdf':
  618. tempStat = jsm.summary_stats(cDists[mask]*-1,nBins=0,binWidth=binWidth,temp_minVal=binRange[0],temp_maxVal=binRange[1],\
  619. calcHist=True, verbose=False, allow_overages = False, splitZeroBin = False, \
  620. calc_hist_log10 = False, calc_cumulative_hist = False, calc_norm_hist = False, calc_cumulative_freq = False, calc_pdf = True,
  621. save_input = False, force_dType=False, dType='float32', exclude_NaNs = True, warningsOn = True)
  622. if fills[m]:
  623. ax.fill_between(tempStat['binCenters'], np.zeros_like(tempStat['binCenters']), tempStat['pdf'], color = colors[m], alpha = alphas[m],edgecolor = 'none')
  624. ax.plot(tempStat['binCenters'],tempStat['pdf'], color = colors[m], lw = linewidth,ls=linestyles[m],label = labels[m])
  625. ax.set_xlabel('Lick Angle (°; relative to CB)',fontsize=fontsize,labelpad=0)
  626. ax.set_ylabel('Density',fontsize=fontsize,labelpad=0)
  627. ax.set_xticks(xticks2)
  628. ax.legend(frameon=False,fontsize=fontsize,loc=loc,handlelength=0.75, markerscale=0.75, handletextpad=0.2, labelspacing=0.1, borderpad=0.3)
  629. ax.set_ylim(ylim2)
  630. ax.set_xlim(xlim2)
  631. for ax in axs:
  632. ax.spines[['top','right']].set_visible(False)
  633. ax.tick_params(axis='both', which='major', labelsize=fontsize)
  634. ax.tick_params(axis='x', which='major', labelsize=fontsize,pad= 2) #
  635. ax.tick_params(axis='y', which='major', labelsize=fontsize,pad= 1) #
  636. plt.show()
  637. with maybe_pdf(os.path.join(FIG_EXPORT_DIR,figName)) as pdf:
  638. pdf.savefig(fig,bbox_inches='tight',pad_inches=0.05,dpi=600) # saves the current figure into a pdf page
  639. # %% [markdown]
  640. # # all animal lick angles and motor errors across shift
  641. # %% [markdown]
  642. # ### setup
  643. # %%
  644. for anm in anms:
  645. anmData = allAnmParams[anm]
  646. for i,mod in enumerate(['f','c', 's']):
  647. azi = anmData[f'{mod}Azi']
  648. ele = anmData[f'{mod}Ele']
  649. lda = anmData['lda']
  650. lickAngles = jsm.get_lickAngle(azi, ele, lda)
  651. anmData[f'{mod}Angles'] = lickAngles
  652. # %% [markdown]
  653. # ### summary stats
  654. # %%
  655. ### % MER/ DER
  656. stats = {'pre_percMER': [], 'pre_percDER': [], 'post_percMER': [], 'post_percDER': []}
  657. statKeys = stats.keys()
  658. for anm in anms:
  659. anmData = allAnmParams[anm]
  660. tracker = anmData['tracker']
  661. me = anmData['me']
  662. de = anmData['de']
  663. for p, phase in enumerate(['pre','post']):
  664. rMask = (tracker[:,2]==0)|(tracker[:,2]==5)
  665. pMask = anmData['shiftMask'][phase]
  666. prMask = (pMask)&(rMask)
  667. percME = np.sum((me)&(prMask))/np.sum(prMask)
  668. percDE = np.sum((de)&(prMask))/np.sum(prMask)
  669. stats[f'{phase}_percMER'].append(percME)
  670. stats[f'{phase}_percDER'].append(percDE)
  671. # fig, axs = plt.subplots(1,2,figsize = [5,3],tight_layout = True, sharey = True)
  672. colors = ['darkred', 'darkblue']
  673. errLabels = ['DER', 'MER']
  674. for a, ax in enumerate(axs):
  675. if a == 0:
  676. phase = 'pre'
  677. else:
  678. phase = 'post'
  679. pMer = stats[f'{phase}_percMER']
  680. pDer = stats[f'{phase}_percDER']
  681. errs = [pDer, pMer]
  682. for e, err in enumerate(errs):
  683. median = np.nanmedian(err)
  684. print(phase, errLabels[e], f'{median:.03f}')
  685. sem = np.nanstd(err)/np.sqrt(np.sum(np.isfinite(err)))
  686. # ax.bar(e, median, yerr = sem, color = colors[e], alpha = 0.5)
  687. # %%
  688. ### test for modality
  689. from sklearn.mixture import GaussianMixture
  690. errAngles = {'pre_MER': [], 'pre_DER': [], 'post_MER': [], 'post_DER': []}
  691. statKeys = errAngles.keys()
  692. for anm in anms:
  693. anmData = allAnmParams[anm]
  694. tracker = anmData['tracker']
  695. me = anmData['me']
  696. de = anmData['de']
  697. angles = anmData['cAngles']
  698. for p, phase in enumerate(['pre','post']):
  699. rMask = (tracker[:,2]==0)|(tracker[:,2]==5)
  700. pMask = anmData['shiftMask'][phase]
  701. prMask = (pMask)&(rMask)
  702. meAngles = angles[(me)&(prMask)]
  703. deAngles = angles[(de)&(prMask)]
  704. errAngles[f'{phase}_MER'].append(meAngles)
  705. errAngles[f'{phase}_DER'].append(deAngles)
  706. modeTests = {}
  707. random_state = 73
  708. # fig, axs = plt.subplots(1,2,figsize = [5,3],tight_layout = True, sharey = True)
  709. colors = ['darkred', 'darkblue']
  710. errLabels = ['DER', 'MER']
  711. for p, phase in enumerate(['pre','post']):
  712. X = np.hstack([np.hstack(errAngles[f'{phase}_MER']), np.hstack(errAngles[f'{phase}_DER'])])*-1
  713. X = X[np.isfinite(X)]
  714. _,bins = np.histogram(X, bins =100)
  715. X = X.reshape(-1,1)
  716. # Fit models
  717. gmm1 = GaussianMixture(n_components=1, random_state=random_state)
  718. gmm2 = GaussianMixture(n_components=2, random_state=random_state)
  719. gmm1.fit(X)
  720. gmm2.fit(X)
  721. # Compute BIC
  722. bic1 = gmm1.bic(X)
  723. bic2 = gmm2.bic(X)
  724. delta_bic = bic1 - bic2 # positive favors 2-component
  725. # Extract parameters for interpretability
  726. means = gmm2.means_.flatten()
  727. variances = gmm2.covariances_.flatten()
  728. weights = gmm2.weights_.flatten()
  729. # Sort components by mean
  730. order = np.argsort(means)
  731. means = means[order]
  732. variances = variances[order]
  733. weights = weights[order]
  734. # Optional: Ashman's D
  735. ashman_d = np.abs(means[1] - means[0]) / np.sqrt(0.5 * (variances[0] + variances[1]))
  736. output = {
  737. "bic_1_component": bic1,
  738. "bic_2_component": bic2,
  739. "delta_bic": delta_bic,
  740. "preferred_model": "2-component" if delta_bic > 0 else "1-component",
  741. "means": means,
  742. 'mid_point': np.mean(means),
  743. "variances": variances,
  744. "weights": weights,
  745. "ashman_d": ashman_d
  746. }
  747. modeTests[phase] = output
  748. for phase in ['pre','post']:
  749. print(f'{phase}: ')
  750. print(modeTests[phase])
  751. print('')
  752. # %%
  753. statsL = {'pre_percMEL': [], 'pre_percDEL': [], 'pre_ratio': [],
  754. 'post_percMEL': [], 'post_percDEL': [], 'post_ratio': []}
  755. statLKeys = statsL.keys()
  756. for anm in anms:
  757. anmData = allAnmParams[anm]
  758. tracker = anmData['tracker']
  759. me = anmData['me']
  760. de = anmData['de']
  761. for p, phase in enumerate(['pre','post']):
  762. lMask = (tracker[:,2]==1)|(tracker[:,2]==6)
  763. pMask = anmData['shiftMask'][phase]
  764. plMask = (pMask)&(lMask)
  765. percME = np.sum((me)&(plMask))/np.sum(plMask)
  766. percDE = np.sum((de)&(plMask))/np.sum(plMask)
  767. statsL[f'{phase}_percMEL'].append(percME)
  768. statsL[f'{phase}_percDEL'].append(percDE)
  769. nAny = (np.sum(((me)|(de))&(plMask)))
  770. statsL[f'{phase}_ratio'].append(nAny/np.sum(plMask))
  771. # %%
  772. statsAny = {'pre_percME': [], 'pre_percDE': [], 'pre_ratio': [], 'pre_err_toCorrect': [],
  773. 'post_percME': [], 'post_percDE': [], 'post_ratio': [], 'post_err_toCorrect': []}
  774. statsAnyKeys = statsAny.keys()
  775. for anm in anms:
  776. anmData = allAnmParams[anm]
  777. tracker = anmData['tracker']
  778. me = anmData['me']
  779. de = anmData['de']
  780. for p, phase in enumerate(['pre','post']):
  781. aMask = tracker[:,2]<10 #only clean trials (no EL)
  782. pMask = anmData['shiftMask'][phase]
  783. paMask = (pMask)&(aMask)
  784. percME = np.sum((me)&(paMask))/np.sum(paMask)
  785. percDE = np.sum((de)&(paMask))/np.sum(paMask)
  786. statsAny[f'{phase}_percME'].append(percME)
  787. statsAny[f'{phase}_percDE'].append(percDE)
  788. nAny = (np.sum(((me)|(de))&(paMask)))
  789. ratio = np.sum((de)&(paMask))/nAny
  790. statsAny[f'{phase}_ratio'].append(ratio)
  791. statsAny[f'{phase}_err_toCorrect'].append(nAny/np.sum(paMask))
  792. # %%
  793. ### response times
  794. pre = []
  795. post = []
  796. change = []
  797. for anm in anms:
  798. anmData = allAnmParams[anm]
  799. tracker = anmData['tracker']
  800. preMask = anmData['shiftMask']['pre']
  801. postMask = anmData['shiftMask']['post']
  802. goCues = anmData['goCues']
  803. cTimes = anmData['cTimes']
  804. rxTime = cTimes-goCues
  805. pre.append(np.nanmean(rxTime[preMask]))
  806. post.append(np.nanmean(rxTime[postMask]))
  807. if np.sum(postMask>0):
  808. change.append(np.nanmean(rxTime[postMask])-np.nanmean(rxTime[preMask]))
  809. print('pre: ', f'{np.nanmedian(pre):.03f}')
  810. print('post: ', f'{np.nanmedian(post):.03f}')
  811. # print('change: ', f'{np.nanmean(post)-np.nanmean(pre):.03f}')
  812. print('change: ', f'{np.nanmean(change):.03f}', f'{np.nanstd(change):.03f}')
  813. # %% [markdown]
  814. # ### figure panels
  815. # %%
  816. figName = 'Fig1_allAnmLickDistro_alt.pdf'
  817. ttypes = ['Right','Left','Motor','Decision']
  818. colors = [(0,0,1),(1,0,0),(0,0,.5),(.1,.1,.1)]
  819. alphas = [.7,.7,.7,.7]
  820. fills = [False, False, True, True]
  821. lws = [0.5,0.5,0.5,0.5]
  822. fontsize = 8
  823. figsize = [4,3]
  824. titles = ['Pre-Shift', 'Post-Shift']
  825. CI = 68
  826. error = 'std'
  827. # ylim = [-7,3]
  828. ylim = [-3,7]
  829. xlim = [-50,50]
  830. xticks = [-50,-25,0,25,50]
  831. if error == 'CI':
  832. errorLabel = "mean±"+str(CI)+"% CI"
  833. elif error == 'sem':
  834. errorLabel = "mean±SEM"
  835. elif error == 'std':
  836. errorLabel = "mean±SD"
  837. nBoot = 10000
  838. fig,axs = jsm.clean_subplots(2,2,figsize,constrained_layout=True,sharey = True, sharex=True)
  839. for q in range(2):
  840. # ax = axs[q]
  841. allAnmDistros = [[],[],[],[]]
  842. for a, anm in enumerate(anms):
  843. anmData = allAnmParams[anm]
  844. if q == 0:
  845. phaseMask = allAnmParams[anm]['shiftMask']['pre']
  846. else:
  847. phaseMask = allAnmParams[anm]['shiftMask']['post']
  848. tracker = allAnmParams[anm]['tracker']#jsm.get_tracker(anmData)
  849. azi = allAnmParams[anm]['cAzi']+90
  850. ele = allAnmParams[anm]['cEle']
  851. me, de, CB = jsm.get_errors_LDA(azi, ele, tracker, returnLDA = True)
  852. dists = jsm.get_lickAngle(azi, ele, CB)
  853. CR = tracker[:,2]==0
  854. CL = tracker[:,2]==1
  855. me = (allAnmParams[anm]['me'])&(tracker[:,2]==5)
  856. de = (allAnmParams[anm]['de'])&(tracker[:,2]==5)
  857. ttypeMasks = [CR,CL,me,de]
  858. distros = []
  859. for m, mask in enumerate(ttypeMasks):
  860. allAnmDistros[m].append(dists[(mask)&(phaseMask)])
  861. flatDistros = np.hstack([np.hstack(allAnmDistros[i]) for i in range(len(allAnmDistros))])
  862. _,bins = np.histogram(flatDistros[np.isfinite(flatDistros)], bins = 30)
  863. for d, distro in enumerate(allAnmDistros):
  864. if d<2:
  865. ax = axs[0,q]
  866. ax.set_title(titles[q]+': Correct',fontsize=fontsize+2)
  867. else:
  868. ax = axs[1,q]
  869. ax.set_title(titles[q]+': Right-Trial Errors',fontsize=fontsize+2)
  870. sns.histplot(np.hstack(distro)*-1, color = colors[d], alpha = alphas[d], ax = ax, bins = bins, stat = 'probability', linewidth=lws[d],label=ttypes[d])
  871. print(f'{titles[q]} {ttypes[d]} median {np.nanmedian(np.hstack(distro)*-1):.1f} std {np.nanstd(np.hstack(distro)*-1):.1f} mean {np.nanmean(np.hstack(distro)*-1):.1f} ± {np.nanstd(np.hstack(distro)*-1)/np.sqrt(np.sum(np.isfinite(np.hstack(distro)*-1))):.1f} (n = {np.nansum(np.isfinite(np.hstack(distro)))})')
  872. for ax in axs.flatten():
  873. ax.legend(frameon=False,fontsize=fontsize,loc='upper right',handlelength=0.75, markerscale=0.75, handletextpad=0.2, labelspacing=0.1, borderpad=0.3)
  874. ax.set_xlabel('Lick Angle\n(°; relative to CB)',fontsize=fontsize)
  875. ax.set_ylabel('Probability',fontsize=fontsize)
  876. ax.set_xticks(xticks)
  877. ax.set_xlim(xlim)
  878. for ax in axs.flatten():
  879. ax.spines[['top','right']].set_visible(False)
  880. ax.tick_params(axis='both', which='major', labelsize=fontsize) #
  881. ax.tick_params(axis='x', which='major', labelsize=fontsize,pad= 2) #
  882. ax.tick_params(axis='y', which='major', labelsize=fontsize,pad= 1) #
  883. plt.show()
  884. with maybe_pdf(os.path.join(FIG_EXPORT_DIR,figName)) as pdf:
  885. pdf.savefig(fig,bbox_inches='tight',pad_inches=0.05,dpi=600) # saves the current figure into a pdf page
  886. # %%
  887. figName = 'Fig1_avg_lick_by_shift_R_ONLY.pdf'
  888. gridspec_kw=dict(
  889. left=0.05,
  890. right=0.95,
  891. bottom=0.05,
  892. top=0.95,
  893. wspace=0.2,
  894. hspace=0.2
  895. )
  896. colors = [(1,0,0),(0,0,1)]
  897. fontsize = 8
  898. figsize = [4,1]
  899. titles = ['First\nContact Lick','First Lick\nRegardless\nof Contact']
  900. CI = 68
  901. error = 'std'
  902. if error == 'CI':
  903. errorLabel = "mean±"+str(CI)+"% CI"
  904. elif error == 'sem':
  905. errorLabel = "mean±SEM"
  906. elif error == 'std':
  907. errorLabel = "mean±SD"
  908. nBoot = 10000
  909. yticks = [-6,-4,-2,0,2,4,6]
  910. ylim = [-6,6]
  911. allBooted = []
  912. fig,axs = jsm.clean_subplots(1,2,figsize,constrained_layout=True,sharey = False, sharex=True, gridspec_kw = gridspec_kw)
  913. axs = list(np.squeeze(axs))
  914. for q in range(2):
  915. ax = axs[q]
  916. ax.axhline(0,color = (0,0,0), linestyle = '-', lw = 1, alpha = 0.5)
  917. ax.axvline(1.5,color = (0,0,0), linestyle = '-', lw = 1, alpha = 0.5)
  918. base = {'Left': np.zeros([len(anms), 4]), 'Right': np.zeros([len(anms), 4])}
  919. for a, anm in enumerate(anms):
  920. anmData = allAnmParams[anm]
  921. if q == 0:
  922. dists = allAnmParams[anm]['cAngles']
  923. else:
  924. dists = allAnmParams[anm]['fAngles']
  925. tracker = anmData['tracker']
  926. me = anmData['me']
  927. preMask =np.logical_or(allAnmParams[anm]['shiftMask']['preShift_early'], allAnmParams[anm]['shiftMask']['preShift_late'])
  928. preRMask = np.logical_and(np.logical_and(preMask, ~allAnmParams[anm]['aw']),
  929. np.logical_or(tracker[:,2]==0,np.logical_and(me,tracker[:,2]==5)))
  930. preR = np.nanmean(dists[preRMask])
  931. preLMask = np.logical_and(np.logical_and(preMask, ~allAnmParams[anm]['aw']),
  932. np.logical_or(tracker[:,2]==1,np.logical_and(me,tracker[:,2]==6)))
  933. preL = np.nanmean(dists[preLMask])
  934. anmR = np.zeros(4)
  935. anmL = np.zeros(4)
  936. for p, phase in enumerate(['preShift_early','preShift_late','early','late']):
  937. pMask = np.logical_and(allAnmParams[anm]['shiftMask'][phase], ~allAnmParams[anm]['aw'])
  938. pT = tracker[pMask,:]
  939. pME = me[pMask]
  940. pDists = dists[pMask]
  941. rMask = np.logical_or(pT[:,2]==0,np.logical_and(pME,pT[:,2]==5))
  942. lMask = np.logical_or(pT[:,2]==1,np.logical_and(pME,pT[:,2]==6))
  943. anmR[p] = np.nanmean(pDists[rMask])-preR
  944. anmL[p] = np.nanmean(pDists[lMask])-preL
  945. base['Left'][a,:] = (anmL)*-1
  946. base['Right'][a,:] = (anmR)*-1
  947. base['Left'] = np.vstack(base['Left'])
  948. base['Right'] = np.vstack(base['Right'])
  949. booted = {'Left': np.zeros([nBoot,4]), 'Right': np.zeros([nBoot,4])}
  950. for i in range(nBoot):
  951. bAnms = np.random.choice(np.arange(len(anms)), len(anms))
  952. booted['Left'][i,:] = np.nanmean(base['Left'][bAnms,:],axis = 0)
  953. booted['Right'][i,:] = np.nanmean(base['Right'][bAnms,:],axis = 0)
  954. for i,side in enumerate(['Left', 'Right']):
  955. if side == 'Right':
  956. data = booted[side] #boot, phase
  957. mean = np.nanmean(data,axis = 0)
  958. std = np.nanstd(data,axis = 0, ddof = 1)
  959. low,high = np.nanpercentile(data, [100-(CI+((100-CI)/2)),(CI+((100-CI)/2))], axis = 0)
  960. if error == 'CI':
  961. mainPlot = med
  962. errorLow = low
  963. errorHigh = high
  964. elif error == 'sem':
  965. mainPlot = mean
  966. errorLow = mean-sem
  967. errorHigh = mean+sem
  968. elif error == 'std':
  969. mainPlot = mean
  970. errorLow = mean-std
  971. errorHigh = mean+std
  972. ax.fill_between(np.arange(4), errorLow, errorHigh, color = (0,0,0), alpha = 0.3,edgecolor = 'none')
  973. ax.plot(['Pre-Shift\nEarly','Pre-Shift\nLate','Post-Shift\nEarly','Post-Shift\nLate'], mainPlot, '.-', lw = 1, ms = 5, color = (0,0,0), label = f'{side}')
  974. ax.text(0,ylim[1],titles[q],ha='left',va='top',fontsize=fontsize)
  975. allBooted.append(booted)
  976. for a,ax in enumerate(axs):
  977. x = 0
  978. y = .95
  979. ax.set_ylabel(f'Δ Exit Trajectory (°)',fontsize=fontsize)
  980. ax.legend(frameon=False,fontsize=fontsize,loc = 'lower right')
  981. ax.spines[['top','right']].set_visible(False)
  982. ax.set_ylim(ylim)
  983. ax.set_yticks(yticks)
  984. ax.tick_params(axis='x', which='major', labelsize=fontsize,pad= 2) #
  985. ax.tick_params(axis='y', which='major', labelsize=fontsize,pad= 1) #
  986. plt.show()
  987. with maybe_pdf(os.path.join(FIG_EXPORT_DIR,figName)) as pdf:
  988. pdf.savefig(fig,bbox_inches='tight',pad_inches=0.05,dpi=600) # saves the current figure into a pdf page
  989. # %%
  990. for b, booted in enumerate(allBooted):
  991. print(titles[b])
  992. for side in ['Right', 'Left']:
  993. data = booted[side] # shape: (nBoot, 4)
  994. # Post-shift late (index 3)
  995. post_late = data[:, 3]
  996. mean = np.mean(post_late)
  997. std = np.std(post_late, ddof=1)
  998. # bootstrap p-value (two-sided)
  999. p = 2 * min(np.mean(post_late > 0), np.mean(post_late < 0))
  1000. print(f"{side} post-shift late: {mean:.2f} ± {std:.2f}°, p = {p:.4f}")
  1001. print('')
  1002. # %%
  1003. figName = 'Fig1_motor_error_fraction.pdf'
  1004. fontsize = 8
  1005. figsize = [4,1]
  1006. titles = ['First Licks', 'Contact Licks']
  1007. # colors = [(0,0.5,0)]
  1008. colors = [(0,0,0)]
  1009. CI = 68
  1010. error = 'std'
  1011. if error == 'CI':
  1012. errorLabel = "mean±"+str(CI)+"% CI"
  1013. elif error == 'std':
  1014. errorLabel = "mean±SD"
  1015. ylim = [-0.02,0.51]
  1016. yticks = [0,0.1,0.2,0.3,0.4,0.5]
  1017. fig,axs = jsm.clean_subplots(1,2,figsize,constrained_layout=True,sharey = False, sharex=True, gridspec_kw = gridspec_kw)
  1018. axs = list(np.squeeze(axs))
  1019. fig.delaxes(axs[1])
  1020. ax = axs[0]
  1021. nBoot = 10000
  1022. base = np.zeros([len(anms),4])
  1023. _nAnms = 0
  1024. for a, anm in enumerate(anms):
  1025. anmData = allAnmParams[anm]
  1026. tracker = anmData['tracker']
  1027. me = anmData['me']
  1028. anmR = np.zeros(4)
  1029. _nAnms+=1
  1030. for p, phase in enumerate(['preShift_early','preShift_late','early','late']):
  1031. pMask = np.logical_and(allAnmParams[anm]['shiftMask'][phase], ~allAnmParams[anm]['aw'])
  1032. pT = tracker[pMask,:]
  1033. pME = me[pMask]
  1034. meMask = np.logical_and(pME,pT[:,2]==5)
  1035. rMask = np.logical_or(pT[:,2]==0,meMask)
  1036. ratio = np.sum(meMask)/np.sum(rMask)
  1037. anmR[p] = ratio
  1038. print(f'{anm} {phase} pMask/total {np.sum(pMask==True)}/{len(pMask)} meMask/rMask = {np.sum(meMask)}/{np.sum(rMask)}')
  1039. base[a,:] = anmR
  1040. print('nAnms:', _nAnms)
  1041. total = np.zeros([nBoot,4])
  1042. for i in range(nBoot):
  1043. bAnms = np.random.choice(np.arange(len(anms)), len(anms))
  1044. total[i,:] = np.nanmean(base[bAnms,:], axis = 0)
  1045. i=0
  1046. mean = np.nanmean(total, axis=0)
  1047. med = np.nanmedian(total, axis = 0)
  1048. std = np.nanstd(total,axis = 0, ddof = 1)
  1049. sem = std / np.sqrt(total.shape[0])
  1050. low,high = np.nanpercentile(total, [100-(CI+((100-CI)/2)),(CI+((100-CI)/2))], axis = 0)
  1051. if error == 'CI':
  1052. mainPlot = med
  1053. errorLow = low
  1054. errorHigh = high
  1055. elif error == 'sem':
  1056. mainPlot = mean
  1057. errorLow = mean-sem
  1058. errorHigh = mean+sem
  1059. elif error == 'std':
  1060. mainPlot = mean
  1061. errorLow = mean-std
  1062. errorHigh = mean+std
  1063. # bootstrap p-value (two-sided)
  1064. print(total.shape)
  1065. preEarly = total[:,0]
  1066. p = 2 * min(np.nanmean(preEarly > 0), np.nanmean(preEarly < 0))
  1067. print(f"preEarly p = {p:.8f}")
  1068. preLate = total[:,1]
  1069. p = 2 * min(np.nanmean(preLate > 0), np.nanmean(preLate < 0))
  1070. print(f"preLate p = {p:.8f}")
  1071. postEarly = total[:,2]
  1072. p = 2 * min(np.nanmean(postEarly > 0), np.nanmean(postEarly < 0))
  1073. print(f"postEarly p = {p:.8f}")
  1074. postLate = total[:,3]
  1075. p = 2 * min(np.nanmean(postLate > 0), np.nanmean(postLate < 0))
  1076. print(f"postLate p = {p:.8f}")
  1077. ax.axhline(0,color = (0,0,0), linestyle = '-', lw = 1, alpha = 0.5)
  1078. ax.axvline(1.5,color = (0,0,0), linestyle = '-', lw = 1, alpha = 0.5)
  1079. ax.set_ylim(ylim)
  1080. ax.set_yticks(yticks)
  1081. ax.fill_between(np.arange(4), errorLow, errorHigh, color = colors[i], alpha = 0.5,edgecolor='none')
  1082. ax.plot(['Pre-Shift\nEarly','Pre-Shift\nLate','Post-Shift\nEarly','Post-Shift\nLate'], mainPlot, '.-', lw = 1, ms = 5, color = colors[i])
  1083. ax.set_ylabel('Motor Error Frequency',fontsize=fontsize)
  1084. ax.spines[['top','right']].set_visible(False)
  1085. ax.tick_params(axis='both', which='major', labelsize=fontsize-2) #
  1086. ax.tick_params(axis='x', which='major', labelsize=fontsize,pad= 2) #
  1087. ax.tick_params(axis='y', which='major', labelsize=fontsize,pad= 1) #
  1088. plt.show()
  1089. with maybe_pdf(os.path.join(FIG_EXPORT_DIR,figName)) as pdf:
  1090. pdf.savefig(fig,bbox_inches='tight',pad_inches=0.05,dpi=600) # saves the current figure into a pdf page
  1091. # %%
  1092. nBoot = 10000
  1093. base = np.zeros([len(anms),2])
  1094. _nAnms = 0
  1095. for a, anm in enumerate(anms):
  1096. anmData = allAnmParams[anm]
  1097. tracker = anmData['tracker']
  1098. me = anmData['me']
  1099. anmR = np.zeros(2)
  1100. _nAnms+=1
  1101. for p, phase in enumerate(['pre','post']):
  1102. pMask = np.logical_and(allAnmParams[anm]['shiftMask'][phase], ~allAnmParams[anm]['aw'])
  1103. pT = tracker[pMask,:]
  1104. pME = me[pMask]
  1105. meMask = np.logical_and(pME,pT[:,2]==5)
  1106. rMask = np.logical_or(pT[:,2]==0,meMask)
  1107. ratio = np.sum(meMask)/np.sum(rMask)
  1108. anmR[p] = ratio
  1109. print(f'{anm} {phase} pMask/total {np.sum(pMask==True)}/{len(pMask)} meMask/rMask = {np.sum(meMask)}/{np.sum(rMask)}')
  1110. base[a,:] = anmR
  1111. print('nAnms:', _nAnms)
  1112. total = np.zeros([nBoot,2])
  1113. for i in range(nBoot):
  1114. bAnms = np.random.choice(np.arange(len(anms)), len(anms))
  1115. total[i,:] = np.nanmean(base[bAnms,:], axis = 0)
  1116. # %%
  1117. diff = total[:,0] - total[:,1]
  1118. p = float(np.clip( 2 * min(np.mean(diff > 0), np.mean(diff < 0)), 1 / nBoot, 1.0))
  1119. print(f'pre {np.nanmedian(total[:,0]):.2f} post {np.nanmedian(total[:,1]):.2f}')
  1120. print(f"pre v post p = {p:.4f}")
  1121. # %%

Fig1.ipynb at commit f2a6377, under MIT · at the source

Overview

Authors: Jackson Scheib1, Zachary L Newman1, Jacob Gable1, Deano M Farinella1, Mitchell Head1, Savannah Bliese1, Benjamin Dougen1, Harishankar Jayakumar1, Sarah Young1, Nicole Miller1, Robert Al Khoury1, Huan Kim Tran1, Tien Dinh1, Aaron Kerlin1
  1. Department of Neuroscience, University of Minnesota Minneapolis United States
Institutions: University of Minnesota (United States)
Journal: eLife, volume 15, article RP111876
Dates: published online 2 September 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.7554/elife.111876 · PMID 42683605 · PMCID PMC13537764 · OpenAlex W7164581309
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: mouse (organism)
Methods: Connectivity, Spectral & time-frequency, Statistics, Machine learning, fMRI & imaging, Single-unit activity, calcium imaging, Smoothing, state filtering, decompositions
Keywords: dendrites, in vivo, motor learning, premotor cortex, Mouse
MeSH: Dendrites*, Learning*, Neurons*, Action Potentials, Animals, Mice (* major topic)
Journal subjects: Neuroscience
Topic: Motor Control and Adaptation (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Citations: not cited yet (Europe PMC); 110 references in the paper
Research resources: pENN-AAV-hSyn-Cre-WPRE-hGH RRID:Addgene_105553, pGP-AAV-syn-FLEX-jGCaMP8m-WPRE RRID:Addgene_162378, C57BL/6 J RRID:IMSR_JAX:000664, MATLAB RRID:SCR_001622, ScanImage RRID:SCR_014307, Bpod RRID:SCR_015943, RRID:SCR_020997, Tongue tracking RRID:SCR_021391, CaImAn RRID:SCR_021533

Abstract

Frontal cortex plays critical roles in action control and motor skill learning. Within the layer 1 apical tuft dendrites of layer 5 (L5) neurons in the frontal cortex, precise input patterns and back-propagating action potentials can trigger powerful regenerative events that may be essential for flexible computation and learning. However, it remains unclear whether tuft activity in frontal cortical L5 circuits encodes sensorimotor information that differs from the information conveyed by their outputs to downstream targets. Using longitudinal two-photon calcium imaging, we investigated sensorimotor encoding in the apical tuft dendrites and somata of L5 extratelencephalic neurons in the frontal cortex of mice during learning of a discrete change to a cued dexterous action. During learning, movement errors either triggered corrective action or did not, allowing us to dissociate error signals from signals selective for corrective action. Somatic activity tracked both instructional cues and action, whereas tuft activity predominantly tracked instructional cues. Movement errors during learning revealed additional distinct tuft activity that was selectively associated with corrective actions. Furthermore, learning induced divergent changes in the response gain and net selectivity of tuft dendrites compared to somata. Our measurements uncover systematic differences between the tuft dendrites and somata in sensorimotor selectivity, sensitivity to corrective action, and functional plasticity, providing a foundation for investigating the contributions of dendritic computation to motor skill learning.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repositories

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

kerlin-lab/TStracker

License: none: the authors keep all their rights
State: the link answers, verified on 26 September 2026
Evidence: files inventoried
Commit: 59448770c29c2330c7749c6ca9b421837c5024e4, 29 June 2020
Languages: C (322), C/C++ (124), C++ (46), Python (3), Shell (1)
Size: 846 files, 496 scripts
Software Heritage: not archived
Found in: the references
Holds: README, documentation
Not found: license file, CITATION.cff, environment file, tests, continuous integration
Tools: Pillow (3 files), tifffile (2 files)
Availability: 1 check, the latest on 26 September 2026: the link answers
  • 26 September 2026: the link answers
497 files, not copied: shown from their source

OSCR keeps no copy of these files: this repository has no license that allows it. The reader above shows each one from its source, fetched by your browser at commit 5944877, when its fingerprint is the one OSCR verified. How this works.

kerlin-lab/tongue-classification

License: none: the authors keep all their rights
State: the link answers, verified on 26 September 2026
Evidence: files inventoried
Commit: 26ad49e4226f06f27ab859abf1b400da45959053, 4 September 2026
Languages: Python (3), Shell (1)
Size: 617 files, 4 scripts
Software Heritage: not archived
Found in: the references
Holds: README
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: PyTorch (3 files), Matplotlib (1 file), NumPy (1 file), pandas (1 file)
Availability: 1 check, the latest on 26 September 2026: the link answers
  • 26 September 2026: the link answers
5 files, not copied: shown from their source

OSCR keeps no copy of these files: this repository has no license that allows it. The reader above shows each one from its source, fetched by your browser at commit 26ad49e, when its fingerprint is the one OSCR verified. How this works.

kerlin-lab/Scheib_2026

License: MIT
State: the link answers, verified on 26 September 2026
Evidence: files inventoried
Commit: f2a637754d671d35920faf9ba949c39039b705aa, 26 August 2026
Languages: Jupyter (6), Python (1)
Size: 481 files, 7 scripts
Software Heritage: not archived
Found in: “Data availability”
Holds: README, license file, environment (requirements.txt), 6 notebooks
Not found: CITATION.cff, tests, continuous integration, documentation
Tools: Matplotlib (7 files), NumPy (7 files), SciPy (7 files), tifffile (6 files), scikit-learn (2 files), seaborn (2 files), statsmodels (2 files)
Availability: 1 check, the latest on 26 September 2026: the link answers
  • 26 September 2026: the link answers
9 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:

  • 3 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 507 scripts, each with its path and the digest of its content;
  • 5 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

No dataset and no data link were found in the paper.

Data availability

Code and data intermediates used in the generation of this manuscript, as well as raw and registered 2P imaging data for one example somata imaging session and one example dendrite imaging session, are available at https://github.com/kerlin-lab/Scheib_2026 (copy archived at kerlin-lab, 2026). The complete raw image series areis approximately 20 TB and will be available on request.

Reproduced under the paper's license (CC BY), from the paper cited above.

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

Recorded: type, language, journal, volume, pages, dates, 14 authors, 5 keywords, 6 MeSH terms, 2 funders, 104 references, 9 RRIDs.

Cite

This paper

Scheib, J., Newman, Z. L., Gable, J., Farinella, D. M., Head, M., Bliese, S., Dougen, B., Jayakumar, H., Young, S., Miller, N., Al Khoury, R., Tran, H. K., Dinh, T., & Kerlin, A. (2026). Distinct sensorimotor encoding in tuft dendrites and somata associated with action, correction, and learning. eLife, 15, RP111876. https://doi.org/10.7554/elife.111876

BibTeX

@article{scheib2026distinct,
author = {Scheib, Jackson and Newman, Zachary L and Gable, Jacob and Farinella, Deano M and Head, Mitchell and Bliese, Savannah and Dougen, Benjamin and Jayakumar, Harishankar and Young, Sarah and Miller, Nicole and Al Khoury, Robert and Tran, Huan Kim and Dinh, Tien and Kerlin, Aaron},
title = {{Distinct sensorimotor encoding in tuft dendrites and somata associated with action, correction, and learning}},
journal = {eLife},
year = {2026},
month = sep,
volume = {15},
pages = {RP111876},
publisher = {eLife Sciences Publications, Ltd},
issn = {2050-084X},
doi = {10.7554/elife.111876},
url = {https://doi.org/10.7554/elife.111876},
pmid = {42683605},
pmcid = {PMC13537764}
}

RIS

TY - JOUR
AU - Scheib, Jackson
AU - Newman, Zachary L
AU - Gable, Jacob
AU - Farinella, Deano M
AU - Head, Mitchell
AU - Bliese, Savannah
AU - Dougen, Benjamin
AU - Jayakumar, Harishankar
AU - Young, Sarah
AU - Miller, Nicole
AU - Al Khoury, Robert
AU - Tran, Huan Kim
AU - Dinh, Tien
AU - Kerlin, Aaron
TI - Distinct sensorimotor encoding in tuft dendrites and somata associated with action, correction, and learning
T2 - eLife
J2 - eLife
PY - 2026
DA - 2026/09/02
VL - 15
SP - RP111876
SN - 2050-084X
PB - eLife Sciences Publications, Ltd
DO - 10.7554/elife.111876
UR - https://doi.org/10.7554/elife.111876
LA - en
ER -

CSL-JSON

{
"id": "10.7554/elife.111876",
"type": "article-journal",
"title": "Distinct sensorimotor encoding in tuft dendrites and somata associated with action, correction, and learning",
"container-title": "eLife",
"author": [
{
"family": "Scheib",
"given": "Jackson"
},
{
"family": "Newman",
"given": "Zachary L"
},
{
"family": "Gable",
"given": "Jacob"
},
{
"family": "Farinella",
"given": "Deano M"
},
{
"family": "Head",
"given": "Mitchell"
},
{
"family": "Bliese",
"given": "Savannah"
},
{
"family": "Dougen",
"given": "Benjamin"
},
{
"family": "Jayakumar",
"given": "Harishankar"
},
{
"family": "Young",
"given": "Sarah"
},
{
"family": "Miller",
"given": "Nicole"
},
{
"family": "Al Khoury",
"given": "Robert"
},
{
"family": "Tran",
"given": "Huan Kim"
},
{
"family": "Dinh",
"given": "Tien"
},
{
"family": "Kerlin",
"given": "Aaron"
}
],
"container-title-short": "eLife",
"volume": "15",
"page": "RP111876",
"DOI": "10.7554/elife.111876",
"PMID": "42683605",
"PMCID": "PMC13537764",
"ISSN": "2050-084X",
"publisher": "eLife Sciences Publications, Ltd",
"URL": "https://doi.org/10.7554/elife.111876",
"language": "en",
"issued": {
"date-parts": [
[
2026,
9,
2
]
]
}
}

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-74869-1 [code]
Complementary roles of cell-type-specific plasticity in shaping neocortical dynamics for learning action timing.
Journal: Nature communications
In common: mouse, 12 references
[2] doi:10.1126/sciadv.adz4123 [code]
Highly attenuated dendritic propagation of isolated synaptic potentials in vivo.
Journal: Science advances
In common: mouse, 10 references
[3] doi:10.1016/j.isci.2026.117010 [code]
Deep learning-assisted mapping of dendritic spines using sequential 2D two-photon calcium imaging.
Journal: iScience
In common: tifffile, PyTorch, seaborn, 5 other tools, 5 references
[4] doi:10.1016/j.celrep.2026.117419 [code]
Conserved role of primary motor cortex in the control of prehension in mice and macaques.
Journal: Cell reports
In common: statsmodels, seaborn, scikit-learn, 4 other tools, mouse, 6 references
[5] doi: [code]
Real-time closed-loop feedback system for mouse mesoscale cortical signal and movement control
Journal: eLife
In common: tifffile, Pillow, statsmodels, 6 other tools, mouse, 4 references
[6] doi:10.1016/j.isci.2026.116206 [code]
Gut distension evokes rapid neural dynamics in vagal and hindbrain populations of larval zebrafish.
Journal: iScience
In common: tifffile, Pillow, statsmodels, 7 other tools, 3 references
[7] doi:10.1038/s41467-026-74460-8 [code]
Spike-based alignment learning solves the weight transport problem.
Journal: Nature communications
In common: PyTorch, seaborn, scikit-learn, 4 other tools, 5 references
[8] doi:10.7554/elife.109717 [code]
Retrosplenial cortex enables context-dependent goal-directed sensorimotor transformation.
Journal: eLife
In common: tifffile, Pillow, statsmodels, 6 other tools, mouse, 3 references
[9] doi:10.1002/advs.202520773 [code]
Mild Focal Cooling Decouples Dendrites to Reconfigure Cortical Output.
Journal: Advanced science (Weinheim, Baden-Wurttemberg, Germany)
In common: SciPy, Matplotlib, NumPy, mouse, 7 references
[10] doi:10.1371/journal.pcbi.1014571 [code]
SynAPSeg: A novel dataset and image analysis framework for deep learning-based synapse detection and quantification.
Journal: PLoS computational biology
In common: tifffile, Pillow, statsmodels, 7 other tools, 2 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.