bindsight 0.3.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. bindsight/README.md +44 -0
  2. bindsight/__init__.py +15 -0
  3. bindsight/benchmark/__init__.py +71 -0
  4. bindsight/benchmark/core.py +497 -0
  5. bindsight/benchmark/defaults.py +28 -0
  6. bindsight/benchmark/designer_bench.py +899 -0
  7. bindsight/benchmark/outcomes.py +508 -0
  8. bindsight/benchmark/panel.py +621 -0
  9. bindsight/benchmark/rediscovery.py +1153 -0
  10. bindsight/benchmark/statistics.py +622 -0
  11. bindsight/benchmark/study.py +1153 -0
  12. bindsight/benchmark/study_figures.py +164 -0
  13. bindsight/benchmark/study_report.py +607 -0
  14. bindsight/cli.py +2429 -0
  15. bindsight/config.py +451 -0
  16. bindsight/cost.py +258 -0
  17. bindsight/deg/__init__.py +7 -0
  18. bindsight/deg/pydeseq2_runner.py +285 -0
  19. bindsight/design/__init__.py +23 -0
  20. bindsight/design/_common.py +501 -0
  21. bindsight/design/bindcraft.py +78 -0
  22. bindsight/design/boltzgen.py +79 -0
  23. bindsight/design/developability.py +140 -0
  24. bindsight/design/embeddings.py +165 -0
  25. bindsight/design/prescreen.py +169 -0
  26. bindsight/design/protocol.py +111 -0
  27. bindsight/design/rfdiff_mpnn.py +93 -0
  28. bindsight/epitopes/__init__.py +7 -0
  29. bindsight/epitopes/surface_bind.py +134 -0
  30. bindsight/export/__init__.py +7 -0
  31. bindsight/export/ro_crate.py +595 -0
  32. bindsight/io/__init__.py +19 -0
  33. bindsight/io/cbioportal.py +118 -0
  34. bindsight/io/gdc.py +343 -0
  35. bindsight/io/paths.py +124 -0
  36. bindsight/pipelines/__init__.py +9 -0
  37. bindsight/pipelines/caveats.py +60 -0
  38. bindsight/pipelines/discover.py +1443 -0
  39. bindsight/pipelines/full_run.py +410 -0
  40. bindsight/plugins.py +335 -0
  41. bindsight/provenance/__init__.py +34 -0
  42. bindsight/provenance/append.py +199 -0
  43. bindsight/provenance/fragments.py +216 -0
  44. bindsight/provenance/manifest.py +506 -0
  45. bindsight/rank/__init__.py +7 -0
  46. bindsight/rank/scoring.py +355 -0
  47. bindsight/report/__init__.py +39 -0
  48. bindsight/report/format.py +100 -0
  49. bindsight/report/html.py +645 -0
  50. bindsight/report/showcase.py +653 -0
  51. bindsight/report/templates/report.css +67 -0
  52. bindsight/report/templates/report.html.j2 +420 -0
  53. bindsight/report/theme.py +231 -0
  54. bindsight/report/web/__init__.py +35 -0
  55. bindsight/report/web/app.py +624 -0
  56. bindsight/report/web/static/binder_viewer.js +245 -0
  57. bindsight/report/web/static/bindsight.css +455 -0
  58. bindsight/report/web/static/charts.js +425 -0
  59. bindsight/report/web/static/vendor/3Dmol-min.js +2 -0
  60. bindsight/report/web/static/vendor/3Dmol.LICENSE +90 -0
  61. bindsight/report/web/static/your_data_check.js +370 -0
  62. bindsight/report/web/templates/base.html.j2 +48 -0
  63. bindsight/report/web/templates/evidence.html.j2 +341 -0
  64. bindsight/report/web/templates/overview.html.j2 +140 -0
  65. bindsight/report/web/templates/runs.html.j2 +125 -0
  66. bindsight/report/web/templates/try.html.j2 +155 -0
  67. bindsight/report/web/templates/your_data.html.j2 +46 -0
  68. bindsight/runners/__init__.py +7 -0
  69. bindsight/runners/colab.py +158 -0
  70. bindsight/runners/job_exec.py +1110 -0
  71. bindsight/runners/kaggle.py +425 -0
  72. bindsight/runners/kaggle_kernel.py +489 -0
  73. bindsight/runners/local_docker.py +154 -0
  74. bindsight/runners/mock.py +167 -0
  75. bindsight/runners/modal_runner.py +199 -0
  76. bindsight/runners/notebook.py +116 -0
  77. bindsight/runners/notebook_content.py +217 -0
  78. bindsight/runners/protocol.py +81 -0
  79. bindsight/runners/source_wheel.py +104 -0
  80. bindsight/runners/tools.py +997 -0
  81. bindsight/structures/__init__.py +7 -0
  82. bindsight/structures/alphafolddb.py +93 -0
  83. bindsight/structures/plddt.py +92 -0
  84. bindsight/structures/topology.py +227 -0
  85. bindsight/surfaceome/__init__.py +42 -0
  86. bindsight/surfaceome/data/surfaceome_extended.tsv +4814 -0
  87. bindsight/surfaceome/data/surfy_offline.txt +17 -0
  88. bindsight/surfaceome/data/surfy_v1.ensembl.tsv +3380 -0
  89. bindsight/surfaceome/data/surfy_v1.uniprot.txt +2901 -0
  90. bindsight/surfaceome/surfy.py +441 -0
  91. bindsight/targets/__init__.py +7 -0
  92. bindsight/targets/ensembl_uniprot.py +56 -0
  93. bindsight/targets/gtex.py +250 -0
  94. bindsight/targets/open_targets.py +239 -0
  95. bindsight/validate/__init__.py +7 -0
  96. bindsight/validate/af2_ig.py +49 -0
  97. bindsight/validate/boltz2.py +256 -0
  98. bindsight/validate/chai1r.py +42 -0
  99. bindsight/validate/protocol.py +188 -0
  100. bindsight-0.3.5.data/data/bindsight_demo/config.yaml +54 -0
  101. bindsight-0.3.5.dist-info/METADATA +495 -0
  102. bindsight-0.3.5.dist-info/RECORD +106 -0
  103. bindsight-0.3.5.dist-info/WHEEL +4 -0
  104. bindsight-0.3.5.dist-info/entry_points.txt +20 -0
  105. bindsight-0.3.5.dist-info/licenses/COPYRIGHT +24 -0
  106. bindsight-0.3.5.dist-info/licenses/LICENSE +681 -0
@@ -0,0 +1,164 @@
1
+ # SPDX-FileCopyrightText: 2026 Mikhaeel Atef Rizk Wahba
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+ """Figures for the rediscovery study, generated from the committed artifacts.
4
+
5
+ Two figures, each answering a question a table answers badly.
6
+
7
+ :func:`plot_surfaced_ranks` shows where each surfaced antigen landed *relative to
8
+ the list it was ranked within*. An absolute rank of 34 means something very
9
+ different in a shortlist of 40 than in one of 285, and a bar chart of raw ranks
10
+ would hide exactly that. The bars are drawn against the shortlist, so the reader
11
+ sees the proportion rather than being asked to divide.
12
+
13
+ :func:`plot_outcome_classes` shows the four outcomes side by side. The single
14
+ most misleading thing the previous results page did was collapse them into one
15
+ recall number, and a stacked bar makes the collapse visibly wrong.
16
+
17
+ Nothing here recomputes anything. Both read the same ``results.json`` the tables
18
+ read, so a figure can never disagree with the text beside it.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from pathlib import Path
24
+ from typing import Any
25
+
26
+ __all__ = ["plot_outcome_classes", "plot_surfaced_ranks", "render_figures"]
27
+
28
+ #: Colour-blind-safe, and consistent between the two figures.
29
+ _COLOURS = {
30
+ "ranked": "#0072B2",
31
+ "gated_out": "#E69F00",
32
+ "not_reachable": "#CC79A7",
33
+ "infrastructure": "#999999",
34
+ }
35
+
36
+ _CLASS_LABEL = {
37
+ "ranked": "Reached the shortlist",
38
+ "gated_out": "Excluded by a filter",
39
+ "not_reachable": "Outside the instrument",
40
+ "infrastructure": "Invalid, must re-run",
41
+ }
42
+
43
+
44
+ def plot_surfaced_ranks(summary: dict[str, Any], out_path: Path) -> Path | None:
45
+ """Rank of each surfaced antigen against the shortlist it was drawn from.
46
+
47
+ Returns the written path, or ``None`` if nothing was surfaced.
48
+ """
49
+ import matplotlib
50
+
51
+ matplotlib.use("Agg")
52
+ import matplotlib.pyplot as plt
53
+
54
+ surfaced = sorted(
55
+ (p for p in summary.get("pairs", []) if isinstance(p.get("rank"), int)),
56
+ key=lambda p: p["rank"],
57
+ reverse=True,
58
+ )
59
+ if not surfaced:
60
+ return None
61
+
62
+ labels = [f"{p['symbol']}\n{str(p['project']).removeprefix('TCGA-')}" for p in surfaced]
63
+ ranks = [p["rank"] for p in surfaced]
64
+ shortlists = [p.get("shortlist_size") or 0 for p in surfaced]
65
+
66
+ fig, ax = plt.subplots(figsize=(8, 0.75 * len(surfaced) + 2))
67
+ y = range(len(surfaced))
68
+ # The full shortlist as a pale bar, the antigen's rank as the filled part:
69
+ # position is read against the list, never in isolation.
70
+ ax.barh(list(y), shortlists, color="#E8E8E8", label="shortlist size")
71
+ ax.barh(list(y), ranks, color=_COLOURS["ranked"], label="rank of the antigen")
72
+ for i, (rank, size) in enumerate(zip(ranks, shortlists, strict=True)):
73
+ ax.text(size + max(shortlists) * 0.01, i, f"{rank} of {size}", va="center", fontsize=9)
74
+
75
+ ax.set_yticks(list(y))
76
+ ax.set_yticklabels(labels, fontsize=9)
77
+ ax.set_xlabel("Position in the candidate shortlist (lower is better)")
78
+ # Scope named, as the outcome figure's is. These two sit side by side and
79
+ # count different panels: this one plots every surfaced antigen, that one
80
+ # counts the pre-registered denominator only.
81
+ ax.set_title(
82
+ "Where each surfaced antigen ranked, against the list it was ranked within\n"
83
+ "(every scored pair; the outcome figure counts the pre-registered denominator)",
84
+ fontsize=11,
85
+ )
86
+ ax.set_xlim(0, max(shortlists) * 1.18)
87
+ ax.legend(loc="lower right", frameon=False, fontsize=9)
88
+ ax.spines[["top", "right"]].set_visible(False)
89
+ fig.tight_layout()
90
+ out_path.parent.mkdir(parents=True, exist_ok=True)
91
+ fig.savefig(out_path, dpi=150)
92
+ plt.close(fig)
93
+ return out_path
94
+
95
+
96
+ def plot_outcome_classes(summary: dict[str, Any], out_path: Path) -> Path | None:
97
+ """The four outcomes as separate bars, because merging them hides the cause.
98
+
99
+ Returns the written path, or ``None`` if there is nothing to plot.
100
+ """
101
+ import matplotlib
102
+
103
+ matplotlib.use("Agg")
104
+ import matplotlib.pyplot as plt
105
+
106
+ counts = summary.get("outcome_class_counts") or {}
107
+ if not counts or not sum(counts.values()):
108
+ return None
109
+
110
+ order = ["ranked", "gated_out", "not_reachable", "infrastructure"]
111
+ values = [counts.get(k, 0) for k in order]
112
+ labels = [_CLASS_LABEL[k] for k in order]
113
+ colours = [_COLOURS[k] for k in order]
114
+
115
+ fig, ax = plt.subplots(figsize=(8, 3.4))
116
+ bars = ax.bar(labels, values, color=colours)
117
+ for bar, value in zip(bars, values, strict=True):
118
+ # Every bar is annotated, zero included. `if value:` suppressed the label
119
+ # on an empty class, so a category with no pairs was drawn as a bar of no
120
+ # height and no number — indistinguishable from a category that was not
121
+ # plotted at all. "None of these" is a finding; "not shown" is not.
122
+ ax.text(
123
+ bar.get_x() + bar.get_width() / 2,
124
+ value,
125
+ str(value),
126
+ ha="center",
127
+ va="bottom",
128
+ fontsize=10,
129
+ )
130
+ ax.set_ylabel("Antigen-cohort pairs")
131
+ # The bars count the pre-registered denominator, which is fewer pairs than
132
+ # the study scored. An untitled denominator reads as a total of the panel.
133
+ tiers = (summary.get("design") or {}).get("tiers_in_primary_denominator") or []
134
+ scope = (
135
+ " ".join(str(t).replace("_", " ") for t in tiers) + "-tier pairs"
136
+ if tiers
137
+ else "every scored pair"
138
+ )
139
+ ax.set_title(f"Four outcomes ({scope}), reported separately rather than as one rate")
140
+ ax.set_ylim(0, max(values) * 1.2 if max(values) else 1)
141
+ ax.spines[["top", "right"]].set_visible(False)
142
+ ax.tick_params(axis="x", labelsize=9)
143
+ fig.tight_layout()
144
+ out_path.parent.mkdir(parents=True, exist_ok=True)
145
+ fig.savefig(out_path, dpi=150)
146
+ plt.close(fig)
147
+ return out_path
148
+
149
+
150
+ def render_figures(summary: dict[str, Any], fig_dir: Path) -> dict[str, Path]:
151
+ """Write every study figure into ``fig_dir``; return what was produced.
152
+
153
+ A figure that could not be drawn is simply absent from the result rather than
154
+ written empty, so a page never links a picture with nothing in it.
155
+ """
156
+ produced: dict[str, Path] = {}
157
+ for name, fn in (
158
+ ("surfaced_ranks", plot_surfaced_ranks),
159
+ ("outcome_classes", plot_outcome_classes),
160
+ ):
161
+ path = fn(summary, fig_dir / f"{name}.png")
162
+ if path is not None:
163
+ produced[name] = path
164
+ return produced