OSCR

Genetically encoded assembly recorder temporally resolves cellular history.

Code ↔ Paper

2 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 2 matches
  1. [1] § Methods › Image processing and data analysis ↔ Codes_R2/Single_particle_tracing_timelapse/crystal_tracer/algorithm/tracking.py, lines 148–253 · score 0.79 · linear sum assignment, linear programming, reverse, algorithm, match, connect
  2. [2] § Methods › Image processing and data analysis ↔ Codes_R2/3D_segmentation/CrystalTracer3D/band.py, lines 182–303 · score 0.50 · perpendicular, edges, noise, width, axis, band

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 · 440 lines · 18 KB · MIT · 1 match

  1. import pandas as pd
  2. from sklearn.neighbors import KDTree
  3. import numpy as np
  4. from tqdm import tqdm
  5. from sklearn.linear_model import LinearRegression
  6. from math import sqrt, atan2, pi
  7. from scipy.optimize import linear_sum_assignment
  8. from collections import Counter
  9. class Predictor:
  10. def __init__(self, min_sampling_count=2, min_sampling_elapse=2):
  11. self.min_sampling_count = min_sampling_count
  12. self.min_sampling_elapse = min_sampling_elapse
  13. self._mod = None
  14. def fit(self, x, y):
  15. # fit time-area model
  16. # no enough points, return just average
  17. if len(x) < self.min_sampling_count or x[-1] - x[0] < self.min_sampling_elapse:
  18. # when there is not enough or time frame is too short
  19. self._mod = np.mean(y, axis=0)
  20. else:
  21. self._mod = LinearRegression()
  22. # increasing weight for new entries
  23. self._mod.fit(np.array(x).reshape(-1, 1), y, [x[0] - i + 1 for i in x])
  24. return self
  25. def predict(self, x):
  26. if type(self._mod) is LinearRegression:
  27. return self._mod.predict([[x]])[0]
  28. else:
  29. return self._mod
  30. def angle_dist(center, other):
  31. vec = other - center
  32. dist = np.linalg.norm(vec, axis=-1)
  33. angle = np.array([atan2(v[0], v[1]) for v in vec])
  34. order = np.argsort(angle)
  35. return dist[order], angle[order]
  36. def neighborhood_match(center, other, centers, others):
  37. dist, rad = angle_dist(center, other)
  38. scores = []
  39. for c, o in zip(centers, others):
  40. d, r = angle_dist(c, o)
  41. diff_r = abs(np.subtract.outer(rad, r))
  42. diff_r = np.clip(diff_r, None, 2 * pi - diff_r)
  43. diff_d = abs(np.subtract.outer(dist, d))
  44. diff = diff_d * diff_r
  45. choice = linear_sum_assignment(diff)
  46. scores.append(diff[choice].mean())
  47. return scores
  48. def independent_match(tables: list[pd.DataFrame], area_normalizer=500., nn=5, time_gap_thr=5, min_sampling_count=5,
  49. min_sampling_elapse=10,
  50. max_area_overflow=.25, max_intensity_overflow=.25, callback=None):
  51. """
  52. Connect the crystals in each frame in and independent manner, starting from the last frame. It forms a track for
  53. each crystal in the last frame until it disappears in reverse time order. The output will be reversed back.
  54. :param tables: a list of dataframes of detected crystals
  55. :param area_normalizer: the distance threshold controller
  56. :param nn: number of nearest neighboring crystals considered for matching
  57. :param time_gap_thr: max time gap allowed in the track
  58. :param min_sampling_count: min No. of sampling points for area fitting
  59. :param min_sampling_elapse: min time elapse of sampling points for area fitting
  60. :param max_area_overflow: the max ratio of area difference
  61. :param max_intensity_overflow: the max ratio of intensity difference
  62. :param callback: a function to call in each iteration
  63. :return: identified tracks, a list of lists of tuples, (frame, index)
  64. """
  65. # init from the last frame
  66. # chains: the tracks. list of (frame, crystal_id)
  67. # pred_area: predicted area based on previous discovery
  68. nn += 1
  69. tracks = []
  70. pred_area = []
  71. pred_gray = []
  72. trees = [KDTree(t[['y', 'x']]) for t in tables]
  73. for ind, row in tables[-1].iterrows():
  74. tracks.append([(len(tables) - 1, ind)])
  75. # init as the start crystal size
  76. pred_area.append(Predictor(min_sampling_count, min_sampling_elapse).fit([0], [row['area']]))
  77. pred_gray.append(Predictor().fit([0], [row['intensity']]))
  78. callback()
  79. # start tracking from the one but last frame
  80. for i_frame in tqdm(range(len(tables) - 2 , -1, -1)):
  81. cur_pos = tables[i_frame][['y', 'x']].to_numpy()
  82. n = min(nn, len(cur_pos))
  83. cur_ind = trees[i_frame].query(cur_pos, n, dualtree=True, return_distance=False)
  84. # check for each crystal which track can be appended to
  85. for track, mod_area, mod_gray in zip(tracks, pred_area, pred_gray):
  86. # these tracks are terminated for big time gap
  87. if track[-1][0] - i_frame > time_gap_thr + 1:
  88. continue
  89. i_frame_last, i_crystal_last = track[-1]
  90. center = tables[i_frame_last].loc[i_crystal_last, ['y', 'x']].to_numpy()
  91. ref_area = mod_area.predict(i_frame)
  92. dist_thr = area_normalizer / sqrt(ref_area)
  93. i_crystals = trees[i_frame].query_radius([center], dist_thr)[0]
  94. # estimate the area for this frame
  95. scale = sqrt(area_normalizer / ref_area)
  96. i_crystals = i_crystals[np.abs(ref_area - tables[i_frame].loc[i_crystals, 'area'].to_numpy()) <
  97. max_area_overflow * scale * ref_area]
  98. ref_gray = mod_gray.predict(i_frame)
  99. i_crystals = i_crystals[np.abs(tables[i_frame].loc[i_crystals, 'intensity'].to_numpy() - ref_gray) <
  100. max_intensity_overflow * scale * ref_gray]
  101. if len(i_crystals) == 0:
  102. continue
  103. ind = trees[i_frame_last].query([center], n, return_distance=False)[0][1:]
  104. ans = neighborhood_match(center, tables[i_frame_last].loc[ind, ['y', 'x']].to_numpy(),
  105. tables[i_frame].loc[i_crystals, ['y', 'x']].to_numpy(),
  106. [tables[i_frame].loc[cur_ind[i][1:], ['y', 'x']].to_numpy() for i in i_crystals])
  107. # update track if time gap is met
  108. track.append((i_frame, i_crystals[np.argmin(ans)]))
  109. # update area prediction
  110. mod_area.fit([c[0] for c in track], [tables[c[0]].at[c[1], 'area'] for c in track])
  111. mod_gray.fit([c[0] for c in track], [tables[c[0]].at[c[1], 'intensity'] for c in track])
  112. if callback is not None:
  113. callback()
  114. for t in tracks:
  115. t.reverse()
  116. return tracks
  117. def ratio_diff(a, b):
  118. t = a / (b + 1e-5)
  119. return (t + 1 / t) / 2
  120. def linear_programming2(tables, area_normalizer=500., nn=10, time_gap_thr=10, min_sampling_count=5, min_sampling_elapse=10,
  121. max_area_overflow=.25, max_intensity_overflow=.25, w_dist=1., w_area=1., w_intensity=1., w_local=1.,
  122. callback=None):
  123. """
  124. Connect the crystals in each frame in and independent manner, starting from the last frame. It forms a track for
  125. each crystal in the last frame until it disappears in reverse time order. The output will be reversed back.
  126. :param tables: a list of dataframes of detected crystals
  127. :param area_normalizer: the distance threshold
  128. :param nn: number of nearest neighboring crystals considered for matching
  129. :param time_gap_thr: max time gap allowed in the track
  130. :param min_sampling_count: min No. of sampling points for area fitting
  131. :param min_sampling_elapse: min time elapse of sampling points for area fitting
  132. :param max_area_overflow: the max ratio of area difference
  133. :param max_intensity_overflow: the max ratio of intensity difference
  134. :return: identified tracks, a list of lists of tuples, (frame, index)
  135. """
  136. def cost(v1, v2):
  137. p1 = v1[:2]
  138. p2 = v2[:2]
  139. dist = np.linalg.norm(p1 - p2)
  140. area_diff = abs(v1[2] - v2[2])
  141. gray_diff = abs(v1[3] - v2[3])
  142. # v1 = v1[4:].reshape(-1, 2)
  143. # v2 = v2[4:].reshape(-1, 2)
  144. # local_score = neighborhood_match(p1, v1, [p2], [v2])[0]
  145. return w_dist * dist ** 2 + w_area * area_diff + w_intensity * gray_diff
  146. cost_func = np.vectorize(cost, signature='(n),(n)->()')
  147. # init from the last frame
  148. # chains: the tracks. list of (frame, crystal_id)
  149. # pred_area: predicted area based on previous discovery
  150. tracks = []
  151. pred_area = []
  152. pred_gray = []
  153. trees = [KDTree(t[['y', 'x']]) for t in tables]
  154. for ind, row in tables[-1].iterrows():
  155. tracks.append([(len(tables) - 1, ind)])
  156. # init as the start crystal size
  157. pred_area.append(Predictor(min_sampling_count, min_sampling_elapse).fit([0], [row['area']]))
  158. pred_gray.append(Predictor().fit([0], [row['intensity']]))
  159. # start tracking from the one but last frame
  160. for i_frame in tqdm(range(len(tables) - 2 , -1, -1)):
  161. if callback is not None:
  162. callback()
  163. cur_features = tables[i_frame][['y', 'x', 'area', 'intensity']].to_numpy()
  164. # n = min(nn, len(cur_features))
  165. # cur_ind = trees[i_frame].query(cur_features[:, :2], n, dualtree=True, return_distance=False)
  166. # cur_pos = np.array([cur_features[i, :2] for i in cur_ind]).reshape(cur_ind.shape[0], -1)
  167. # cur_features = np.concatenate([cur_features, cur_pos], axis=1)
  168. active = []
  169. pre_features = []
  170. for t, mod_area, mod_gray in zip(tracks, pred_area, pred_gray):
  171. i_frame_last, i_crystal_last = t[-1]
  172. if i_frame_last - i_frame > 1:
  173. continue
  174. active.append(t)
  175. feature = tables[i_frame_last].loc[i_crystal_last, ['y', 'x']].to_list()
  176. # prev_ind = trees[i_frame_last].query([feature], n, return_distance=False)[0]
  177. # prev_pos = tables[i_frame_last].loc[prev_ind, ['y', 'x']].to_numpy().reshape(-1)
  178. feature.append(mod_area.predict(i_frame))
  179. feature.append(mod_gray.predict(i_frame))
  180. # feature.extend(list(prev_pos))
  181. pre_features.append(feature)
  182. if len(active) == 0:
  183. continue
  184. pre_features = np.array(pre_features)
  185. # linear programming
  186. mat = cost_func(pre_features.reshape(-1, 1, pre_features.shape[1]),
  187. cur_features.reshape(1, -1, cur_features.shape[1]))
  188. choice = linear_sum_assignment(mat)
  189. choice = dict(zip(*choice))
  190. for i, (t, mod_area, mod_gray, f1) in enumerate(zip(active, pred_area, pred_gray, pre_features)):
  191. # update tracks and models
  192. coord = f1[:2]
  193. ref_area = f1[2]
  194. ref_gray = f1[3]
  195. dist_thr = area_normalizer / sqrt(ref_area)
  196. if i not in choice:
  197. continue
  198. c = choice[i]
  199. f2 = cur_features[c]
  200. cur_coord = f2[:2]
  201. cur_area = f2[2]
  202. cur_gray = f2[3]
  203. scale = sqrt(area_normalizer / ref_area)
  204. if np.linalg.norm(coord - cur_coord) > dist_thr or \
  205. abs(ref_area - cur_area) > max_area_overflow * scale * ref_area or \
  206. abs(cur_gray - ref_gray) > max_intensity_overflow * scale * ref_gray:
  207. continue
  208. t.append((i_frame, c))
  209. mod_area.fit([c[0] for c in t], [tables[c[0]].at[c[1], 'area'] for c in t])
  210. mod_gray.fit([c[0] for c in t], [tables[c[0]].at[c[1], 'intensity'] for c in t])
  211. for t in tracks:
  212. t.reverse()
  213. callback()
  214. return tracks
  215. def linear_programming(tables, area_normalizer=500., use_contig=True, callback=None):
  216. """
  217. Connect the crystals in each frame in and independent manner, starting from the last frame. It forms a track for
  218. each crystal in the last frame until it disappears in reverse time order. The output will be reversed back.
  219. :param tables: a list of dataframes of detected crystals
  220. :param area_normalizer: the distance threshold
  221. :param use_contig: if allow the tracing to be continued on broken tracks (will affect all the tracks)
  222. :return: identified tracks, a list of lists of tuples, (frame, index)
  223. """
  224. # preprocessing: rmdup
  225. for i, tab in enumerate(tables):
  226. flag = [True] * len(tab)
  227. coords = tab[['y', 'x']].to_numpy()
  228. radii = np.sqrt(tab['area'].to_numpy() / pi)
  229. points1 = coords[:, np.newaxis, :]
  230. points2 = coords[np.newaxis, :, :]
  231. dist = np.sqrt(np.sum((points1 - points2) ** 2, axis=-1))
  232. dist[dist == 0] = np.inf
  233. for a, b in np.argwhere(dist < radii):
  234. if radii[a] < radii[b]:
  235. flag[a] = False
  236. else:
  237. flag[b] = False
  238. tables[i] = tab[flag]
  239. # init from the last frame
  240. # chains: the tracks. list of (frame, crystal_id)
  241. # pred_area: predicted area based on previous discovery
  242. tracks = [[(len(tables) - 1, i)] for i in tables[-1].index]
  243. # start tracking from the 2nd last frame
  244. for i_frame in tqdm(range(len(tables) - 2 , -1, -1)):
  245. if callback is not None:
  246. callback()
  247. pre_features = tables[i_frame + 1][['y', 'x', 'area', 'intensity']].to_numpy()
  248. cur_features = tables[i_frame][['y', 'x', 'area', 'intensity']].to_numpy()
  249. # linear programming
  250. sq_diff = (pre_features[:, np.newaxis, :2] - cur_features[np.newaxis, :, :2]) ** 2
  251. distances = np.sqrt(np.sum(sq_diff, axis=-1))
  252. area_diff = ratio_diff(pre_features[:, np.newaxis, 2], cur_features[np.newaxis, :, 2])
  253. intensity_diff = ratio_diff(pre_features[:, np.newaxis, 3], cur_features[np.newaxis, :, 3])
  254. s = np.log((distances + 1) * area_diff * intensity_diff)
  255. choice = linear_sum_assignment(s)
  256. pre_ind = tables[i_frame + 1].index.to_numpy()
  257. cur_ind = tables[i_frame].index.to_numpy()
  258. choice = dict(zip(pre_ind[choice[0]], cur_ind[choice[1]]))
  259. used = set()
  260. for t in tracks:
  261. # update tracks and models
  262. i_frame_last, i_crystal_last = t[-1]
  263. if i_frame_last - i_frame > 1:
  264. continue
  265. if i_crystal_last not in choice:
  266. continue
  267. c = choice[i_crystal_last]
  268. pre_area = tables[i_frame_last].at[i_crystal_last, 'area']
  269. pre_coords = tables[i_frame_last].loc[i_crystal_last, ['y', 'x']]
  270. cur_coords = tables[i_frame].loc[c, ['y', 'x']]
  271. dist_thr = area_normalizer / sqrt(pre_area)
  272. if np.linalg.norm(pre_coords - cur_coords) > dist_thr:
  273. continue
  274. t.append((i_frame, c))
  275. used.add(c)
  276. if use_contig:
  277. for c in set(cur_ind) - used:
  278. tracks.append([(i_frame, c)])
  279. for t in tracks:
  280. t.reverse()
  281. callback()
  282. return tracks
  283. def linear_programming3(tables, area_normalizer=500., trace_frames=5, callback=None):
  284. """
  285. Connect the crystals in each frame in and independent manner, starting from the last frame. It forms a track for
  286. each crystal in the last frame until it disappears in reverse time order. The output will be reversed back.
  287. In this version, the LP is performed between not only adjacent frames but those within a time range to make the connectivity more
  288. robust.
  289. :param tables: a list of dataframes of detected crystals
  290. :param area_normalizer: the distance threshold
  291. :param trace_frames: the number of adjacent frames to consider
  292. :return: identified tracks, a list of lists of tuples, (frame, index)
  293. """
  294. # preprocessing: rmdup
  295. for i, tab in enumerate(tables):
  296. flag = [True] * len(tab)
  297. coords = tab[['y', 'x']].to_numpy()
  298. radii = np.sqrt(tab['area'].to_numpy() / pi)
  299. points1 = coords[:, np.newaxis, :]
  300. points2 = coords[np.newaxis, :, :]
  301. dist = np.sqrt(np.sum((points1 - points2) ** 2, axis=-1))
  302. dist[dist == 0] = np.inf
  303. for a, b in np.argwhere(dist < radii):
  304. if radii[a] < radii[b]:
  305. flag[a] = False
  306. else:
  307. flag[b] = False
  308. tables[i] = tab[flag]
  309. # init from the last frame
  310. # chains: the tracks. list of (frame, crystal_id)
  311. # pred_area: predicted area based on previous discovery
  312. # tracks:
  313. # [
  314. # [(time_frame, crystal_id), ...]
  315. # ]
  316. tracks = [[(len(tables) - 1, i)] for i in tables[-1].index]
  317. tables[-1]['parent'] = range(len(tables[-1])) # this is a map of all crystals to tracks
  318. # start tracking from the 2nd last frame
  319. for i_frame in tqdm(range(len(tables) - 2 , -1, -1)):
  320. if callback is not None:
  321. callback()
  322. cur_features = tables[i_frame][['y', 'x', 'area', 'intensity']].to_numpy()
  323. cur_ind = tables[i_frame].index.to_numpy()
  324. choices = {}
  325. for pre in range(i_frame + 1, min(i_frame + 1 + trace_frames, len(tables))):
  326. pre_features = tables[pre][['y', 'x', 'area', 'intensity']].to_numpy()
  327. pre_track_ind = tables[pre]['parent'].to_numpy()
  328. # linear programming
  329. sq_diff = (pre_features[:, np.newaxis, :2] - cur_features[np.newaxis, :, :2]) ** 2
  330. distances = np.sqrt(np.sum(sq_diff, axis=-1))
  331. area_diff = ratio_diff(pre_features[:, np.newaxis, 2], cur_features[np.newaxis, :, 2])
  332. intensity_diff = ratio_diff(pre_features[:, np.newaxis, 3], cur_features[np.newaxis, :, 3])
  333. s = np.log((distances + 1) * area_diff * intensity_diff)
  334. choice = linear_sum_assignment(s)
  335. # map each current crystals to existing tracks
  336. for a, b in zip(pre_track_ind[choice[0]], cur_ind[choice[1]]):
  337. if b not in choices:
  338. choices[b] = []
  339. choices[b].append(a)
  340. def most_frequent_word(word_list):
  341. word_counts = Counter(word_list)
  342. most_common_word = word_counts.most_common(1)
  343. return most_common_word[0][0]
  344. # choose the most frequent preceding track for the crystals
  345. choice = {}
  346. for k, v in choices.items():
  347. a = int(most_frequent_word(v))
  348. if a == -1:
  349. continue
  350. if a not in choice:
  351. choice[a] = []
  352. choice[a].append(k)
  353. tables[i_frame]['parent'] = -1
  354. for k, v in choice.items():
  355. i_frame_last, i_crystal_last = tracks[k][-1]
  356. pre_area = tables[i_frame_last].at[i_crystal_last, 'area']
  357. pre_coords = tables[i_frame_last].loc[i_crystal_last, ['y', 'x']]
  358. dists = []
  359. chs = []
  360. # if multiple choices exist, choose the nearest one
  361. for ch in v:
  362. cur_coords = tables[i_frame].loc[ch, ['y', 'x']]
  363. dist_thr = area_normalizer / sqrt(pre_area)
  364. dist = np.linalg.norm(pre_coords - cur_coords)
  365. if dist > dist_thr:
  366. continue
  367. dists.append(dist)
  368. chs.append(ch)
  369. if len(dists) > 0:
  370. ch = chs[np.argmin(dists)]
  371. tracks[k].append((i_frame, ch))
  372. tables[i_frame].at[ch, 'parent'] = k # update the track belonging of each crystal
  373. for t in tracks:
  374. t.reverse()
  375. callback()
  376. return tracks

tracking.py at commit 6b8d1a2, under MIT · at the source

Overview

Authors: Yuqing Yan1,2,3,4, Jiaxi Lu1,2,4, Zhe Li5,6,4, Zuohan Zhao1,2, Timothy F Shay7, Shunzhi Wang5, Yaping Lei7, Yimei Wang1,2, Wei Chen8, Patrick Parker8, Hongru Yang1,2, Aileen Qi1, Yongzhi Sun1,2, Dwight E Bergles3,8, David Baker5, Dingchang Lin1,2,3,9
  1. Department of Materials Science and Engineering, Whiting School of Engineering, Johns Hopkins University, Baltimore, MD, USA
  2. Institute for NanoBiotechnology, Whiting School of Engineering, Johns Hopkins University, Baltimore, MD, USA
  3. Kavli Neuroscience Discovery Institute, Johns Hopkins University, Baltimore, MD, USA
  4. These authors contributed equally: Yuqing Yan, Jiaxi Lu, Zhe Li
  5. Institute for Protein Design, University of Washington, Seattle, WA, USA
  6. Present address: Department of Biomedical Engineering, Southern University of Science and Technology, Shenzhen, China
  7. Division of Biology and Biological Engineering, California Institute of Technology, Pasadena, CA, USA
  8. Solomon H. Snyder Department of Neuroscience, School of Medicine, Johns Hopkins University, Baltimore, MD, USA
  9. Center for Cell Dynamics, School of Medicine, Johns Hopkins University, Baltimore, MD, USA
Journal: Nature, volume 652, issue 8111, pages 1049-1059
Dates: published online 3 March 2026; in print April 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41586-026-10323-y · PMID 41775935 · PMCID PMC13102709 · OpenAlex W7133339032
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Categories: human (organism), mouse (organism), cellular / molecular (subfield)
Methods: Spectral & time-frequency, Statistics, Preprocessing, Evoked potentials, Machine learning
MeSH: Cells*, Spatio-Temporal Analysis*, Animals, Brain, Cell Lineage, Female, Humans, Inflammation, Male, Mice, Neurons, NF-kappa B, Signal Transduction, Time Factors, Transcription, Genetic (* major topic)
Topic: Genomics and Chromatin Dynamics (Molecular Biology, Biochemistry, Genetics and Molecular Biology), according to OpenAlex
Funding: NIGMS NIH HHS (R35 GM147274); NEI NIH HHS (R21 EY035955)
Citations: cited by 6 papers (Europe PMC); 49 references in the paper

Abstract

Cells constantly change their molecular state in response to internal and external cues1. Mapping cellular activity in tissues with spatiotemporal precision is essential for understanding organ physiology, pathology and regenerative processes. Current cell-sensing modalities primarily rely on either end point analysis that takes static snapshots2 or real-time sensing that monitors a small subset of cells3,4. Here we introduce granularly expanding memory for intracellular narrative integration (GEMINI), an in cellulo recording platform that leverages a computationally designed protein assembly as an intracellular memory device to record the history of individual cells. GEMINI grows predictably within live cells, capturing cellular events as tree-ring-like fluorescent patterns for imaging-based retrospective readout. Absolute chronological information of activity histories is attainable with hour-level accuracy. GEMINI effectively maps differential NF-κB-mediated transcriptional changes, resolving fast dynamics of 15 min and providing quantifiable signal amplitudes. In a xenograft model, GEMINI records inflammation-induced signalling dynamics across tissue, revealing spatial heterogeneity linked to vascular density. When expressed in the mouse brain, GEMINI minimally impacts neuronal functions and can resolve both transcriptional changes and activity patterns of neurons. Together, GEMINI provides a robust and generalizable means for spatiotemporal mapping of cell dynamics underlying physiological and pathological processes in both culture and intact tissues.

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

DCLinLab/GEMINI

License: MIT
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: 6b8d1a2e5c2d5f767665eaeb44d0a18510ce786d, 31 March 2026
Languages: Python (58), Jupyter (7), C++ (1)
Size: 779 files, 66 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: license file, environment (Codes_R2/3D_segmentation/setup.py, Codes_R2/Single_particle_tracing_timelapse/pyproject.toml, Codes_R2/Single_particle_tracing_timelapse/requirements.txt, Codes_R2/Single_particle_tracing_timelapse/setup.py), 7 notebooks
Not found: README, CITATION.cff, tests, continuous integration, documentation
Tools: NumPy (38 files), scikit-image (28 files), pandas (24 files), Matplotlib (17 files), SciPy (12 files), napari (7 files), OpenCV (7 files), seaborn (7 files), scikit-learn (5 files), Cellpose (2 files), tifffile (2 files), Pillow (1 file), scikit-posthocs (1 file)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
67 files

Code availability

Custom data analysis code developed and used for this project is available at GitHub (https://github.com/DCLinLab/GEMINI).

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

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;
  • 66 scripts, each with its path and the digest of its content;
  • 2 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

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

Data

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

Data Availability Statement

Source datasets for the in vivo experiments are provide with the paper. Complete datasets for this article, including those for main figures, extended data figures and supplementary figures, are available at GitHub (https://github.com/DCLinLab/GEMINI).

Custom data analysis code developed and used for this project is available at GitHub (https://github.com/DCLinLab/GEMINI).

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

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 30 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 16 authors, 15 MeSH terms, 2 funders, 49 references.

Cite

This paper

Yan, Y., Lu, J., Li, Z., Zhao, Z., Shay, T. F., Wang, S., Lei, Y., Wang, Y., Chen, W., Parker, P., Yang, H., Qi, A., Sun, Y., Bergles, D. E., Baker, D., & Lin, D. (2026). Genetically encoded assembly recorder temporally resolves cellular history. Nature, 652(8111), 1049-1059. https://doi.org/10.1038/s41586-026-10323-y

BibTeX

@article{yan2026genetically,
author = {Yan, Yuqing and Lu, Jiaxi and Li, Zhe and Zhao, Zuohan and Shay, Timothy F and Wang, Shunzhi and Lei, Yaping and Wang, Yimei and Chen, Wei and Parker, Patrick and Yang, Hongru and Qi, Aileen and Sun, Yongzhi and Bergles, Dwight E and Baker, David and Lin, Dingchang},
title = {{Genetically encoded assembly recorder temporally resolves cellular history}},
journal = {Nature},
year = {2026},
month = mar,
volume = {652},
number = {8111},
pages = {1049--1059},
publisher = {Nature Portfolio},
issn = {0028-0836},
doi = {10.1038/s41586-026-10323-y},
url = {https://doi.org/10.1038/s41586-026-10323-y},
pmid = {41775935},
pmcid = {PMC13102709}
}

RIS

TY - JOUR
AU - Yan, Yuqing
AU - Lu, Jiaxi
AU - Li, Zhe
AU - Zhao, Zuohan
AU - Shay, Timothy F
AU - Wang, Shunzhi
AU - Lei, Yaping
AU - Wang, Yimei
AU - Chen, Wei
AU - Parker, Patrick
AU - Yang, Hongru
AU - Qi, Aileen
AU - Sun, Yongzhi
AU - Bergles, Dwight E
AU - Baker, David
AU - Lin, Dingchang
TI - Genetically encoded assembly recorder temporally resolves cellular history
T2 - Nature
J2 - Nature
PY - 2026
DA - 2026/03/03
VL - 652
IS - 8111
SP - 1049
EP - 1059
SN - 0028-0836
PB - Nature Portfolio
DO - 10.1038/s41586-026-10323-y
UR - https://doi.org/10.1038/s41586-026-10323-y
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41586-026-10323-y",
"type": "article-journal",
"title": "Genetically encoded assembly recorder temporally resolves cellular history",
"container-title": "Nature",
"author": [
{
"family": "Yan",
"given": "Yuqing"
},
{
"family": "Lu",
"given": "Jiaxi"
},
{
"family": "Li",
"given": "Zhe"
},
{
"family": "Zhao",
"given": "Zuohan"
},
{
"family": "Shay",
"given": "Timothy F"
},
{
"family": "Wang",
"given": "Shunzhi"
},
{
"family": "Lei",
"given": "Yaping"
},
{
"family": "Wang",
"given": "Yimei"
},
{
"family": "Chen",
"given": "Wei"
},
{
"family": "Parker",
"given": "Patrick"
},
{
"family": "Yang",
"given": "Hongru"
},
{
"family": "Qi",
"given": "Aileen"
},
{
"family": "Sun",
"given": "Yongzhi"
},
{
"family": "Bergles",
"given": "Dwight E"
},
{
"family": "Baker",
"given": "David"
},
{
"family": "Lin",
"given": "Dingchang"
}
],
"container-title-short": "Nature",
"volume": "652",
"issue": "8111",
"page": "1049-1059",
"DOI": "10.1038/s41586-026-10323-y",
"PMID": "41775935",
"PMCID": "PMC13102709",
"ISSN": "0028-0836",
"publisher": "Nature Portfolio",
"URL": "https://doi.org/10.1038/s41586-026-10323-y",
"language": "en",
"issued": {
"date-parts": [
[
2026,
3,
3
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1371/journal.pcbi.1014571 [code]
SynAPSeg: A novel dataset and image analysis framework for deep learning-based synapse detection and quantification.
Journal: PLoS computational biology
In common: Cellpose, napari, tifffile, 9 other tools
[2] doi:10.1038/s42003-026-10063-9 [code]
Conserved Kir channel mechanisms governing intrinsic excitability in human and rodent parvalbumin neurons.
Journal: Communications biology
In common: Cellpose, napari, tifffile, 7 other tools, mouse, cellular / molecular
[3] doi: [code]
Real-time closed-loop feedback system for mouse mesoscale cortical signal and movement control
Journal: eLife
In common: napari, tifffile, OpenCV, 8 other tools, mouse
[4] doi:10.1038/s41467-026-74823-1 [code]
Cerebellar activity is triggered by reach endpoint during learning of a complex locomotor task.
Journal: Nature communications
In common: scikit-posthocs, tifffile, OpenCV, 7 other tools, mouse, cellular / molecular
[5] doi:10.7554/elife.107635 [code]
Stable excitatory-inhibitory synapse balance despite dynamic turnover.
Journal: eLife
In common: tifffile, scikit-image, Pillow, 5 other tools, mouse, cellular / molecular, 2 references
[6] doi:10.1093/neuonc/noag128 [code]
Spatially-resolved single-cell imaging of melanoma brain metastases identifies localized immune patterns predictive of immune checkpoint blockade response.
Journal: Neuro-oncology
In common: Cellpose, napari, tifffile, 5 other tools
[7] doi:10.1016/j.celrep.2026.117420 [code]
Neural population dynamics of direct electrical stimulation of neocortex.
Journal: Cell reports
In common: napari, OpenCV, scikit-image, 7 other tools, mouse
[8] doi:10.1038/s41467-026-73045-9 [code]
Aberration-aware 3D localization microscopy via self-supervised neural-physics learning.
Journal: Nature communications
In common: napari, tifffile, OpenCV, 6 other tools, cellular / molecular
[9] doi:10.64898/2026.03.12.711239 [code]
Back-illumination Phase Imaging Enables Nanoscale Drift Stabilization in Non-transparent Biological Tissues
Journal: bioRxiv (preprint)
In common: napari, tifffile, OpenCV, 6 other tools
[10] doi:10.1038/s41467-026-75352-7 [code]
Mechanosensory encoding of surface mechanics optimizes locomotion.
Journal: Nature communications
In common: napari, tifffile, OpenCV, 6 other tools, cellular / molecular

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.