OSCR

Naturalistic behavior and self-generated neural activity predictive of self-correction

Code ↔ Paper

1 match 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 1 match
  1. [1] § Hippocampal representations of alternatives predict correct choices. ↔ src/gucompaper/changeOfMind_remote_interval.py, lines 421–467 · score 0.60 · remote arm representations, home arm, interval, segments, location, decoded

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 · 675 lines · 31 KB · no license · 1 match

  1. import numpy as np
  2. import pandas as pd
  3. import pickle
  4. from spyglass.utils.nwb_helper_fn import get_nwb_copy_filename
  5. from ripple_detection.core import segment_boolean_series
  6. from gucompaper.decodeQuality import return_low_hpd_time
  7. from gucompaper.decodeHelpers import runSessionNames
  8. from gucompaper.changeOfMind_triggered import region
  9. from gucompaper.ripple_add_replay import (find_start_end,
  10. position_posterior2arm_posterior,
  11. select_subset_helper,select_subset_helper_pd)
  12. from gucompaper.load import load_decode
  13. from gucompaper.changeOfMind_triggered import linear_map
  14. from gucompaper.changeOfMind_triggered_position import load_triggered_position_decode_day
  15. from gucompaper.Analysis_SGU import TrialChoice,DecodeIngredients,ChangeofMind,get_linearization_map,ChangeofMindTriggeredDecode,ChangeofMindRemoteTheta
  16. from gucompaper.changeOfMind_remote import is_rat_interior
  17. from gucompaper.changeOfMind import nodes, vectors
  18. from scipy import stats
  19. def parse_remote_master(animal,list_of_days,params,minimum_duration = 0.02, min_sum_posterior = 0.2, fill_spyglass = False):
  20. """This function calls find_remote_theta_animal() and saves the data"""
  21. all_info_animal, info_animal, time_intervals_animal, arm_identities_animal = find_remote_theta_animal(
  22. animal,list_of_days,fill_spyglass = fill_spyglass,
  23. minimum_duration = minimum_duration,
  24. min_sum_posterior = min_sum_posterior,
  25. **params)
  26. success = 1
  27. #all_info_animal_control, info_animal_control, time_intervals_animal_control, arm_identities_animal_control = find_remote_theta_animal(
  28. # animal,list_of_days,classifier_param_name,encoding_set,control=True,fill_spyglass = False,**params)
  29. #success = save_remote_animal(animal, list_of_days, encoding_set, classifier_param_name,params,
  30. # all_info_animal, info_animal, time_intervals_animal, arm_identities_animal,
  31. # all_info_animal_control, info_animal_control, time_intervals_animal_control, arm_identities_animal_control,
  32. # )
  33. return success
  34. output_folder = '/stelmo/shijie/change_of_mind_analysis/figure4/'
  35. def return_save_name_remote_parser(animal, encoding_set, classifier_param_name, d1, d2, proportion = 0.1, use_1d = 1):
  36. save_name = f'{animal.lower()}_{encoding_set}_{classifier_param_name}_{d1}_{d2}_p{proportion}_use1d{use_1d}'
  37. return save_name
  38. def save_remote_animal(animal, list_of_days, encoding_set, classifier_param_name, params,
  39. all_info_animal, info_animal, time_intervals_animal, arm_identities_animal,
  40. all_info_animal_control, info_animal_control, time_intervals_animal_control, arm_identities_animal_control
  41. ):
  42. d1= list_of_days[0]
  43. d2= list_of_days[-1]
  44. proportion = params["proportion"]
  45. use_1d = int(params["use_1d"])
  46. save_name = return_save_name_remote_parser(animal, encoding_set, classifier_param_name, d1, d2, proportion, use_1d)
  47. file_path = output_folder + save_name + '.pkl'
  48. data = {}
  49. (data["all_info_animal"],data["all_info_animal_control"],
  50. data["info_animal"], data["info_animal_control"],
  51. data["time_intervals_animal"], data["time_intervals_animal_control"],
  52. data["arm_identities_animal"], data["arm_identities_animal_control"]) = (
  53. all_info_animal,all_info_animal_control,
  54. info_animal, info_animal_control,
  55. time_intervals_animal,time_intervals_animal_control,
  56. arm_identities_animal, arm_identities_animal_control)
  57. # Open the file in binary write mode and dump the data
  58. with open(file_path, 'wb') as file:
  59. pickle.dump(data, file, protocol=pickle.HIGHEST_PROTOCOL)
  60. print(f"Data successfully pickled and saved to {file_path}")
  61. return 1
  62. def load_remote_animal(animal, list_of_days, encoding_set, classifier_param_name,
  63. proportion = 0.1, use_1d = 1, minimum_duration = 0.02, min_posterior = 0.2, spyglass = False):
  64. if spyglass:
  65. print("Loading from spyglass database instead of pickle file.")
  66. loaded_data = load_remote_animal_spyglass(animal, list_of_days,
  67. encoding_set,
  68. minimum_duration = minimum_duration,
  69. min_posterior = min_posterior,
  70. proportion = proportion, use_1d = use_1d)
  71. return loaded_data
  72. d1, d2 = list_of_days[0], list_of_days[-1]
  73. save_name = return_save_name_remote_parser(animal, encoding_set, classifier_param_name, d1, d2, proportion, use_1d)
  74. file_path = output_folder + save_name + '.pkl'
  75. with open(file_path, 'rb') as file:
  76. loaded_data = pickle.load(file)
  77. print(f"Successfully loaded data from '{file_path}':")
  78. return loaded_data
  79. def load_remote_animal_spyglass(animal, list_of_days, parameter_name, minimum_duration = 0.02,min_posterior=0.2,
  80. proportion = 0.1, use_1d = 1):
  81. """load from spyglass database instead of pickle file."""
  82. day_session_animal = []
  83. time_intervals_animal = []
  84. arm_identities_animal = []
  85. change_of_mind_num_animal = []
  86. for day in list_of_days:
  87. nwb_file_name = animal.lower() + day + '.nwb'
  88. nwb_copy_file_name = get_nwb_copy_filename(nwb_file_name)
  89. print(nwb_copy_file_name)
  90. session_interval, position_interval = runSessionNames(nwb_copy_file_name)
  91. remote_parameter = f"dur_{minimum_duration}_sum_{min_posterior}"
  92. for ind in range(len(session_interval)):
  93. session_name = session_interval[ind]
  94. position_name = position_interval[ind]
  95. epoch_num = int(session_name[:2])
  96. key_pre = {"nwb_file_name": nwb_copy_file_name, "epoch":epoch_num,
  97. "minimum_duration":minimum_duration,"remote_parameter":remote_parameter,
  98. "proportion":proportion, "parameter": parameter_name}
  99. query = ChangeofMindRemoteTheta & key_pre
  100. if len(query) == 0:
  101. print("No triggered decode found for ", key_pre)
  102. continue
  103. df = ChangeofMindRemoteTheta().fetch1_dataframe(key_pre)
  104. trials = df[df.has_remote_interval].index
  105. for trial in trials:
  106. remote_interval = df.loc[trial,'remote_interval']
  107. remote_content = df.loc[trial,'remote_content']
  108. change_of_mind_num = df.loc[trial,'change_of_mind_num']
  109. # if use post stopping content only
  110. # initial_stopping = df.loc[trial,'initial_time']
  111. # post_ind = [ind for ind in range(len(remote_interval)) if remote_interval[ind][0] >= initial_stopping]
  112. # remote_interval = [remote_interval[ind] for ind in post_ind]
  113. # remote_content = [remote_content[ind] for ind in post_ind]
  114. # if len(remote_content) == 0:
  115. # continue
  116. change_of_mind_num_animal.append(change_of_mind_num)
  117. day_session_animal.append([nwb_copy_file_name, session_name, [trial for i in range(len(remote_interval))]])
  118. time_intervals_animal.append(remote_interval)
  119. arm_identities_animal.append(remote_content)
  120. return {
  121. "info_animal": day_session_animal,
  122. "change_of_mind_num_animal": change_of_mind_num_animal,
  123. "time_intervals_animal": time_intervals_animal,
  124. "arm_identities_animal": arm_identities_animal
  125. }
  126. def find_remote_theta_animal(animal,list_of_days,
  127. parameter_name = None,
  128. use_1d = True,
  129. use_center = False, use_outer = False, use_home = True,
  130. proportion = 0.05,
  131. speed_threshold = 4,
  132. minimum_duration = 0.02,
  133. min_sum_posterior = 0.2,
  134. fill_spyglass = False):
  135. """
  136. default parameters should be:
  137. multiple_CoM = True, single_CoM = True, first_CoM = False,
  138. max_flag = True,
  139. delta_t_minus = 5,delta_t_plus = 5,
  140. "segment_only": False,
  141. Similar to find_remote_theta_animal_new(), but instead of a lumpsum of posterior in arms, it classfies intervals.
  142. use_1d: if True, use 1D decoding. if False, use 1D decoding collapsed from 2D decoding
  143. use_center: if True, consider moments when the rat in the center platform, and find decodes that in are outer arms
  144. use_home: if True, consider moments when the rat is in home arm, and find decodes that are in outer arms
  145. use_outer: if True, consider moments when the rat is in outer arms, and find decodes that are in all other outer arms including the home arm
  146. """
  147. if "2_state" in parameter_name:
  148. classifier_param_name = "default_decoding_gpu_4armMaze"
  149. elif "3_state" in parameter_name:
  150. classifier_param_name = "default_decoding_gpu_4armMaze_3states"
  151. else:
  152. classifier_param_name = "default_decoding_gpu_4armMaze"
  153. if "all_maze" in parameter_name:
  154. encoding_set = "all_maze"
  155. elif "run" in parameter_name:
  156. encoding_set = '2Dheadspeed_above_4'
  157. else:
  158. encoding_set = '2Dheadspeed_above_4'
  159. (day_session_animal, time_intervals_animal, arm_identities_animal) = (
  160. [],[],[])
  161. all_day_session_animal = [] # all the trials considered
  162. for day in list_of_days:
  163. nwb_file_name = animal.lower() + day + '.nwb'
  164. nwb_copy_file_name = get_nwb_copy_filename(nwb_file_name)
  165. print(nwb_copy_file_name)
  166. session_interval, position_interval = runSessionNames(nwb_copy_file_name)
  167. for ind in range(len(session_interval)):
  168. session_name = session_interval[ind]
  169. position_name = position_interval[ind]
  170. epoch_num = int(session_name[:2])
  171. # load triggered position and decode
  172. key_pre = {"nwb_file_name": nwb_copy_file_name, "epoch":epoch_num,
  173. "proportion":proportion, "parameter": parameter_name}
  174. query = ChangeofMindTriggeredDecode & key_pre
  175. if len(query) == 0:
  176. print("No triggered decode found for ", key_pre)
  177. continue
  178. parameters = (ChangeofMindTriggeredDecode & key_pre).fetch1("parameter_value")
  179. loaded_data = ChangeofMindTriggeredDecode().fetch1_dataframe(key_pre)
  180. (triggered_positions, triggered_positions_abs,
  181. triggered_times_triggered, triggered_times_abs,
  182. triggered_trial_infos) = (
  183. loaded_data["triggered_positions_baseoff"], loaded_data["triggered_positions"],
  184. loaded_data["time_triggered"], loaded_data["time_abs"],
  185. loaded_data["triggered_trial_info"],
  186. )
  187. # make triggered_positions a dataframe, with index of triggered_times_abs
  188. for tp_ind in range(len(triggered_positions)):
  189. triggered_positions[tp_ind] = pd.DataFrame({
  190. 'linear_position': triggered_positions[tp_ind],
  191. }, index = triggered_times_abs[tp_ind])
  192. triggered_positions_abs[tp_ind] = pd.DataFrame({
  193. 'linear_position': triggered_positions_abs[tp_ind],
  194. }, index = triggered_times_triggered[tp_ind])
  195. entry = DecodeIngredients & {'nwb_file_name':nwb_copy_file_name,
  196. 'interval_list_name':session_name}
  197. # position_1d,position_2d,
  198. position_1d = pd.read_csv(entry.fetch1('position_1d')) #still need 1D position
  199. position_2d = pd.read_csv(entry.fetch1('position_2d')) # need 2D position
  200. # load ChangeofMind info
  201. key={'nwb_file_name':nwb_copy_file_name,'epoch':epoch_num,'proportion': proportion}
  202. print(ChangeofMind & key)
  203. log = ChangeofMind().fetch1_dataframe(key)
  204. log2 = log.copy()
  205. # initialization, for spyglass insertion
  206. log2.insert(6,'has_remote_interval',[False for i in range(len(log2))])
  207. log2.insert(7,'remote_interval',[[] for i in range(len(log2))])
  208. log2.insert(8,'remote_content',[[] for i in range(len(log2))])
  209. log2.insert(9,'change_of_mind_num',[[] for i in range(len(log2))])
  210. # load decode
  211. results1d = load_decode(nwb_copy_file_name,
  212. session_name,
  213. classifier_param_name = classifier_param_name,
  214. encoding_set = encoding_set,
  215. use_1d = use_1d)
  216. #posterior1d = results1d.sum("state")
  217. event_indices_session = np.arange(len(triggered_positions))#[ind for ind in range(len(triggered_trial_infos)) if triggered_trial_infos[ind][1] == session_name]
  218. for event_index in event_indices_session:
  219. triggered_position = triggered_positions[event_index]
  220. triggered_position_abs = triggered_positions_abs[event_index]
  221. triggered_trial_info = triggered_trial_infos[event_index]
  222. (trial,
  223. time_interval,
  224. replayed_arm_identity) = find_remote_theta_interval(
  225. triggered_position, triggered_position_abs, triggered_trial_info,
  226. results1d, log, position_1d, position_2d,
  227. parameters["max_flag"],use_home,use_outer,use_center,
  228. minimum_duration = minimum_duration,
  229. min_sum_posterior = min_sum_posterior)
  230. if len(trial) > 0:
  231. print("Found remote theta in trial ", trial)
  232. day_session_animal.append([nwb_copy_file_name,session_name,trial])
  233. time_intervals_animal.append(time_interval)
  234. arm_identities_animal.append(replayed_arm_identity)
  235. trialID = trial[0]
  236. log2.loc[trialID,'has_remote_interval'] = True
  237. log2.at[trialID,'remote_interval'] += time_interval
  238. log2.at[trialID,'remote_content'] += replayed_arm_identity
  239. if len(log2.at[trialID,'change_of_mind_num']) == 0:
  240. log2.at[trialID,'change_of_mind_num'] += [0 for _ in range(len(replayed_arm_identity))]
  241. else:
  242. change_of_mind_num = int(np.max(log2.loc[trialID,'change_of_mind_num'])) + 1
  243. log2.at[trialID,'change_of_mind_num'] += [change_of_mind_num for _ in range(len(replayed_arm_identity))]
  244. all_day_session_animal.append([nwb_copy_file_name,session_name,trial])
  245. # save back to spyglass
  246. if fill_spyglass:
  247. q = {}
  248. q["parameter"] = parameter_name
  249. q["pandas"] = log2.to_dict()
  250. q["nwb_file_name"] = nwb_copy_file_name
  251. q["epoch"] = epoch_num
  252. q["proportion"] = proportion
  253. q["remote_parameter"] = f"dur_{minimum_duration}_sum_{min_sum_posterior}"
  254. ChangeofMindRemoteTheta().insert1(q, replace = True)
  255. return all_day_session_animal, day_session_animal,time_intervals_animal,arm_identities_animal
  256. def find_remote_interval(decode_subset, position2d, threshold = 20, minimum_duration = 0.02):
  257. position_axis = np.array(decode_subset.coords['position'])
  258. posterior_position_subset = decode_subset.causal_posterior.sum(dim='state')
  259. max_posterior_position1d = np.array(position_axis[posterior_position_subset.argmax(dim = 'position')])
  260. max_posterior_position2d = loc1d_to_2d_vector(max_posterior_position1d)
  261. # find remote time
  262. is_remote = np.sqrt(np.sum((max_posterior_position2d - position2d) ** 2, axis = 1)) > threshold
  263. is_remote_pd = pd.Series(is_remote, index = decode_subset.time)
  264. is_remote_segments = np.array(segment_boolean_series(
  265. is_remote_pd, minimum_duration=minimum_duration))
  266. if len(is_remote_segments) == 0:
  267. return [],[]
  268. time_intervals = []
  269. arm_identity = []
  270. for i in range(is_remote_segments.shape[0]):
  271. (t0,t1) = is_remote_segments[i]
  272. # restrict to continuous state:
  273. # length of the continuous state should be greater than 20ms
  274. decode_subset_ = select_subset_helper(decode_subset,(t0,t1))
  275. state_subset = np.array(decode_subset_.causal_posterior.sum(dim='position'))
  276. time=np.array(decode_subset_.causal_posterior.time)
  277. snippets_conti = find_start_end(state_subset[:,0] > 0.5) #continuous
  278. snippets = [time[s] for s in snippets_conti if np.diff(time[s])[0]>minimum_duration]
  279. for s in range(len(snippets)):
  280. (t0_peak,t1_peak) = snippets[s]
  281. # overall sum of decode posterior in the max posterior arm should be greater than 0.2
  282. posterior_by_arm = position_posterior2arm_posterior(
  283. select_subset_helper(posterior_position_subset,snippets[s]),
  284. linear_map)
  285. # classify the max/mean posterior arm, exclude the arm the animal is physically at
  286. subset_ind = (decode_subset.time >= t0_peak) & (decode_subset.time <= t1_peak)
  287. subset_arm_snippet = linear2arm_including_home(max_posterior_position1d[subset_ind])
  288. if len(subset_arm_snippet) == 0:
  289. continue
  290. subset_arm_snippet = subset_arm_snippet[~np.isnan(subset_arm_snippet)]
  291. if len(subset_arm_snippet) == 0:
  292. continue
  293. modes = np.unique(subset_arm_snippet)
  294. final_arms = []
  295. for mode in modes:
  296. max_arm_ind = int(mode - 5)
  297. if np.mean(posterior_by_arm[max_arm_ind,:]) < 0.2:
  298. continue
  299. final_arms.append(max_arm_ind)
  300. time_intervals.append(snippets[s])
  301. arm_identity.append(final_arms)
  302. return time_intervals, arm_identity
  303. def find_remote_theta_interval(triggered_position,triggered_position_abs,triggered_trial_info,
  304. decode,log_df,position_1d,position_2d,
  305. max_flag = 1,use_home = False,use_outer = True, use_center = False,
  306. minimum_duration = 0.02, min_sum_posterior = 0.2): # in seconds
  307. """
  308. if home = 1: find remote representation at home arm during running instead of at outer well.
  309. # 1. find time points out side of arm position
  310. # 2. for each time interval, find arm
  311. # decode should pass certain criteria:
  312. # (a) be continuous in decoder state
  313. # (b) posterior >= threshold%
  314. # 3. return for each trial a list of time range and arm identity for the decode
  315. INPUT: decode should be the absolute
  316. """
  317. position_axis = np.array(decode.coords['position'])
  318. # find the arm the animal is at
  319. subset_arm = triggered_trial_info[-1] + 5
  320. # find the trial
  321. # find t0, t1 to consider
  322. trialID = triggered_trial_info[-2]
  323. (t0, t1) = (triggered_position.index[0],triggered_position.index[-1])
  324. if use_home:
  325. timestamp_H = log_df.loc[trialID,'timestamp_H']
  326. if not np.isnan(timestamp_H):
  327. t0 = timestamp_H
  328. # set by time
  329. position2d_subset = position_2d[np.logical_and(position_2d.time>=t0, position_2d.time<=t1)]
  330. position1d_subset = position_1d[np.logical_and(position_1d.time>=t0, position_1d.time<=t1)]
  331. decode_subset = select_subset_helper(decode,(t0,t1))
  332. if abs(len(position1d_subset) - len(decode_subset.time)) > 3:
  333. print("skipped due to decode and camera time frame do not fully match.")
  334. return [],[],[]
  335. # set by location
  336. if use_home:
  337. subset_arm = 5
  338. # set by location
  339. #animal is physically at the home segment and not in the well area
  340. #stricter: remove well area
  341. subset_ind = np.logical_and(np.array(position1d_subset.linear_position) >= 10,
  342. np.array(position1d_subset.linear_position) <= linear_map[1][1])
  343. elif use_outer:
  344. # set by location
  345. subset_ind = position1d_subset.track_segment_id == subset_arm
  346. else: # use center
  347. subset_arm = 5
  348. p_rat = np.hstack((np.array(position2d_subset.head_position_x).reshape((-1,1)),
  349. np.array(position2d_subset.head_position_y).reshape((-1,1))))
  350. subset_ind = is_rat_interior(p_rat)
  351. position2d_subset = position2d_subset[subset_ind]
  352. position1d_subset = position1d_subset[subset_ind]
  353. decode_subset = decode_subset.isel(time = np.argwhere(subset_ind).ravel())
  354. # all previous operations restrict time to consider
  355. posterior_position_subset = decode_subset.causal_posterior.sum(dim='state')
  356. # chew down decode to either mean or max position
  357. # get max posterior
  358. if max_flag:
  359. max_posterior_position = np.array(position_axis[posterior_position_subset.argmax(dim = 'position')])
  360. # get mean posterior
  361. else:
  362. posterior_position_subset_array = np.array(posterior_position_subset).T
  363. posterior_position_subset_array = posterior_position_subset_array/np.sum(posterior_position_subset_array, axis = 0)
  364. max_posterior_position = np.matmul(position_axis,posterior_position_subset_array)
  365. # find remote time
  366. is_remote = np.zeros_like(max_posterior_position) #just to initialize
  367. if use_home: # find remote arm representations when the animal is in the home arm
  368. for k in region.keys():
  369. (arm_base, arm_top) = region[k]
  370. is_remote = is_remote + np.logical_and(max_posterior_position <= arm_top, max_posterior_position >= arm_base)
  371. elif use_outer: # find remote representations when the animal is in outer arms
  372. # find representation in other arms
  373. for k in region.keys():
  374. if k == int(subset_arm):
  375. continue
  376. (arm_base, arm_top) = region[k]
  377. is_remote = is_remote + np.logical_and(max_posterior_position <= arm_top, max_posterior_position >= arm_base)
  378. # find remote representation at home
  379. is_remote = is_remote + np.logical_and(max_posterior_position >= 0, max_posterior_position <= linear_map[0][1])
  380. else: # use center
  381. for k in region.keys():
  382. (arm_base, arm_top) = region[k]
  383. is_remote = is_remote + np.logical_and(max_posterior_position <= arm_top, max_posterior_position >= arm_base)
  384. # restrict to moving time
  385. is_moving = np.array(position2d_subset.head_speed) > 4
  386. min_len = np.min([len(is_moving),len(is_remote)])
  387. # choose min because one variable is a subset of decode and the other is a subset of position.
  388. # there could be 1 or 2 time point difference.
  389. is_moving = is_moving[:min_len]
  390. is_remote = is_remote[:min_len]
  391. is_remote = np.logical_and(is_remote, is_moving)
  392. if min_sum_posterior == 0:
  393. # if no posterior threshold, there will be no continuity and state requirement,
  394. trials = [trialID]
  395. arm_identity = [0,1,2,3,4] # all arms including home arm
  396. time_intervals = []
  397. # for each region, count the number of time bins that the max posterior position falls into that region,
  398. for k in [5,6,7,8,9]:
  399. (arm_base, arm_top) = region[k]
  400. time_in_arm = np.logical_and(max_posterior_position <= arm_top, max_posterior_position >= arm_base)
  401. time_in_arm = time_in_arm[:min_len]
  402. time_in_arm = np.logical_and(time_in_arm, is_remote)
  403. delta_t = np.sum(time_in_arm) * np.median(np.diff(posterior_position_subset.time))
  404. t0 = float(posterior_position_subset.time[0])
  405. time_intervals.append((t0, t0 + delta_t))
  406. return trials, time_intervals, arm_identity
  407. is_remote_pd = pd.Series(is_remote, index = posterior_position_subset.time)
  408. is_remote_segments = np.array(segment_boolean_series(
  409. is_remote_pd, minimum_duration=minimum_duration))
  410. if len(is_remote_segments) == 0:
  411. return [],[],[]
  412. time_intervals = []
  413. arm_identity = []
  414. trials = []
  415. for i in range(is_remote_segments.shape[0]):
  416. (t0,t1) = is_remote_segments[i]
  417. # restrict to continuous state:
  418. # length of the continuous state should be greater than 20ms
  419. decode_subset_ = select_subset_helper(decode_subset,(t0,t1))
  420. state_subset = np.array(decode_subset_.causal_posterior.sum(dim='position'))
  421. time=np.array(decode_subset_.causal_posterior.time)
  422. snippets_conti = find_start_end(state_subset[:,0] > 0.5) #continuous
  423. snippets = [time[s] for s in snippets_conti if np.diff(time[s])[0]>minimum_duration]
  424. for s in range(len(snippets)):
  425. (t0_peak,t1_peak) = snippets[s]
  426. # overall sum of decode posterior in the max posterior arm should be greater than 0.2
  427. posterior_by_arm = position_posterior2arm_posterior(
  428. select_subset_helper(posterior_position_subset,snippets[s]),
  429. linear_map)
  430. # classify the max/mean posterior arm, exclude the arm the animal is physically at
  431. subset_ind = (posterior_position_subset.time >= t0_peak) & (posterior_position_subset.time <= t1_peak)
  432. subset_arm_snippet = linear2arm_including_home(max_posterior_position[subset_ind])
  433. if len(subset_arm_snippet) == 0:
  434. continue
  435. subset_arm_snippet = subset_arm_snippet[~np.isnan(subset_arm_snippet)]
  436. if len(subset_arm_snippet) == 0:
  437. continue
  438. mode, count = stats.mode(subset_arm_snippet)
  439. if mode == subset_arm or count <= (len(subset_arm_snippet) * 0.8):
  440. #ambiguous situation, we will not consider those
  441. continue
  442. max_arm_ind = int(mode - 5)
  443. if np.mean(posterior_by_arm[max_arm_ind,:]) < min_sum_posterior:
  444. continue
  445. time_intervals.append(snippets[s])
  446. arm_identity.append(max_arm_ind)
  447. trials.append(trialID)
  448. return trials, time_intervals, arm_identity
  449. def linear2arm(position):
  450. arm = np.zeros_like(position) + np.nan
  451. for p_ind in range(len(position)):
  452. p = position[p_ind]
  453. for k in region.keys():
  454. if p>=region[k][0] and p<region[k][1]:
  455. arm[p_ind] = k
  456. continue
  457. return arm
  458. def linear2arm_including_home(position):
  459. arm = np.zeros_like(position) + np.nan
  460. for p_ind in range(len(position)):
  461. p = position[p_ind]
  462. for k in region.keys():
  463. if p>=region[k][0] and p<region[k][1]:
  464. arm[p_ind] = k
  465. continue
  466. if p >= 0 and p <= linear_map[0][1]:
  467. arm[p_ind] = 5
  468. return arm
  469. def add_trial(t0,log_df):
  470. trial_ind=np.array(log_df.index)
  471. trial_number = trial_ind[np.argwhere((np.array(log_df.timestamp_O[:-1])-t0) > 0).ravel()[0]]
  472. return trial_number
  473. def dotproduct(head_direction, arm):
  474. # arms are 0,1,2,3,4.
  475. # get arm direction
  476. arm_vector = vectors[arm + 5]
  477. return np.dot(head_direction, arm_vector)
  478. #### return angle between remote content and the rat head direction
  479. ### code in figure4d calls the following functions
  480. def find_angle(max_posterior_2d,head_orientation,animal_location):
  481. # in radian
  482. # make unit vector
  483. head_orientation_unit = np.hstack((np.cos(head_orientation).reshape((-1,1)),np.sin(head_orientation).reshape((-1,1)))) #unit vector
  484. displacement_unit = max_posterior_2d - animal_location
  485. displacement_unit = displacement_unit / np.linalg.norm(displacement_unit, axis = 1).reshape((-1,1))
  486. # shape of both displacement_unit and head_orientation_unit are (number of time bin, 2)
  487. # finally: return radian between head orientation and remote content
  488. dot_product = [np.dot(head_orientation_unit[i],displacement_unit[i].T) for i in range(displacement_unit.shape[0])]
  489. angle = np.arccos(np.clip(dot_product, -1.0, 1.0))
  490. return angle, head_orientation_unit, displacement_unit
  491. linear_map,welllocations = get_linearization_map()
  492. linear_map_arms = linear_map[[0,3,5,7,9]]
  493. def loc1d_to_2d_vector(loc1d_vector, arm = None):
  494. loc2d_vector = np.array([loc1d_to_2d(loc1d, arm) for loc1d in loc1d_vector])
  495. return loc2d_vector
  496. def loc1d_to_2d(loc1d, arm_avoid = None):
  497. # linear_map is like this:
  498. # array([[ 57.63896252, 0. ], arm 0 base - outer
  499. # [165.66041604, 252.64353221], arm 1 base - outer
  500. # [331.10211383, 418.29227956], arm 2 base - outer
  501. # [496.49241045, 583.18763812], arm 3 base - outer
  502. # [657.72023142, 743.03215907]]). arm 4 base - outer
  503. # the arm the 1d location belongs, 1-indexed
  504. row_ind = np.argwhere(np.logical_and(linear_map_arms[:,0] <= loc1d, linear_map_arms[:,1] > loc1d)).ravel()
  505. if len(row_ind) == 0:
  506. return np.array([np.nan,np.nan])
  507. arm_id = int(row_ind)
  508. if arm_id == arm_avoid:
  509. return np.array([np.nan,np.nan])
  510. # the base - outer 1d
  511. arm = linear_map_arms[arm_id].ravel()
  512. if arm_id == 0:
  513. arm = arm[::-1]
  514. # convert to proportion
  515. proportion = (loc1d - arm[0])/(arm[1]-arm[0])
  516. # get 2D node location
  517. node = nodes[int(arm_id + 5)]
  518. loc2d = (node[1] - node[0]) * proportion + node[0]
  519. return loc2d
  520. def loc1d_to_baseoff_vector(loc1d_vector, arm_avoid = None):
  521. loc2d_vector = np.array([loc1d_to_baseoff(loc1d) for loc1d in loc1d_vector])
  522. return loc2d_vector
  523. def loc1d_to_baseoff(loc1d, arm_avoid = None):
  524. # linear_map is like this:
  525. # array([[ 0, 57.63896252], arm 0 base - outer
  526. # [165.66041604, 252.64353221], arm 1 base - outer
  527. # [331.10211383, 418.29227956], arm 2 base - outer
  528. # [496.49241045, 583.18763812], arm 3 base - outer
  529. # [657.72023142, 743.03215907]]). arm 4 base - outer
  530. # the arm the 1d location belongs, 1-indexed
  531. row_ind = np.argwhere(np.logical_and(linear_map_arms[:,0] <= loc1d, linear_map_arms[:,1] > loc1d)).ravel()
  532. if len(row_ind) == 0:
  533. return np.array([np.nan,np.nan])
  534. arm_id = int(row_ind)
  535. if arm_id == arm_avoid:
  536. return np.array([np.nan,np.nan])
  537. # the base - outer 1d
  538. arm = linear_map_arms[arm_id].ravel()
  539. d = loc1d - arm[0]
  540. if arm_id == 0:
  541. d = arm[1] - loc1d
  542. return arm_id, d

changeOfMind_remote_interval.py at commit 0f0c9a7, no license · at the source

Overview

  1. UC Berkeley – UCSF Joint Graduate Program in Bioengineering, University of California, San Francisco, 94143, USA
  2. Department of Physiology, University of California, San Francisco, 94143, USA
  3. Graduate Program in Computational Neuroscience, University of Chicago, Chicago, 60637, USA
  4. Department of Neurobiology & Biophysics, University of Washington, Seattle, 98195, USA
  5. Neuroscience Graduate Program, University of California San Francisco, San Francisco, 94143, USA
  6. Howard Hughes Medical Institute, University of California, San Francisco, 94143, USA
  7. Kavli Institute for Fundamental Neuroscience, University of California, San Francisco, 94143, USA
Journal: bioRxiv : the preprint server for biology, article 2026.05.26.727951
Dates: published online 27 May 2026
Type: Research article · Language: English
License: CC BY-NC
Identifiers: DOI · PMCID PMC13232212
Status: code verified
Methods: Machine learning
Funding: UC Berkeley – UCSF Joint Graduate Program in Bioengineering, SG; Simons Foundation, SG, CL, AKG, MEC, LF (542981); K99, NIA, AKG (K99AG068425); Howard Hughes Medical Institute (RN, ELD, KK, LF)
Citations: 38 references in the paper

Abstract

Changing one’s mind — revising a past decision independently of external cues — can lead to better outcomes, but its neural basis remains poorly understood. We therefore developed a challenging spatial task for rats where a conflict between innate foraging biases and task rules leads to abundant, spontaneous, and characteristically corrective changes-of-mind (COMs). Neural recordings in the hippocampus, a brain region implicated in counterfactual thinking, revealed two distinct stages wherein more local representations gave way to representations of distant alternatives after animals had begun to reverse course. These representations predicted their eventual choice, and often serially encoded trial start and end locations. Our novel task paradigm reveals distinct representational phases engaged during self-correction and uncovers a rich repertoire of hippocampal spatial representations tied to behavior.

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

Repositories

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

Zenodo 20371883

License: CC-BY-4.0
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: the end of the paper
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (76 files), pandas (63 files), Matplotlib (48 files), SciPy (42 files), xarray (35 files), Neurodata Without Borders (PyNWB, MatNWB) (9 files), statsmodels (8 files), seaborn (6 files), CuPy (5 files), scikit-learn (5 files), SpikeInterface (4 files), Elephant (2 files), Neo (2 files), OpenCV (2 files), JAX (1 file), NetworkX (1 file), Pillow (1 file), scikit-image (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers (HTTP 200)
  • 28 September 2026: the link answers (HTTP 200)
86 files

shijiegu/gu2026

License: none: the authors keep all their rights
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: 0f0c9a7dadba3b5c9cbf2e8f3ac3372cff9726ee, 24 May 2026
Languages: Python (84), Jupyter (1)
Size: 89 files, 85 scripts
Software Heritage: not archived
Found in: the Zenodo archive record
Holds: README, environment (pyproject.toml), 1 notebook
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: NumPy (76 files), pandas (63 files), Matplotlib (48 files), SciPy (42 files), xarray (36 files), Neurodata Without Borders (PyNWB, MatNWB) (9 files), statsmodels (8 files), CuPy (5 files), scikit-learn (5 files), seaborn (5 files), SpikeInterface (4 files), Elephant (2 files), Neo (2 files), OpenCV (2 files), JAX (1 file), NetworkX (1 file), Pillow (1 file), scikit-image (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
86 files

Tracing map

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

What the map holds:

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

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

Data

Datasets cited

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, pages, dates, 8 authors, 4 funders, 30 references.

Cite

This paper

Gu, S., Liu, C., Gillespie, A. K., Nevers, R., Denovellis, E. L., Coulter, M. E., Kay, K., & Frank, L. M. (2026). Naturalistic behavior and self-generated neural activity predictive of self-correction. bioRxiv : the preprint server for biology, 2026.05.26.727951.

BibTeX

@article{gu2026naturalistic,
author = {Gu, Shijie and Liu, Chenyan and Gillespie, Anna K. and Nevers, Rhino and Denovellis, Eric L. and Coulter, Michael E. and Kay, Kenneth and Frank, Loren M.},
title = {{Naturalistic behavior and self-generated neural activity predictive of self-correction}},
journal = {bioRxiv : the preprint server for biology},
year = {2026},
month = may,
pages = {2026.05.26.727951},
publisher = {bioRxiv},
issn = {2692-8205},
pmcid = {PMC13232212}
}

RIS

TY - JOUR
AU - Gu, Shijie
AU - Liu, Chenyan
AU - Gillespie, Anna K.
AU - Nevers, Rhino
AU - Denovellis, Eric L.
AU - Coulter, Michael E.
AU - Kay, Kenneth
AU - Frank, Loren M.
TI - Naturalistic behavior and self-generated neural activity predictive of self-correction
T2 - bioRxiv : the preprint server for biology
J2 - bioRxiv
PY - 2026
DA - 2026/05/27
SP - 2026.05.26.727951
SN - 2692-8205
PB - bioRxiv
LA - en
ER -

CSL-JSON

{
"id": "pmcid:PMC13232212",
"type": "article-journal",
"title": "Naturalistic behavior and self-generated neural activity predictive of self-correction",
"container-title": "bioRxiv : the preprint server for biology",
"author": [
{
"family": "Gu",
"given": "Shijie"
},
{
"family": "Liu",
"given": "Chenyan"
},
{
"family": "Gillespie",
"given": "Anna K."
},
{
"family": "Nevers",
"given": "Rhino"
},
{
"family": "Denovellis",
"given": "Eric L."
},
{
"family": "Coulter",
"given": "Michael E."
},
{
"family": "Kay",
"given": "Kenneth"
},
{
"family": "Frank",
"given": "Loren M."
}
],
"container-title-short": "bioRxiv",
"page": "2026.05.26.727951",
"PMCID": "PMC13232212",
"ISSN": "2692-8205",
"publisher": "bioRxiv",
"language": "en",
"issued": {
"date-parts": [
[
2026,
5,
27
]
]
}
}

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/s41593-026-02232-0 [code]
Entorhinal cortex represents task-relevant remote locations independently of CA1.
Journal: Nature neuroscience
In common: SpikeInterface, Neurodata Without Borders (PyNWB, MatNWB), xarray, 11 other tools, 2 references
[2] doi:10.1002/hipo.70131 [code]
Decoding Medial Entorhinal Cortical Dynamics Produces Planning-Like Alternations in Hippocampal theta Sequences.
Journal: Hippocampus
In common: JAX, xarray, OpenCV, 5 other tools, 6 references
[3] doi:10.1016/j.isci.2026.115488 [code]
An integrated &lt;i&gt;i&lt;/i&gt; &lt;i&gt;n vitro&lt;/i&gt; platform and biophysical modeling approach for studying synaptic transmission in isolated neuronal pairs.
Journal: iScience
In common: Elephant, Neo, SpikeInterface, 10 other tools
[4] doi:10.7554/elife.110588 [code]
Opening the black box toward a modular approach to spike sorting.
Journal: eLife
In common: Neo, SpikeInterface, Neurodata Without Borders (PyNWB, MatNWB), 9 other tools
[5] doi:10.1016/j.celrep.2026.117420 [code]
Neural population dynamics of direct electrical stimulation of neocortex.
Journal: Cell reports
In common: Neurodata Without Borders (PyNWB, MatNWB), JAX, NetworkX, 10 other tools
[6] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: CuPy, JAX, NetworkX, 10 other tools
[7] doi:10.1126/sciadv.aeg6797 [code]
Dorsoventral gradient of theta sweeps in the medial entorhinal cortex.
Journal: Science advances
In common: JAX, NetworkX, statsmodels, 6 other tools, 3 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: CuPy, NetworkX, OpenCV, 9 other tools
[9] doi:10.1371/journal.pcbi.1014752 [code]
Hierarchical feature binding in a spiking neural network model of the primate ventral visual pathway.
Journal: PLoS computational biology
In common: Elephant, Neo, xarray, 6 other tools
[10] doi:10.1038/s41593-026-02365-2 [code]
Hippocampal theta sweeps indicate goal direction during navigation.
Journal: Nature neuroscience
In common: JAX, SciPy, Matplotlib, 1 other tool, 6 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.