OSCR

Real-time closed-loop feedback system for mouse mesoscale cortical signal and movement control

Code ↔ Paper

11 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 11 matches
  1. [1] § Materials and methods › Seed-pixel correlation matrices ↔ helper.py, lines 649–730 · score 0.68 · seed pixels, Allen, ALM, OB, RS, BC
  2. [2] § Results ↔ analysis/plot_clmf.py, lines 271–294 · score 0.62 · snout front, snout top, snout bottom, CLMF, mice
  3. [3] § Materials and methods › CLNF, CLMF setup, and behavior experiments ↔ PiCameraStream.py, lines 8–102 · score 0.58 · Pi Camera, OV5647, exposure, RGB, sensor, frame
  4. [4] § Materials and methods › CLNF, CLMF setup, and behavior experiments ↔ brain/cla_reward_punish_1roi.py, lines 287–373 · score 0.56 · config.ini, mouse_id, configured, channels, duration, pi
  5. [5] § Results › Cortical responses became focal and more correlated as mice learned the rewarded behavior in CLMF ↔ analysis/plot_clmf.py, lines 665–730 · score 0.55 · Bonferroni corrected, RM ANOVA, variables, correlations, seed, days
  6. [6] § Results ↔ analysis/plot_clmf.py, lines 271–294 · score 0.54 · Kullback Leibler divergence, divergence scores, snout, CLMF, day, mouse
  7. [7] § Materials and methods › CLNF, CLMF setup, and behavior experiments ↔ behavior/cla_dlc_trials_speed.py, lines 247–317 · score 0.53 · config.ini, mouse_id, licking, configured, speed, video
  8. [8] § Materials and methods › CLNF, CLMF setup, and behavior experiments ↔ brain/cla_reward_punish_1roi.py, lines 287–373 · score 0.52 · behavior pi, behavior recording, TTL, configured, brain, LED
  9. [9] § Materials and methods › CLNF, CLMF setup, and behavior experiments ↔ brain/cla_reward_punish_2roi.py, lines 289–334 · score 0.52 · behavior pi, behavior recording, TTL, configured, brain, LED
  10. [10] § Results › Cortical responses became focal and more correlated as mice learned the rewarded behavior in CLMF ↔ analysis/plot_clmf.py, lines 527–608 · score 0.52 · Benjamini Hochberg, Mann Whitney, mm, CLMF, days, Reward
  11. [11] § Results › Mice can explore and learn an arbitrary task, rule, and target conditions ↔ analysis/plot_clnf.py, lines 353–370 · score 0.51 · ROI1 ROI2, ROI rule, fast, CLNF, day, reward

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 · 730 lines · 41 KB · GPL-3.0 · 4 matches

  1. """
  2. Created on Thu Jul 25 12:09:03 2024
  3. use trials_df and plot data stats and performance
  4. Requires file clmf_df_speed_daily_all.pkl clmf_df_beh_log_daily_all.pkl, clmf_avg_dff_response_daily.pkl
  5. Please download from here-https://drive.google.com/drive/folders/1eq0muLtc36jcU4sUy8fVOSmN4dxVxdn8?usp=sharing
  6. @author: pankaj gupta
  7. """
  8. import os
  9. import platform
  10. import sys
  11. from os.path import dirname, realpath
  12. filepath = realpath(__file__)
  13. dir_of_file = dirname(filepath)
  14. parent_dir_of_file = dirname(dir_of_file)
  15. sys.path.append(parent_dir_of_file)
  16. import matplotlib.pyplot as plt
  17. import matplotlib
  18. matplotlib.use('agg')
  19. from matplotlib.backends.backend_pdf import PdfPages
  20. from matplotlib import colors
  21. import numpy as np
  22. from statannot import add_stat_annotation
  23. import pymannkendall as mk
  24. from statsmodels.stats.anova import AnovaRM
  25. import pingouin as pg
  26. from scipy.stats import f_oneway
  27. from statannotations.Annotator import Annotator
  28. import itertools
  29. import tifffile as tif
  30. import pandas as pd
  31. import seaborn as sns
  32. from pathlib import Path
  33. import glob
  34. from tqdm import tqdm
  35. from scipy import signal
  36. import scipy.io as sio
  37. from scipy.ndimage import gaussian_filter
  38. import imageio
  39. import argparse
  40. from configparser import ConfigParser
  41. import napari
  42. import pickle
  43. os.environ[ 'NUMBA_CACHE_DIR' ] = '/tmp/numba_cache/'
  44. import dlc2kinematics
  45. from statsmodels.stats.multitest import multipletests
  46. # from pygifsicle import optimize
  47. from get_clmf_data import \
  48. expt1_data_list, expt2_data_list, expt3_data_list
  49. from helper import *
  50. data_root = '/home/pankaj/teamshare/pkg/closedloop_rig5_data/'
  51. out_dir = '../processed_data/'
  52. data_list = expt1_data_list
  53. data_list.extend(expt2_data_list)
  54. data_list.extend(expt3_data_list)
  55. # %%
  56. # Initiate the parser
  57. parser = argparse.ArgumentParser()
  58. # Add long and short argument
  59. parser.add_argument("--process_mouse", "-n", required=False, help="Folder name to process")
  60. parser.add_argument("--process_dir", "-d", type=int, required=False, help="Set recording dir to process")
  61. # Read arguments from the command line
  62. args = parser.parse_args()
  63. if args.process_mouse: # Check for --name
  64. print("Process mouse id %s" % args.process_mouse)
  65. if args.process_dir: # Check for --day
  66. print("Process dir %s" % args.process_dir)
  67. ppmm_brain = 14.56
  68. ppmm_beh = 3.4
  69. mice_list = [e[0] for e in data_list]
  70. groups = [e[2] for e in data_list]
  71. cal_days = list(itertools.chain.from_iterable([e[1] for e in data_list]))
  72. pp = PdfPages(out_dir + os.path.basename(__file__).split('.')[0] + '_summary_stats.pdf')
  73. #%%
  74. file_sessions_df_csv = out_dir + os.sep + 'clmf_sessions_df' + '.csv'
  75. if os.path.isfile(file_sessions_df_csv):
  76. sessions_df = pd.read_csv(file_sessions_df_csv, dtype = {'mouse_id':str, 'cal_day':str, 'day':int, 'control_joint':str,
  77. 'trials':int, 'rewards':int, 'fll_based_rewards':int,
  78. 'flr_based_rewards':int, 'fll_totaldist': float, 'flr_totaldist': float,
  79. 'pca_dims_brain': float, 'pca_dims_behavior': float, 'group':str})
  80. sessions_df = sessions_df[sessions_df['mouse_id'].isin( mice_list)]
  81. sessions_df = sessions_df[sessions_df['group'].isin( groups)]
  82. sessions_df = sessions_df[sessions_df['cal_day'].isin(cal_days)]
  83. #%% Figure not included in paper: Plot left and right paw movements over days
  84. for grp in np.unique(groups):
  85. print(grp)
  86. a = sessions_df[sessions_df['group'].isin([grp])]
  87. sessions_df_melted = a.melt(id_vars=['cal_day','day'], value_vars=['fll_totaldist', 'flr_totaldist'])
  88. fig = plt.figure();
  89. # gfg = sns.boxplot(x="day", y="value", hue="variable", data=sessions_df_melted)
  90. gfg = sns.pointplot(x="day", y="value", hue="variable", data=sessions_df_melted, errorbar=('ci', 95), dodge=True)
  91. gfg.legend(fontsize=15)
  92. sns.despine(offset=10, trim=True)
  93. plt.xlabel('Days', fontweight='bold', fontsize=14)
  94. plt.xticks(sessions_df.day.unique(), sessions_df.day.unique() +1, fontsize=20)
  95. plt.ylabel('Paw movement (mm)', fontweight='bold', fontsize=14)
  96. plt.yticks(fontsize=20)
  97. plt.title(grp + ' paw movements', fontsize=14)
  98. plt.tight_layout();
  99. pp.savefig(fig); plt.close()
  100. #%% Figure not included in paper: Left and right paw movement based rewards over days
  101. for grp in np.unique(groups):
  102. print(grp)
  103. a = sessions_df[sessions_df['group'].isin([grp])]
  104. sessions_df_melted = a.melt(id_vars=['cal_day','day'], value_vars=['fll_based_rewards', 'flr_based_rewards'])
  105. sessions_df_melted['value'] = sessions_df_melted['value'].div(60).mul(100)
  106. fig = plt.figure();
  107. # gfg = sns.boxplot(x="day", y="value", hue="variable", data=sessions_df_melted)
  108. # gfg = sns.lineplot(x="day", y="value", hue="variable", data=sessions_df_melted, err_style="bars", alpha=0.5)
  109. gfg = sns.pointplot(x="day", y="value", hue="variable", data=sessions_df_melted, errorbar=('ci', 95), dodge=True)
  110. gfg.legend(fontsize=15)
  111. sns.despine(offset=10, trim=True)
  112. plt.xlabel('Days', fontweight='bold', fontsize=14)
  113. plt.xticks(sessions_df.day.unique(), sessions_df.day.unique() +1, fontsize=20)
  114. plt.ylabel('Success Rate (%)', fontweight='bold', fontsize=14)
  115. plt.yticks(fontsize=20)
  116. plt.title(grp + ' FLL and FLR based rewards', fontsize=14);
  117. plt.tight_layout();
  118. pp.savefig(fig); plt.close()
  119. #%% Figure 4B Plot success rate over days for each group
  120. sessions_df['performance'] = (sessions_df['rewards'] - sessions_df['rewards'].min()) / (sessions_df['rewards'].max() - sessions_df['rewards'].min())
  121. fig = plt.figure(figsize=(12,8));
  122. gfg = sns.pointplot(x="day", y="performance", hue="group", data=sessions_df, dodge=0.2)
  123. gfg.legend(fontsize=15)
  124. sns.despine(offset=10, trim=True)
  125. plt.xlabel('Days', fontweight='bold', fontsize=20)
  126. plt.xticks(sessions_df.day.unique(), sessions_df.day.unique() +1, fontsize=20)
  127. plt.ylabel('Normalized', fontweight='bold', fontsize=20)
  128. plt.yticks(fontsize=20)
  129. plt.title('Group success rate', fontsize=20)
  130. plt.tight_layout();
  131. pp.savefig(fig); plt.close()
  132. #%% Statistical tests for Figure 4B Write statistical results summary in the pdf
  133. fig = plt.figure(figsize=(12,10))
  134. fig.clf()
  135. a = sessions_df[(sessions_df['day']<=4) & (sessions_df['group']=='grp1')]
  136. fig.text(0.05,0.97, 'RM-ANOVA grp1 before rule change', weight='bold')
  137. fig.text(0.05,0.89, pg.rm_anova(dv='performance', within=['day'], subject='mouse_id', data=a, detailed=True).to_markdown())
  138. a = sessions_df[(sessions_df['day']>4) & (sessions_df['group']=='grp1')]
  139. fig.text(0.05,0.81, 'RM-ANOVA grp1 after rule change', weight='bold')
  140. fig.text(0.05,0.73, pg.rm_anova(dv='performance', within=['day'], subject='mouse_id', data=a, detailed=True).to_markdown())
  141. a = sessions_df[(sessions_df['group']=='grp1')]
  142. fig.text(0.05,0.65, 'RM-ANOVA grp1 on all days', weight='bold')
  143. fig.text(0.05,0.57, pg.rm_anova(dv='performance', within=['day'], subject='mouse_id', data=a, detailed=True).to_markdown())
  144. a = sessions_df[(sessions_df['group']=='grp2')]
  145. fig.text(0.05,0.49, 'RM-ANOVA grp2 on all days', weight='bold')
  146. fig.text(0.05,0.41, pg.rm_anova(dv='performance', within=['day'], subject='mouse_id', data=a, detailed=True).to_markdown())
  147. a = sessions_df[(sessions_df['group']=='grp3')]
  148. fig.text(0.05,0.33, 'RM-ANOVA grp3 on all days', weight='bold')
  149. fig.text(0.05,0.25, pg.rm_anova(dv='performance', within=['day'], subject='mouse_id', data=a, detailed=True).to_markdown())
  150. pp.savefig(fig)
  151. plt.close()
  152. #%%
  153. file_trials_df_csv = out_dir + os.sep + 'clmf_trials_df' + '.csv'
  154. if os.path.isfile(file_trials_df_csv):
  155. trials_df = pd.read_csv(file_trials_df_csv, dtype = {'mouse_id':str, 'cal_day':str, 'day':int, 'control_joint':str, 'group':str,
  156. 'trial':int, 'start_ix':int, 'end_ix':int, 'tr_duration':float,
  157. 'reward': int, 'manual_label': str,
  158. 'fll_dist_tr': float, 'fll_dist_prestart': float, 'fll_dist_poststart': float,
  159. 'fll_dist_prereward': float, 'fll_dist_postreward': float, 'fll_maxspeed_prereward': float,
  160. 'flr_dist_tr': float, 'flr_dist_prestart': float, 'flr_dist_poststart': float,
  161. 'flr_dist_prereward': float, 'flr_dist_postreward': float, 'flr_maxspeed_prereward': float,
  162. 'fll_tortuosity_prereward': float, 'flr_tortuosity_prereward': float})
  163. # we can select part of the dataframe based on current analysis
  164. trials_df = trials_df[trials_df['mouse_id'].isin( mice_list)]
  165. trials_df = trials_df[trials_df['group'].isin( groups)]
  166. trials_df = trials_df[trials_df['cal_day'].isin(cal_days)]
  167. #%% re-assign 'day' column
  168. for expt in data_list:
  169. mouse_id, list_rec_dir, grp = expt
  170. for day, rec_dir in enumerate(list_rec_dir):
  171. if ((trials_df['mouse_id'] == mouse_id) & (trials_df['cal_day'] == rec_dir)).any():
  172. #row already exists, update all values except the manual_label
  173. trials_df.loc[(trials_df['mouse_id'] == mouse_id) & (trials_df['cal_day'] == rec_dir), ['day']] = day
  174. #%% Figure 4D maximum speeds
  175. for grp in np.unique(groups):
  176. print(grp)
  177. a = trials_df[trials_df['group'].isin([grp])]
  178. trials_df_melted = a.melt(id_vars=['cal_day','day'], value_vars=['fll_maxspeed_prereward', 'flr_maxspeed_prereward'])
  179. fig = plt.figure(figsize=(8,5));
  180. # gfg = sns.boxplot(x="day", y="value", hue="variable", data=trials_df_melted)
  181. gfg = sns.pointplot(x="day", y="value", hue="variable", data=trials_df_melted, errorbar=('ci', 95), dodge=True) #husl, colorblind, Set1, bright
  182. gfg.legend(fontsize=15)
  183. sns.despine(offset=10, trim=True)
  184. # plt.ylim([0,120])
  185. plt.xlabel('Days', fontweight='bold', fontsize=18)
  186. plt.xticks(np.arange(len(trials_df.day.unique())), trials_df.day.unique(), fontsize=20)
  187. plt.ylabel('Speed (mm/sec.)', fontweight='bold', fontsize=18)
  188. plt.yticks(fontsize=20)
  189. plt.title(grp + ': Maximum L/R paw speed during trials', fontsize=14)
  190. plt.tight_layout();
  191. pp.savefig(fig); plt.close()
  192. #%% Figure 4B success rate by group (another way to plot Figure 4B)
  193. trialcounts_by_mouseid_all = trials_df.groupby(['mouse_id', 'day', 'group'])['reward'].count()
  194. trialcounts_by_mouseid_rewarded = trials_df[trials_df.reward==1].groupby(['mouse_id', 'day', 'group'])['reward'].count()
  195. successrate_by_mouseid = (trialcounts_by_mouseid_rewarded / trialcounts_by_mouseid_all).reset_index()
  196. hue_plot_params = {
  197. 'data': successrate_by_mouseid,
  198. 'x': 'day',
  199. 'y': 'reward',
  200. "hue": "group",
  201. "palette": 'husl'
  202. }
  203. fig = plt.figure();
  204. ax = sns.pointplot(**hue_plot_params, dodge=0.2)
  205. sns.despine(offset=10, trim=True)
  206. plt.xlabel('Days', fontweight='bold', fontsize=14)
  207. plt.xticks(trials_df.day.unique(), trials_df.day.unique() +1, fontsize=20)
  208. plt.ylabel('Success Rate (%)', fontweight='bold', fontsize=14)
  209. plt.yticks(fontsize=20)
  210. plt.title('Group success rate')
  211. plt.tight_layout(pad=2)
  212. pairs = [
  213. [(1, 'grp1'), (3, 'grp1')],
  214. [(1, 'grp1'), (4, 'grp1')],
  215. [(1, 'grp1'), (5, 'grp1')],
  216. [(4, 'grp1'), (5, 'grp1')],
  217. [(5, 'grp1'), (6, 'grp1')],
  218. [(1, 'grp3'), (4, 'grp3')],
  219. ]
  220. # annotator = Annotator(ax, pairs, data=successrate_by_mouseid, x='day', y='reward')
  221. annotator = Annotator(ax, pairs, **hue_plot_params)
  222. # # test: Brunner-Munzel, Levene, Mann-Whitney, Mann-Whitney-gt, Mann-Whitney-ls, t-test_ind, t-test_welch, t-test_paired, Wilcoxon, Kruskal
  223. # # comparisons_correction: Bonferroni, Holm-Bonferroni, Benjamini-Hochberg, Benjamini-Yekutieli
  224. annotator.configure(test='Mann-Whitney', show_test_name=True, comparisons_correction='Benjamini-Hochberg', text_format='star') #bejamini hochberg, Bonferroni
  225. annotator.apply_and_annotate()
  226. pp.savefig(fig); plt.close()
  227. #%% Figure 4C task latency
  228. fig = plt.figure(figsize=(8,5))
  229. # ax = sns.violinplot(x='day', y='tr_duration', hue='group', data=trials_df,inner="box", palette="husl", linewidth=0.5, saturation=0.5)
  230. # ax = sns.lineplot(x='day', y='tr_duration', hue='group', data=trials_df, errorbar=('ci', 95), err_style="bars", markers = True, linewidth=2, alpha=0.5)
  231. ax = sns.pointplot(x='day', y='tr_duration', hue='group', data=trials_df, errorbar=('ci', 95))
  232. sns.despine(offset=10, trim=True)
  233. plt.xlabel('Day', fontweight='bold', fontsize=14)
  234. plt.xticks(trials_df.day.unique(), trials_df.day.unique() +1, fontsize=20)
  235. # plt.axvline(x=5, c='k')
  236. plt.ylabel('Task delay (s)', fontweight='bold', fontsize=14)
  237. plt.yticks(fontsize=20)
  238. plt.title('Group task delay', fontsize=14)
  239. plt.tight_layout();
  240. # pairs=[(0,3), (0,4), (3,4), (4,5),(4,9),(8,9)]
  241. # annotator = Annotator(ax, pairs, data=trials_df, x='day', y='tr_duration')
  242. # annotator.configure(test='Mann-Whitney', show_test_name=True, comparisons_correction='Benjamini-Hochberg', text_format='star')
  243. # annotator.apply_and_annotate()
  244. pp.savefig(fig)
  245. plt.close()
  246. print('Done')
  247. #%% Supplementary figure 4B Requires kld_df.csv located under data directory
  248. bp_select = ['snout_top', 'fl_l', 'fl_r', 'hl_l', 'hl_r', 'tailbase_bottom']
  249. file_kld_df_csv = out_dir + os.sep + 'clmf_kld_df' + '.csv'
  250. if os.path.isfile(file_kld_df_csv):
  251. kld_df = pd.read_csv(file_kld_df_csv, dtype = {'snout_front': float, 'fl_l_front': float, 'fl_r_front': float,
  252. 'snout_top': float, 'snout_bottom': float, 'fl_l': float, 'fl_r': float,
  253. 'hl_l': float, 'hl_r': float, 'tailbase_bottom': float,
  254. 'tailbase_top': float, 'day':str, 'mouse_id':str, 'group':str})
  255. kld_df = kld_df[kld_df['mouse_id'].isin( mice_list)]
  256. kld_df = kld_df[kld_df['group'].isin( groups)]
  257. days = kld_df.day.unique()
  258. for grp in np.unique(groups):
  259. print(grp)
  260. a = kld_df[kld_df['group'].isin([grp])].replace([np.nan, np.inf], 0)
  261. kld_days = np.stack([a[a.day==day][bp_select].mean() for day in days])
  262. fig, axs = plt.subplots(1,1, sharey=True, figsize=(5,3))
  263. im = sns.heatmap(kld_days.T, cmap='Greys', ax=axs)
  264. im.invert_yaxis()
  265. axs.set_xticklabels(days)
  266. axs.set_yticklabels(bp_select, rotation=0)
  267. plt.title(grp + ' Kullback-Leibler Divergence scores', fontsize=14)
  268. plt.tight_layout();
  269. pp.savefig(fig); plt.close()
  270. #%% correlation matrix of paw speeds on each trial
  271. file_df_speed_daily_all_pkl = out_dir + os.sep + 'clmf_df_speed_daily_all' + '.pkl'
  272. file_df_beh_log_daily_all_pkl = out_dir + os.sep + 'clmf_df_beh_log_daily_all' + '.pkl'
  273. df_speed_daily_all_file = open(file_df_speed_daily_all_pkl, 'rb')
  274. df_speed_daily_all = pickle.load(df_speed_daily_all_file)
  275. df_speed_daily_all_file.close()
  276. df_beh_log_daily_all_file = open(file_df_beh_log_daily_all_pkl, 'rb')
  277. df_beh_log_daily_all = pickle.load(df_beh_log_daily_all_file)
  278. df_beh_log_daily_all_file.close()
  279. # to select data for plotting
  280. data_list = [('FW2_ai94', 'grp1'),
  281. ('FW3_ai94', 'grp1'),
  282. ('FW22_ai94', 'grp1'),
  283. ('GT3_tta', 'grp1'),
  284. ('GT33_tta', 'grp1'), ('HA2+_tta', 'grp1'), ('GER2_ai94', 'grp1'), ('HYL3_tta', 'grp1'),
  285. ('GER2_ai94', 'grp2'), ('HYL3_tta', 'grp2'),
  286. ('BR1_vgat-ai203', 'grp2'), ('BR2_vgat-ai203', 'grp2'),
  287. ('GIL3_ai94', 'grp3'),
  288. ('GIR2_ai94', 'grp3')]
  289. groups = np.unique([d[1] for d in data_list])
  290. days = [1,2,3,4,5,6,7,8,9,10]
  291. beh_fps=30
  292. bp_select = ['fl_l', 'fl_r', 'hl_l', 'hl_r', 'tailbase_bottom', 'tailbase_top']
  293. #%% Figure 7D total bodypart movement during trial over days
  294. for g in groups:
  295. bodypart_distance_grp_stack = []
  296. for expt in data_list:
  297. mouse_id, grp = expt
  298. if grp != g:
  299. continue
  300. bodyparts_distance_stack = []
  301. for day in days:
  302. df = df_beh_log_daily_all[(df_beh_log_daily_all['mouse_id']==mouse_id) &
  303. (df_beh_log_daily_all['group']==grp) &
  304. (df_beh_log_daily_all['day']==day)]
  305. starts_ix = np.where(np.diff(df.trial.values) > 0)[0]
  306. ends_ix = np.where(np.diff(df.trial.values) < 0)[0]
  307. reward_ix = df[df.reward == 1].index
  308. fail_ix = df[df.reward == -1].index
  309. bodyparts_distance_stack.append(np.vstack([np.sum(df_speed_daily_all[bp_select].iloc[tr - 60:tr])/beh_fps for tr in reward_ix]).mean(axis=0))
  310. bodypart_distance_grp_stack.append(bodyparts_distance_stack)
  311. fig, axs = plt.subplots(1,1, sharey=True, figsize=(5,3))
  312. im = sns.heatmap(np.stack(bodypart_distance_grp_stack).mean(axis=0).T, cmap='Greys', ax=axs, vmin=10, vmax=40)
  313. im.invert_yaxis()
  314. axs.set_xticklabels(days)
  315. axs.set_yticklabels(bp_select, rotation=0)
  316. plt.title(g + ' bodyparts distances during trial')
  317. pp.savefig(fig); plt.close()
  318. #%% Figure 6C speed correlation matrix for each group (average) during rewards
  319. for g in groups:
  320. speed_corrmat_stack = []
  321. for expt in data_list:
  322. mouse_id, grp = expt
  323. if grp != g:
  324. continue
  325. fll_speed_stack = []
  326. flr_speed_stack = []
  327. for day in days:
  328. df = df_beh_log_daily_all[(df_beh_log_daily_all['mouse_id']==mouse_id) &
  329. (df_beh_log_daily_all['group']==grp) &
  330. (df_beh_log_daily_all['day']==day)]
  331. starts_ix = np.where(np.diff(df.trial.values) > 0)[0]
  332. ends_ix = np.where(np.diff(df.trial.values) < 0)[0]
  333. reward_ix = df[df.reward == 1].index
  334. fail_ix = df[df.reward == -1].index
  335. fll_speed_stack.append(np.stack([df_speed_daily_all['fl_l'].iloc[tr - 60:tr] for tr in reward_ix]).mean(axis=0))
  336. flr_speed_stack.append(np.stack([df_speed_daily_all['fl_r'].iloc[tr - 60:tr] for tr in reward_ix]).mean(axis=0))
  337. speed_corrmat_stack.append(np.corrcoef(np.vstack((fll_speed_stack, flr_speed_stack))))
  338. fig, axs = plt.subplots(1,1, sharey=True)
  339. im = sns.heatmap(np.stack(speed_corrmat_stack).mean(axis=0), cmap='Greys', ax=axs, vmin=0.5, vmax=1)
  340. im.invert_yaxis()
  341. axs.set_xticklabels(np.append(['FLL_'+str(day) for day in days], ['FLR_'+str(day) for day in days]), rotation=90)
  342. axs.set_yticklabels(np.append(['FLL_'+str(day) for day in days], ['FLR_'+str(day) for day in days]), rotation=0)
  343. plt.title(g + ' speed profile correlations')
  344. pp.savefig(fig); plt.close()
  345. ## speed correlation matrix for each group (average) during rest
  346. for g in groups:
  347. speed_corrmat_stack = []
  348. for expt in data_list:
  349. mouse_id, grp = expt
  350. if grp != g:
  351. continue
  352. fll_speed_stack = []
  353. flr_speed_stack = []
  354. for day in days:
  355. df = df_beh_log_daily_all[(df_beh_log_daily_all['mouse_id']==mouse_id) &
  356. (df_beh_log_daily_all['group']==grp) &
  357. (df_beh_log_daily_all['day']==day)]
  358. starts_ix = np.where(np.diff(df.trial.values) > 0)[0]
  359. ends_ix = np.where(np.diff(df.trial.values) < 0)[0]
  360. reward_ix = df[df.reward == 1].index
  361. fail_ix = df[df.reward == -1].index
  362. fll_speed_stack.append(np.stack([df_speed_daily_all['fl_l'].iloc[tr - 60:tr] for tr in starts_ix]).mean(axis=0))
  363. flr_speed_stack.append(np.stack([df_speed_daily_all['fl_r'].iloc[tr - 60:tr] for tr in starts_ix]).mean(axis=0))
  364. speed_corrmat_stack.append(np.corrcoef(np.vstack((fll_speed_stack, flr_speed_stack))))
  365. fig, axs = plt.subplots(1,1, sharey=True)
  366. im = sns.heatmap(np.stack(speed_corrmat_stack).mean(axis=0), cmap='Greys', ax=axs, vmin=0, vmax=1, square=True)
  367. im.invert_yaxis()
  368. axs.set_xticklabels(np.append(['FLL_'+str(day) for day in days], ['FLR_'+str(day) for day in days]), rotation=90)
  369. axs.set_yticklabels(np.append(['FLL_'+str(day) for day in days], ['FLR_'+str(day) for day in days]), rotation=0)
  370. plt.title(g + ' speed profile correlations')
  371. plt.tight_layout()
  372. #%%
  373. data_list = [('FW2_ai94', 'grp1'),
  374. ('FW22_ai94', 'grp1'),
  375. ('GT33_tta', 'grp1'), ('HA2+_tta', 'grp1'), ('GER2_ai94', 'grp1'), ('HYL3_tta', 'grp1'),
  376. ('GER2_ai94', 'grp2'), ('HYL3_tta', 'grp2'),
  377. ('GIL3_ai94', 'grp3'),
  378. ('GIR2_ai94', 'grp3')]
  379. days = [1,4,5,7,10]
  380. beh_fps = 30
  381. epochSize = 5*beh_fps # should be same as used in generating the pkl file
  382. t = np.arange(-epochSize, epochSize) / beh_fps
  383. file_dff_response_all_pkl = out_dir + os.sep + 'clmf_avg_dff_response_daily' + '.pkl'
  384. if not os.path.isfile(file_dff_response_all_pkl):
  385. sys.exit('DFF responses file doesnt exist')
  386. dff_response_file = open(file_dff_response_all_pkl, 'rb')
  387. dff_response_daily_all = pickle.load(dff_response_file)
  388. dff_response_file.close()
  389. #%% Figure 7A 7B Plot average DFF activity for each seed location, per day for all mice
  390. for expt in data_list:
  391. mouse_id, grp = expt
  392. for se in clmf_seeds_mm:
  393. print(mouse_id + ' ' + grp + ' ' + se)
  394. df = dff_response_daily_all[(dff_response_daily_all.mouse_id == mouse_id) &
  395. (dff_response_daily_all.group == grp) &
  396. (dff_response_daily_all.seedname == se)]
  397. if df.empty:
  398. print('No data')
  399. continue
  400. ## dff profiles at the seed location over the days
  401. fig, axs = plt.subplots(1, 2, sharey=True, figsize=(12,5))
  402. axs[0].set_title(mouse_id + ' ' + grp + ' ' + se + ' L', fontsize=14)
  403. axs[1].set_title(mouse_id + ' ' + grp + ' ' + se + ' R', fontsize=14)
  404. g1=sns.lineplot(data=np.stack([df[df.day==day]['reward_stack_l'].values[0].mean(axis=0) for day in days]).T, ax=axs[0], legend=False, palette='Greens');
  405. # g1=sns.lineplot(data=np.stack([df[df.day==day]['rest_stack_l'].values[0].mean(axis=0) for day in days]).T, ax=axs[0], legend=False, palette='Reds');
  406. g2=sns.lineplot(data=np.stack([df[df.day==day]['reward_stack_r'].values[0].mean(axis=0) for day in days]).T, ax=axs[1], palette='Greens');
  407. # g2=sns.lineplot(data=np.stack([df[df.day==day]['rest_stack_r'].values[0].mean(axis=0) for day in days]).T, ax=axs[1], palette='Reds');
  408. plt.legend(days)
  409. plt.xlabel('Time (sec.)', fontweight='bold')
  410. # plt.xticks(np.arange(0, len(t),45), t[::45])
  411. g1.set_xticks(np.arange(0, len(t),60)) # <--- set the ticks first
  412. g1.set_xticklabels(t[::60])
  413. axs[0].axvline(epochSize, c='b')
  414. axs[0].axvline(epochSize-beh_fps, c='g')
  415. g2.set_xticks(np.arange(0, len(t),60)) # <--- set the ticks first
  416. g2.set_xticklabels(t[::60])
  417. axs[1].axvline(epochSize, c='b')
  418. axs[1].axvline(epochSize-beh_fps, c='g')
  419. plt.ylabel('DFF', fontweight='bold')
  420. sns.despine(offset=10, trim=True)
  421. # plt.yticks(fontsize=20)
  422. plt.tight_layout();
  423. pp.savefig(fig); plt.close()
  424. ## correlation matrix of dff responses at seedpixel over days
  425. fig, axs = plt.subplots(1,2, sharey=True)
  426. # im = axs[0].imshow(np.corrcoef(np.vstack([stk for stk in df['reward_stack_l']])), cmap='Greys');
  427. # im = axs[1].imshow(np.corrcoef(np.vstack([stk for stk in df['reward_stack_r']])), cmap='Greys');
  428. im = axs[0].imshow(np.corrcoef(np.vstack([stk.mean(axis=0) for stk in df['reward_stack_l']])), cmap='Greys');
  429. im = axs[1].imshow(np.corrcoef(np.vstack([stk.mean(axis=0) for stk in df['reward_stack_r']])), cmap='Greys');
  430. axs[0].set_title(mouse_id + ' ' + grp + ' ' + se + ' L', fontsize=14)
  431. axs[0].set_xticks(np.arange(len(df['day'].unique()))); axs[0].set_xticklabels(df['day'].unique())
  432. axs[0].set_yticks(np.arange(len(df['day'].unique()))); axs[0].set_yticklabels(df['day'].unique())
  433. axs[1].set_title(mouse_id + ' ' + grp + ' ' + se + ' R', fontsize=14)
  434. axs[1].set_xticks(np.arange(len(df['day'].unique()))); axs[1].set_xticklabels(df['day'].unique())
  435. fig.colorbar(im, ax=axs.ravel().tolist(), shrink=0.5)
  436. pp.savefig(fig); plt.close()
  437. ## heatmap of dff responses at seedpixel over days
  438. minimum = np.nanmin([np.nanquantile(np.vstack([stk.mean(axis=0) for stk in df['reward_stack_l']]), 0.2), -0.001]); maximum = np.nanquantile(np.vstack([stk.mean(axis=0) for stk in df['reward_stack_l']]), 0.90)
  439. divnorm = colors.TwoSlopeNorm(vmin=minimum, vcenter=0, vmax=maximum)
  440. fig, axs = plt.subplots(1,2, sharey=True, figsize=(12,4))
  441. im = sns.heatmap(np.vstack([stk.mean(axis=0) for stk in df['reward_stack_l']]), cmap='coolwarm', vmin=minimum, vmax=maximum, center=0, ax=axs[0]);
  442. im = sns.heatmap(np.vstack([stk.mean(axis=0) for stk in df['reward_stack_r']]), cmap='coolwarm', vmin=minimum, vmax=maximum, center=0, ax=axs[1]);
  443. axs[0].set_title(mouse_id + ' ' + grp + ' ' + se + ' L', fontsize=14)
  444. axs[0].set_xlabel('Time (s)'); axs[0].set_ylabel('Day')
  445. axs[0].set_xticks(np.arange(0, len(t),30)); axs[0].set_xticklabels(t[::30])
  446. axs[0].set_yticklabels(df['day'].unique())
  447. axs[0].axvline(epochSize, c='k'); axs[0].axvline(epochSize-beh_fps, c='g')
  448. axs[1].set_title(mouse_id + ' ' + grp + ' ' + se + ' R', fontsize=14)
  449. axs[1].set_xlabel('Time (s)');
  450. axs[1].axvline(0, color='gray'); #axs[1].set_yticklabels(clmf_seeds_mm.keys())
  451. axs[1].set_xticks(np.arange(0, len(t),30)); axs[1].set_xticklabels(t[::30])
  452. axs[1].axvline(epochSize, c='k'); axs[1].axvline(epochSize-beh_fps, c='g')
  453. pp.savefig(fig); plt.close()
  454. ## seedpix corr during rewards on days
  455. for day in days:
  456. df = dff_response_daily_all[(dff_response_daily_all.mouse_id == mouse_id) &
  457. (dff_response_daily_all.group == grp) &
  458. (dff_response_daily_all.day == day)]
  459. fig, axs = plt.subplots(1,1, sharey=True)
  460. im = sns.heatmap(np.corrcoef(np.vstack([[stk.mean(axis=0) for stk in df['reward_stack_l']], [stk.mean(axis=0) for stk in df['reward_stack_r']]])), cmap='Greys', vmin=0, vmax=1);
  461. axs.set_title(mouse_id + ' ' + grp + ' ' + str(day) + ' seedpix corr rewards', fontsize=14)
  462. axs.set_xticklabels(np.concatenate([df.seedname.values + '_L', df.seedname.values + '_R']), rotation=90)
  463. axs.set_yticklabels(np.concatenate([df.seedname.values + '_L', df.seedname.values + '_R']), rotation=0)
  464. pp.savefig(fig); plt.close()
  465. ## heatmap of dff activity around reward on days
  466. df = dff_response_daily_all[(dff_response_daily_all.mouse_id == mouse_id) & (dff_response_daily_all.group == grp)]
  467. minimum = np.nanmin([np.nanquantile(np.vstack([stk.mean(axis=0) for stk in df['reward_stack_l']]), 0.2), -0.001]); maximum = np.nanquantile(np.vstack([stk.mean(axis=0) for stk in df['reward_stack_l']]), 0.90)
  468. divnorm = colors.TwoSlopeNorm(vmin=minimum, vcenter=0, vmax=maximum)
  469. for day in days:
  470. df = dff_response_daily_all[(dff_response_daily_all.mouse_id == mouse_id) &
  471. (dff_response_daily_all.group == grp) &
  472. (dff_response_daily_all.day == day)]
  473. fig, axs = plt.subplots(1,1, sharey=True)
  474. im = sns.heatmap(np.vstack([[stk.mean(axis=0) for stk in df['reward_stack_l']], [stk.mean(axis=0) for stk in df['reward_stack_r']]]), cmap='Greys', vmin=minimum, vmax=maximum);
  475. axs.set_title(mouse_id + ' ' + grp + ' ' + str(day) + ' dff response', fontsize=14)
  476. axs.set_xticks(np.arange(0, len(t),30)); axs.set_xticklabels(t[::30])
  477. axs.set_yticklabels(np.concatenate([df.seedname.values + '_L', df.seedname.values + '_R']), rotation=0)
  478. axs.axvline(epochSize, c='c'); axs.axvline(epochSize-beh_fps, c='g')
  479. pp.savefig(fig); plt.close()
  480. #%% Figure 7C Plot max dff values before reward and before trial start for all mice combined
  481. data_list = [('FW2_ai94', 'grp1'),
  482. ('FW22_ai94', 'grp1'),
  483. ('GT33_tta', 'grp1'), ('HA2+_tta', 'grp1'), ('GER2_ai94', 'grp1'), ('HYL3_tta', 'grp1'),
  484. ('GER2_ai94', 'grp2'), ('HYL3_tta', 'grp2'),
  485. ('GIR2_ai94', 'grp3')]
  486. days = [1,2,3,4,5,6,7,8,9,10]
  487. # First build a dataframe with max dff values for all mice in all groups we can plot by groups later
  488. for expt in data_list:
  489. mouse_id, grp = expt
  490. for se in clmf_seeds_mm:
  491. for day in days:
  492. df = dff_response_daily_all.loc[(dff_response_daily_all.mouse_id == mouse_id) &
  493. (dff_response_daily_all.group == grp) &
  494. (dff_response_daily_all.day == day) &
  495. (dff_response_daily_all.seedname == se)]
  496. if df.empty:
  497. print('No data')
  498. continue
  499. dff_response_daily_all.loc[(dff_response_daily_all.mouse_id == mouse_id) &
  500. (dff_response_daily_all.group == grp) &
  501. (dff_response_daily_all.day == day) &
  502. (dff_response_daily_all.seedname == se), ['max_prereward_avg_l']] = np.max(df['reward_stack_l'].values[0].mean(axis=0)[int(epochSize/2):epochSize])
  503. dff_response_daily_all.loc[(dff_response_daily_all.mouse_id == mouse_id) &
  504. (dff_response_daily_all.group == grp) &
  505. (dff_response_daily_all.day == day) &
  506. (dff_response_daily_all.seedname == se), ['max_prereward_avg_r']] = np.max(df['reward_stack_r'].values[0].mean(axis=0)[int(epochSize/2):epochSize])
  507. dff_response_daily_all.loc[(dff_response_daily_all.mouse_id == mouse_id) &
  508. (dff_response_daily_all.group == grp) &
  509. (dff_response_daily_all.day == day) &
  510. (dff_response_daily_all.seedname == se), ['max_postreward_avg_l']] = np.max(df['reward_stack_l'].values[0].mean(axis=0)[epochSize:epochSize+int(epochSize/2)])
  511. dff_response_daily_all.loc[(dff_response_daily_all.mouse_id == mouse_id) &
  512. (dff_response_daily_all.group == grp) &
  513. (dff_response_daily_all.day == day) &
  514. (dff_response_daily_all.seedname == se), ['max_postreward_avg_r']] = np.max(df['reward_stack_r'].values[0].mean(axis=0)[epochSize:epochSize+int(epochSize/2)])
  515. dff_response_daily_all.loc[(dff_response_daily_all.mouse_id == mouse_id) &
  516. (dff_response_daily_all.group == grp) &
  517. (dff_response_daily_all.day == day) &
  518. (dff_response_daily_all.seedname == se), ['max_rest_avg_l']] = np.max(df['rest_stack_l'].values[0].mean(axis=0)[int(epochSize/2):epochSize])
  519. dff_response_daily_all.loc[(dff_response_daily_all.mouse_id == mouse_id) &
  520. (dff_response_daily_all.group == grp) &
  521. (dff_response_daily_all.day == day) &
  522. (dff_response_daily_all.seedname == se), ['max_rest_avg_r']] = np.max(df['rest_stack_r'].values[0].mean(axis=0)[int(epochSize/2):epochSize])
  523. mouse_list = [d[0] for d in data_list]
  524. grp = 'grp1'
  525. for se in clmf_seeds_mm:
  526. df = dff_response_daily_all[(dff_response_daily_all.mouse_id.isin(mouse_list)) &
  527. (dff_response_daily_all.day.isin(days)) &
  528. (dff_response_daily_all.group == grp) &
  529. (dff_response_daily_all.seedname == se)]
  530. dfl = df.melt(id_vars=['day'], value_vars=['max_prereward_avg_l', 'max_rest_avg_l'], var_name='condition', value_name='DFF')
  531. dfr = df.melt(id_vars=['day'], value_vars=['max_prereward_avg_r', 'max_rest_avg_r'], var_name='condition', value_name='DFF')
  532. hue_plot_params = {
  533. 'data': dfl,
  534. 'x': 'day',
  535. 'y': 'DFF',
  536. "hue": "condition",
  537. "palette": 'husl'
  538. }
  539. pairs = [
  540. [(1, 'max_prereward_avg_l'), (4, 'max_prereward_avg_l')],
  541. [(4, 'max_prereward_avg_l'), (5, 'max_prereward_avg_l')]
  542. ]
  543. fig, axs = plt.subplots(1, 2, sharey=True, figsize=(12,6))
  544. ax1 = sns.pointplot(**hue_plot_params, dodge=0.2, errorbar=('ci', 95), ax=axs[0])
  545. ax2 = sns.pointplot(x='day', y='DFF', data=dfr, hue='condition', palette='husl', dodge=0.2, errorbar=('ci', 95), ax=axs[1])
  546. plt.title(grp + ' ' + se)
  547. sns.despine(offset=10, trim=True)
  548. # plt.yticks(fontsize=20)
  549. plt.tight_layout();
  550. # Annotate the figure with stats
  551. annotator = Annotator(ax1, pairs, **hue_plot_params)
  552. # # test: Brunner-Munzel, Levene, Mann-Whitney, Mann-Whitney-gt, Mann-Whitney-ls, t-test_ind, t-test_welch, t-test_paired, Wilcoxon, Kruskal
  553. # # comparisons_correction: Bonferroni, Holm-Bonferroni, Benjamini-Hochberg, Benjamini-Yekutieli
  554. annotator.configure(test='Mann-Whitney', show_test_name=True, comparisons_correction='Benjamini-Hochberg', text_format='star') #bejamini hochberg, Bonferroni
  555. _, test_results = annotator.apply_and_annotate()
  556. for res in test_results: print(res.data)
  557. pp.savefig(fig); plt.close()
  558. #%%
  559. file_corrmat_daily_all_pkl = out_dir + os.sep + 'clmf_corrmat_daily_all' + '.pkl'
  560. if not os.path.isfile(file_corrmat_daily_all_pkl):
  561. sys.exit('Correlation matrices file doesnt exist')
  562. corrmat_file = open(file_corrmat_daily_all_pkl, 'rb')
  563. df_corrmat_daily_all = pickle.load(corrmat_file)
  564. corrmat_file.close()
  565. bregma_loc = {'x': 64, 'y': 64}
  566. bregma = Position(bregma_loc['y'], bregma_loc['x'])
  567. seeds = generate_seeds(bregma, clmf_seeds_mm, ppmm_brain, 'u')
  568. #%% p-value matrix of statistical test
  569. data_list = [
  570. ('FW2_ai94', 'grp1'), ('FW3_ai94', 'grp1'), ('FW22_ai94', 'grp1'),
  571. ('GT33_tta', 'grp1'), ('HA2+_tta', 'grp1'), ('GER2_ai94', 'grp1'),
  572. ('GIL3_ai94', 'grp3'), ('GIR2_ai94', 'grp3')
  573. ]
  574. days = [1,2,3,4,5,6,7,8,9,10]
  575. mouse_list = [d[0] for d in data_list]
  576. groups = np.unique([d[1] for d in data_list])
  577. # Figure 8A average corrmat during trial on all days, each group
  578. for grp in groups:
  579. aa1 = np.mean(df_corrmat_daily_all[(df_corrmat_daily_all.mouse_id.isin(mouse_list)) &
  580. (df_corrmat_daily_all.group == grp)]['reward_corrmat'].values)
  581. aa2 = np.mean(df_corrmat_daily_all[(df_corrmat_daily_all.mouse_id.isin(mouse_list)) &
  582. (df_corrmat_daily_all.group == grp)]['rest_corrmat'].values)
  583. aa3 = aa1-aa2
  584. vmin = np.nanquantile(aa1, 0.2); vmax = np.quantile(aa3, 0.95)
  585. ## plot each of the difference matrix in the stack we created above
  586. fig = plt.figure(figsize=(6, 6))
  587. plt.subplot(1, 1, 1); ax=sns.heatmap(aa1, center=0, vmin=vmin, vmax=vmax, linewidths=.5,
  588. cmap='coolwarm', square=True, xticklabels=seeds.keys(),
  589. yticklabels=seeds.keys(), cbar_kws={"shrink":.5, "orientation":'horizontal'})
  590. plt.title(grp + ' mean seedpixel correlation during trial, all days')
  591. plt.tight_layout(); pp.savefig(fig); plt.close()
  592. fig = plt.figure(figsize=(6, 6))
  593. plt.subplot(1, 1, 1); ax=sns.heatmap(aa2, center=0, vmin=vmin, vmax=vmax, linewidths=.5,
  594. cmap='coolwarm', square=True, xticklabels=seeds.keys(),
  595. yticklabels=seeds.keys(), cbar_kws={"shrink":.5, "orientation":'horizontal'})
  596. plt.title(grp + ' mean seedpixel correlation during rest, all days')
  597. plt.tight_layout(); pp.savefig(fig); plt.close()
  598. fig = plt.figure(figsize=(6, 6))
  599. plt.subplot(1, 1, 1); ax=sns.heatmap(aa3, center=0, vmin=vmin, vmax=vmax, linewidths=.5,
  600. cmap='coolwarm', square=True, xticklabels=seeds.keys(),
  601. yticklabels=seeds.keys(), cbar_kws={"shrink":.5, "orientation":'horizontal'})
  602. plt.title(grp + ' difference of correlations between trial and rest, all days')
  603. plt.tight_layout(); pp.savefig(fig); plt.close()
  604. #%% Figure 8B 8C
  605. df_seeds_corrmat = pd.DataFrame()
  606. for expt in data_list:
  607. mouse_id, grp = expt
  608. for day in days:
  609. df = df_corrmat_daily_all[(df_corrmat_daily_all.mouse_id == mouse_id) &
  610. (df_corrmat_daily_all.group == grp) &
  611. (df_corrmat_daily_all.day == day)]
  612. if df.empty:
  613. print(mouse_id + ' ' + grp + ' No data')
  614. continue
  615. reward_corrmat = np.arctanh(df['reward_corrmat'].values[0]) # np.arctanh for fisher z transform the correlation values before applying statistical test
  616. rest_corrmat = np.arctanh(df['rest_corrmat'].values[0]) # np.arctanh for fisher z transform the correlation values before applying statistical test
  617. for i, se1 in enumerate(seeds):
  618. for j, se2 in enumerate(seeds):
  619. df_seeds_corrmat = pd.concat([df_seeds_corrmat, pd.DataFrame({'mouse_id': mouse_id, 'group': grp,
  620. 'day': day, 'condition': 'trial',
  621. 'seed1': se1, 'seed2': se2,
  622. 'correlation': reward_corrmat[i,j]}, index=[0])],
  623. ignore_index=True)
  624. df_seeds_corrmat = pd.concat([df_seeds_corrmat, pd.DataFrame({'mouse_id': mouse_id, 'group': grp,
  625. 'day': day, 'condition': 'rest',
  626. 'seed1': se1, 'seed2': se2,
  627. 'correlation': rest_corrmat[i,j]}, index=[0])],
  628. ignore_index=True)
  629. for grp in groups:
  630. # initialize dict to hold pvalues of statistical test anova
  631. pvalue_day_corrmat = np.ones((len(seeds), len(seeds)))
  632. pvalue_condition_corrmat = np.ones((len(seeds), len(seeds)))
  633. for i, se1 in enumerate(seeds):
  634. for j, se2 in enumerate(seeds):
  635. df = df_seeds_corrmat[(df_seeds_corrmat['seed1']==se1)
  636. & (df_seeds_corrmat['seed2']==se2)
  637. & (df_seeds_corrmat['group']==grp)
  638. # & (df_seeds_corrmat['condition']=='rest')
  639. ].reset_index()
  640. # repeated measure ANOVA because we have samples over days. this is two way anova
  641. # becasue we are adding condition as a variable too
  642. res = pg.rm_anova(dv='correlation', within=['day', 'condition'], subject='mouse_id', data=df, detailed=True)
  643. if 'p-unc' in res:
  644. pvalue_day_corrmat[i,j] = res['p-unc'][0]
  645. pvalue_condition_corrmat[i,j] = res['p-unc'][1]
  646. #### bonferroni correction
  647. pvalue_day_corrmat_corrected = multipletests(pvalue_day_corrmat.flatten(), method='bonferroni')
  648. fig = plt.figure(); ax=sns.heatmap(pvalue_day_corrmat_corrected[1].reshape(pvalue_day_corrmat.shape), linewidths=.5, cmap='Greens_r', square=True,
  649. vmin= 0.0001, vmax = 0.06, xticklabels=seeds.keys(), yticklabels=seeds.keys(),
  650. cbar_kws={"shrink":.5, "orientation":'horizontal'})
  651. plt.title(grp + ' RM-ANOVA pvalues on correlation values over days')
  652. pp.savefig(fig); plt.close()
  653. #### bonferroni correction
  654. pvalue_condition_corrmat_corrected = multipletests(pvalue_condition_corrmat.flatten(), method='bonferroni')
  655. fig = plt.figure(); ax=sns.heatmap(pvalue_condition_corrmat_corrected[1].reshape(pvalue_condition_corrmat.shape), linewidths=.5, cmap='Greens_r', square=True,
  656. vmin= 0.0001, vmax = 0.06, xticklabels=seeds.keys(), yticklabels=seeds.keys(),
  657. cbar_kws={"shrink":.5, "orientation":'horizontal'})
  658. plt.title(grp + ' RM-ANOVA pvalues on correlation values between trial and rest, over days')
  659. pp.savefig(fig); plt.close()
  660. print('done')
  661. pp.close()

plot_clmf.py at commit 6dfe0df, under GPL-3.0 · at the source

Overview

  1. Department of Psychiatry, University of British Columbia, Detwiller Pavilion Vancouver Canada
  2. Djavad Mowafaghian Centre for Brain Health (DMCBH), University of British Columbia Vancouver Canada
Institutions: University of British Columbia (Canada)
Journal: eLife, volume 14, article RP105070
Dates: published online 24 September 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI · PMCID PMC13609482
Status: code verified
Categories: optical imaging (calcium, voltage, 2-photon) (modality), mouse (organism)
Methods: Spectral & time-frequency, Statistics, Preprocessing, fMRI & imaging, Single-unit activity, calcium imaging, Smoothing, state filtering, decompositions, Physiology & signal measures, Connectivity
Keywords: real-time, closed-loop, widefield, calcium imaging, GCaMP6s, clopy, Mouse
Journal subjects: Computational and Systems Biology, Neuroscience
Citations: not cited yet (Europe PMC); 58 references in the paper
Research resources: RRID:SCR_019086

Abstract

Increasingly, experiments designed to provide practical perturbations to circuits or behavior are required for hypothesis testing in various disciplines ranging from motor learning to recovery after injury. We present the implementation and efficacy of an open-source closed-loop neurofeedback (CLNF) and closed-loop movement feedback (CLMF) system. In CLNF, we measure mm-scale cortical mesoscale activity with GCaMP6s and provide graded auditory feedback (within ~63 ms) based on changes in dorsal-cortical activation within regions of interest (ROIs) and with a specified rule. Single or dual ROIs (ROI1, ROI2) on the dorsal cortical map were selected as targets. Both motor and sensory regions supported closed-loop training in male and female mice. Mice modulated activity in rule-specific target cortical ROIs to get increasing rewards over days (repeated-measures ANOVA [RM-ANOVA], p=2.83e-5) and adapted to changes in ROI rules (RM-ANOVA, p=8.3e-10, Table 4 for different rule changes). In CLMF, feedback (within ~67 ms) was based on tracking a specified body movement, and rewards were generated when the behavior reached a threshold. For movement training, the group that received graded auditory feedback performed significantly better (RM-ANOVA, p=9.6e-7) than a control group (RM-ANOVA, p=0.49) within 4 training days. Additionally, mice can learn a change in task rule from left forelimb to right forelimb within a day, after a brief performance drop on day 5. Offline analysis of neural data and behavioral tracking revealed changes in the overall distribution of Ca2+ fluorescence values in CLNF and body-part speed values in CLMF experiments. Increased CLMF performance was accompanied by a decrease in task latency and cortical ΔF/F0 amplitude during the task, indicating lower cortical activation as the task gets more familiar.

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

Repository

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

pankajkgupta/clopy

License: GPL-3.0
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: 6dfe0df1dc47e403111314ca90b301e8ac251dba, 15 September 2026
Languages: Python (13)
Size: 52 files, 13 scripts
Software Heritage: archived
Found in: “Data availability”
Holds: README, license file, environment (docs/requirements.txt), documentation
Not found: CITATION.cff, tests, continuous integration
Tools: OpenCV (10 files), NumPy (8 files), Matplotlib (3 files), pandas (3 files), SciPy (3 files), imageio (2 files), Pingouin (2 files), seaborn (2 files), statannotations (2 files), statsmodels (2 files), h5py (1 file), Keras (1 file), napari (1 file), Numba (1 file), Pillow (1 file), scikit-image (1 file), scikit-learn (1 file), TensorFlow (1 file), tifffile (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
15 files

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

Tracing map

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

What the map holds:

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

The source data used in this paper is available here- https://doi.org/10.20383/103.01152. Code to replicate the system, recreate the figures, and associated pre-processed data are publicly available and hosted on GitHub - https://github.com/pankajkgupta/clopy (copy archived at Gupta, 2026). Detailed documentation can be found here https://clopy-docs.readthedocs.io.

The following dataset was generated:

GuptaKP MurphyT 2024Real-Time Closed-Loop Feedback System For Mouse Mesoscale Cortical Signal And Movement Control: CLoPyFRDR10.20383/103.0115242781854

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

Recorded: type, language, journal, volume, pages, dates, 2 authors, 7 keywords, 4 funders, 56 references, 1 RRID.

Cite

This paper

Gupta, P. K., & Murphy, T. H. (2026). Real-time closed-loop feedback system for mouse mesoscale cortical signal and movement control. eLife, 14, RP105070.

BibTeX

@article{gupta2026real,
author = {Gupta, Pankaj Kumar and Murphy, Timothy H},
title = {{Real-time closed-loop feedback system for mouse mesoscale cortical signal and movement control}},
journal = {eLife},
year = {2026},
month = sep,
volume = {14},
pages = {RP105070},
publisher = {eLife Sciences Publications, Ltd},
issn = {2050-084X},
pmcid = {PMC13609482}
}

RIS

TY - JOUR
AU - Gupta, Pankaj Kumar
AU - Murphy, Timothy H
TI - Real-time closed-loop feedback system for mouse mesoscale cortical signal and movement control
T2 - eLife
J2 - eLife
PY - 2026
DA - 2026/09/25
VL - 14
SP - RP105070
SN - 2050-084X
PB - eLife Sciences Publications, Ltd
LA - en
ER -

CSL-JSON

{
"id": "pmcid:PMC13609482",
"type": "article-journal",
"title": "Real-time closed-loop feedback system for mouse mesoscale cortical signal and movement control",
"container-title": "eLife",
"author": [
{
"family": "Gupta",
"given": "Pankaj Kumar"
},
{
"family": "Murphy",
"given": "Timothy H"
}
],
"container-title-short": "eLife",
"volume": "14",
"page": "RP105070",
"PMCID": "PMC13609482",
"ISSN": "2050-084X",
"publisher": "eLife Sciences Publications, Ltd",
"language": "en",
"issued": {
"date-parts": [
[
2026,
9,
25
]
]
}
}

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.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: napari, imageio, Pingouin, 14 other tools, 1 reference
[2] doi:10.1016/j.xcrm.2026.102766 [code]
A longitudinal single-cell and spatial multiomic atlas of pediatric high-grade glioma.
Journal: Cell reports. Medicine
In common: imageio, tifffile, Keras, 12 other tools
[3] doi:10.1364/boe.605322 [code]
Generalized plaque digitization framework for multi-dimensional mesoscopic images.
Journal: Biomedical optics express
In common: imageio, tifffile, Keras, 11 other tools, mouse, 1 reference
[4] 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: statannotations, Keras, Numba, 12 other tools
[5] doi:10.7554/elife.109717 [code]
Retrosplenial cortex enables context-dependent goal-directed sensorimotor transformation.
Journal: eLife
In common: imageio, tifffile, Numba, 11 other tools, mouse, 1 reference
[6] doi:10.1016/j.celrep.2026.117420 [code]
Neural population dynamics of direct electrical stimulation of neocortex.
Journal: Cell reports
In common: napari, imageio, OpenCV, 9 other tools, mouse, 2 references
[7] doi:10.1038/s41593-026-02232-0 [code]
Entorhinal cortex represents task-relevant remote locations independently of CA1.
Journal: Nature neuroscience
In common: Pingouin, Numba, OpenCV, 10 other tools, mouse, 2 references
[8] doi:10.1016/j.isci.2026.116825 [code]
Social hierarchy shapes behavioral and transcriptional responses to chronic stress and ketamine in male mice.
Journal: iScience
In common: Pingouin, Numba, OpenCV, 10 other tools, mouse, 1 reference
[9] doi:10.1093/neuonc/noag128 [code]
Spatially-resolved single-cell imaging of melanoma brain metastases identifies localized immune patterns predictive of immune checkpoint blockade response.
Journal: Neuro-oncology
In common: napari, imageio, tifffile, 8 other tools
[10] 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: tifffile, Keras, Numba, 10 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.