Umesh1608 commited on
Commit
7af26d8
Β·
verified Β·
1 Parent(s): f0f78b7

UI overhaul: architecture diagram, dark-readable theme, formatted Ki cards

Browse files
Files changed (3) hide show
  1. .gitattributes +1 -0
  2. app.py +177 -80
  3. architecture.png +3 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ architecture.png filter=lfs diff=lfs merge=lfs -text
app.py CHANGED
@@ -1,7 +1,7 @@
1
- """SugarKi frontend β€” public UI for sugar-chemistry Ki prediction.
2
 
3
- Lightweight Gradio interface that calls the SugarKi backend Space via
4
- gradio_client. No model weights here; pure UI.
5
  """
6
  from __future__ import annotations
7
 
@@ -9,16 +9,15 @@ import os
9
  from gradio_client import Client
10
  import gradio as gr
11
 
12
- # Backend Space (private; requires HF_TOKEN with read access to umesh1608)
13
  BACKEND = os.environ.get("SUGARKI_BACKEND", "Umesh1608/sugarki-backend")
14
  HF_TOKEN = os.environ.get("HF_TOKEN")
15
 
16
  if not HF_TOKEN:
17
  print("WARNING: HF_TOKEN not set; backend calls will fail if backend is private.")
18
 
19
- # -----------------------------------------------------------------------
20
- # Curated example library β€” common sugar inhibitors and enzymes
21
- # -----------------------------------------------------------------------
22
 
23
  SUGAR_INHIBITORS = {
24
  "D-mannitol": "OCC(O)C(O)C(O)C(O)CO",
@@ -51,12 +50,11 @@ EXAMPLES = [
51
  [EXAMPLE_MDH_WT, "D-mannitol", "substrate", "OCC(=O)C(O)C(O)C(O)CO"],
52
  ]
53
 
54
- # -----------------------------------------------------------------------
55
- # Backend client (lazy)
56
- # -----------------------------------------------------------------------
57
 
58
  _client = None
59
-
60
  def _backend():
61
  global _client
62
  if _client is None:
@@ -69,76 +67,178 @@ def _backend():
69
 
70
  def predict(sequence, inhibitor_choice, inhibitor_custom, inh_type, substrate_smiles):
71
  if not sequence or not sequence.strip():
72
- return "**Error:** Please paste an enzyme sequence.", None, None
73
 
74
- # Resolve inhibitor SMILES
75
- if inhibitor_choice == "Custom (paste SMILES)":
76
- smiles = (inhibitor_custom or "").strip()
77
- if not smiles:
78
- return "**Error:** Custom SMILES cannot be empty.", None, None
79
- else:
80
- smiles = SUGAR_INHIBITORS.get(inhibitor_choice, "")
81
- if not smiles:
82
- return f"**Error:** Unknown inhibitor `{inhibitor_choice}`.", None, None
83
 
84
  try:
85
  result = _backend().predict(
86
- sequence.strip(),
87
- smiles,
88
- inh_type,
89
  substrate_smiles.strip() if substrate_smiles else "",
90
  api_name="/predict_ki",
91
  )
92
  except Exception as e:
93
- return f"**Backend error:** {e}", None, None
94
 
95
- if isinstance(result, dict) and "error" in result:
96
- return f"**Error:** {result['error']}", None, None
 
97
 
98
- # Format
99
  ki_mm = result.get("Ki_mM")
100
  ki_um = result.get("Ki_uM")
101
  log_ki = result.get("log10_Ki_mM")
102
- inh_used = result.get("inh_type_used")
103
- his_stripped = result.get("his_tag_stripped", False)
104
- notes = result.get("notes", "")
 
 
 
 
 
 
 
 
 
 
 
105
 
106
- summary_md = (
107
- f"### Predicted Ki: **{ki_mm:.3f} mM** ({ki_um:,.1f} Β΅M)\n\n"
108
- f"- log₁₀(Ki / mM) = **{log_ki:+.3f}**\n"
109
- f"- Inhibition mode used: **{inh_used}**\n"
110
- f"- His-tag prefix detected and stripped: {'yes' if his_stripped else 'no'}\n"
111
- f"- Ensemble Οƒ (across 5 inhibition modes) = {result.get('ensemble_std_log10', '?')}\n\n"
112
- f"**Model:** {result.get('model_version', 'SugarKi')}\n\n"
113
- f"_{notes}_\n"
114
- )
 
 
 
 
 
115
 
116
  # Per-mode table
117
- mode_table = result.get("mode_predictions_mM", {})
118
- table_rows = [(k, f"{v:.3f}", f"{v*1000:,.1f}") for k, v in mode_table.items()]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
 
120
- return summary_md, table_rows, result
121
 
122
 
123
- # -----------------------------------------------------------------------
124
- # UI
125
- # -----------------------------------------------------------------------
126
 
127
- with gr.Blocks(title="SugarKi β€” Ki prediction for sugar-chemistry enzymes",
128
- theme=gr.themes.Soft()) as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  gr.HTML(
130
  """
131
- <div style='text-align:center;margin-bottom:0.5em'>
132
- <h1 style='margin-bottom:0'>πŸ§ͺ SugarKi</h1>
133
- <p style='color:#666;margin-top:0'>
134
- Sugar-chemistry Ki predictor β€” family-specialized for polyol DHs,
135
- sugar kinases, glycosidases, sugar phosphatases, aldolases,
136
- isomerases, and phosphomutases.
137
  </p>
138
  </div>
139
  """
140
  )
141
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  with gr.Row():
143
  with gr.Column(scale=3):
144
  seq_in = gr.Textbox(
@@ -146,6 +246,7 @@ with gr.Blocks(title="SugarKi β€” Ki prediction for sugar-chemistry enzymes",
146
  placeholder="MGSSHHHHHH... or just the catalytic domain (30–1000 aa)",
147
  lines=10,
148
  value=EXAMPLE_MDH_WT,
 
149
  )
150
  with gr.Row():
151
  inh_choice = gr.Dropdown(
@@ -158,7 +259,7 @@ with gr.Blocks(title="SugarKi β€” Ki prediction for sugar-chemistry enzymes",
158
  "product_or_substrate", "unknown"],
159
  value="auto",
160
  label="Inhibition type",
161
- info="`product` for product inhibition; `substrate` for substrate inhibition at high [S]; `auto` averages all 5",
162
  )
163
  inh_custom = gr.Textbox(
164
  label="Custom inhibitor SMILES (only used if inhibitor = 'Custom (paste SMILES)')",
@@ -168,60 +269,56 @@ with gr.Blocks(title="SugarKi β€” Ki prediction for sugar-chemistry enzymes",
168
  label="Native substrate SMILES (optional, helps product-inhibition prediction)",
169
  value="",
170
  )
171
- btn = gr.Button("Predict Ki", variant="primary", size="lg")
172
 
173
  with gr.Column(scale=2):
174
- summary_out = gr.Markdown(label="Result")
175
  mode_table = gr.Dataframe(
176
- headers=["Inhibition mode", "Ki (mM)", "Ki (Β΅M)"],
177
- label="Per-mode predictions",
178
  wrap=True,
 
179
  )
180
- raw_out = gr.JSON(label="Full response", visible=False)
181
- show_raw = gr.Checkbox(label="Show raw JSON", value=False)
182
- show_raw.change(lambda x: gr.update(visible=x), show_raw, raw_out)
183
 
184
  btn.click(
185
  predict,
186
  inputs=[seq_in, inh_choice, inh_custom, inh_type, sub_smiles],
187
- outputs=[summary_out, mode_table, raw_out],
188
  )
189
 
190
  gr.Examples(
191
  examples=EXAMPLES,
192
  inputs=[seq_in, inh_choice, inh_type, sub_smiles],
 
193
  )
194
 
 
195
  gr.Markdown(
196
  """
197
- ### About SugarKi
198
 
199
- SugarKi is a sugar-chemistry-specialized Ki predictor. Two integrated
200
- components: a **specialist** (Plan E2) trained on a curated 5,041-row
201
- sugar-Ki benchmark, and a rule-based **router** that dispatches
202
- non-sugar queries to CatPred zero-shot.
203
 
204
- | | Sugar-chemistry Ki | General Ki |
205
  |---|---|---|
206
  | CatPred zero-shot | RΒ² = 0.243 (catastrophic on monosaccharides/polyols) | RΒ² = 0.578 |
207
  | SELFprot zero-shot | RΒ² = 0.623 | RΒ² = 0.314 |
208
- | **SugarKi specialist** | **RΒ² = 0.702** | (route to CatPred) |
209
-
210
- ### Calibration
211
-
212
- On MDH-006 WT (mannitol DH) + D-mannitol β€” predicted Ki = 11.78 mM in
213
- substrate mode vs literature 12 mM (1.8% relative error).
214
 
215
  ### Limitations
216
 
217
- - Test MAE ~0.6 log units; relative ranking more reliable than absolute values
218
- - Best on EC families 1.1.1.x / 2.7.1.x / 3.2.1.x / 3.1.3.x / 4.1.2.x / 5.3.1.x / 5.4.2.x
219
- - Allosteric inhibition not separately modeled
 
220
 
221
  ### Citation
222
 
223
- Paper in preparation. Hosted by [MWBC](https://huggingface.co/MWBC),
224
- backed by [Umesh1608/sugarki-backend](https://huggingface.co/spaces/Umesh1608/sugarki-backend).
225
  """
226
  )
227
 
 
1
+ """SugarKi public UI β€” sugar-chemistry Ki predictor (frontend, MWBC/sugarki).
2
 
3
+ Pretty Gradio interface that calls the SugarKi backend Space (private,
4
+ ZeroGPU). No model weights here β€” just the UI.
5
  """
6
  from __future__ import annotations
7
 
 
9
  from gradio_client import Client
10
  import gradio as gr
11
 
 
12
  BACKEND = os.environ.get("SUGARKI_BACKEND", "Umesh1608/sugarki-backend")
13
  HF_TOKEN = os.environ.get("HF_TOKEN")
14
 
15
  if not HF_TOKEN:
16
  print("WARNING: HF_TOKEN not set; backend calls will fail if backend is private.")
17
 
18
+ # ----------------------------------------------------------------------
19
+ # Curated example library
20
+ # ----------------------------------------------------------------------
21
 
22
  SUGAR_INHIBITORS = {
23
  "D-mannitol": "OCC(O)C(O)C(O)C(O)CO",
 
50
  [EXAMPLE_MDH_WT, "D-mannitol", "substrate", "OCC(=O)C(O)C(O)C(O)CO"],
51
  ]
52
 
53
+ # ----------------------------------------------------------------------
54
+ # Backend client
55
+ # ----------------------------------------------------------------------
56
 
57
  _client = None
 
58
  def _backend():
59
  global _client
60
  if _client is None:
 
67
 
68
  def predict(sequence, inhibitor_choice, inhibitor_custom, inh_type, substrate_smiles):
69
  if not sequence or not sequence.strip():
70
+ return "### ⚠️ Please paste an enzyme sequence", None, "", None
71
 
72
+ smiles = (
73
+ inhibitor_custom.strip()
74
+ if inhibitor_choice == "Custom (paste SMILES)"
75
+ else SUGAR_INHIBITORS.get(inhibitor_choice, "")
76
+ )
77
+ if not smiles:
78
+ return "### ⚠️ Inhibitor SMILES is empty", None, "", None
 
 
79
 
80
  try:
81
  result = _backend().predict(
82
+ sequence.strip(), smiles, inh_type,
 
 
83
  substrate_smiles.strip() if substrate_smiles else "",
84
  api_name="/predict_ki",
85
  )
86
  except Exception as e:
87
+ return f"### ❌ Backend error\n```\n{e}\n```", None, "", None
88
 
89
+ if isinstance(result, dict) and result.get("error"):
90
+ msg = result.get("error_message", str(result))
91
+ return f"### ❌ Prediction error\n```\n{msg}\n```", None, "", result
92
 
93
+ # Headline card
94
  ki_mm = result.get("Ki_mM")
95
  ki_um = result.get("Ki_uM")
96
  log_ki = result.get("log10_Ki_mM")
97
+ inh_used = result.get("inh_type_used", "?")
98
+ his_stripped = "βœ“ stripped" if result.get("his_tag_stripped") else "β€”"
99
+ sigma = result.get("ensemble_std_log10", "?")
100
+
101
+ # Format Ki nicely with appropriate units
102
+ if ki_mm is not None:
103
+ if ki_mm < 0.001:
104
+ ki_display = f"**{ki_mm * 1e6:.2f} nM**"
105
+ elif ki_mm < 1:
106
+ ki_display = f"**{ki_um:.1f} Β΅M**"
107
+ else:
108
+ ki_display = f"**{ki_mm:.3f} mM**"
109
+ else:
110
+ ki_display = "β€”"
111
 
112
+ summary_md = f"""
113
+ ### πŸ§ͺ Predicted Ki: {ki_display}
114
+
115
+ | | |
116
+ |---|---|
117
+ | **Ki (mM)** | {ki_mm:.4f} |
118
+ | **Ki (Β΅M)** | {ki_um:,.1f} |
119
+ | **log₁₀(Ki / mM)** | {log_ki:+.3f} |
120
+ | **Inhibition mode** | `{inh_used}` |
121
+ | **His-tag prefix** | {his_stripped} |
122
+ | **Ensemble Οƒ across 5 modes** | {sigma} |
123
+
124
+ > **Model:** {result.get('model_version', 'SugarKi')}
125
+ """
126
 
127
  # Per-mode table
128
+ mode_mm = result.get("mode_predictions_mM", {})
129
+ mode_log = result.get("mode_predictions_log10", {})
130
+
131
+ mode_rows = []
132
+ for mode in ["external", "product", "substrate", "product_or_substrate", "unknown"]:
133
+ if mode in mode_mm:
134
+ mm = mode_mm[mode]
135
+ um = mm * 1000
136
+ log = mode_log.get(mode, 0)
137
+ highlight = " ⭐" if mode == inh_used else ""
138
+ mode_rows.append([
139
+ f"{mode.replace('_', ' ').title()}{highlight}",
140
+ f"{log:+.3f}",
141
+ f"{mm:.3f}",
142
+ f"{um:,.1f}",
143
+ ])
144
+
145
+ notes = result.get("notes", "")
146
+ notes_md = f"\n\n**ℹ️ Notes:** _{notes}_\n" if notes else ""
147
 
148
+ return summary_md, mode_rows, notes_md, result
149
 
150
 
151
+ # ----------------------------------------------------------------------
152
+ # UI β€” uses theme-aware colors so text stays readable on any background
153
+ # ----------------------------------------------------------------------
154
 
155
+ CSS = """
156
+ .title-block {
157
+ text-align: center;
158
+ padding: 1em 0;
159
+ margin-bottom: 0.5em;
160
+ }
161
+ .title-block h1 { margin: 0; font-size: 2.2em; }
162
+ .title-block p { margin: 0.3em 0 0; opacity: 0.75; font-size: 1.05em; }
163
+ .ki-card {
164
+ border: 1px solid var(--border-color-primary);
165
+ border-radius: 8px;
166
+ padding: 1em 1.5em;
167
+ background: var(--background-fill-secondary);
168
+ }
169
+ """
170
+
171
+ with gr.Blocks(title="SugarKi β€” Ki prediction", theme=gr.themes.Default(), css=CSS) as demo:
172
+
173
+ # ----- header -----
174
  gr.HTML(
175
  """
176
+ <div class='title-block'>
177
+ <h1>πŸ§ͺ SugarKi</h1>
178
+ <p>
179
+ Family-specialized Ki prediction for sugar-chemistry enzymes β€”
180
+ polyol DHs, sugar kinases, glycosidases, sugar phosphatases,
181
+ aldolases, isomerases, phosphomutases.
182
  </p>
183
  </div>
184
  """
185
  )
186
 
187
+ # ----- architecture diagram -----
188
+ with gr.Accordion("πŸ“ How does SugarKi work?", open=True):
189
+ gr.Markdown(
190
+ """
191
+ SugarKi takes an **enzyme sequence** and an **inhibitor SMILES** and predicts the
192
+ inhibition constant **Ki** specifically for sugar-chemistry enzymes β€” where current
193
+ SOTA models like CatPred fail catastrophically (RΒ² = βˆ’0.95 on monosaccharide
194
+ inhibitors).
195
+
196
+ ### Pipeline (~30 seconds end-to-end on ZeroGPU A100)
197
+
198
+ ```
199
+ Enzyme sequence ──► ESMFold ─────────► 3D backbone PDB
200
+ β”‚
201
+ β–Ό
202
+ P2Rank ──► binding pocket residues
203
+ β”‚
204
+ Inhibitor SMILES ──► ChEBIFormer ──┐ β”‚
205
+ Substrate SMILES ──► ChEBIFormer ──┼──┐ β”‚
206
+ β–Ό β–Ό β–Ό
207
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
208
+ β”‚ ESM-2 + LoRA (rank 16) β”‚
209
+ β”‚ + GVP-GNN structure β”‚
210
+ β”‚ + pocket-aware pool β”‚
211
+ β”‚ + mechanism aux head β”‚
212
+ β”‚ = SugarKi-Specialist β”‚
213
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
214
+ β–Ό
215
+ log₁₀(Ki / mM) β†’ Ki value
216
+ ```
217
+
218
+ **Inputs**
219
+
220
+ - `Enzyme sequence` β€” paste any sugar-family enzyme (His-tag auto-stripped). Best for EC 1.1.1.x, 2.7.1.x, 3.1.3.x, 3.2.1.x, 4.1.2.x, 5.3.1.x, 5.4.2.x.
221
+ - `Inhibitor SMILES` β€” SMILES of the small molecule whose Ki you want.
222
+ - `Inhibition type` β€” choose `product` for product inhibition (e.g., mannitol on mannitol DH), `substrate` for substrate-mode at high [S], or `auto` for an ensemble across all 5 modes.
223
+ - `Substrate SMILES` (optional) β€” native substrate, helps disambiguate product-inhibition cases.
224
+
225
+ ### Validation
226
+
227
+ - WT MDH-006 (mannitol DH) + D-mannitol β†’ predicted **11.78 mM** in substrate mode vs literature **12 mM** (1.8% error).
228
+ - Sugar-Ki test set RΒ² = **0.702** vs CatPred 0.243 / SELFprot 0.623.
229
+ - Typical MAE β‰ˆ 0.6 log units; relative ranking is more reliable than absolute Ki values.
230
+ """
231
+ )
232
+ gr.Image(
233
+ value="architecture.png",
234
+ label="SugarKi specialist architecture (Plan E2)",
235
+ show_label=True,
236
+ interactive=False,
237
+ container=True,
238
+ height=400,
239
+ )
240
+
241
+ # ----- input + output -----
242
  with gr.Row():
243
  with gr.Column(scale=3):
244
  seq_in = gr.Textbox(
 
246
  placeholder="MGSSHHHHHH... or just the catalytic domain (30–1000 aa)",
247
  lines=10,
248
  value=EXAMPLE_MDH_WT,
249
+ show_copy_button=True,
250
  )
251
  with gr.Row():
252
  inh_choice = gr.Dropdown(
 
259
  "product_or_substrate", "unknown"],
260
  value="auto",
261
  label="Inhibition type",
262
+ info="`product` for product inhibition; `substrate` for substrate inhibition at high [S]",
263
  )
264
  inh_custom = gr.Textbox(
265
  label="Custom inhibitor SMILES (only used if inhibitor = 'Custom (paste SMILES)')",
 
269
  label="Native substrate SMILES (optional, helps product-inhibition prediction)",
270
  value="",
271
  )
272
+ btn = gr.Button("🧬 Predict Ki", variant="primary", size="lg")
273
 
274
  with gr.Column(scale=2):
275
+ summary_out = gr.Markdown(elem_classes=["ki-card"])
276
  mode_table = gr.Dataframe(
277
+ headers=["Inhibition mode", "log₁₀(Ki/mM)", "Ki (mM)", "Ki (Β΅M)"],
278
+ label="Per-mode predictions (across 5 inhibition assumptions)",
279
  wrap=True,
280
+ interactive=False,
281
  )
282
+ notes_out = gr.Markdown()
283
+ with gr.Accordion("Raw JSON response", open=False):
284
+ raw_out = gr.JSON()
285
 
286
  btn.click(
287
  predict,
288
  inputs=[seq_in, inh_choice, inh_custom, inh_type, sub_smiles],
289
+ outputs=[summary_out, mode_table, notes_out, raw_out],
290
  )
291
 
292
  gr.Examples(
293
  examples=EXAMPLES,
294
  inputs=[seq_in, inh_choice, inh_type, sub_smiles],
295
+ label="Example: MDH-006 WT + D-mannitol (substrate-mode)",
296
  )
297
 
298
+ # ----- footer -----
299
  gr.Markdown(
300
  """
301
+ ---
302
 
303
+ ### How SugarKi compares to existing Ki predictors
 
 
 
304
 
305
+ | Model | Sugar-chemistry Ki | General Ki |
306
  |---|---|---|
307
  | CatPred zero-shot | RΒ² = 0.243 (catastrophic on monosaccharides/polyols) | RΒ² = 0.578 |
308
  | SELFprot zero-shot | RΒ² = 0.623 | RΒ² = 0.314 |
309
+ | **SugarKi specialist** | **RΒ² = 0.702** | (router falls back to CatPred) |
 
 
 
 
 
310
 
311
  ### Limitations
312
 
313
+ - Test MAE ~0.6 log units; **rankings** more reliable than **absolute** values.
314
+ - Best on EC families 1.1.1.x / 2.7.1.x / 3.2.1.x / 3.1.3.x / 4.1.2.x / 5.3.1.x / 5.4.2.x.
315
+ - Allosteric inhibition not separately modeled.
316
+ - First call cold-starts ~30 s while ESMFold loads; subsequent calls re-use cached structures.
317
 
318
  ### Citation
319
 
320
+ Paper in preparation. Hosted by [MWBC](https://huggingface.co/MWBC), backed by
321
+ a private SugarKi inference Space ([Umesh1608](https://huggingface.co/Umesh1608)).
322
  """
323
  )
324
 
architecture.png ADDED

Git LFS Details

  • SHA256: 3a43bbdc8068594439669356d575193fd83dec59c83a4cccfcf6158146a863ae
  • Pointer size: 131 Bytes
  • Size of remote file: 434 kB