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,155 @@
1
+ {% extends "base.html.j2" %}
2
+ {% block title %}Try it — bindsight{% endblock %}
3
+
4
+ {% block content %}
5
+ <h1>Try it</h1>
6
+ <p class="lede">
7
+ The discovery half, end to end, on a real cohort. No GPU, no account, nothing
8
+ to configure.
9
+ </p>
10
+
11
+ <div class="card">
12
+ <h2 class="card__title">What this runs</h3>
13
+ <div class="grid grid--2">
14
+ {#- `.fact`, not `.stat`. A `.stat` is a measurement and must carry its
15
+ denominator or interval; these are descriptions, and borrowing the
16
+ figure component for them would make that rule unenforceable. -#}
17
+ {% for key, value in cohort.items() %}
18
+ <div class="fact">
19
+ <span class="fact__label">{{ key }}</span>
20
+ <span class="fact__value">{{ value }}</span>
21
+ </div>
22
+ {% endfor %}
23
+ </div>
24
+
25
+ <div class="btn-row">
26
+ <button class="btn btn--primary" id="start">Run the demo</button>
27
+ </div>
28
+
29
+ <!-- Announced, not just drawn. The run takes minutes on a cold cache and
30
+ every update happens without the focus moving, so without these a
31
+ screen-reader user hears nothing between pressing the button and giving
32
+ up. `polite` for progress, which is a running commentary; the outcome
33
+ node carries `role="status"` or `role="alert"` depending on which it
34
+ turns out to be. -->
35
+ <ol class="steps" id="steps" aria-live="polite" aria-atomic="false" hidden></ol>
36
+ <div id="outcome" aria-live="polite" aria-atomic="true"></div>
37
+ </div>
38
+
39
+ <div class="note note--info">
40
+ <span class="note__glyph" aria-hidden="true">i</span>
41
+ <div class="note__body">
42
+ <p class="small" style="margin:0">
43
+ Well-known antigens appear among the candidates when their signal is
44
+ present in the cohort. They are not guaranteed to: in whole unstratified
45
+ TCGA-BRCA, ERBB2 measures a log2 fold change below the filter, and the
46
+ study says so rather than quietly stratifying until it does not.
47
+ </p>
48
+ </div>
49
+ </div>
50
+ {% endblock %}
51
+
52
+ {% block scripts %}
53
+ <script>
54
+ // Per-stage progress. A single indeterminate spinner cannot tell a reader
55
+ // whether a run is slow or stuck, and on a cold cache this is minutes.
56
+ //
57
+ // Every value below comes from `/api/job/<id>`, whose `error` is `str(exc)`
58
+ // from a bare `except` -- so it carries whatever text the pipeline raised,
59
+ // including a design table's own column names. This built that into markup by
60
+ // string concatenation, which is the defect fixed in `your_data_check.js` and
61
+ // then missed here because the sweep that enforces the rule read `.js` files
62
+ // and this script lives in a template. Nodes and textContent, same as there.
63
+ const GLYPH = { pending: "\u25cb", running: "\u25d0", done: "\u2713", failed: "\u2717" };
64
+
65
+ function el(tag, className) {
66
+ const node = document.createElement(tag);
67
+ if (className) node.className = className;
68
+ return node;
69
+ }
70
+
71
+ function note(kind, glyph, bodyNodes) {
72
+ const wrap = el("div", "note note--" + kind);
73
+ const mark = el("span", "note__glyph");
74
+ mark.setAttribute("aria-hidden", "true");
75
+ mark.textContent = glyph;
76
+ const body = el("div", "note__body");
77
+ bodyNodes.forEach(function (n) { body.appendChild(n); });
78
+ wrap.appendChild(mark);
79
+ wrap.appendChild(body);
80
+ return wrap;
81
+ }
82
+
83
+ function stepItem(s) {
84
+ const li = el("li");
85
+ li.setAttribute("data-state", s.state);
86
+ const glyph = el("span", "steps__glyph");
87
+ glyph.setAttribute("aria-hidden", "true");
88
+ glyph.textContent = GLYPH[s.state] || "";
89
+ const spoken = el("span", "visually-hidden");
90
+ spoken.textContent = " (" + s.state + ")";
91
+ li.appendChild(glyph);
92
+ li.appendChild(document.createTextNode(s.name));
93
+ li.appendChild(spoken);
94
+ return li;
95
+ }
96
+
97
+ document.getElementById("start").addEventListener("click", async function () {
98
+ const btn = this;
99
+ btn.disabled = true;
100
+ btn.textContent = "Running\u2026";
101
+ const outcome = document.getElementById("outcome");
102
+ outcome.innerHTML = "";
103
+ const steps = document.getElementById("steps");
104
+ steps.hidden = false;
105
+
106
+ const started = await fetch("/try/run", { method: "POST" });
107
+ const { job } = await started.json();
108
+
109
+ const poll = setInterval(async function () {
110
+ const res = await fetch("/api/job/" + job);
111
+ const state = await res.json();
112
+
113
+ steps.innerHTML = "";
114
+ state.stages.forEach(function (s) { steps.appendChild(stepItem(s)); });
115
+
116
+ if (state.state === "done" || state.state === "failed") {
117
+ clearInterval(poll);
118
+ btn.disabled = false;
119
+ btn.textContent = "Run it again";
120
+
121
+ let card;
122
+ if (state.state === "done") {
123
+ const line = el("p");
124
+ line.style.margin = "0";
125
+ const where = el("code");
126
+ where.textContent = String(state.run_dir);
127
+ const link = el("a");
128
+ link.href = "/runs";
129
+ link.textContent = "browse it";
130
+ line.appendChild(document.createTextNode("Complete. The run is in "));
131
+ line.appendChild(where);
132
+ line.appendChild(document.createTextNode(" \u2014 "));
133
+ line.appendChild(link);
134
+ line.appendChild(document.createTextNode("."));
135
+ card = note("ok", "\u2713", [line]);
136
+ card.setAttribute("role", "status");
137
+ } else {
138
+ const headline = el("p");
139
+ headline.style.margin = "0";
140
+ const strong = el("strong");
141
+ strong.textContent = "The demo did not finish.";
142
+ headline.appendChild(strong);
143
+ const detail = el("p", "small");
144
+ detail.style.margin = ".5rem 0 0";
145
+ // textContent: this is the one value an attacker can reach.
146
+ detail.textContent = String(state.error == null ? "" : state.error);
147
+ card = note("err", "\u2717", [headline, detail]);
148
+ card.setAttribute("role", "alert");
149
+ }
150
+ outcome.appendChild(card);
151
+ }
152
+ }, 700);
153
+ });
154
+ </script>
155
+ {% endblock %}
@@ -0,0 +1,46 @@
1
+ {% extends "base.html.j2" %}
2
+ {% block title %}Your data — bindsight{% endblock %}
3
+
4
+ {% block content %}
5
+ <h1>Your data</h1>
6
+ <p class="lede">
7
+ A counts matrix and a sample design table naming a two-level factor. Nothing
8
+ on this path knows about cancer — <code>tumor</code> and <code>normal</code>
9
+ are values you supply, not values the code expects.
10
+ </p>
11
+
12
+ <div class="card">
13
+ <h2 class="card__title">Files</h3>
14
+ <div class="field">
15
+ <label for="counts">Counts matrix <span class="muted">(.tsv, .txt, .gz)</span></label>
16
+ <input type="file" id="counts" accept=".tsv,.txt,.gz">
17
+ <p class="field__help">Genes as rows, samples as columns, integer counts.</p>
18
+ </div>
19
+ <div class="field">
20
+ <label for="design">Sample design table <span class="muted">(.tsv, .txt, .gz)</span></label>
21
+ <input type="file" id="design" accept=".tsv,.txt,.gz">
22
+ <p class="field__help">One row per sample, with a column naming the condition.</p>
23
+ </div>
24
+
25
+ <div id="checks" aria-live="polite" aria-atomic="false"></div>
26
+ </div>
27
+
28
+ <div class="note note--info">
29
+ <span class="note__glyph" aria-hidden="true">i</span>
30
+ <div class="note__body">
31
+ <p class="small" style="margin:0">
32
+ Files are checked the moment you choose them — shape, separator, and
33
+ whether the sample names in the two tables agree. The contrast levels are
34
+ read from your design file and offered back to you, rather than typed: a
35
+ mistyped level used to surface as a generic pipeline failure minutes
36
+ later, after the cohort had already been processed.
37
+ </p>
38
+ </div>
39
+ </div>
40
+ {% endblock %}
41
+
42
+ {% block scripts %}
43
+ {#- The checker is bindsight/report/web/static/your_data_check.js, shared with
44
+ the published documentation site so the two cannot diverge. -#}
45
+ <script src="/static/your_data_check.js"></script>
46
+ {% endblock %}
@@ -0,0 +1,7 @@
1
+ # SPDX-FileCopyrightText: 2026 Mikhaeel Atef Rizk Wahba
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+ """GPU-runner abstraction (Colab / Modal / Kaggle / local Docker / mock)."""
4
+
5
+ from bindsight.runners.protocol import CostEstimate, GPURunner, JobHandle, JobStatus
6
+
7
+ __all__ = ["CostEstimate", "GPURunner", "JobHandle", "JobStatus"]
@@ -0,0 +1,158 @@
1
+ # SPDX-FileCopyrightText: 2026 Mikhaeel Atef Rizk Wahba
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+ """Colab GPU runner.
4
+
5
+ Colab is a free-or-cheap GPU on-ramp. bindsight doesn't have a way to *launch*
6
+ a Colab notebook from the CLI (Google's API doesn't permit it for free-tier
7
+ users), so the workflow is:
8
+
9
+ 1. ``submit()`` writes a self-contained ``.ipynb`` to disk and prints the
10
+ command for the user to open it in Colab.
11
+ 2. The notebook installs deps, runs the GPU step, tarballs results, writes
12
+ them to a known location (Google Drive folder, or anywhere the user can
13
+ download from).
14
+ 3. ``poll()`` checks whether ``results.tar.gz`` has appeared in the local
15
+ results directory (the user downloads it from Drive after the notebook
16
+ finishes).
17
+ 4. ``fetch()`` returns the local path once it's there.
18
+
19
+ This is intentionally simple. v0.2 may add Drive API auto-fetch via OAuth;
20
+ v0.0.x explicitly favors "user clicks two buttons in Colab" over "wire
21
+ OAuth and fight rate limits".
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import logging
27
+ import uuid
28
+ from datetime import UTC, datetime
29
+ from pathlib import Path
30
+ from typing import Any
31
+
32
+ from bindsight.cost import estimate
33
+ from bindsight.runners.protocol import CostEstimate, JobHandle, JobStatus
34
+
35
+ LOG = logging.getLogger(__name__)
36
+
37
+
38
+ class ColabRunner:
39
+ """Local-disk Colab job orchestrator.
40
+
41
+ Args:
42
+ notebooks_dir: where templated ``.ipynb`` files are written. Default
43
+ is ``./runs/<run_id>/colab_notebooks/``.
44
+ designer: passed through to the cost estimator.
45
+ n_units_per_target: trajectories per design (used for cost estimate).
46
+ gpu_type: GPU to request from Colab (``T4`` for free, ``A100`` for Pro+).
47
+ """
48
+
49
+ name = "colab"
50
+
51
+ def __init__(
52
+ self,
53
+ notebooks_dir: Path | None = None,
54
+ *,
55
+ designer: str = "rfdiff_mpnn",
56
+ n_units_per_target: int = 50,
57
+ gpu_type: str = "T4",
58
+ ) -> None:
59
+ self.notebooks_dir = Path(notebooks_dir) if notebooks_dir else None
60
+ self.designer = designer
61
+ self.n_units_per_target = n_units_per_target
62
+ self.gpu_type = gpu_type
63
+
64
+ def estimate_cost(self, spec_size: int) -> CostEstimate:
65
+ """Estimate cost for ``spec_size`` design trajectories."""
66
+ return estimate(
67
+ backend=self.name,
68
+ stage="design",
69
+ plugin=self.designer,
70
+ n_units=spec_size,
71
+ gpu_type=self.gpu_type,
72
+ )
73
+
74
+ def submit(self, spec_path: Path, *, results_dir: Path) -> JobHandle:
75
+ """Write a Colab notebook + spec to disk, return a JobHandle.
76
+
77
+ The user is responsible for opening the notebook in Colab. The
78
+ ``handle.id`` is used as the cache key for results.
79
+ """
80
+ results_dir.mkdir(parents=True, exist_ok=True)
81
+ handle_id = str(uuid.uuid4())
82
+ notebook_dest = results_dir / f"{handle_id}.ipynb"
83
+
84
+ notebook = self._build_notebook(spec_path=spec_path, handle_id=handle_id)
85
+ notebook_dest.write_text(notebook, encoding="utf-8", newline="\n")
86
+
87
+ LOG.info(
88
+ "Colab notebook written to %s. Open it in Colab, run all cells, "
89
+ "and place the resulting %s.tar.gz in %s.",
90
+ notebook_dest,
91
+ handle_id,
92
+ results_dir,
93
+ )
94
+
95
+ return JobHandle(
96
+ backend=self.name,
97
+ id=handle_id,
98
+ submitted_at=datetime.now(UTC).isoformat(timespec="seconds"),
99
+ notebook_path=str(notebook_dest),
100
+ results_dir=str(results_dir),
101
+ )
102
+
103
+ def poll(self, handle: JobHandle) -> JobStatus:
104
+ """Check whether the user has placed the results tarball in the dir."""
105
+ results_dir = Path((handle.model_extra or {}).get("results_dir") or ".")
106
+ archive = results_dir / f"{handle.id}.tar.gz"
107
+ if archive.exists():
108
+ return JobStatus(handle=handle, state="succeeded", progress=1.0)
109
+ return JobStatus(
110
+ handle=handle,
111
+ state="running",
112
+ progress=None,
113
+ log_tail="awaiting results.tar.gz from Colab user",
114
+ )
115
+
116
+ def fetch(self, handle: JobHandle) -> Path:
117
+ """Return the local path to the results tarball.
118
+
119
+ Raises ``FileNotFoundError`` if the user hasn't dropped the tarball
120
+ in the configured results_dir yet — call :meth:`poll` first.
121
+ """
122
+ results_dir = Path((handle.model_extra or {}).get("results_dir") or ".")
123
+ archive = results_dir / f"{handle.id}.tar.gz"
124
+ if not archive.exists():
125
+ raise FileNotFoundError(
126
+ f"results tarball not yet available: {archive}. "
127
+ "Run the Colab notebook and place the output here."
128
+ )
129
+ return archive
130
+
131
+ # ---------------------------------------------------------------- #
132
+ # Notebook construction #
133
+ # ---------------------------------------------------------------- #
134
+ def _build_notebook(self, *, spec_path: Path, handle_id: str) -> str:
135
+ """Build the design + validation notebook with REAL RFdiff+MPNN+Boltz cells.
136
+
137
+ Delegates to :mod:`bindsight.runners.notebook_content` which holds the
138
+ canonical install + inference cell templates patterned on ColabDesign,
139
+ dl_binder_design, and the upstream Boltz-2 README.
140
+ """
141
+ import json as _json # local alias keeps it out of module init time
142
+
143
+ from bindsight.runners.notebook_content import build_design_notebook
144
+
145
+ spec: dict[str, Any] = {}
146
+ if Path(spec_path).exists():
147
+ try:
148
+ spec = _json.loads(Path(spec_path).read_text(encoding="utf-8"))
149
+ except _json.JSONDecodeError:
150
+ spec = {}
151
+
152
+ notebook = build_design_notebook(
153
+ handle_id=handle_id,
154
+ designer=self.designer,
155
+ gpu_type=self.gpu_type,
156
+ spec=spec,
157
+ )
158
+ return _json.dumps(notebook, indent=1)