OSCR

Decoding Medial Entorhinal Cortical Dynamics Produces Planning-Like Alternations in Hippocampal theta Sequences.

Code ↔ Paper

8 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 8 matches
  1. [1] § Materials and Methods › Clustering of Theta‐Skipping Cells ↔ zenodo-sciad-share/scripts/analysis-2.py, lines 495–541 · score 0.76 · silhouette scores, Spectral clustering, affinity matrix, theta skipping, Cells
  2. [2] § Materials and Methods › Simulation of Left–Right Sweeps ↔ zenodo-sciad-share/shared_src/ratinabox_utils.py, lines 210–314 · score 0.67 · theta frequency, theta phase, fraction, theta cycle, RatInABox, speed
  3. [3] § Materials and Methods › Firing Rate Map/Head‐Direction Tuning Curve ↔ zenodo-sciad-share/shared_src/kde.py, lines 17–120 · score 0.59 · Gaussian kernel, kernel density, resolution, firing rates, bandwidth, dimensions
  4. [4] § Results › Simultaneous MEC–Hippocampal Recordings Reveal Consistent Coupling and Left–Right Alternation at the T‐Maze Decision Point ↔ zenodo-sciad-share/scripts/analysis-2.py, lines 1155–1212 · score 0.58 · map correlation, tuning curve, Theta skipping cells, matrix, Firing rate, windows
  5. [5] § Materials and Methods › Firing Rate Map/Head‐Direction Tuning Curve ↔ zenodo-sciad-share/shared_src/kde_fast.py, lines 76–129 · score 0.56 · von Mises kernel, Circular, smoothed, density, bins
  6. [6] § Results › MEC‐Hippocampal Coupling Occurs Under Less Cognitively Demanding Conditions ↔ zenodo-sciad-share/scripts/analysis-1.py, lines 556–629 · score 0.55 · egocentric internal direction, MEC internal direction, MEC cell, theta phase, Head direction, frames
  7. [7] § Results › MEC‐Hippocampal Coupling Persists Even at Forced Turn Corners ↔ zenodo-sciad-share/scripts/analysis-1.py, lines 556–629 · score 0.55 · egocentric internal direction, MEC internal direction, MEC cell, theta phase, Head direction, frames
  8. [8] § Results › Simultaneous MEC–Hippocampal Recordings Reveal Consistent Coupling and Left–Right Alternation at the T‐Maze Decision Point ↔ zenodo-sciad-share/shared_src/ratinabox_utils.py, lines 210–314 · score 0.50 · theta sequence, pre, theta phase, agent, sweeps, position

Paper

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

The paper is loaded when this pane is shown.

The authors' code

Python · 1,664 lines · 54 KB · CC-BY-4.0 · 2 matches

  1. import sys
  2. from functools import partial
  3. from pathlib import Path
  4. import pandas as pd
  5. from scipy.io import loadmat
  6. import scipy.signal
  7. import numpy as np
  8. from sklearn.cluster import SpectralClustering
  9. from sklearn.metrics import silhouette_score
  10. from scipy.stats import circmean
  11. from scipy.ndimage import gaussian_filter1d
  12. from scipy import stats
  13. import matplotlib
  14. import matplotlib.path as mpath
  15. from matplotlib.patches import Rectangle
  16. from matplotlib import colors
  17. import matplotlib.patches as mpatches
  18. import matplotlib.pyplot as plt
  19. from matplotlib.collections import LineCollection
  20. from kalmax.kde import poisson_log_likelihood
  21. import jax.numpy as jnp
  22. # root_dir = Path("/Users/nakanomasahiro/PycharmProjects/research/hpc-analysis")
  23. root_dir = Path("/Users/nakanomasahiro/PycharmProjects/research/hpc-analysis")
  24. source_dir = Path(
  25. "/Users/nakanomasahiro/Desktop/research/research-projects/left-right-sweeps-paper/zenodo-sciad"
  26. )
  27. # Define the dataset
  28. dataset = "left-right-sweeps"
  29. # Setup
  30. # fmt: off
  31. # sys.path.append(str(root_dir))
  32. sys.path.append(str(source_dir))
  33. from shared_src.utils import get_S, apply_mask_lambda, chunker, find_nearest, chunker_v2
  34. from shared_src.kalmax_utils import kalmax_place_field_pos
  35. from shared_src.kde_fast import get_circular_tuning_curve_all, kernel_density_estimation, get_circular_tuning_curve_all_v2
  36. from shared_src.decoding_utils import make_bool_visited_map, get_error_stats
  37. from shared_src.spike_autocorrelation import spike_correlation
  38. from shared_src.linearlize import (
  39. linearize_for_mmaze_v2,
  40. )
  41. from shared_src.fig_utils import (
  42. erase_topright_axes,
  43. erase_toprightleft_axes,
  44. )
  45. # sys.path.append(str(root_dir / "projects" / dataset))
  46. from src.dataset import (
  47. get_xarray_dataset_v1,
  48. get_range_mask_from_ds,
  49. )
  50. from src.figure.ds_fig import (
  51. plot_spike_correlation_from_ds_v2
  52. )
  53. # fmt: on
  54. data_dir = root_dir / "data" / dataset
  55. root_fig_dir = root_dir / "figs" / dataset
  56. # fig_dir = root_fig_dir / "papers-conferences" / "paper" / "ScienceAdvances"
  57. root_fig_dir = source_dir / "figs"
  58. fig_dir = root_fig_dir
  59. fig_dir.mkdir(parents=True, exist_ok=True)
  60. #### Processing data
  61. xs = [
  62. 0,
  63. 0,
  64. -0.5,
  65. -0.5,
  66. 0.475,
  67. 0.475,
  68. ]
  69. ys = [-0.6, 0.55, 0.55, -0.6, 0.55, -0.6]
  70. task_name = "mmaze"
  71. session_name = "29502_1"
  72. file_path = data_dir / "navigation" / task_name / f"{session_name}.mat"
  73. area_name = "hc"
  74. # edit here add some other functions
  75. ds, df = get_xarray_dataset_v1(
  76. file_path=file_path,
  77. area_name=area_name,
  78. fast_kde=True,
  79. run_decoding=False,
  80. include_unvisited_location=False,
  81. # 2026-01-19 use proper binning mode
  82. bin_mode="center",
  83. )
  84. ds_mec, df_mec = get_xarray_dataset_v1(
  85. file_path=file_path,
  86. area_name="mec",
  87. fast_kde=True,
  88. run_decoding=False,
  89. include_unvisited_location=False,
  90. # 2026-01-19 use proper binning mode
  91. bin_mode="center",
  92. )
  93. X_1d, direction, X_proj, path_ids, borders = linearize_for_mmaze_v2(
  94. X=ds.X.values, xs=xs, ys=ys
  95. )
  96. ds["X_1d"] = (("frame",), X_1d)
  97. ds["direction"] = (("frame",), direction)
  98. points = np.stack([xs, ys], axis=1)
  99. points_1d, _, _, _, _ = linearize_for_mmaze_v2(X=points, xs=xs, ys=ys)
  100. borders["top_left"] = points_1d[2]
  101. borders["top_right"] = points_1d[4]
  102. ds.attrs["borders"] = borders
  103. ds["path_ids"] = (("frame",), path_ids)
  104. area_name = "mec"
  105. data_ = loadmat(file_path, squeeze_me=True, struct_as_record=False)
  106. data = data_["Dsession"]
  107. lmt_comp = getattr(data.lmt, area_name)
  108. lmt_pos = lmt_comp.pos.XA # shape: [n_time_bins × 2]
  109. ds["lmt"] = (["frame", "space"], lmt_pos)
  110. lmt_id = lmt_comp.id.XA
  111. ds["lmt_id"] = (["frame"], lmt_id)
  112. ds_mec["lmt_id"] = (["frame"], lmt_id)
  113. phi, tuning_curves = get_circular_tuning_curve_all_v2(
  114. ds.head_direction.values, ds_mec.S.values
  115. )
  116. ds_mec["bin_angles"] = (["n_hd_bin"], phi)
  117. ds_mec["hd_tuning_curve"] = (["cluster", "n_hd_bin"], tuning_curves)
  118. ds_mec["lmt_id_tuning_curve"] = (
  119. ["cluster", "n_hd_bin"],
  120. get_circular_tuning_curve_all_v2(ds.lmt_id.values, ds_mec.S.values)[1],
  121. )
  122. ds["cosine_head_direction"] = np.cos(ds.head_direction)
  123. assert np.all(lmt_comp.hd.XA == ds.head_direction.values)
  124. ds["ego_id"] = (ds.lmt_id - ds.head_direction + np.pi) % (2 * np.pi) - np.pi
  125. # color_lis = ['red', 'blue']
  126. color_lis = ["darkblue", "crimson"]
  127. # colors = {1: "C0", -1: "C1"}
  128. # 1D population decoding
  129. X_1d_expanded = np.stack([X_1d, np.zeros_like(X_1d)], axis=1)
  130. border_central_left = (borders["central_arm_end"] + borders["left_arm_start"]) / 2
  131. border_left_right = (borders["left_arm_end"] + borders["right_arm_start"]) / 2
  132. barriers = np.array(
  133. [
  134. [border_central_left, border_central_left, -0.02, 0.02],
  135. [border_left_right, border_left_right, -0.02, 0.02],
  136. ]
  137. )
  138. firing_rate, firing_maps, spike_maps, position_d, bins, shape, env = (
  139. kernel_density_estimation(X_1d, S=ds.S.values, barriers=barriers)
  140. )
  141. arm_labels = np.zeros(100).astype(int)
  142. max_ = borders["right_arm_end"]
  143. i1 = int(border_central_left / max_ * 100) + 2
  144. i2 = int(border_left_right / max_ * 100) + 2
  145. arm_labels[i1:i2] = 1
  146. arm_labels[i2:] = 2
  147. log_likelihoods = poisson_log_likelihood(
  148. spikes=ds.S.values, mean_rate=firing_rate
  149. ) # shape (T, N_bins)
  150. likelihoods = np.exp(log_likelihoods) # shape (T, N_bins)
  151. normed_likelihoods = likelihoods / (likelihoods.sum(axis=1, keepdims=True) + 1e-12)
  152. ML_modes = np.argmax(likelihoods, axis=1)
  153. ml_pos = np.array(bins[ML_modes])
  154. S_count = ds.S.values.sum(axis=1)
  155. use_frames = np.where(S_count > 0)[0]
  156. bin_labels = np.zeros_like(bins)
  157. bin_labels = np.where(
  158. (bins > border_central_left) & (bins <= border_left_right), 1, bin_labels
  159. )
  160. bin_labels = np.where((bins >= border_left_right), 2, bin_labels)
  161. cluster_colors = ["lightgray", "darkblue", "crimson"]
  162. # cluster_colors = ['lightgray', 'darkblue', 'crimson']
  163. stripe_y = np.zeros((100, 1, 3))
  164. for i, lbl in enumerate(arm_labels):
  165. stripe_y[i, 0] = colors.to_rgb(cluster_colors[lbl])
  166. chunk_dicts = {}
  167. chunks, sizes = chunker(np.where((direction == 1) & (path_ids == 0))[0], 50)
  168. chunks = [chunk for chunk, size in zip(chunks, sizes) if size > 200]
  169. sizes = [size for size in sizes if size > 200]
  170. chunk_dicts["outbound_central_arm"] = chunks
  171. arm_ids = {"left": 1, "right": 2}
  172. for arm_name, arm_id in arm_ids.items():
  173. chunks, sizes = chunker(np.where((direction == -1) & (path_ids == arm_id))[0], 50)
  174. chunks = [chunk for chunk, size in zip(chunks, sizes) if size > 200]
  175. sizes = [size for size in sizes if size > 200]
  176. chunk_dicts[f"inbound_from_{arm_name}_arm"] = chunks
  177. arm_ids = {"left": 1, "right": 2}
  178. for arm_name, arm_id in arm_ids.items():
  179. chunks, sizes = chunker(np.where((direction == 1) & (path_ids == arm_id))[0], 50)
  180. chunks = [chunk for chunk, size in zip(chunks, sizes) if size > 200]
  181. sizes = [size for size in sizes if size > 200]
  182. chunk_dicts[f"outbound_to_{arm_name}_arm"] = chunks
  183. ds_dicts = {"hc": ds, "mec": ds_mec}
  184. print(f"HPC cluster size: {ds.cluster.size}")
  185. print(f"MEC cluster size: {ds_mec.cluster.size}")
  186. range_dicts = {
  187. "outbound_central_arm": {
  188. "X_1d": (0.2, 1.1),
  189. "direction": (0.9, 1.1),
  190. "head_direction": (np.pi / 3, np.pi * 2 / 3),
  191. },
  192. "inbound_from_left_arm": {
  193. "X_1d": (1.2, 1.7),
  194. "direction": (-2, 0),
  195. "head_direction": (-np.pi / 3, np.pi / 3),
  196. },
  197. "outbound_topleft_corner": {
  198. "X_1d": (1.3, 1.7),
  199. "direction": (0, 2),
  200. "cosine_head_direction": (-2, -0.5),
  201. },
  202. }
  203. range_dicts_supp = {
  204. "inbound_from_right_arm": {
  205. "X_1d": (3, 3.5),
  206. "direction": (-2, 0),
  207. "cosine_head_direction": (-2, -0.5),
  208. },
  209. "inbound_topright_corner": {
  210. "X_1d": (3.6, 4.2),
  211. "direction": (-2, 0),
  212. "head_direction": (np.pi / 3, np.pi * 2 / 3),
  213. },
  214. "outbound_topright_corner": {
  215. "X_1d": (3, 3.5),
  216. "direction": (0, 2),
  217. "head_direction": (-np.pi / 3, np.pi / 3),
  218. },
  219. "inbound_topleft_corner": {
  220. "X_1d": (1.8, 2.4),
  221. "direction": (-2, 0),
  222. "head_direction": (np.pi / 3, np.pi * 2 / 3),
  223. },
  224. }
  225. def get_theta_skipping_index_v2(
  226. spike_times,
  227. window_size=40,
  228. spike_times_2=None,
  229. method="numba_v2",
  230. ):
  231. bins, counts = spike_correlation(
  232. spike_times_1=spike_times,
  233. spike_times_2=spike_times_2,
  234. bin_size=1,
  235. window=window_size,
  236. method=method,
  237. )
  238. p1 = counts[window_size + 6 : window_size + 18].mean()
  239. p2 = counts[window_size + 18 : window_size + 30].mean()
  240. p_even = p2
  241. p_odd = p1
  242. skipping_index = (p_even - p_odd) / max(p_even, p_odd, 1e-12)
  243. return skipping_index, p_even, p_odd
  244. def show_correlogram(
  245. spike_times,
  246. window_size=40,
  247. color="lightgray",
  248. ax=None,
  249. alpha=0.7,
  250. label=None,
  251. spike_times_2=None,
  252. ):
  253. """f'shuffled within mask, {skipping_index:.2f}'"""
  254. bins, counts = spike_correlation(
  255. spike_times_1=spike_times,
  256. spike_times_2=spike_times_2,
  257. bin_size=1,
  258. window=window_size,
  259. method="numba_v2",
  260. )
  261. if not ax:
  262. fig, ax = plt.subplots(figsize=(1, 1))
  263. ax.bar(
  264. bins[window_size:],
  265. counts[window_size:],
  266. width=1,
  267. alpha=alpha,
  268. color=color,
  269. label=label,
  270. )
  271. ax.set_xticks([0, 10, 40])
  272. ax.set_xticklabels([0, 100, 400], rotation=45)
  273. erase_topright_axes(ax)
  274. return ax
  275. def make_theta_cycle_chunks(theta_phase_in_mask):
  276. """
  277. theta_phase_in_mask = ds.theta_phase.sel(frame=mask).values
  278. """
  279. chunks, sizes = chunker_v2(np.where(np.diff(theta_phase_in_mask) > 0)[0], 1)
  280. theta_cycle_chunks = []
  281. last_idx = -1
  282. for chunk in chunks:
  283. if chunk[0] - last_idx > 1:
  284. chunk.insert(0, chunk[0] - 1)
  285. chunk.append(chunk[-1] + 1)
  286. theta_cycle_chunks.append(chunk)
  287. last_idx = chunk[-1]
  288. sizes = [len(c) for c in theta_cycle_chunks]
  289. # assert np.concatenate(theta_cycle_chunks).shape == mask.sum()
  290. return theta_cycle_chunks, sizes
  291. def run_shuffling_test_for_latent_position_cycling(
  292. ds,
  293. ML_modes,
  294. bin_labels,
  295. range_dict,
  296. target_arm_1=1,
  297. target_arm_2=2,
  298. window_size=40,
  299. n_samples=1000,
  300. show_plots=False,
  301. ):
  302. range_dict["S_count"] = (0, 100)
  303. mask = get_range_mask_from_ds(ds, range_dict)
  304. frames = np.where(mask)[0]
  305. theta_cycle_chunks, _ = make_theta_cycle_chunks(
  306. ds.theta_phase.sel(frame=mask).values
  307. )
  308. # Real data latent position
  309. latent_pos_masked = bin_labels[ML_modes[mask]]
  310. spike_times = frames[np.where(latent_pos_masked == target_arm_1)[0]]
  311. spike_times_2 = frames[np.where(latent_pos_masked == target_arm_2)[0]]
  312. real_skipping_index, _, _ = get_theta_skipping_index_v2(
  313. spike_times, window_size=window_size, spike_times_2=spike_times_2
  314. )
  315. # Getting shuffling idx list
  316. shuffled_idx_list = []
  317. for i in range(n_samples):
  318. shuffled_idx = np.concatenate(
  319. [
  320. theta_cycle_chunks[i]
  321. for i in np.random.permutation(len(theta_cycle_chunks))
  322. ]
  323. )
  324. shuffled_idx_list.append(shuffled_idx)
  325. latent_pos_masked = bin_labels[ML_modes[mask]]
  326. n_samples = n_samples
  327. # Perform shuffling
  328. lis = []
  329. for i in range(n_samples):
  330. if shuffled_idx_list is not None:
  331. shuffled_idx = shuffled_idx_list[i]
  332. else:
  333. shuffled_idx = np.concatenate(
  334. [
  335. theta_cycle_chunks[i]
  336. for i in np.random.permutation(len(theta_cycle_chunks))
  337. ]
  338. )
  339. latent_pos_shuffled = bin_labels[ML_modes[shuffled_idx]]
  340. spike_times_shuffled = frames[np.where(latent_pos_shuffled == target_arm_1)[0]]
  341. spike_times_shuffled_2 = frames[
  342. np.where(latent_pos_shuffled == target_arm_2)[0]
  343. ]
  344. shuffled_skipping_index, _, _ = get_theta_skipping_index_v2(
  345. spike_times_shuffled,
  346. window_size=window_size,
  347. spike_times_2=spike_times_shuffled_2,
  348. )
  349. lis.append(shuffled_skipping_index)
  350. is_cycling = real_skipping_index < np.percentile(lis, 5)
  351. print("Real skipping index:", real_skipping_index)
  352. print("5th percentile of shuffled skipping index:", np.percentile(lis, 5))
  353. print("Is cycling between latent positions:", is_cycling)
  354. print("")
  355. if show_plots:
  356. titles = ["center arm", "left arm", "right arm"]
  357. fig, axes = plt.subplots(1, 4, figsize=(5, 1))
  358. for i, j in enumerate([0, 1, 2]):
  359. ax = axes[i]
  360. show_correlogram(
  361. frames[np.where(latent_pos_masked == j)[0]],
  362. ax=ax,
  363. color=cluster_colors[j],
  364. )
  365. ax.set_title(titles[i])
  366. ax.set_yticks([])
  367. ax = axes[3]
  368. show_correlogram(
  369. frames[np.where(latent_pos_masked == 1)[0]],
  370. spike_times_2=frames[np.where(latent_pos_masked == 2)[0]],
  371. color="#262626",
  372. ax=ax,
  373. )
  374. ax.set_title("left vs right")
  375. ax.set_yticks([])
  376. plt.figure(figsize=(2, 1.5))
  377. plt.hist(lis, bins=30, color="gray", alpha=0.7)
  378. plt.axvline(real_skipping_index, color="red")
  379. erase_topright_axes()
  380. ################################
  381. ### Get theta skipping cells ###
  382. ################################
  383. def compare_real_and_shuffled_correlogram(
  384. ds,
  385. cluster_idx,
  386. mask,
  387. theta_cycle_chunks,
  388. ax=None,
  389. c1="lightgray",
  390. c2="#262626",
  391. alpha1=0.7,
  392. alpha2=0.3,
  393. ):
  394. frames = np.where(mask)[0]
  395. shuffled_idx = np.concatenate(
  396. [theta_cycle_chunks[i] for i in np.random.permutation(len(theta_cycle_chunks))]
  397. )
  398. S = ds.S.sel(cluster=cluster_idx).sel(frame=mask).values
  399. S_shuffled = S[shuffled_idx]
  400. ax = show_correlogram(
  401. frames[np.where(S)[0]], alpha=alpha1, color=c1, label="real", ax=ax
  402. )
  403. show_correlogram(
  404. frames[np.where(S_shuffled)[0]],
  405. ax=ax,
  406. color=c2,
  407. alpha=alpha2,
  408. label="shuffled",
  409. )
  410. ax.legend(loc="upper right", bbox_to_anchor=(2.4, 1))
  411. return ax
  412. ################################################
  413. ### Apply clustering to theta skipping cells ###
  414. ################################################
  415. def get_affinity_matrix(ds, mask, cluster_lis, window_size=40):
  416. affinity_mat = np.zeros((len(cluster_lis), len(cluster_lis)))
  417. for i, clu1 in enumerate(cluster_lis):
  418. spike_times_1 = np.where(ds.S.sel(cluster=clu1).sel(frame=mask))[0]
  419. for j, clu2 in enumerate(cluster_lis):
  420. spike_times_2 = np.where(ds.S.sel(cluster=clu2).sel(frame=mask))[0]
  421. affinity_mat[i, j] = get_theta_skipping_index_v2(
  422. spike_times=spike_times_1,
  423. spike_times_2=spike_times_2,
  424. window_size=window_size,
  425. )[0]
  426. return affinity_mat
  427. def cluster_affinity_matrix(
  428. affinity_mat, cluster_lis, random_state=42, cluster_range=range(2, 9)
  429. ):
  430. assert affinity_mat.shape[0] == len(cluster_lis)
  431. min_cluster = cluster_range.start
  432. max_cluster = cluster_range.stop - 1
  433. cluster_range = range(min_cluster, min(max_cluster, affinity_mat.shape[0] - 1) + 1)
  434. M = affinity_mat.copy()
  435. np.fill_diagonal(M, 0)
  436. M = np.nan_to_num((M + M.T) / 2, nan=0.0)
  437. M_min, M_max = M.min(), M.max()
  438. if M_min < 0:
  439. M = (M - M_min) / (M_max - M_min + 1e-12)
  440. np.fill_diagonal(M, 0) # spectral doesn’t need self-similarity
  441. D = 1 - M # simple distance from affinity in [0,1]
  442. np.fill_diagonal(D, 0)
  443. best = {"k": None, "score": -np.inf, "labels": None}
  444. for k in cluster_range:
  445. sc = SpectralClustering(
  446. n_clusters=k,
  447. affinity="precomputed",
  448. assign_labels="kmeans",
  449. random_state=random_state,
  450. ).fit(M)
  451. score = silhouette_score(D, sc.labels_, metric="precomputed")
  452. if score > best["score"]:
  453. best = {"k": k, "score": score, "labels": sc.labels_}
  454. labels = best[
  455. "labels"
  456. ] # cluster label per cell (same order as real_theta_skipping_cells)
  457. labels_dict = {}
  458. for i in range(0, best["k"]):
  459. labels_dict[i] = cluster_lis[labels == i]
  460. order = np.argsort(labels)
  461. M_ord = M[order][:, order]
  462. cells_ord = [cluster_lis[i] for i in order]
  463. return {
  464. "best_k": best["k"],
  465. "best_score": best["score"],
  466. "labels": labels,
  467. "labels_dict": labels_dict,
  468. "M_ord": M_ord,
  469. "cells_ord": cells_ord,
  470. }
  471. # affinity_mat = get_affinity_matrix(
  472. # ds, mask, theta_skipping_cell_result["real_theta_skipping_cells"], window_size=40
  473. # )
  474. # clustering_result = cluster_affinity_matrix(
  475. # affinity_mat, cluster_lis=theta_skipping_cell_result["real_theta_skipping_cells"]
  476. # )
  477. ########################################################
  478. ### Overlap of place cell rate maps within the group ###
  479. ########################################################
  480. def within_across_values(M, labels):
  481. """Extract within- and across-group metric values from symmetric matrix M."""
  482. n = len(labels)
  483. ut = np.triu(np.ones((n, n), bool), k=1) # upper triangle only (no diagonal)
  484. same = labels[:, None] == labels[None, :]
  485. diff = ~same
  486. within_vals = M[same & ut]
  487. across_vals = M[diff & ut]
  488. return within_vals, across_vals
  489. def permutation_test_for_within_across_group_representation_similarity(
  490. M, labels, n_perm=5000, random_seed=None
  491. ):
  492. if random_seed is None:
  493. rng = np.random.default_rng()
  494. else:
  495. rng = np.random.default_rng(random_seed)
  496. real_within, real_across = within_across_values(M, labels)
  497. real_diff = np.nanmean(real_within) - np.nanmean(real_across)
  498. n = len(labels)
  499. null_diffs = np.empty(n_perm)
  500. for i in range(n_perm):
  501. perm = rng.permutation(labels)
  502. w, a = within_across_values(M, perm)
  503. null_diffs[i] = np.nanmean(w) - np.nanmean(a)
  504. p = (1 + np.sum(null_diffs >= real_diff)) / (n_perm + 1)
  505. z = (real_diff - np.mean(null_diffs)) / (np.std(null_diffs, ddof=1) + 1e-12)
  506. return {"real_diff": real_diff, "p": p, "z": z, "null_diffs": null_diffs}
  507. def show_within_across_distribution(M, labels):
  508. real_within, real_across = within_across_values(M, labels)
  509. fig, ax = plt.subplots(figsize=(1.5, 1.5))
  510. ax.hist(real_within, bins=np.linspace(-1, 1, 25), alpha=0.5, label="within")
  511. ax.hist(real_across, bins=np.linspace(-1, 1, 25), alpha=0.5, label="between")
  512. ax.legend()
  513. erase_topright_axes(ax)
  514. ax.set_title(f"{np.nanmean(real_within) - np.nanmean(real_across):.3f}")
  515. def show_matrix_with_labels(M, labels, vmin=-1, vmax=1):
  516. order = np.argsort(labels)
  517. M_ord = M[order][:, order]
  518. fig, ax = plt.subplots(figsize=(2, 2))
  519. im = ax.imshow(M_ord, cmap="viridis", vmin=vmin, vmax=vmax)
  520. plt.colorbar(im, ax=ax, fraction=0.046, pad=0.04)
  521. erase_topright_axes(ax)
  522. def show_correlation_matrix(
  523. M,
  524. skipping_cells,
  525. labels=None,
  526. color_for=["blue", "red"],
  527. ax=None,
  528. plot_labels=True,
  529. group_order=None,
  530. vmin=None,
  531. vmax=None,
  532. ):
  533. if labels is None:
  534. labels = np.arange(M.shape[0])
  535. if color_for is None:
  536. cmap = plt.get_cmap("hsv")
  537. color_for = {i: cmap(i % cmap.N) for i in range(len(np.unique(labels)))}
  538. if group_order is None:
  539. order = np.argsort(labels)
  540. else:
  541. order = np.concatenate([np.where(labels == g)[0] for g in group_order])
  542. M_ord = M[order][:, order]
  543. cells_ord = [skipping_cells[i] for i in order]
  544. labels_ord = np.asarray(labels)[order]
  545. if ax is None:
  546. fig, ax = plt.subplots(figsize=(1.2, 1.2))
  547. im = ax.imshow(M_ord, aspect="equal", interpolation="nearest", vmin=vmin, vmax=vmax)
  548. if plot_labels:
  549. ax.set_xticks(np.arange(len(cells_ord)))
  550. ax.set_xticklabels(cells_ord, rotation=90)
  551. ax.set_yticks(np.arange(len(cells_ord)))
  552. ax.set_yticklabels(cells_ord)
  553. else:
  554. ax.set_xticks([])
  555. ax.set_yticks([])
  556. # --- find contiguous cluster blocks along the diagonal ---
  557. edges = np.flatnonzero(
  558. np.r_[True, labels_ord[1:] != labels_ord[:-1], True]
  559. ) # block boundaries
  560. erase_topright_axes(ax)
  561. # ax.axis("off")
  562. # --- draw rectangles ---
  563. for i, (a, b) in enumerate(zip(edges[:-1], edges[1:])): # block = [a, b)
  564. n = b - a
  565. cid = labels_ord[a]
  566. rect = Rectangle(
  567. (a - 0.5, a - 0.5), n, n, fill=False, lw=2, ec=color_for[i], zorder=3
  568. )
  569. ax.add_patch(rect)
  570. ax.set_xlim(-len(cells_ord) * 0.1, len(cells_ord) * 1.1 - 1)
  571. ax.set_ylim(len(cells_ord) * 1.1 - 1, -len(cells_ord) * 0.1)
  572. ax.spines.left.set(visible=False)
  573. ax.spines.bottom.set(visible=False)
  574. return ax
  575. def get_percentile_shuffle_with_theta_preserved_v2(
  576. frames,
  577. S_in_mask,
  578. theta_cycle_chunks,
  579. percentile=95,
  580. n_samples=10000,
  581. window_size=40,
  582. return_lis=False,
  583. shuffled_idx_list=None,
  584. ):
  585. lis = []
  586. p_even_lis = []
  587. p_odd_lis = []
  588. for i in range(n_samples):
  589. if shuffled_idx_list is not None:
  590. shuffled_idx = shuffled_idx_list[i]
  591. else:
  592. shuffled_idx = np.concatenate(
  593. [
  594. theta_cycle_chunks[i]
  595. for i in np.random.permutation(len(theta_cycle_chunks))
  596. ]
  597. )
  598. S_shuffled = S_in_mask[shuffled_idx]
  599. spike_times_shuffled = frames[np.where(S_shuffled)[0]]
  600. skipping_index, p_even, p_odd = get_theta_skipping_index_v2(
  601. spike_times_shuffled, window_size=window_size
  602. )
  603. lis.append(skipping_index)
  604. p_even_lis.append(p_even)
  605. p_odd_lis.append(p_odd)
  606. if return_lis:
  607. return (
  608. np.percentile(lis, percentile),
  609. lis,
  610. np.percentile(p_even_lis, percentile),
  611. np.percentile(p_odd_lis, percentile),
  612. )
  613. else:
  614. return (
  615. np.percentile(lis, percentile),
  616. np.percentile(p_even_lis, percentile),
  617. np.percentile(p_odd_lis, percentile),
  618. )
  619. def get_theta_skipping_cells_with_and_wo_hard_threshold(
  620. ds,
  621. mask,
  622. n_samples=1000,
  623. firing_rate_threshold={"hc": 1, "mec": 5},
  624. percentile=95,
  625. hard_threshold_theta_skipping_index=0.5,
  626. ):
  627. """
  628. Skip cells that have less than 1 hz for HPC and 5 hz for MEC firing rate within the mask
  629. """
  630. theta_cycle_chunks, sizes = make_theta_cycle_chunks(
  631. ds.theta_phase.sel(frame=mask).values
  632. )
  633. shuffled_idx_list = []
  634. for i in range(n_samples):
  635. shuffled_idx = np.concatenate(
  636. [
  637. theta_cycle_chunks[i]
  638. for i in np.random.permutation(len(theta_cycle_chunks))
  639. ]
  640. )
  641. shuffled_idx_list.append(shuffled_idx)
  642. shuffle_test_result = {}
  643. loose_theta_skipping_cells = []
  644. strict_theta_skipping_cells = []
  645. fake_theta_skipping_cells = []
  646. # bool_is_theta_skipping = []
  647. frames = ds.frame.sel(frame=mask).values
  648. for cluster_idx in ds.cluster.values:
  649. S_in_mask = ds.S.sel(cluster=cluster_idx).sel(frame=mask).values
  650. if S_in_mask.sum() < ((mask.sum() / 100) * firing_rate_threshold[ds.area_name]):
  651. continue
  652. spike_times_1 = frames[np.where(S_in_mask)[0]]
  653. real_skipping_index, real_p_even, real_p_odd = get_theta_skipping_index_v2(
  654. spike_times_1
  655. )
  656. (
  657. shuffled_percentile_skipping_index,
  658. shuffled_percentile_p_even,
  659. shuffled_percentile_p_odd,
  660. ) = get_percentile_shuffle_with_theta_preserved_v2(
  661. frames,
  662. S_in_mask,
  663. theta_cycle_chunks,
  664. n_samples=n_samples,
  665. return_lis=False,
  666. percentile=percentile,
  667. shuffled_idx_list=shuffled_idx_list,
  668. )
  669. # is_skipping = (real_skipping_index > shuffled_percentile_skipping_index) & (real_p_even > shuffled_percentile_p_even) & (real_p_odd < shuffled_percentile_p_odd)
  670. is_skipping = real_skipping_index > shuffled_percentile_skipping_index
  671. is_hard_threshold_skipping = (
  672. real_skipping_index > hard_threshold_theta_skipping_index
  673. )
  674. shuffle_test_result[cluster_idx] = {
  675. "real_skipping_index": real_skipping_index,
  676. "shuffled_percentile_skipping_index": shuffled_percentile_skipping_index,
  677. "real_p_even": real_p_even,
  678. "shuffled_percentile_p_even": shuffled_percentile_p_even,
  679. "real_p_odd": real_p_odd,
  680. "shuffled_percentile_p_odd": shuffled_percentile_p_odd,
  681. "is_skipping": is_skipping,
  682. "is_hard_threshold_skipping": is_hard_threshold_skipping,
  683. }
  684. if is_skipping:
  685. loose_theta_skipping_cells.append(cluster_idx)
  686. if is_hard_threshold_skipping:
  687. strict_theta_skipping_cells.append(cluster_idx)
  688. else:
  689. fake_theta_skipping_cells.append(cluster_idx)
  690. # bool_is_theta_skipping.append(is_skipping)
  691. result = {
  692. "shuffle_test_result": shuffle_test_result,
  693. "loose_theta_skipping_cells": np.array(loose_theta_skipping_cells),
  694. "strict_theta_skipping_cells": np.array(strict_theta_skipping_cells),
  695. "fake_theta_skipping_cells": np.array(fake_theta_skipping_cells),
  696. # "bool_is_theta_skipping": np.array(bool_is_theta_skipping),
  697. }
  698. return result
  699. def show_all_theta_skipping_cells(ds, theta_skipping_cell_result, mask, n_cols=10):
  700. n_cells = len(theta_skipping_cell_result["real_theta_skipping_cells"])
  701. n_rows = int(np.ceil(n_cells / n_cols))
  702. fig = plt.figure(figsize=(n_cols * 1, n_rows * 1))
  703. for i, cluster_idx in enumerate(
  704. theta_skipping_cell_result["real_theta_skipping_cells"]
  705. ):
  706. ax = fig.add_subplot(n_rows, n_cols, i + 1)
  707. plot_spike_correlation_from_ds_v2(
  708. ds, cluster_idx, mask=mask, ax=ax, window_size=40
  709. )
  710. ax.axvspan(0, 6, color="lightgreen", alpha=0.5)
  711. ax.axvspan(6, 18, color="pink", alpha=0.5)
  712. ax.axvspan(18, 30, color="lightgreen", alpha=0.5)
  713. ax.set_xlim(0, 40)
  714. ax.set_title(f"clu {cluster_idx}", fontsize=6)
  715. erase_topright_axes(ax)
  716. ax.set_xticks([])
  717. ax.set_xlabel("")
  718. ax.set_ylabel("")
  719. # ax.spines['left'].set_fontsize(4)
  720. plt.tight_layout()
  721. return fig
  722. def show_all_theta_skipping_cells_v2(
  723. ds, theta_skipping_cells, mask, n_cols=10, yticks=False
  724. ):
  725. n_cells = len(theta_skipping_cells)
  726. n_rows = int(np.ceil(n_cells / n_cols))
  727. fig = plt.figure(figsize=(n_cols * 1, n_rows * 1))
  728. for i, cluster_idx in enumerate(theta_skipping_cells):
  729. ax = fig.add_subplot(n_rows, n_cols, i + 1)
  730. plot_spike_correlation_from_ds_v2(
  731. ds, cluster_idx, mask=mask, ax=ax, window_size=40
  732. )
  733. ax.axvspan(0, 6, color="lightgreen", alpha=0.5)
  734. ax.axvspan(6, 18, color="pink", alpha=0.5)
  735. ax.axvspan(18, 30, color="lightgreen", alpha=0.5)
  736. ax.set_xlim(0, 40)
  737. ax.set_title(f"clu {cluster_idx}", fontsize=6)
  738. erase_topright_axes(ax)
  739. ax.set_xticks([])
  740. ax.set_xlabel("")
  741. ax.set_ylabel("")
  742. if not yticks:
  743. ax.set_yticks([])
  744. # ax.spines['left'].set_fontsize(4)
  745. plt.tight_layout()
  746. return fig
  747. def get_hpc_group_direction(
  748. ds,
  749. mask,
  750. labels_dict,
  751. ):
  752. assert list(labels_dict.keys()) == [0, 1]
  753. res = []
  754. for i in [0, 1]:
  755. cum_place_field_center = ds.bin_positions.values[
  756. np.nanargmax(
  757. ds.firing_rate_map_mask.sel(cluster=labels_dict[i])
  758. .mean(dim="cluster")
  759. .values.flatten()
  760. )
  761. ]
  762. mean_position = ds.X.sel(frame=mask).mean(dim="frame").values
  763. mean_hd = circmean(ds.head_direction.sel(frame=mask).values)
  764. offset_direction = np.arctan2(
  765. cum_place_field_center[1] - mean_position[1],
  766. cum_place_field_center[0] - mean_position[0],
  767. )
  768. is_relative_left = np.sin(offset_direction - mean_hd) > 0
  769. res.append(is_relative_left)
  770. assert (res == [0, 1]) or (
  771. res == [1, 0]
  772. ), "The two groups are not opposite in direction"
  773. left_then_right = np.argsort(res)[::-1]
  774. return left_then_right
  775. def get_mec_group_direction(
  776. ds_mec,
  777. mask,
  778. labels_dict,
  779. ):
  780. assert list(labels_dict.keys()) == [0, 1]
  781. res = []
  782. for i in [0, 1]:
  783. # tuning_curve_peak = ds_mec.bin_angles.values[np.argmax(ds_mec.hd_tuning_curve.sel(cluster=labels_dict[i]).values.mean(axis=0))]
  784. y = ds_mec.hd_tuning_curve.sel(cluster=labels_dict[i]).values.mean(axis=0)
  785. # plt.plot(ds_mec.bin_angles, y)
  786. vector_sum = np.sum(y * np.exp(1j * ds_mec.bin_angles.values))
  787. tuning_curve_peak = np.angle(vector_sum) - np.pi
  788. mean_hd = circmean(ds.head_direction.sel(frame=mask).values)
  789. is_relative_left = int(np.sin(tuning_curve_peak - mean_hd) > 0)
  790. res.append(is_relative_left)
  791. assert (res == [0, 1]) or (
  792. res == [1, 0]
  793. ), "The two groups are not opposite in direction"
  794. left_then_right = np.argsort(res)[::-1]
  795. return left_then_right
  796. def get_group_direction(ds, mask, labels_dict, area_name):
  797. if area_name == "hc":
  798. return get_hpc_group_direction(ds, mask, labels_dict)
  799. elif area_name == "mec":
  800. return get_mec_group_direction(ds, mask, labels_dict)
  801. else:
  802. raise ValueError("area_name must be either 'hc' or 'mec'")
  803. def show_cumulative_firing_rate_map(ds, labels_dict, group_order=[0, 1]):
  804. fig = plt.figure(figsize=(2, 1))
  805. for i, j in enumerate(group_order):
  806. clusters = labels_dict[j]
  807. n_clusters = len(clusters)
  808. ax = fig.add_subplot(1, 2, i + 1)
  809. # cumulative_rate_map = np.zeros_like(
  810. # ds.firing_rate_map_mask.isel(cluster=0)
  811. # )
  812. # for j, cluster_idx in enumerate(clusters):
  813. # cumulative_rate_map += ds.firing_rate_map_mask.sel(
  814. # cluster=cluster_idx
  815. # )
  816. cumulative_rate_map = ds.firing_rate_map_mask.sel(cluster=clusters).mean(
  817. dim="cluster"
  818. )
  819. ax.imshow(cumulative_rate_map.T[::-1], interpolation="none")
  820. ax.axis("off")
  821. def show_overlaid_hd_tuning_curve(
  822. ds_mec,
  823. mask,
  824. labels_dict,
  825. plot_var="head_direction",
  826. group_order=[0, 1],
  827. plot_hd_and_id=False,
  828. density=False,
  829. ):
  830. angles = ds_mec.head_direction.sel(frame=mask)
  831. mean_angle = circmean(angles, high=np.pi, low=-np.pi)
  832. fig = plt.figure(figsize=(1.4, 0.6))
  833. for i, j in enumerate(group_order):
  834. ax = fig.add_subplot(1, 2, i + 1, polar=True)
  835. ax.grid(True, linewidth=0.25) # default is around 1.0
  836. ax.spines["polar"].set_linewidth(0.25) # default is around 1.0
  837. clusters = labels_dict[j]
  838. y = ds_mec.hd_tuning_curve.sel(cluster=labels_dict[j]).values
  839. if density:
  840. y = y / np.sum(y, axis=1, keepdims=True)
  841. # for cluster_idx in clusters:
  842. # plot_head_direction_selectivity_from_ds(
  843. # ds_mec, cluster_idx, plot_hd_and_id=plot_hd_and_id,
  844. # plot_var=plot_var,
  845. # ax=ax, linewidth=0.5
  846. # )
  847. # ax.set_title("")
  848. ax.plot(
  849. np.append(ds_mec.bin_angles, ds_mec.bin_angles[0]),
  850. # np.append(ds_mec.hd_tuning_curve.sel(cluster=labels_dict[i]).values, ds_mec.hd_tuning_curve.sel(cluster=labels_dict[i]).values[0]).T,
  851. np.concatenate([y, y[:, 0][:, None]], axis=1).T,
  852. color="#262626",
  853. linewidth=0.5,
  854. )
  855. ax.set_theta_zero_location("W") # optional: 0° at top
  856. ax.set_theta_direction(1) # optional: increase clockwise
  857. ax.set_yticks([])
  858. # ax.set_xticks([])
  859. ax.set_xticks([0, np.pi / 2, np.pi, 3 * np.pi / 2])
  860. ax.set_xticklabels([])
  861. ax.arrow(
  862. 0,
  863. 0,
  864. np.cos(mean_angle) * 0.5 * ax.get_rmax(),
  865. np.sin(mean_angle) * 0.5 * ax.get_rmax(),
  866. transform=ax.transData._b,
  867. facecolor="#2BB04F",
  868. edgecolor="#A7F2BC",
  869. linewidth=0.4,
  870. width=0.1 * ax.get_rmax(),
  871. head_length=0.25 * ax.get_rmax(),
  872. zorder=100,
  873. )
  874. def show_group_response(
  875. ds, area_name, mask, labels_dict, group_order=[0, 1], density=True
  876. ):
  877. if area_name == "hc":
  878. show_cumulative_firing_rate_map(ds, labels_dict, group_order=group_order)
  879. elif area_name == "mec":
  880. show_overlaid_hd_tuning_curve(
  881. ds, mask, labels_dict, group_order=group_order, density=density
  882. )
  883. else:
  884. raise ValueError("area_name must be either 'hc' or 'mec'")
  885. def get_mec_hc_group_average_lag(
  886. ds_dicts, stats_all, range_dicts, condition, threshold, window_size=15
  887. ):
  888. mask = get_range_mask_from_ds(ds_dicts["hc"], range_dicts[condition])
  889. hc_labels_dict = stats_all[condition]["hc"][threshold]["clustering_result"][
  890. "labels_dict"
  891. ]
  892. mec_labels_dict = stats_all[condition]["mec"][threshold]["clustering_result"][
  893. "labels_dict"
  894. ]
  895. hc_group_order = get_hpc_group_direction(ds_dicts["hc"], mask, hc_labels_dict)
  896. mec_group_order = get_mec_group_direction(ds_dicts["mec"], mask, mec_labels_dict)
  897. group_lags = {}
  898. for i, (hc_group, mec_group) in enumerate(zip(hc_group_order, mec_group_order)):
  899. peak_frame_lis = []
  900. for hc_clu in hc_labels_dict[hc_group]:
  901. for mec_clu in mec_labels_dict[mec_group]:
  902. spike_times_1 = np.where(
  903. ds_dicts["hc"].S.sel(cluster=hc_clu).sel(frame=mask)
  904. )[0]
  905. spike_times_2 = np.where(
  906. ds_dicts["mec"].S.sel(cluster=mec_clu).sel(frame=mask)
  907. )[0]
  908. counts = spike_correlation(
  909. spike_times_1=spike_times_1,
  910. spike_times_2=spike_times_2,
  911. bin_size=1,
  912. window=window_size,
  913. method="numba_v2",
  914. )[1]
  915. peak_frame_lis.append(int(np.argmax(counts)))
  916. lags = (np.array(peak_frame_lis) - window_size) * 10
  917. group_lags[f"group_{i}_peak_frame_lis"] = peak_frame_lis
  918. group_lags[f"group_{i}_mean_lag_ms"] = lags.mean()
  919. t_stat, p_val = stats.ttest_1samp(lags, popmean=0, alternative="less")
  920. group_lags[f"group_{i}_p_value"] = p_val
  921. group_lags[f"group_{i}_t_stat"] = t_stat
  922. group_lags[f"group_{i}_sample_size"] = len(lags)
  923. group_lags["hc_group_order"] = hc_group_order
  924. group_lags["mec_group_order"] = mec_group_order
  925. return group_lags
  926. def show_mec_hc_group_average_lag(
  927. ds_dicts,
  928. stats_all,
  929. range_dicts,
  930. condition,
  931. threshold,
  932. random_seed=42,
  933. window_size=15,
  934. colors=["darkblue", "crimson"],
  935. ):
  936. np.random.seed(random_seed)
  937. res = get_mec_hc_group_average_lag(
  938. ds_dicts,
  939. stats_all,
  940. range_dicts,
  941. condition=condition,
  942. threshold=threshold,
  943. window_size=window_size,
  944. )
  945. fig, ax = plt.subplots(figsize=(1.5, 0.8))
  946. for group in [0, 1]:
  947. ax.hist(
  948. res[f"group_{group}_peak_frame_lis"],
  949. bins=np.arange(0, window_size * 2),
  950. alpha=0.75,
  951. label=f"group {group+1}",
  952. color=colors[group],
  953. density=True,
  954. )
  955. ax.axvline(window_size, color="k", linestyle="--", linewidth=0.5)
  956. ax.set_xticks([0, window_size, window_size * 2])
  957. ax.tick_params(axis="x", length=1, width=0.5)
  958. ax.set_xticklabels([])
  959. ax.set_yticks([])
  960. ax.spines["bottom"].set_linewidth(0.25) # default is around 1.0
  961. erase_toprightleft_axes(ax)
  962. for group in [0, 1]:
  963. print(f"group {group+1}")
  964. print(f'Mean lag from HPC to MEC is {res[f"group_{group}_mean_lag_ms"]:.1f} ms')
  965. print(f'P-value is {res[f"group_{group}_p_value"]}')
  966. print(f'P-value is {res[f"group_{group}_p_value"]:.10f}')
  967. print(f'T-statistic is {res[f"group_{group}_t_stat"]}')
  968. print(f'T-statistic is {res[f"group_{group}_t_stat"]:.10f}')
  969. print(f'Sample size is {res[f"group_{group}_sample_size"]}')
  970. print()
  971. def compare_strict_and_loose_theta_skipping_cells(
  972. ds_dicts, stats_all, condition, area_name, mask, fig_dir=None
  973. ):
  974. strict_theta_skipping_cells = stats_all[condition][area_name]["strict"][
  975. "theta_skipping_cells"
  976. ]
  977. only_loose_theta_skipping_cells = np.setdiff1d(
  978. stats_all[condition][area_name]["loose"]["theta_skipping_cells"],
  979. stats_all[condition][area_name]["strict"]["theta_skipping_cells"],
  980. )
  981. fig = show_all_theta_skipping_cells_v2(
  982. ds_dicts[area_name], strict_theta_skipping_cells, mask, n_cols=6
  983. )
  984. fig.suptitle(f"{condition} - {area_name} - Strict theta skipping cells", y=1.05)
  985. if fig_dir is not None:
  986. fig.savefig(
  987. fig_dir / f"{condition}_{area_name}_strict_theta_skipping_cells.png",
  988. dpi=600,
  989. )
  990. plt.close(fig)
  991. fig = show_all_theta_skipping_cells_v2(
  992. ds_dicts[area_name], only_loose_theta_skipping_cells, mask, n_cols=6
  993. )
  994. fig.suptitle(f"{condition} - {area_name} - Only loose theta skipping cells", y=1.05)
  995. if fig_dir is not None:
  996. fig.savefig(
  997. fig_dir / f"{condition}_{area_name}_only_loose_theta_skipping_cells.png",
  998. dpi=600,
  999. )
  1000. plt.close(fig)
  1001. def show_firing_rate_maps(ds, clusters, n_cols=8, title=False):
  1002. n_clusters = len(clusters)
  1003. n_rows = (n_clusters + n_cols - 1) // n_cols
  1004. fig = plt.figure(figsize=(n_cols * 1, n_rows * 1))
  1005. for i, cluster_idx in enumerate(clusters):
  1006. ax = fig.add_subplot(n_rows, n_cols, i + 1)
  1007. ax.imshow(
  1008. ds.firing_rate_map_mask.sel(cluster=cluster_idx).T[::-1],
  1009. interpolation="none",
  1010. )
  1011. ax.axis("off")
  1012. if title:
  1013. ax.set_title(f"clu {cluster_idx}", fontsize=6)
  1014. def get_stats_all(
  1015. ds_dicts,
  1016. range_dicts,
  1017. random_seed=2025,
  1018. ):
  1019. stats_all = {}
  1020. for condition in range_dicts.keys():
  1021. print(condition)
  1022. mask = get_range_mask_from_ds(ds_dicts["hc"], range_dicts[condition])
  1023. stats_all[condition] = {
  1024. "hc": {"loose": {}, "strict": {}},
  1025. "mec": {"loose": {}, "strict": {}},
  1026. }
  1027. for area_name in ["hc", "mec"]:
  1028. print(f" {area_name}")
  1029. ds_ = ds_dicts[area_name]
  1030. theta_skipping_cell_result = (
  1031. get_theta_skipping_cells_with_and_wo_hard_threshold(
  1032. ds_, mask, n_samples=1000, hard_threshold_theta_skipping_index=0.5
  1033. )
  1034. )
  1035. for threshold in ["loose", "strict"]:
  1036. print(f" {area_name} - {threshold}")
  1037. theta_skipping_cells = theta_skipping_cell_result[
  1038. f"{threshold}_theta_skipping_cells"
  1039. ]
  1040. affinity_mat = get_affinity_matrix(
  1041. ds_, mask, theta_skipping_cells, window_size=40
  1042. )
  1043. clustering_result = cluster_affinity_matrix(
  1044. affinity_mat, cluster_lis=theta_skipping_cells
  1045. )
  1046. if area_name == "hc":
  1047. fmaps = ds_.firing_rate.sel(xy_bin=ds_.track_indices.values).sel(
  1048. cluster=theta_skipping_cells
  1049. )
  1050. elif area_name == "mec":
  1051. fmaps = ds_.hd_tuning_curve.sel(cluster=theta_skipping_cells)
  1052. corr_map = np.corrcoef(fmaps)
  1053. result = (
  1054. permutation_test_for_within_across_group_representation_similarity(
  1055. corr_map, clustering_result["labels"], random_seed=random_seed
  1056. )
  1057. )
  1058. stats_ = {
  1059. "theta_skipping_cells": theta_skipping_cells,
  1060. "affinity_mat": affinity_mat,
  1061. "fmaps": fmaps,
  1062. "clustering_result": clustering_result,
  1063. "firing_map_correlation_result": result,
  1064. "theta_skipping_cell_result": theta_skipping_cell_result,
  1065. "mask": mask,
  1066. }
  1067. stats_all[condition][area_name][threshold] = stats_
  1068. return stats_all
  1069. ## Process
  1070. np.random.seed(2025)
  1071. stats_all = get_stats_all(ds_dicts, range_dicts)
  1072. stats_supp = get_stats_all(ds_dicts, range_dicts_supp)
  1073. ## Plotting figures
  1074. # for condition in range_dicts.keys():
  1075. # mask = get_range_mask_from_ds(ds, range_dicts[condition])
  1076. # # for threshold in ['loose', 'strict']:
  1077. # for threshold in ['strict']:
  1078. # for area_name in ['hc', 'mec']:
  1079. # ds_ = ds_dicts[area_name]
  1080. # stats_ = stats_all[condition][area_name][threshold]# All theta skipping cells
  1081. # fig = show_all_theta_skipping_cells_v2(ds_, stats_['theta_skipping_cells'], mask, n_cols=6)
  1082. # fig.suptitle(f'{condition} - {area_name} - {threshold} theta skipping cells', y=1.02)
  1083. # labels_dict = stats_['clustering_result']['labels_dict']
  1084. # group_order = get_group_direction(ds_, mask, labels_dict, area_name)
  1085. # # Theta skipping correlation matrix
  1086. # # Fig2g left, Fig3f left, Fig4f left
  1087. # fig, ax = plt.subplots(figsize=(3, 3))
  1088. # show_correlation_matrix((stats_['affinity_mat'] + stats_['affinity_mat'].T) / 2, stats_['theta_skipping_cells'],
  1089. # stats_['clustering_result']['labels'], ax=ax,
  1090. # group_order=group_order
  1091. # )
  1092. # # Summary figure for the clusters
  1093. # # Fig2g right, Fig3f right, Fig4f right
  1094. # show_group_response(ds_, area_name, mask, stats_['clustering_result']['labels_dict'], group_order=group_order)
  1095. # # Time lag
  1096. # # Fig2h, Fig3g, Fig4g
  1097. # show_mec_hc_group_average_lag(ds_dicts, stats_all, condition, threshold, window_size=20)
  1098. ### Main figures
  1099. condition = "outbound_central_arm"
  1100. mask = get_range_mask_from_ds(ds, range_dicts[condition])
  1101. threshold = "strict"
  1102. for area_name in ["hc", "mec"]:
  1103. ds_ = ds_dicts[area_name]
  1104. stats_ = stats_all[condition][area_name][threshold] # All theta skipping cells
  1105. labels_dict = stats_["clustering_result"]["labels_dict"]
  1106. group_order = get_group_direction(ds_, mask, labels_dict, area_name)
  1107. # Theta skipping correlation matrix
  1108. # Fig2g left, Fig3f left, Fig4f left
  1109. show_correlation_matrix(
  1110. (stats_["affinity_mat"] + stats_["affinity_mat"].T) / 2,
  1111. stats_["theta_skipping_cells"],
  1112. stats_["clustering_result"]["labels"],
  1113. group_order=group_order,
  1114. plot_labels=False,
  1115. )
  1116. plt.savefig(
  1117. fig_dir / f"Fig2-g-left-{area_name}_skipping_cells_affinity_matrix.pdf",
  1118. dpi=600,
  1119. format="pdf",
  1120. bbox_inches="tight",
  1121. transparent=True,
  1122. )
  1123. # Summary figure for the clusters
  1124. # Fig2g right, Fig3f right, Fig4f right
  1125. show_group_response(
  1126. ds_,
  1127. area_name,
  1128. mask,
  1129. stats_["clustering_result"]["labels_dict"],
  1130. group_order=group_order,
  1131. )
  1132. plt.savefig(
  1133. fig_dir / f"Fig2-g-right-{area_name}_group_response.png",
  1134. transparent=True,
  1135. bbox_inches="tight",
  1136. format="png",
  1137. dpi=600,
  1138. )
  1139. # Time lag
  1140. # Fig2h, Fig3g, Fig4g
  1141. show_mec_hc_group_average_lag(
  1142. ds_dicts, stats_all, range_dicts, condition, threshold, window_size=15
  1143. )
  1144. plt.savefig(
  1145. fig_dir / "Fig2-h-hpc_mec_cycling_cross_correlation_peak_frames.pdf",
  1146. dpi=600,
  1147. format="pdf",
  1148. bbox_inches="tight",
  1149. transparent=True,
  1150. )
  1151. # Fig 3
  1152. condition = "inbound_from_left_arm"
  1153. mask = get_range_mask_from_ds(ds, range_dicts[condition])
  1154. threshold = "strict"
  1155. for area_name in ["hc", "mec"]:
  1156. ds_ = ds_dicts[area_name]
  1157. stats_ = stats_all[condition][area_name][threshold] # All theta skipping cells
  1158. labels_dict = stats_["clustering_result"]["labels_dict"]
  1159. group_order = get_group_direction(ds_, mask, labels_dict, area_name)
  1160. # Theta skipping correlation matrix
  1161. # Fig2g left, Fig3f left, Fig4f left
  1162. show_correlation_matrix(
  1163. (stats_["affinity_mat"] + stats_["affinity_mat"].T) / 2,
  1164. stats_["theta_skipping_cells"],
  1165. stats_["clustering_result"]["labels"],
  1166. group_order=group_order,
  1167. plot_labels=False,
  1168. )
  1169. plt.savefig(
  1170. fig_dir / f"Fig3-f-left-{area_name}_skipping_cells_affinity_matrix.pdf",
  1171. dpi=600,
  1172. format="pdf",
  1173. bbox_inches="tight",
  1174. transparent=True,
  1175. )
  1176. # Summary figure for the clusters
  1177. # Fig2g right, Fig3f right, Fig4f right
  1178. show_group_response(
  1179. ds_,
  1180. area_name,
  1181. mask,
  1182. stats_["clustering_result"]["labels_dict"],
  1183. group_order=group_order,
  1184. )
  1185. plt.savefig(
  1186. fig_dir / f"Fig3-f-right-{area_name}_group_response.png",
  1187. transparent=True,
  1188. bbox_inches="tight",
  1189. format="png",
  1190. dpi=600,
  1191. )
  1192. # Time lag
  1193. # Fig2h, Fig3g, Fig4g
  1194. show_mec_hc_group_average_lag(
  1195. ds_dicts, stats_all, range_dicts, condition, threshold, window_size=15
  1196. )
  1197. plt.savefig(
  1198. fig_dir / "Fig3-g-hpc_mec_cycling_cross_correlation_peak_frames.pdf",
  1199. dpi=600,
  1200. format="pdf",
  1201. bbox_inches="tight",
  1202. transparent=True,
  1203. )
  1204. # Fig 4
  1205. condition = "outbound_topleft_corner"
  1206. mask = get_range_mask_from_ds(ds, range_dicts[condition])
  1207. threshold = "strict"
  1208. for area_name in ["hc", "mec"]:
  1209. ds_ = ds_dicts[area_name]
  1210. stats_ = stats_all[condition][area_name][threshold] # All theta skipping cells
  1211. labels_dict = stats_["clustering_result"]["labels_dict"]
  1212. group_order = get_group_direction(ds_, mask, labels_dict, area_name)
  1213. # Theta skipping correlation matrix
  1214. # Fig2g left, Fig3f left, Fig4f left
  1215. show_correlation_matrix(
  1216. (stats_["affinity_mat"] + stats_["affinity_mat"].T) / 2,
  1217. stats_["theta_skipping_cells"],
  1218. stats_["clustering_result"]["labels"],
  1219. group_order=group_order,
  1220. plot_labels=False,
  1221. )
  1222. plt.savefig(
  1223. fig_dir / f"Fig4-f-left-{area_name}_skipping_cells_affinity_matrix.pdf",
  1224. dpi=600,
  1225. format="pdf",
  1226. bbox_inches="tight",
  1227. transparent=True,
  1228. )
  1229. # Summary figure for the clusters
  1230. # Fig2g right, Fig3f right, Fig4f right
  1231. show_group_response(
  1232. ds_,
  1233. area_name,
  1234. mask,
  1235. stats_["clustering_result"]["labels_dict"],
  1236. group_order=group_order,
  1237. )
  1238. plt.savefig(
  1239. fig_dir / f"Fig4-f-right-{area_name}_group_response.png",
  1240. transparent=True,
  1241. bbox_inches="tight",
  1242. format="png",
  1243. dpi=600,
  1244. )
  1245. # Time lag
  1246. # Fig2h, Fig3g, Fig4g
  1247. show_mec_hc_group_average_lag(
  1248. ds_dicts, stats_all, range_dicts, condition, threshold, window_size=15
  1249. )
  1250. plt.savefig(
  1251. fig_dir / "Fig4-g-hpc_mec_cycling_cross_correlation_peak_frames.pdf",
  1252. dpi=600,
  1253. format="pdf",
  1254. bbox_inches="tight",
  1255. transparent=True,
  1256. )
  1257. ### Supp Figures
  1258. ## Supp Fig2c hpc clustering results, individual rate maps
  1259. condition = "outbound_central_arm"
  1260. for group in [0, 1]:
  1261. cells = stats_all[condition]["hc"]["strict"]["clustering_result"]["labels_dict"][
  1262. group
  1263. ]
  1264. show_firing_rate_maps(ds, cells, n_cols=len(cells))
  1265. plt.savefig(
  1266. fig_dir / f"SuppFig2-c-hpc-group{group+1}-firing-rate-maps.png",
  1267. dpi=600,
  1268. format="png",
  1269. bbox_inches="tight",
  1270. transparent=True,
  1271. )
  1272. ## Supp Fig3a hpc clustering results, individual rate maps
  1273. condition = "inbound_from_left_arm"
  1274. for group in [0, 1]:
  1275. cells = stats_all[condition]["hc"]["strict"]["clustering_result"]["labels_dict"][
  1276. group
  1277. ]
  1278. show_firing_rate_maps(ds, cells, n_cols=len(cells))
  1279. plt.savefig(
  1280. fig_dir / f"SuppFig3-a-hpc-group{group+1}-firing-rate-maps.png",
  1281. dpi=600,
  1282. format="png",
  1283. bbox_inches="tight",
  1284. transparent=True,
  1285. )
  1286. # Other corners
  1287. supp_figures = {
  1288. "SuppFig3-b": "inbound_from_right_arm",
  1289. "SuppFig4-b": "inbound_topleft_corner",
  1290. "SuppFig4-c": "outbound_topright_corner",
  1291. "SuppFig4-d": "inbound_topright_corner",
  1292. }
  1293. threshold = "strict"
  1294. # for condition in range_dicts_supp.keys():
  1295. for supp_fig, condition in supp_figures.items():
  1296. mask = get_range_mask_from_ds(ds, range_dicts_supp[condition])
  1297. for area_name in ["hc", "mec"]:
  1298. ds_ = ds_dicts[area_name]
  1299. stats_ = stats_supp[condition][area_name][threshold] # All theta skipping cells
  1300. labels_dict = stats_["clustering_result"]["labels_dict"]
  1301. group_order = get_group_direction(ds_, mask, labels_dict, area_name)
  1302. # Theta skipping correlation matrix
  1303. # Fig2g left, Fig3f left, Fig4f left
  1304. show_correlation_matrix(
  1305. (stats_["affinity_mat"] + stats_["affinity_mat"].T) / 2,
  1306. stats_["theta_skipping_cells"],
  1307. stats_["clustering_result"]["labels"],
  1308. group_order=group_order,
  1309. plot_labels=False,
  1310. )
  1311. plt.savefig(
  1312. fig_dir
  1313. / f"{supp_fig}-{condition}-{area_name}_skipping_cells_affinity_matrix.pdf",
  1314. dpi=600,
  1315. format="pdf",
  1316. bbox_inches="tight",
  1317. transparent=True,
  1318. )
  1319. # Summary figure for the clusters
  1320. # Fig2g right, Fig3f right, Fig4f right
  1321. show_group_response(
  1322. ds_,
  1323. area_name,
  1324. mask,
  1325. stats_["clustering_result"]["labels_dict"],
  1326. group_order=group_order,
  1327. )
  1328. plt.savefig(
  1329. fig_dir / f"{supp_fig}-{condition}-{area_name}_group_response.png",
  1330. transparent=True,
  1331. bbox_inches="tight",
  1332. format="png",
  1333. dpi=600,
  1334. )
  1335. # Time lag
  1336. # Fig2h, Fig3g, Fig4g
  1337. show_mec_hc_group_average_lag(
  1338. ds_dicts, stats_supp, range_dicts_supp, condition, threshold, window_size=15
  1339. )
  1340. plt.savefig(
  1341. fig_dir
  1342. / f"{supp_fig}-{condition}-hpc_mec_cycling_cross_correlation_peak_frames.pdf",
  1343. dpi=600,
  1344. format="pdf",
  1345. bbox_inches="tight",
  1346. transparent=True,
  1347. )
  1348. ###########
  1349. ## stats ##
  1350. ###########
  1351. import sys
  1352. # open file in write mode (or append 'a')
  1353. logfile = open(str(fig_dir / "stats.txt"), "w")
  1354. sys.stdout = logfile
  1355. #####################################################
  1356. ### Left-right alternation of population decoding ###
  1357. #####################################################
  1358. print("#####################################################")
  1359. print("### Left-right alternation of population decoding ###")
  1360. print("#####################################################")
  1361. condition = "outbound_central_arm"
  1362. print("")
  1363. print("Condition:", condition)
  1364. run_shuffling_test_for_latent_position_cycling(
  1365. ds,
  1366. ML_modes,
  1367. bin_labels,
  1368. range_dicts[condition],
  1369. target_arm_1=1,
  1370. target_arm_2=2,
  1371. window_size=40,
  1372. n_samples=1000,
  1373. show_plots=False,
  1374. )
  1375. # This should output:
  1376. # Real skipping index: -0.4034334763948498
  1377. # 5th percentile of shuffled skipping index: -0.08027716910775382
  1378. # Is cycling between latent positions: True
  1379. condition = "inbound_from_left_arm"
  1380. print("")
  1381. print("Condition:", condition)
  1382. run_shuffling_test_for_latent_position_cycling(
  1383. ds,
  1384. ML_modes,
  1385. bin_labels,
  1386. range_dicts[condition],
  1387. target_arm_1=2,
  1388. target_arm_2=0,
  1389. window_size=40,
  1390. n_samples=1000,
  1391. show_plots=False,
  1392. )
  1393. #####################################################
  1394. ### Clustering analysis of theta skipping cells ###
  1395. #####################################################
  1396. window_size = 15
  1397. for condition in range_dicts.keys():
  1398. for threshold in ["loose", "strict"]:
  1399. print("\n-------------------------")
  1400. print(f"Condition: {condition}, Threshold: {threshold}\n")
  1401. for area_name in ["hc", "mec"]:
  1402. stats_ = stats_all[condition][area_name][threshold]
  1403. print(f"{area_name} - {threshold}:")
  1404. print(
  1405. f'Number of theta skipping cells: {len(stats_["theta_skipping_cells"])}'
  1406. )
  1407. print(
  1408. f'Firing map correlation within-across group p-value: {stats_["firing_map_correlation_result"]["p"]:.4f}'
  1409. )
  1410. res = get_mec_hc_group_average_lag(
  1411. ds_dicts,
  1412. stats_all,
  1413. range_dicts,
  1414. condition=condition,
  1415. threshold=threshold,
  1416. window_size=window_size,
  1417. )
  1418. for group in [0, 1]:
  1419. print(f"group {group+1}")
  1420. print(
  1421. f'Mean lag from HPC to MEC is {res[f"group_{group}_mean_lag_ms"]:.1f} ms'
  1422. )
  1423. print(f'P-value is {res[f"group_{group}_p_value"]}')
  1424. print(f'P-value is {res[f"group_{group}_p_value"]:.10f}')
  1425. print(f'T-statistic is {res[f"group_{group}_t_stat"]}')
  1426. print(f'T-statistic is {res[f"group_{group}_t_stat"]:.10f}')
  1427. print(f'Sample size is {res[f"group_{group}_sample_size"]}')
  1428. print()
  1429. for condition in range_dicts_supp.keys():
  1430. print("\n-------------------------")
  1431. for threshold in ["loose", "strict"]:
  1432. print("\n-------------------------")
  1433. print(f"Condition: {condition}, Threshold: {threshold}\n")
  1434. for area_name in ["hc", "mec"]:
  1435. stats_ = stats_supp[condition][area_name][threshold]
  1436. print(f"{area_name} - {threshold}:")
  1437. print(
  1438. f'Number of theta skipping cells: {len(stats_["theta_skipping_cells"])}'
  1439. )
  1440. print(
  1441. f'Firing map correlation within-across group p-value: {stats_["firing_map_correlation_result"]["p"]:.4f}'
  1442. )
  1443. res = get_mec_hc_group_average_lag(
  1444. ds_dicts,
  1445. stats_supp,
  1446. range_dicts_supp,
  1447. condition=condition,
  1448. threshold=threshold,
  1449. window_size=window_size,
  1450. )
  1451. for group in [0, 1]:
  1452. print(f"group {group+1}")
  1453. print(
  1454. f'Mean lag from HPC to MEC is {res[f"group_{group}_mean_lag_ms"]:.1f} ms'
  1455. )
  1456. print(f'P-value is {res[f"group_{group}_p_value"]}')
  1457. print(f'P-value is {res[f"group_{group}_p_value"]:.10f}')
  1458. print(f'T-statistic is {res[f"group_{group}_t_stat"]}')
  1459. print(f'T-statistic is {res[f"group_{group}_t_stat"]:.10f}')
  1460. print(f'Sample size is {res[f"group_{group}_sample_size"]}')
  1461. print()
  1462. logfile.close()

analysis-2.py, under CC-BY-4.0 · at the source

Overview

Authors: Masahiro Nakano1, Caswell Barry2, Claudia Clopath1,3
  1. Sainsbury Wellcome Centre for Neural Circuits and Behaviour University College London London UK
  2. Department of Cell and Developmental Biology University College London London UK
  3. Department of Bioengineering Imperial College London London UK
Institutions: Sainsbury Wellcome Centre (United Kingdom); University College London (United Kingdom); Imperial College London (United Kingdom)
Journal: Hippocampus, volume 36, issue 5, article e70131
Dates: received 13 February 2026; accepted 26 August 2026; published online 18 September 2026; in print September 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1002/hipo.70131 · PMID 42760272 · PMCID PMC13588937 · OpenAlex W7213600936
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Categories: systems (subfield)
Methods: Connectivity, Statistics, Machine learning, Single-unit activity, calcium imaging
Keywords: Bayesian decoding, Hippocampus, medial entorhinal cortex, planning, theta sequences, theta skipping
MeSH: Entorhinal Cortex*, Hippocampus*, Theta Rhythm*, Animals, Bayes Theorem, Decision Making, Male, Maze Learning (* major topic)
Topic: Memory and Neural Mechanisms (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Citations: not cited yet (Europe PMC); 76 references in the paper

Abstract

The hippocampus is central to memory and spatial planning. A prominent candidate mechanism supporting navigational decision making is hippocampal theta sequences—brief (~120 ms) place‐cell sequences representing future trajectories—which have been interpreted as planning signals based on their alternation between maze arms in T‐maze tasks. However, recent work suggests that intrinsic left–right alternation in medial entorhinal cortex (MEC) theta sequences may underlie this phenomenon. Here, we test this hypothesis using a model of MEC theta dynamics in a T‐maze and show that standard Bayesian decoding yields hippocampal theta sequences that alternate between arms. We then analyzed existing simultaneous MEC‐hippocampal recordings from Vollan et al. (2025). We found tight coupling between MEC and hippocampus, along with coordinated left–right alternation, while the animal was performing an alternation task. Notably, this conjoint alternation occurs not only at choice points but also during inbound and forced‐turn trials. These findings suggest that left–right alternation in hippocampal theta sequences is a ubiquitous phenomenon driven by MEC dynamics, challenging the interpretation that these specifically reflect intentional decision‐making or planning processes only.

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 8 matches between paragraphs and lines of code.

Zenodo 18340856

License: CC-BY-4.0
State: the link answers, verified on 26 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: “Data Availability Statement”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (17 files), Matplotlib (10 files), JAX (7 files), SciPy (7 files), pandas (5 files), scikit-learn (2 files), xarray (2 files), h5py (1 file), Numba (1 file), OpenCV (1 file)
Availability: 1 check, the latest on 26 September 2026: the link answers (HTTP 200)
  • 26 September 2026: the link answers (HTTP 200)
20 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;
  • 20 scripts, each with its path and the digest of its content;
  • 8 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

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

Data

Datasets cited

Data Availability Statement

Data analyzed in this paper are available at EBRAINS, 10.25493/R5FR‐EDG (https://doi.org/10.25493/R5FR-EDG). Code for reproducing the simulation and analyses in this article will be available at Zenodo 10.5281/zenodo.18340856 (https://doi.org/10.5281/zenodo.18340856).

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 3, 28 September 2026

  • Publisher: n/a → Wiley

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 3 authors, 6 keywords, 8 MeSH terms, 5 funders, 75 references.

Cite

This paper

Nakano, M., Barry, C., & Clopath, C. (2026). Decoding Medial Entorhinal Cortical Dynamics Produces Planning-Like Alternations in Hippocampal theta Sequences. Hippocampus, 36(5), e70131. https://doi.org/10.1002/hipo.70131

BibTeX

@article{nakano2026decoding,
author = {Nakano, Masahiro and Barry, Caswell and Clopath, Claudia},
title = {{Decoding Medial Entorhinal Cortical Dynamics Produces Planning-Like Alternations in Hippocampal theta Sequences}},
journal = {Hippocampus},
year = {2026},
month = sep,
volume = {36},
number = {5},
pages = {e70131},
publisher = {Wiley},
issn = {1050-9631},
doi = {10.1002/hipo.70131},
url = {https://doi.org/10.1002/hipo.70131},
pmid = {42760272},
pmcid = {PMC13588937}
}

RIS

TY - JOUR
AU - Nakano, Masahiro
AU - Barry, Caswell
AU - Clopath, Claudia
TI - Decoding Medial Entorhinal Cortical Dynamics Produces Planning-Like Alternations in Hippocampal theta Sequences
T2 - Hippocampus
J2 - Hippocampus
PY - 2026
DA - 2026/09/01
VL - 36
IS - 5
SP - e70131
SN - 1050-9631
PB - Wiley
DO - 10.1002/hipo.70131
UR - https://doi.org/10.1002/hipo.70131
LA - en
ER -

CSL-JSON

{
"id": "10.1002/hipo.70131",
"type": "article-journal",
"title": "Decoding Medial Entorhinal Cortical Dynamics Produces Planning-Like Alternations in Hippocampal theta Sequences",
"container-title": "Hippocampus",
"author": [
{
"family": "Nakano",
"given": "Masahiro"
},
{
"family": "Barry",
"given": "Caswell"
},
{
"family": "Clopath",
"given": "Claudia"
}
],
"container-title-short": "Hippocampus",
"volume": "36",
"issue": "5",
"page": "e70131",
"DOI": "10.1002/hipo.70131",
"PMID": "42760272",
"PMCID": "PMC13588937",
"ISSN": "1050-9631",
"publisher": "Wiley",
"URL": "https://doi.org/10.1002/hipo.70131",
"language": "en",
"issued": {
"date-parts": [
[
2026,
9,
1
]
]
}
}

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.1126/sciadv.aeg6797 [code]
Dorsoventral gradient of theta sweeps in the medial entorhinal cortex.
Journal: Science advances
In common: JAX, h5py, scikit-learn, 4 other tools, DOI 10.25493/r5fr-edg, systems, 13 references
[2] 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, 15 references
[3] doi:10.1038/s41593-026-02232-0 [code]
Entorhinal cortex represents task-relevant remote locations independently of CA1.
Journal: Nature neuroscience
In common: xarray, Numba, OpenCV, 6 other tools, systems, 6 references
[4] doi: [code]
Naturalistic behavior and self-generated neural activity predictive of self-correction
Journal: bioRxiv : the preprint server for biology
In common: JAX, xarray, OpenCV, 5 other tools, 6 references
[5] doi:10.1016/j.celrep.2026.117646 [code]
Medial entorhinal-hippocampal desynchronization parallels the emergence of memory impairment in a mouse model of Alzheimer's disease pathology.
Journal: Cell reports
In common: OpenCV, pandas, SciPy, 2 other tools, systems, 8 references
[6] doi:10.7554/elife.100642 [code]
Disrupted hippocampal theta-gamma coupling and spike-field coherence following experimental traumatic brain injury.
Journal: eLife
In common: systems, 10 references
[7] doi:10.1038/s42256-026-01254-4 [code]
Neural sampling from cognitive maps enables goal-directed imagination and planning.
Journal: Nature machine intelligence
In common: h5py, scikit-learn, Matplotlib, 1 other tool, 7 references
[8] doi:10.1371/journal.pbio.3003824 [code]
Flexible goal learning involves coordinated population activity in dCA1 and medial orbitofrontal cortex.
Journal: PLoS biology
In common: OpenCV, scikit-learn, pandas, 3 other tools, systems, 5 references
[9] doi:10.1126/sciadv.aea1037 [code]
Distinct cortical spatial representations learned along disparate visual pathways.
Journal: Science advances
In common: Numba, pandas, SciPy, 2 other tools, systems, 5 references
[10] doi:10.1038/s41467-026-75959-w [code]
Charting higher-order models of brain function beyond pairwise interactions.
Journal: Nature communications
In common: JAX, xarray, Numba, 6 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.