multimodalart HF Staff commited on
Commit
cf62ce5
·
verified ·
1 Parent(s): e401aae

Upload folder using huggingface_hub

Browse files
Files changed (4) hide show
  1. README.md +21 -6
  2. __pycache__/app.cpython-311.pyc +0 -0
  3. app.py +239 -0
  4. requirements.txt +14 -0
README.md CHANGED
@@ -1,13 +1,28 @@
1
  ---
2
- title: Nesso
3
- emoji:
4
  colorFrom: gray
5
- colorTo: indigo
6
  sdk: gradio
7
- sdk_version: 6.22.0
8
- python_version: '3.12'
9
  app_file: app.py
 
 
 
10
  pinned: false
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Nesso-1 Binding Affinity
3
+ emoji: 🧬
4
  colorFrom: gray
5
+ colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 5.49.1
 
8
  app_file: app.py
9
+ short_description: Fast protein-ligand binding affinity prediction (Nesso-1)
10
+ python_version: "3.12"
11
+ startup_duration_timeout: 1h
12
  pinned: false
13
  ---
14
 
15
+ # Nesso-1 · Protein–Ligand Binding Affinity
16
+
17
+ Interactive demo of [`recursionpharma/nesso`](https://huggingface.co/recursionpharma/nesso)
18
+ (Nesso-1) — a fast, structure-based protein–ligand binding-affinity prediction model from
19
+ [Valence Labs](https://valencelabs.com) (a Recursion company).
20
+
21
+ Provide a protein amino-acid sequence and a ligand (SMILES) and the model predicts the
22
+ binding affinity as log₁₀(IC₅₀ / µM) plus a binder / non-binder probability.
23
+
24
+ - Model card: https://huggingface.co/recursionpharma/nesso
25
+ - Code: https://github.com/recursionpharma/nesso
26
+ - License: Apache-2.0
27
+
28
+ Example inputs are taken from the Nesso-1 tutorial in the official repository.
__pycache__/app.cpython-311.pyc ADDED
Binary file (11.4 kB). View file
 
app.py ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ # Point every cache (nesso checkpoint, CCD dict, ESM-2 weights) at a persistent,
4
+ # writable location so downloads happen once and are reused across GPU calls.
5
+ os.environ.setdefault("NESSO_CACHE", "/tmp/nesso_cache")
6
+ os.environ.setdefault("HF_HOME", "/tmp/nesso_cache/huggingface")
7
+ os.environ.setdefault("HF_HUB_CACHE", "/tmp/nesso_cache/huggingface")
8
+
9
+ import spaces # noqa: E402 — must precede torch / CUDA-touching imports
10
+
11
+ import json # noqa: E402
12
+ import tempfile # noqa: E402
13
+ import time # noqa: E402
14
+ from pathlib import Path # noqa: E402
15
+
16
+ import gradio as gr # noqa: E402
17
+ import yaml as pyyaml # noqa: E402
18
+
19
+
20
+ TITLE = "Nesso-1 · Protein–Ligand Binding Affinity"
21
+
22
+ DESCRIPTION = """
23
+ # 🧬 Nesso-1 · Binding Affinity Prediction
24
+
25
+ **[Nesso-1](https://huggingface.co/recursionpharma/nesso)** is a fast, structure-based
26
+ protein–ligand binding-affinity model from
27
+ [Valence Labs](https://valencelabs.com) (a Recursion company). Give it a **protein
28
+ amino-acid sequence** and a **ligand** (as a SMILES string), and it predicts binding
29
+ affinity (as log₁₀(IC₅₀ / µM)) together with a binder / non-binder probability.
30
+
31
+ Lower affinity ⇒ stronger binding: `-3.0 ≈ 1 nM` (strong) · `0.0 ≈ 1 µM` (moderate) ·
32
+ `2.0 ≈ 100 µM` (weak).
33
+
34
+ Links: [Model card](https://huggingface.co/recursionpharma/nesso) ·
35
+ [Code](https://github.com/recursionpharma/nesso) ·
36
+ [Blog](https://huggingface.co/blog/recursionpharma/nesso1)
37
+ """
38
+
39
+ # A representative protein used by the model authors' own tutorial examples.
40
+ EXAMPLE_PROTEIN = (
41
+ "MVTPEGNVSLVDESLLVGVTDEDRAVRSAHQFYERLIGLWAPAVMEAAHELGVFAALAEAPADSGELARRLDCDARAMRVLLDALYAY"
42
+ "DVIDRIHDTNGFRYLLSAEARECLLPGTLFSLVGKFMHDINVAWPAWRNLAEVVRHGARDTSGAESPNGIAQEDYESLVGGINFWAPP"
43
+ "IVTTLSRKLRASGRSGDATASVLDVGCGTGLYSQLLLREFPRWTATGLDVERIATLANAQALRLGVEERFATRAGDFWRGGWGTGYDL"
44
+ "VLFANIFHLQTPASAVRLMRHAAACLAPDGLVAVVDQIVDADREPKTPQDRFALLFAASMTNTGGGDAYTFQEYEEWFTAAGLQRIET"
45
+ "LDTPMHRILLARRATEPSAVPEGQASENLYFQ"
46
+ )
47
+
48
+
49
+ def _build_yaml(protein_seq: str, ligand_smiles: str) -> str:
50
+ doc = {
51
+ "sequences": [
52
+ {"protein": {"id": "A", "sequence": protein_seq.strip()}},
53
+ {"ligand": {"id": "B", "smiles": ligand_smiles.strip()}},
54
+ ],
55
+ "properties": [{"affinity": {"binder": "B"}}],
56
+ }
57
+ return pyyaml.safe_dump(doc, sort_keys=False)
58
+
59
+
60
+ def _run_nesso_cli(yaml_path: Path, out_dir: Path, recycling_steps: int, seed: int):
61
+ """Invoke the official `nesso predict` command in-process (Click callback)."""
62
+ from click.testing import CliRunner
63
+ from nesso.main import cli
64
+
65
+ args = [
66
+ "predict",
67
+ str(yaml_path),
68
+ "--out_dir",
69
+ str(out_dir),
70
+ "--accelerator",
71
+ "gpu",
72
+ "--recycling_steps",
73
+ str(int(recycling_steps)),
74
+ "--seed",
75
+ str(int(seed)),
76
+ ]
77
+ try:
78
+ runner = CliRunner(mix_stderr=False)
79
+ except TypeError:
80
+ # Click >= 8.2 removed the mix_stderr kwarg.
81
+ runner = CliRunner()
82
+ result = runner.invoke(cli, args, catch_exceptions=True)
83
+ return result
84
+
85
+
86
+ @spaces.GPU(duration=180)
87
+ def predict_affinity(
88
+ protein_seq: str,
89
+ ligand_smiles: str,
90
+ recycling_steps: int = 5,
91
+ seed: int = 42,
92
+ progress=gr.Progress(track_tqdm=True),
93
+ ):
94
+ """Predict protein–ligand binding affinity with Nesso-1.
95
+
96
+ Args:
97
+ protein_seq: Target protein amino-acid sequence (single-letter codes).
98
+ ligand_smiles: Ligand as a SMILES string.
99
+ recycling_steps: Number of trunk recycling iterations (higher = more compute).
100
+ seed: Random seed for reproducibility.
101
+
102
+ Returns:
103
+ A markdown summary of the predicted binding affinity and binder probability,
104
+ and the full raw prediction JSON.
105
+ """
106
+ protein_seq = (protein_seq or "").strip().replace("\n", "").replace(" ", "")
107
+ ligand_smiles = (ligand_smiles or "").strip()
108
+ if not protein_seq:
109
+ raise gr.Error("Please provide a protein amino-acid sequence.")
110
+ if not ligand_smiles:
111
+ raise gr.Error("Please provide a ligand SMILES string.")
112
+
113
+ t0 = time.perf_counter()
114
+ work = Path(tempfile.mkdtemp(prefix="nesso_"))
115
+ yaml_path = work / "complex.yaml"
116
+ yaml_path.write_text(_build_yaml(protein_seq, ligand_smiles))
117
+ out_dir = work / "output"
118
+
119
+ result = _run_nesso_cli(yaml_path, out_dir, recycling_steps, seed)
120
+
121
+ # Locate the produced affinity.json
122
+ affinity_files = list(out_dir.glob("predictions/*/affinity.json"))
123
+ if not affinity_files:
124
+ stdout = getattr(result, "stdout", "") or ""
125
+ stderr = getattr(result, "stderr", "") or ""
126
+ exc = ""
127
+ if getattr(result, "exception", None) is not None:
128
+ import traceback
129
+
130
+ exc = "".join(
131
+ traceback.format_exception(
132
+ type(result.exception),
133
+ result.exception,
134
+ result.exception.__traceback__,
135
+ )
136
+ )
137
+ raise gr.Error(
138
+ "Prediction failed — no output produced.\n"
139
+ f"stdout:\n{stdout[-1500:]}\n\nstderr:\n{stderr[-1500:]}\n\n{exc[-1500:]}"
140
+ )
141
+
142
+ data = json.loads(affinity_files[0].read_text())
143
+ elapsed = time.perf_counter() - t0
144
+
145
+ aff = data.get("affinity_pred_value")
146
+ prob = data.get("affinity_probability_binary")
147
+
148
+ def _ic50_str(v):
149
+ if v is None:
150
+ return "n/a"
151
+ # v = log10(IC50 / uM) -> IC50 in uM = 10**v
152
+ ic50_um = 10 ** v
153
+ if ic50_um < 1e-3:
154
+ return f"{ic50_um * 1e6:.2f} pM"
155
+ if ic50_um < 1.0:
156
+ return f"{ic50_um * 1e3:.2f} nM"
157
+ if ic50_um < 1e3:
158
+ return f"{ic50_um:.2f} µM"
159
+ return f"{ic50_um / 1e3:.2f} mM"
160
+
161
+ strength = ""
162
+ if aff is not None:
163
+ if aff <= -2:
164
+ strength = "🟢 strong binder"
165
+ elif aff <= 0:
166
+ strength = "🟡 moderate binder"
167
+ elif aff <= 2:
168
+ strength = "🟠 weak binder"
169
+ else:
170
+ strength = "🔴 very weak / non-binder"
171
+
172
+ summary = f"""### Prediction
173
+
174
+ | Metric | Value |
175
+ |---|---|
176
+ | **Binding affinity** (log₁₀ IC₅₀/µM) | `{aff:.3f}` |
177
+ | **Estimated IC₅₀** | **{_ic50_str(aff)}** |
178
+ | **Binder probability** | `{prob:.3f}` |
179
+ | **Interpretation** | {strength} |
180
+
181
+ _Lower affinity ⇒ stronger binding. Computed in {elapsed:.1f}s._
182
+ """
183
+ return summary, data
184
+
185
+
186
+ CSS = """
187
+ #col-container { max-width: 1000px; margin: 0 auto; }
188
+ .dark .gradio-container { color: var(--body-text-color); }
189
+ """
190
+
191
+ with gr.Blocks(theme=gr.themes.Citrus(), css=CSS, title=TITLE) as demo:
192
+ with gr.Column(elem_id="col-container"):
193
+ gr.Markdown(DESCRIPTION)
194
+
195
+ with gr.Row():
196
+ with gr.Column(scale=3):
197
+ protein = gr.Textbox(
198
+ label="Protein sequence",
199
+ placeholder="MKTAYIAKQ... (single-letter amino-acid codes)",
200
+ lines=5,
201
+ )
202
+ ligand = gr.Textbox(
203
+ label="Ligand SMILES",
204
+ placeholder="e.g. N[C@@H](Cc1ccc(O)cc1)C(=O)O",
205
+ lines=1,
206
+ )
207
+ with gr.Accordion("Advanced settings", open=False):
208
+ recycling = gr.Slider(
209
+ 1, 10, value=5, step=1, label="Recycling steps"
210
+ )
211
+ seed = gr.Number(value=42, precision=0, label="Seed")
212
+ run = gr.Button("Predict binding affinity", variant="primary")
213
+ with gr.Column(scale=2):
214
+ out_md = gr.Markdown(label="Result")
215
+ out_json = gr.JSON(label="Full prediction")
216
+
217
+ gr.Examples(
218
+ examples=[
219
+ [EXAMPLE_PROTEIN, "N[C@@H](Cc1ccc(O)cc1)C(=O)O"],
220
+ [EXAMPLE_PROTEIN, "CCO"],
221
+ [EXAMPLE_PROTEIN, "Fc1ccc(cc1)C(=O)Nc1ccc(cc1)S(=O)(=O)N"],
222
+ ],
223
+ inputs=[protein, ligand],
224
+ outputs=[out_md, out_json],
225
+ fn=predict_affinity,
226
+ cache_examples=True,
227
+ cache_mode="lazy",
228
+ )
229
+
230
+ run.click(
231
+ predict_affinity,
232
+ inputs=[protein, ligand, recycling, seed],
233
+ outputs=[out_md, out_json],
234
+ api_name="predict",
235
+ )
236
+
237
+
238
+ if __name__ == "__main__":
239
+ demo.launch(mcp_server=True)
requirements.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Nesso-1 runtime deps (from its pyproject), plus the package itself with --no-deps
2
+ # so it does not override the platform-managed gradio / spaces / huggingface_hub.
3
+ numpy>=2.0
4
+ lightning>=2.6.0
5
+ rdkit>=2024.3.2
6
+ einops>=0.8.0
7
+ mashumaro>=3.14
8
+ safetensors>=0.7.0
9
+ transformers>=4.40.0
10
+ scipy>=1.13.0
11
+ pyyaml
12
+ click>=8.1.7
13
+ tqdm
14
+ nesso @ git+https://github.com/recursionpharma/nesso.git