OSCR

A Protein Language Model Reveals Organellar Ca <sup>2+</sup> ATPases at Neuronal Synapses

Code ↔ Paper

5 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 5 matches
  1. [1] § Results ↔ scripts/run_crosslink_ppi_network.py, lines 116–141 · score 0.80 · ion channel, postsynaptic density, cellular components, SynGO, receptors, database
  2. [2] § Results ↔ scripts/run_crosslink_ppi_network.py, lines 143–188 · score 0.59 · presynaptic Ca2, synaptic vesicles, Vamp2, canonical, synaptophysin, postsynaptic
  3. [3] § Results ↔ esmc_600m/scripts/train_esmc.sh, lines 1–39 · score 0.53 · cellular compartments, protein language model, GPU, trained, ESM, localize
  4. [4] § Results ↔ esmc_600m/models/esmc_encoder.py, lines 18–32 · score 0.52 · evolutionary scale, protein language model, embeddings, ESM
  5. [5] § Results ↔ scripts/run_slim_enrichment_volcano.py, lines 1–66 · score 0.51 · binding motifs, arbitrary, channel, PDZ, strict, enrichment

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 · 506 lines · 23 KB · MIT · 2 matches

  1. """
  2. Build and visualize the inter-protein crosslink-validated PPI network:
  3. genes containing exact-crosslink-matched motifs, linked to their crosslink
  4. interactor genes, coloured by their SynGO GO term (Cellular Component /
  5. Biological Process, from the SynGO_2024 gene set database) -- genes not
  6. annotated in SynGO at all are coloured black ("Unannotated in SynGO").
  7. Inputs:
  8. - crosslink_motif_interactors.xlsx / Inter_Protein_Only, Inter_Protein_Pair_Summary
  9. (built by run_crosslink_interactor_lookup.py)
  10. - SynGO_2024.gmt (predictions dir) -- SynGO GO term -> gene set
  11. Outputs:
  12. - crosslink_ppi_network.xlsx (Nodes, Edges, Pathway_Assignment sheets)
  13. - interactive_html/crosslink_ppi_network.html (vis-network, offline HTML)
  14. """
  15. import json
  16. import os
  17. import re
  18. import pandas as pd
  19. OUT_DIR = "/home/au729231/SynapseGigamapper/notebook/ESMC_outputs"
  20. INTERACTOR_XL = f"{OUT_DIR}/crosslink_motif_interactors.xlsx"
  21. SYNGO_GMT = f"{OUT_DIR}/predictions/SynGO_2024.gmt"
  22. STATS_XL = f"{OUT_DIR}/crosslink_ppi_network.xlsx"
  23. HTML_DIR = f"{OUT_DIR}/interactive_html"
  24. HTML_OUT = f"{HTML_DIR}/crosslink_ppi_network.html"
  25. VIS_JS = f"{HTML_DIR}/vis-network.min.js"
  26. VIS_CDN = "https://unpkg.com/vis-network@9.1.9/standalone/umd/vis-network.min.js"
  27. TOP_N_PATHWAYS = 12 # distinct SynGO-term colour categories in the legend; rest -> "Other"
  28. os.makedirs(HTML_DIR, exist_ok=True)
  29. # ── load edges ──────────────────────────────────────────────────────────────────
  30. edges_detail = pd.read_excel(INTERACTOR_XL, sheet_name="Inter_Protein_Only")
  31. pair_summary = pd.read_excel(INTERACTOR_XL, sheet_name="Inter_Protein_Pair_Summary")
  32. edges_detail["GeneA"] = edges_detail["Gene Name"].astype(str).str.upper()
  33. edges_detail["GeneB"] = edges_detail["Interactor_Gene"].astype(str).str.upper()
  34. # aggregate edge list: one row per unordered gene pair, with supporting evidence
  35. def pair_key(a, b):
  36. return tuple(sorted([a, b]))
  37. edges_detail["PairKey"] = edges_detail.apply(lambda r: pair_key(r["GeneA"], r["GeneB"]), axis=1)
  38. edge_rows = []
  39. for pk, grp in edges_detail.groupby("PairKey"):
  40. g1, g2 = pk
  41. motifs_evidence = "; ".join(sorted(set(
  42. f"{row['Motif Sequence']} ({row['Entry Name']} {row['Motif Start']}-{row['Motif End']})"
  43. for _, row in grp.iterrows()
  44. )))
  45. edge_rows.append({
  46. "Gene1": g1, "Gene2": g2,
  47. "N_Motif_Links": len(grp),
  48. "Supporting_Motifs": motifs_evidence,
  49. })
  50. edges = pd.DataFrame(edge_rows).sort_values("N_Motif_Links", ascending=False).reset_index(drop=True)
  51. print(f"Edges (unique gene pairs): {len(edges)}")
  52. nodes_set = sorted(set(edges["Gene1"]) | set(edges["Gene2"]))
  53. print(f"Nodes (unique genes): {len(nodes_set)}")
  54. # genes that carry an exact-crosslink-matched motif themselves ("Gene Name" column)
  55. # vs. genes that only appear as someone else's interactor and carry no motif of
  56. # their own -- the latter get drawn as hollow circles in the network, and the
  57. # former get a circle size scaled by how many distinct motifs they carry.
  58. motif_carriers = set(edges_detail["GeneA"])
  59. has_motif = {n: (n in motif_carriers) for n in nodes_set}
  60. print(f"Genes with their own motif: {sum(has_motif.values())} / {len(nodes_set)} "
  61. f"(interactor-only, no motif: {len(nodes_set) - sum(has_motif.values())})")
  62. n_motifs = (
  63. edges_detail.groupby("GeneA")[["Entry Name", "Motif Start", "Motif End"]]
  64. .apply(lambda g: g.drop_duplicates().shape[0])
  65. .to_dict()
  66. )
  67. motif_count = {n: n_motifs.get(n, 0) for n in nodes_set}
  68. degree = {}
  69. for _, r in edges.iterrows():
  70. degree[r["Gene1"]] = degree.get(r["Gene1"], 0) + 1
  71. degree[r["Gene2"]] = degree.get(r["Gene2"], 0) + 1
  72. # ── assign each gene its SynGO GO term (direct database membership, not a
  73. # statistical enrichment test) ─────────────────────────────────────────────────
  74. # SynGO_2024.gmt term names carry a trailing "(GO:0000000) CC"/"BP" suffix;
  75. # split off the category, then split off the GO id so the id can go in the
  76. # tooltip while the legend/label shows the function descriptor only.
  77. syngo_terms = {} # clean_name -> {"category", "go_id", "genes", "size"}
  78. with open(SYNGO_GMT) as fh:
  79. for line in fh:
  80. parts = line.rstrip("\n").split("\t")
  81. if len(parts) < 3:
  82. continue
  83. term_field = parts[0].strip()
  84. genes = {g.strip().upper() for g in parts[2:] if g.strip()}
  85. if not genes:
  86. continue
  87. m = re.search(r"\b(BPp|BP|CC)$", term_field)
  88. if not m:
  89. continue
  90. category = "BP" if m.group(1).startswith("BP") else "CC"
  91. display_name = term_field[: m.start()].strip()
  92. m2 = re.match(r"^(.*)\s\((GO:\d+)\)$", display_name)
  93. clean_name, go_id = (m2.group(1), m2.group(2)) if m2 else (display_name, "")
  94. syngo_terms[clean_name] = {"category": category, "go_id": go_id, "genes": genes, "size": len(genes)}
  95. print(f"\nSynGO_2024.gmt: {len(syngo_terms)} GO terms")
  96. # composite/ambiguous symbols (e.g. "CALM1;CALM2;CALM3") -> any member counts
  97. def gene_parts(node):
  98. return [p.upper() for p in str(node).split(";")]
  99. # each gene may belong to many SynGO terms -- prefer Cellular Component
  100. # (physical location, e.g. "integral component of postsynaptic membrane")
  101. # over Biological Process (activity/function, e.g. "ligand-gated ion channel
  102. # activity involved in regulation of X membrane potential") when a gene has
  103. # both, since CC is what a reader intuitively expects from "annotation". The
  104. # remaining tie-break is smallest GLOBAL (SynGO database-wide) gene-set size
  105. # -- the most specific term over the most generic ("synapse", "postsynapse").
  106. #
  107. # This default rule mislabels GRIA1-4 as presynaptic: their smallest global
  108. # CC term is "presynaptic active zone membrane" (52 genes) even though their
  109. # well-known primary role is postsynaptic. Tried several universal
  110. # alternatives (largest-global, smallest-local, largest-local) -- none work
  111. # across the board; e.g. smallest-local fixes GRIA but then mislabels
  112. # genuinely presynaptic proteins like VAMP2/SYT1 as postsynaptic, because
  113. # "fewest co-occurrences" just means "rare annotation," not "primary/
  114. # best-known role." So this is a targeted override for the GRIA family only:
  115. # use smallest LOCAL (within this network) gene-set size for those genes
  116. # specifically, where it happens to correctly resolve to postsynaptic
  117. # membrane (10 other network genes) over the presynaptic alternatives
  118. # (14-15) -- every other gene keeps the smallest-global default.
  119. CATEGORY_RANK = {"CC": 0, "BP": 1}
  120. # same "postsynaptic receptor subunit with a smaller/rarer presynaptic
  121. # co-annotation" situation as GRIA1-4: GRIN1/GRIN2B's smallest-local CC term
  122. # ties at 14 between "Postsynaptic Density Membrane" and "Presynaptic
  123. # Membrane", which the alphabetical final tie-break resolves to postsynaptic.
  124. LOCAL_SPECIFICITY_OVERRIDE = {"GRIA1", "GRIA2", "GRIA3", "GRIA4", "GRIN1", "GRIN2B"}
  125. # Neither smallest-global nor smallest-local resolves every gene correctly --
  126. # VAMP2 (synaptobrevin-2, the canonical presynaptic vesicle SNARE) has
  127. # "Postsynaptic Cytosol" as its smallest term BOTH globally (51 genes) and
  128. # locally (8 genes in this network), narrowly beating its actual textbook
  129. # annotation "Integral Component Of Synaptic Vesicle Membrane" (56 global /
  130. # 11 local) either way. Rather than chase another universal metric, force
  131. # specific genes to a specific (verified-present-in-their-match-list) term.
  132. # SYT1 (synaptotagmin-1, the presynaptic Ca2+ sensor for vesicle fusion) has
  133. # the same problem, resolved the same way -- it too is a canonical integral
  134. # synaptic-vesicle-membrane protein.
  135. TERM_OVERRIDE = {
  136. "VAMP2": "Integral Component Of Synaptic Vesicle Membrane",
  137. "SYT1": "Integral Component Of Synaptic Vesicle Membrane",
  138. }
  139. node_to_pathway = {}
  140. node_to_goid = {}
  141. node_matches = {}
  142. for node in nodes_set:
  143. parts = gene_parts(node)
  144. node_matches[node] = [(tname, info["category"], info["size"], info["go_id"]) for tname, info in syngo_terms.items()
  145. if any(p in info["genes"] for p in parts)]
  146. local_term_count = pd.Series(
  147. [tname for matches in node_matches.values() for tname, _, _, _ in matches]
  148. ).value_counts().to_dict()
  149. for node in nodes_set:
  150. matches = node_matches[node]
  151. if matches:
  152. forced_term = next((p for p in gene_parts(node) if p in TERM_OVERRIDE), None)
  153. forced = [m for m in matches if forced_term and m[0] == TERM_OVERRIDE[forced_term]]
  154. if forced:
  155. best = forced[0]
  156. else:
  157. use_local = any(p in LOCAL_SPECIFICITY_OVERRIDE for p in gene_parts(node))
  158. specificity = (lambda x: local_term_count[x[0]]) if use_local else (lambda x: x[2])
  159. best = sorted(matches, key=lambda x: (CATEGORY_RANK.get(x[1], 9), specificity(x), x[0]))[0]
  160. node_to_pathway[node] = best[0]
  161. node_to_goid[node] = best[3]
  162. else:
  163. node_to_pathway[node] = "Unannotated in SynGO"
  164. node_to_goid[node] = ""
  165. n_assigned = sum(1 for v in node_to_pathway.values() if v != "Unannotated in SynGO")
  166. print(f" {n_assigned}/{len(nodes_set)} genes annotated in SynGO")
  167. # keep only the top-N most-populous SynGO terms as distinct colour categories
  168. term_counts = pd.Series(list(node_to_pathway.values())).value_counts()
  169. top_terms = [t for t in term_counts.index if t != "Unannotated in SynGO"][:TOP_N_PATHWAYS]
  170. print(f"\nTop SynGO term categories (by # genes assigned):")
  171. print(term_counts.head(TOP_N_PATHWAYS + 1).to_string())
  172. def bucket(term):
  173. # "group"/legend bucketing only -- caps the legend at the top-N most
  174. # populous terms so it stays readable. NOT used for node colour: every
  175. # SynGO-annotated gene gets its own (cyclically-repeating) colour below,
  176. # so "not in SynGO" (black) is never confused with "in SynGO but a less
  177. # common term".
  178. if term == "Unannotated in SynGO":
  179. return "Unannotated in SynGO"
  180. return term if term in top_terms else "Other SynGO term"
  181. node_category = {n: bucket(node_to_pathway[n]) for n in nodes_set}
  182. # ── colour palette (Cell-style: muted, colourblind-safe qualitative set) ──────
  183. PALETTE = [
  184. "#D55E00", "#0072B2", "#009E73", "#CC79A7", "#E69F00", "#56B4E9",
  185. "#F0E442", "#8B008B", "#046A38", "#B0413E", "#4C72B0", "#8C6D31",
  186. ]
  187. categories = top_terms + ["Other SynGO term", "Unannotated in SynGO"]
  188. # every distinct SynGO term (not just the top-N shown in the legend) gets its
  189. # own colour, cycling through the palette -- black is reserved exclusively for
  190. # genes with NO SynGO annotation at all ("Unannotated in SynGO")
  191. all_terms = [t for t in term_counts.index if t != "Unannotated in SynGO"]
  192. term_colors = {term: PALETTE[i % len(PALETTE)] for i, term in enumerate(all_terms)}
  193. term_colors["Unannotated in SynGO"] = "#000000"
  194. cat_colors = {cat: term_colors[cat] for cat in top_terms}
  195. cat_colors["Other SynGO term"] = "#999999" # legend swatch only; actual node colours vary, see above
  196. cat_colors["Unannotated in SynGO"] = "#000000"
  197. # ── build node table ────────────────────────────────────────────────────────────
  198. nodes_df = pd.DataFrame([{
  199. "Gene": n,
  200. "Degree": degree.get(n, 0),
  201. "Has_Motif": has_motif[n],
  202. "N_Motifs": motif_count[n],
  203. "Top_Pathway_Term": node_to_pathway[n],
  204. "Pathway_Category": node_category[n],
  205. "SynGO_GO_ID": node_to_goid[n],
  206. "Color": term_colors[node_to_pathway[n]],
  207. } for n in nodes_set]).sort_values("Degree", ascending=False).reset_index(drop=True)
  208. print(f"\nWriting -> {STATS_XL}")
  209. with pd.ExcelWriter(STATS_XL, engine="openpyxl") as writer:
  210. nodes_df.to_excel(writer, sheet_name="Network_Nodes", index=False)
  211. edges.to_excel(writer, sheet_name="Network_Edges", index=False)
  212. term_counts.rename("N_Genes").reset_index().rename(columns={"index": "Pathway_Term"}).to_excel(
  213. writer, sheet_name="Pathway_Assignment_Summary", index=False)
  214. for ws in writer.sheets.values():
  215. for col in ws.columns:
  216. ml = max(len(str(c.value)) if c.value is not None else 0 for c in col)
  217. ws.column_dimensions[col[0].column_letter].width = min(ml + 2, 60)
  218. print(f"Saved {len(nodes_df)} nodes, {len(edges)} edges")
  219. # ── build interactive HTML (vis-network) ───────────────────────────────────────
  220. vis_nodes = []
  221. for _, r in nodes_df.iterrows():
  222. go_ref = f" ({r['SynGO_GO_ID']})" if r["SynGO_GO_ID"] else ""
  223. motif_note = "Yes" if r["Has_Motif"] else "No (interactor only)"
  224. tooltip = (f"{r['Gene']}\nDegree: {r['Degree']}\nCarries own motif: {motif_note}"
  225. f" ({int(r['N_Motifs'])} motif{'s' if r['N_Motifs'] != 1 else ''})\n"
  226. f"SynGO term: {r['Top_Pathway_Term']}{go_ref}")
  227. if r["Has_Motif"]:
  228. color = {"background": r["Color"], "border": r["Color"]}
  229. border_width = 0
  230. else:
  231. # interactor-only genes with no motif of their own -> hollow circle
  232. color = {"background": "#ffffff", "border": r["Color"]}
  233. border_width = 2.5
  234. vis_nodes.append({
  235. # circle size (vis-network "value" + scaling) encodes # motifs carried,
  236. # not network degree -- interactor-only (hollow) nodes carry 0 motifs
  237. # and render at the minimum size.
  238. # NB: deliberately NOT using vis-network's reserved "group" field here --
  239. # setting it silently made vis-network auto-generate its OWN colour for
  240. # each group name and override our explicit per-node "color" (that's
  241. # why e.g. NCAM1 rendered as an unrelated green instead of its assigned
  242. # olive tan). "pathwayGroup" is a plain custom property vis-network
  243. # doesn't touch, kept only for reference/debugging.
  244. "id": r["Gene"], "label": r["Gene"],
  245. "value": int(r["N_Motifs"]), "color": color, "borderWidth": border_width,
  246. "title": tooltip, "pathwayGroup": r["Pathway_Category"],
  247. })
  248. vis_edges = []
  249. for _, r in edges.iterrows():
  250. evidence = r["Supporting_Motifs"]
  251. if len(evidence) > 500:
  252. evidence = evidence[:500] + " ..."
  253. tooltip = f"{r['Gene1']} <-> {r['Gene2']}\nMotif links: {r['N_Motif_Links']}\n{evidence}"
  254. vis_edges.append({
  255. "from": r["Gene1"], "to": r["Gene2"],
  256. "value": int(r["N_Motif_Links"]), "title": tooltip,
  257. })
  258. legend_rows = "".join(
  259. f'<div class="lg-row"><span class="lg-sw" style="background:{cat_colors[c]}"></span>'
  260. f'<span class="lg-lbl">{c}</span><span class="lg-n">{term_counts.get(c, 0)}</span></div>'
  261. for c in categories
  262. )
  263. with open(VIS_JS) as fh:
  264. vis_js = fh.read()
  265. HTML = r"""<!DOCTYPE html>
  266. <html lang="en">
  267. <head>
  268. <meta charset="UTF-8">
  269. <title>Crosslink-Validated PPI Network</title>
  270. <style>
  271. *{box-sizing:border-box;margin:0;padding:0}
  272. html,body{width:100%;height:100%;background:#fff;font-family:Arial,sans-serif;color:#222;overflow:hidden}
  273. #layout{display:flex;width:100vw;height:100vh}
  274. #net{flex:1;min-width:0;position:relative;background:#fff}
  275. #sidebar{width:320px;min-width:320px;display:flex;flex-direction:column;
  276. border-left:1px solid #e0e0e0;background:#fff;overflow-y:auto}
  277. #hdr{padding:12px 16px;border-bottom:1px solid #eee;background:#fafafa}
  278. #hdr h1{font-size:14px;font-weight:700;color:#111;margin-bottom:4px}
  279. #hdr p{font-size:10.5px;color:#888;line-height:1.5}
  280. #ctrl{padding:10px 16px;border-bottom:1px solid #eee}
  281. .sh{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:#777;margin:0 0 7px}
  282. .rw{display:flex;align-items:center;gap:6px;font-size:11px;margin-bottom:8px}
  283. input[type=text]{flex:1;font-size:12px;padding:5px 7px;border:1px solid #ccc;border-radius:4px}
  284. .btn{padding:5px 10px;border:1.5px solid #ccc;border-radius:4px;font-size:11px;
  285. cursor:pointer;background:#fff;color:#333}
  286. .btn:hover{background:#f0f0f0}
  287. #legend{padding:10px 16px;border-bottom:1px solid #eee}
  288. .lg-row{display:flex;align-items:center;gap:7px;font-size:10.5px;margin-bottom:5px}
  289. .lg-sw{display:inline-block;width:11px;height:11px;border-radius:3px;flex-shrink:0}
  290. .lg-lbl{flex:1;color:#333}
  291. .lg-n{color:#999;font-size:9.5px}
  292. #info{padding:12px 16px;font-size:11.5px;line-height:1.7;white-space:pre-wrap;color:#333}
  293. .vis-tooltip{white-space:pre-line !important;max-width:320px;font-size:11px !important;line-height:1.5 !important}
  294. #info b{color:#111}
  295. .note{font-size:9.5px;color:#999;padding:10px 16px;line-height:1.5;border-top:1px solid #eee;margin-top:auto}
  296. </style>
  297. </head>
  298. <body>
  299. <div id="layout">
  300. <div id="net"></div>
  301. <div id="sidebar">
  302. <div id="hdr">
  303. <h1>Crosslink-Validated PPI Network</h1>
  304. <p>N_NODES_HERE genes &middot; N_EDGES_HERE interactions<br>
  305. Nodes = genes with exact-crosslink-matched motifs &amp; their interactors.
  306. Edges = inter-protein crosslink evidence. Colour = SynGO GO term
  307. (Cellular Component / Biological Process); black "Unannotated in SynGO" = not
  308. annotated in SynGO. Hollow circles = genes that only exist in the
  309. crosslinked proteome as an interactor and carry no motif of their own.<br>
  310. Drag=pan &middot; Scroll=zoom &middot; Click node/edge=details &middot; Drag node=reposition</p>
  311. </div>
  312. <div id="ctrl">
  313. <div class="sh">Search</div>
  314. <div class="rw">
  315. <input type="text" id="search" placeholder="Gene symbol..." onkeyup="doSearch()">
  316. </div>
  317. <div class="rw">
  318. <button class="btn" onclick="network.fit()">Reset view</button>
  319. <button class="btn" onclick="togglePhysics()" id="physBtn">Physics: On</button>
  320. </div>
  321. <div class="sh">Label font size</div>
  322. <div class="rw">
  323. <input type="range" id="fontSlider" min="6" max="24" value="11" step="1"
  324. oninput="setFontSize(this.value)" style="flex:1">
  325. <span id="fontVal" style="width:22px;text-align:right">11</span>
  326. </div>
  327. <div class="sh">Export</div>
  328. <div class="rw">
  329. <select id="exportScale" style="flex:1;font-size:11px;padding:4px 6px;border:1px solid #ccc;border-radius:4px">
  330. <option value="2">2x</option>
  331. <option value="4" selected>4x (print, ~300 DPI)</option>
  332. <option value="8">8x (poster)</option>
  333. </select>
  334. <button class="btn" onclick="exportHiRes()">Save PNG</button>
  335. </div>
  336. </div>
  337. <div id="legend">
  338. <div class="sh">SynGO term (node colour)</div>
  339. LEGEND_ROWS_HERE
  340. </div>
  341. <div id="info">Click a node or edge for details.</div>
  342. <div class="note">
  343. Each gene is coloured by its most specific (smallest gene-set) SynGO GO
  344. term; only genes with NO SynGO annotation at all are black ("Unannotated in SynGO").
  345. The legend lists the top TOP_N_PATHWAYS_HERE terms by gene count --
  346. "Other SynGO term" covers every remaining term, each still drawn in its
  347. own (non-black) colour on the network, just not individually listed here.
  348. Node size = number of distinct motifs the gene carries (0 for hollow,
  349. interactor-only nodes).
  350. </div>
  351. </div>
  352. </div>
  353. <script>
  354. VIS_JS_HERE
  355. </script>
  356. <script>
  357. const NODES = new vis.DataSet(NODES_JSON_HERE);
  358. const EDGES = new vis.DataSet(EDGES_JSON_HERE);
  359. const container = document.getElementById('net');
  360. const data = {nodes: NODES, edges: EDGES};
  361. const options = {
  362. nodes: {
  363. shape: 'dot', scaling: {min: 6, max: 28},
  364. font: {size: 11, color: '#222'},
  365. borderWidth: 0,
  366. },
  367. edges: {
  368. color: {color: '#7a7a7a', highlight: '#333', opacity: 0.8},
  369. smooth: {type: 'continuous'},
  370. scaling: {min: 1, max: 6},
  371. },
  372. physics: {
  373. solver: 'barnesHut',
  374. barnesHut: {gravitationalConstant: -12000, springLength: 110, springConstant: 0.03},
  375. stabilization: {iterations: 200},
  376. },
  377. interaction: {hover: true, tooltipDelay: 100},
  378. };
  379. const network = new vis.Network(container, data, options);
  380. let physicsOn = true;
  381. function togglePhysics() {
  382. physicsOn = !physicsOn;
  383. network.setOptions({physics: {enabled: physicsOn}});
  384. document.getElementById('physBtn').textContent = 'Physics: ' + (physicsOn ? 'On' : 'Off');
  385. }
  386. let baseFontSize = 11;
  387. function setFontSize(v) {
  388. baseFontSize = parseInt(v);
  389. document.getElementById('fontVal').textContent = baseFontSize;
  390. network.setOptions({nodes: {font: {size: baseFontSize}}});
  391. doSearch();
  392. }
  393. // Re-renders vis-network's own canvas into a much bigger pixel buffer (same
  394. // pan/zoom, just higher pixel density) and saves that as a PNG -- publication
  395. // / print-resolution export, not just a screen-resolution screenshot.
  396. // NB: vis-network's redraw() keeps its canvas's drawing-buffer size in sync
  397. // with its container element, so directly resizing canvas.width/height gets
  398. // silently reset on the next redraw() call -- the container itself has to be
  399. // enlarged first (network.setSize matches the buffer to it), captured, then
  400. // both put back.
  401. function exportHiRes() {
  402. const scale = parseInt(document.getElementById('exportScale').value);
  403. const container = document.getElementById('net');
  404. const canvas = network.canvas.frame.canvas;
  405. const w = container.clientWidth, h = container.clientHeight;
  406. container.style.width = (w * scale) + 'px';
  407. container.style.height = (h * scale) + 'px';
  408. network.setSize((w * scale) + 'px', (h * scale) + 'px');
  409. network.redraw();
  410. const dataURL = canvas.toDataURL('image/png');
  411. container.style.width = '';
  412. container.style.height = '';
  413. network.setSize('100%', '100%');
  414. network.redraw();
  415. const a = document.createElement('a');
  416. a.href = dataURL;
  417. a.download = 'crosslink_ppi_network_' + scale + 'x.png';
  418. document.body.appendChild(a);
  419. a.click();
  420. document.body.removeChild(a);
  421. }
  422. network.on('click', function (params) {
  423. const info = document.getElementById('info');
  424. if (params.nodes.length > 0) {
  425. const n = NODES.get(params.nodes[0]);
  426. const connected = network.getConnectedNodes(n.id);
  427. info.innerHTML = '<b>' + n.label + '</b>\n' + n.title.split('\n').slice(1).join('\n') +
  428. '\nConnected genes (' + connected.length + '): ' + connected.join(', ');
  429. } else if (params.edges.length > 0) {
  430. const e = EDGES.get(params.edges[0]);
  431. info.innerHTML = e.title;
  432. }
  433. });
  434. function doSearch() {
  435. const q = document.getElementById('search').value.trim().toUpperCase();
  436. if (!q) { NODES.forEach(n => NODES.update({id: n.id, font: {size: baseFontSize, color: '#222'}})); return; }
  437. NODES.forEach(function (n) {
  438. const hit = n.id.toUpperCase().includes(q);
  439. NODES.update({id: n.id, font: {size: hit ? baseFontSize + 5 : baseFontSize, color: hit ? '#D55E00' : '#ccc'}});
  440. });
  441. const hitIds = NODES.get({filter: n => n.id.toUpperCase().includes(q)}).map(n => n.id);
  442. if (hitIds.length) network.selectNodes(hitIds);
  443. }
  444. </script>
  445. </body>
  446. </html>
  447. """
  448. html = HTML
  449. html = html.replace("N_NODES_HERE", str(len(nodes_df)))
  450. html = html.replace("N_EDGES_HERE", str(len(edges)))
  451. html = html.replace("LEGEND_ROWS_HERE", legend_rows)
  452. html = html.replace("TOP_N_PATHWAYS_HERE", str(TOP_N_PATHWAYS))
  453. html = html.replace("VIS_JS_HERE", vis_js)
  454. html = html.replace("NODES_JSON_HERE", json.dumps(vis_nodes))
  455. html = html.replace("EDGES_JSON_HERE", json.dumps(vis_edges))
  456. with open(HTML_OUT, "w") as fh:
  457. fh.write(html)
  458. print(f"Interactive network saved -> {HTML_OUT} ({os.path.getsize(HTML_OUT)//1024} KB)")

run_crosslink_ppi_network.py at commit a06873c, under MIT · at the source

Overview

Authors: Valentina Villani1,2, Silvia Turchetto1,2, Lars Boye Brandt1, Markus Ørnsvig Christensen1,2, Erika Uddström1,2, Sara Derosa1,2, Esben Lorentzen1, Chao Sun1,2,3
  1. Department of Molecular Biology and Genetics, Aarhus University, Aarhus, 8000, Denmark
  2. Danish Research Institute of Translational Neuroscience - DANDRITE, Nordic-EMBL Partnership for Molecular Medicine, Aarhus, 8000, Denmark
  3. Center for Proteins in Memory - PROMEMO, Danish National Research Foundation, Dept. of Molecular Biology and Genetics, Aarhus University, Aarhus, 8000, Denmark
Institutions: Aarhus University (Denmark); Danish National Research Foundation (Denmark)
Dates: published online 4 March 2026
Type: Preprint
License: CC BY-NC
Identifiers: DOI 10.64898/2026.03.02.709014 · OpenAlex W7133554795
Open access: green, a free copy (OpenAlex)
Status: code verified
Categories: cellular / molecular (subfield)
Topic: Cellular transport and secretion (Cell Biology, Biochemistry, Genetics and Molecular Biology), according to OpenAlex
Funding: Danmarks Grundforskningsfond (DNRF133); Novo Nordisk Fonden (NNF25OC0105061, NNF23OC0085864)
Citations: not cited yet (Europe PMC); 51 references in the paper

Abstract

The synapse between neurons hosts the protein machinery for information transfer and storage in the brain. Its local proteome, however, contains many proteins with unclear roles for synaptic function. To glean hypotheses from this local proteome, we developed Synapse Gigamapper (SyGi), a protein language model for predicting protein localization at synapses. SyGi identified 152 amino-acid motifs that are indicative of localization at excitatory or inhibitory synapses and revealed >100 candidate constituents from key cellular pathways. Among these candidates is the endoplasmic reticulum (ER)-bound ATPase for cytosolic Ca2+ clearance, SERCA. We found clusters of SERCA copies at excitatory synapses without ER. Rather, SERCA is colocalized with compartment-specific organelles at synapses— the spine apparatus and synaptic vesicles. Taken together, SyGi is useful for exposing hidden components of neuronal synapses.

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

Repository

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

Synaptic-Logistics-Lab/SynapseGigamapper

License: MIT
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: a06873c32301e4a3285bab77fbd3fba24a1138f6, 11 August 2026
Languages: Python (52), Jupyter (9), Shell (1)
Size: 76 files, 62 scripts
Software Heritage: not archived
Found in: “Data, code, and materials availability”
Holds: README, license file, environment (environment.yml, pyproject.toml, esmc_600m/environment_esmc.yml), tests, 9 notebooks
Not found: CITATION.cff, continuous integration, documentation
Tools: PyTorch (24 files), pandas (13 files), NumPy (12 files), PyTorch Lightning (11 files), Matplotlib (6 files), seaborn (4 files), SciPy (2 files), statsmodels (2 files), scikit-learn (1 file), Hugging Face Transformers (1 file)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
64 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;
  • 62 scripts, each with its path and the digest of its content;
  • 5 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

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

Data

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

Data, code, and materials availability

All data are available in the main text or the supplementary materials. Code, trained models, and analysis scripts are available at GitHub repository: https://github.com/Synaptic-Logistics-Lab/SynapseGigamapper

Reproduced under the paper's license (CC BY-NC), 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, journal, dates, 8 authors, 2 funders, 51 references.

Cite

This paper

Villani, V., Turchetto, S., Brandt, L. B., Christensen, M. Ø., Uddström, E., Derosa, S., Lorentzen, E., & Sun, C. (2026). A Protein Language Model Reveals Organellar Ca <sup>2+</sup> ATPases at Neuronal Synapses. bioRxiv (preprint). https://doi.org/10.64898/2026.03.02.709014

BibTeX

@article{villani2026protein,
author = {Villani, Valentina and Turchetto, Silvia and Brandt, Lars Boye and Christensen, Markus Ørnsvig and Uddström, Erika and Derosa, Sara and Lorentzen, Esben and Sun, Chao},
title = {{A Protein Language Model Reveals Organellar Ca <sup>2+</sup> ATPases at Neuronal Synapses}},
journal = {bioRxiv (preprint)},
year = {2026},
month = mar,
publisher = {bioRxiv},
issn = {2692-8205},
doi = {10.64898/2026.03.02.709014},
url = {https://doi.org/10.64898/2026.03.02.709014}
}

RIS

TY - JOUR
AU - Villani, Valentina
AU - Turchetto, Silvia
AU - Brandt, Lars Boye
AU - Christensen, Markus Ørnsvig
AU - Uddström, Erika
AU - Derosa, Sara
AU - Lorentzen, Esben
AU - Sun, Chao
TI - A Protein Language Model Reveals Organellar Ca <sup>2+</sup> ATPases at Neuronal Synapses
T2 - bioRxiv (preprint)
J2 - bioRxiv
PY - 2026
DA - 2026/03/04
SN - 2692-8205
PB - bioRxiv
DO - 10.64898/2026.03.02.709014
UR - https://doi.org/10.64898/2026.03.02.709014
ER -

CSL-JSON

{
"id": "10.64898/2026.03.02.709014",
"type": "article",
"title": "A Protein Language Model Reveals Organellar Ca <sup>2+</sup> ATPases at Neuronal Synapses",
"container-title": "bioRxiv (preprint)",
"author": [
{
"family": "Villani",
"given": "Valentina"
},
{
"family": "Turchetto",
"given": "Silvia"
},
{
"family": "Brandt",
"given": "Lars Boye"
},
{
"family": "Christensen",
"given": "Markus Ørnsvig"
},
{
"family": "Uddström",
"given": "Erika"
},
{
"family": "Derosa",
"given": "Sara"
},
{
"family": "Lorentzen",
"given": "Esben"
},
{
"family": "Sun",
"given": "Chao"
}
],
"container-title-short": "bioRxiv",
"DOI": "10.64898/2026.03.02.709014",
"ISSN": "2692-8205",
"publisher": "bioRxiv",
"URL": "https://doi.org/10.64898/2026.03.02.709014",
"issued": {
"date-parts": [
[
2026,
3,
4
]
]
}
}

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

Similar papers

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

[1] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: PyTorch Lightning, Hugging Face Transformers, statsmodels, 7 other tools, cellular / molecular
[2] doi:10.1162/imag.a.1299 [code]
A modular semantic-structural pipeline for visual decoding from primate spiking data via selective temporal integration.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: PyTorch Lightning, Hugging Face Transformers, PyTorch, 6 other tools
[3] doi:10.1038/s41467-026-73996-z [code]
Genetic architecture of white matter microstructure captured by unsupervised deep representation learning of fractional anisotropy maps.
Journal: Nature communications
In common: PyTorch Lightning, statsmodels, PyTorch, 6 other tools, cellular / molecular
[4] doi:10.1186/s12864-026-12965-8 [code]
Systematic evaluation of single-cell foundation model interpretability: attention-derived edge scores add no incremental value over gene-level features for perturbation-target prediction.
Journal: BMC genomics
In common: Hugging Face Transformers, statsmodels, PyTorch, 6 other tools, cellular / molecular
[5] doi:10.1093/bib/bbag182 [code]
NyxBind: enhancing deep neural representations for transcription factor binding site prediction via contrastive learning.
Journal: Briefings in bioinformatics
In common: Hugging Face Transformers, statsmodels, PyTorch, 6 other tools, cellular / molecular
[6] doi:10.1523/eneuro.0362-25.2026 [code]
Similarities between &lt;i&gt;Ciona&lt;/i&gt; Dorsal Motor Ganglion and Vertebrate Cerebellum: Did a Chordate Ancestor Already Show D/V Subdivision within a Hindbrain Precursor?
Journal: eNeuro
In common: PyTorch Lightning, statsmodels, PyTorch, 6 other tools
[7] doi:10.1038/s41467-026-76939-w [code]
HIPPIE: a generative model for electrophysiological analysis across species, technologies, and modalities.
Journal: Nature communications
In common: PyTorch Lightning, statsmodels, PyTorch, 6 other tools
[8] doi:10.1093/bioinformatics/btag652 [code]
mmVelo: a deep generative model for estimating cell state-dependent dynamics across multiple modalities.
Journal: Bioinformatics (Oxford, England)
In common: PyTorch Lightning, statsmodels, PyTorch, 6 other tools
[9] doi:10.1038/s41467-026-75455-1 [code]
Shared latent representations of speech production for cross-patient speech decoding.
Journal: Nature communications
In common: PyTorch Lightning, statsmodels, PyTorch, 6 other tools
[10] doi:10.1016/j.isci.2026.116027 [code]
Domain adaptive uplift modeling across heterogeneous mental health cohorts.
Journal: iScience
In common: PyTorch Lightning, statsmodels, PyTorch, 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.