pswap commited on
Commit
bbb3a59
·
1 Parent(s): f28dce2

remove cap

Browse files
Files changed (1) hide show
  1. app.py +93 -77
app.py CHANGED
@@ -16,7 +16,6 @@ from transformers.models.esmfold2.protein_utils import output_to_pdb, prepare_pr
16
  # reasonable on a shared ZeroGPU slot. Swap for "biohub/ESMFold2" for MSA-conditioned
17
  # accuracy on hard targets (slower, and needs an MSA pipeline this app doesn't provide).
18
  MODEL_NAME = "biohub/ESMFold2-Fast"
19
- MAX_TOTAL_RESIDUES = 600 # keeps inference within the ZeroGPU time/memory budget
20
  MAX_ENTITY_ROWS = 8
21
  VALID_AMINO_ACIDS = set("ACDEFGHIKLMNPQRSTVWYXBZUO")
22
  ENTITY_TYPES = ["protein", "dna", "rna"]
@@ -118,8 +117,44 @@ def prepare_multi_chain_protein_features(sequences: list[str], device) -> dict[s
118
  return {k: v.to(device) for k, v in combined.items()}
119
 
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  @spaces.GPU(duration=180)
122
- def fold_entities(entities: list[dict], num_loops: int, num_sampling_steps: int, progress=gr.Progress()):
 
 
 
 
 
123
  chain_sequences: list[str] = []
124
  for entity in entities:
125
  sequence = clean_sequence(entity.get("sequence", ""))
@@ -140,12 +175,6 @@ def fold_entities(entities: list[dict], num_loops: int, num_sampling_steps: int,
140
  raise gr.Error("Please enter at least one protein sequence.")
141
 
142
  total_residues = sum(len(s) for s in chain_sequences)
143
- if total_residues > MAX_TOTAL_RESIDUES:
144
- raise gr.Error(
145
- f"Total length is {total_residues} residues across {len(chain_sequences)} chain(s); this "
146
- f"Space is limited to {MAX_TOTAL_RESIDUES} residues to keep inference within the ZeroGPU "
147
- "time budget."
148
- )
149
 
150
  progress(0.1, desc="Moving model to GPU...")
151
  model.to("cuda")
@@ -186,28 +215,6 @@ def fold_entities(entities: list[dict], num_loops: int, num_sampling_steps: int,
186
  return viewer_html, " \n".join(summary_lines), pdb_path
187
 
188
 
189
- def add_entity(entities: list[dict]) -> list[dict]:
190
- if len(entities) >= MAX_ENTITY_ROWS:
191
- return entities
192
- return entities + [dict(DEFAULT_ENTITY)]
193
-
194
-
195
- def remove_entity(entities: list[dict], idx: int) -> list[dict]:
196
- if len(entities) <= 1:
197
- return entities
198
- return [e for i, e in enumerate(entities) if i != idx]
199
-
200
-
201
- def update_entity_field(entities: list[dict], idx: int, field: str, value) -> list[dict]:
202
- entities = [dict(e) for e in entities]
203
- entities[idx][field] = value
204
- return entities
205
-
206
-
207
- def load_example(sequence: str) -> list[dict]:
208
- return [{"type": "protein", "copies": 1, "sequence": sequence}]
209
-
210
-
211
  with gr.Blocks(title="Foldy — ESMFold2 Protein Structure Prediction") as demo:
212
  gr.Markdown(
213
  f"""
@@ -226,48 +233,10 @@ with gr.Blocks(title="Foldy — ESMFold2 Protein Structure Prediction") as demo:
226
  example1_btn = gr.Button("Load: Ubiquitin", size="sm")
227
  example2_btn = gr.Button("Load: CAP protein", size="sm")
228
 
229
- @gr.render(inputs=entities_state)
230
- def render_entities(entities):
231
- for idx, entity in enumerate(entities):
232
- with gr.Row():
233
- etype = gr.Dropdown(ENTITY_TYPES, value=entity["type"], label="Type", scale=2)
234
- copies = gr.Number(
235
- value=entity["copies"], label="Copies", precision=0, minimum=1, scale=1
236
- )
237
- seq = gr.Textbox(
238
- value=entity["sequence"],
239
- label="Sequence",
240
- lines=4,
241
- max_lines=4,
242
- scale=6,
243
- placeholder="Amino acid sequence...",
244
- )
245
- remove_btn = gr.Button("×", scale=0, min_width=40, visible=len(entities) > 1)
246
-
247
- etype.change(
248
- lambda val, ents, i=idx: update_entity_field(ents, i, "type", val),
249
- inputs=[etype, entities_state],
250
- outputs=entities_state,
251
- )
252
- copies.input(
253
- lambda val, ents, i=idx: update_entity_field(ents, i, "copies", int(val or 1)),
254
- inputs=[copies, entities_state],
255
- outputs=entities_state,
256
- )
257
- seq.change(
258
- lambda val, ents, i=idx: update_entity_field(ents, i, "sequence", val),
259
- inputs=[seq, entities_state],
260
- outputs=entities_state,
261
- )
262
- remove_btn.click(
263
- lambda ents, i=idx: remove_entity(ents, i),
264
- inputs=entities_state,
265
- outputs=entities_state,
266
- )
267
-
268
- add_btn.click(add_entity, inputs=entities_state, outputs=entities_state)
269
- example1_btn.click(lambda: load_example(EXAMPLE_UBIQUITIN), outputs=entities_state)
270
- example2_btn.click(lambda: load_example(EXAMPLE_CAP), outputs=entities_state)
271
 
272
  with gr.Accordion("Advanced settings", open=False):
273
  num_loops = gr.Slider(1, 3, value=1, step=1, label="Recycling loops")
@@ -281,11 +250,58 @@ with gr.Blocks(title="Foldy — ESMFold2 Protein Structure Prediction") as demo:
281
  with gr.Column(scale=2):
282
  structure_output = gr.HTML(label="Predicted structure")
283
 
284
- fold_btn.click(
285
- fn=fold_entities,
286
- inputs=[entities_state, num_loops, num_sampling_steps],
287
- outputs=[structure_output, summary_output, pdb_output],
288
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
 
290
  if __name__ == "__main__":
291
  demo.queue(max_size=20).launch(theme=gr.themes.Soft())
 
16
  # reasonable on a shared ZeroGPU slot. Swap for "biohub/ESMFold2" for MSA-conditioned
17
  # accuracy on hard targets (slower, and needs an MSA pipeline this app doesn't provide).
18
  MODEL_NAME = "biohub/ESMFold2-Fast"
 
19
  MAX_ENTITY_ROWS = 8
20
  VALID_AMINO_ACIDS = set("ACDEFGHIKLMNPQRSTVWYXBZUO")
21
  ENTITY_TYPES = ["protein", "dna", "rna"]
 
117
  return {k: v.to(device) for k, v in combined.items()}
118
 
119
 
120
+ def harvest_entities(etypes: list[str], copiess: list, seqs: list[str]) -> list[dict]:
121
+ """Read the currently displayed value of every row's components directly.
122
+
123
+ Used instead of syncing a ``gr.State`` copy on every keystroke, so editing a field never
124
+ depends on a change/input event having already round-tripped to the server — the value the
125
+ user sees is always exactly the value Add/Remove/Fold act on.
126
+ """
127
+ return [
128
+ {"type": t, "copies": max(1, int(c or 1)), "sequence": s or ""}
129
+ for t, c, s in zip(etypes, copiess, seqs)
130
+ ]
131
+
132
+
133
+ def add_entity_live(etypes: list[str], copiess: list, seqs: list[str]) -> list[dict]:
134
+ entities = harvest_entities(etypes, copiess, seqs)
135
+ if len(entities) < MAX_ENTITY_ROWS:
136
+ entities.append(dict(DEFAULT_ENTITY))
137
+ return entities
138
+
139
+
140
+ def remove_entity_live(etypes: list[str], copiess: list, seqs: list[str], remove_idx: int) -> list[dict]:
141
+ entities = harvest_entities(etypes, copiess, seqs)
142
+ if len(entities) <= 1:
143
+ return entities
144
+ return [e for i, e in enumerate(entities) if i != remove_idx]
145
+
146
+
147
+ def load_example(sequence: str) -> list[dict]:
148
+ return [{"type": "protein", "copies": 1, "sequence": sequence}]
149
+
150
+
151
  @spaces.GPU(duration=180)
152
+ def fold_from_rows(*args, progress=gr.Progress()):
153
+ *row_values, num_loops, num_sampling_steps = args
154
+ num_rows = len(row_values) // 3
155
+ etypes, copiess, seqs = row_values[:num_rows], row_values[num_rows : 2 * num_rows], row_values[2 * num_rows :]
156
+ entities = harvest_entities(etypes, copiess, seqs)
157
+
158
  chain_sequences: list[str] = []
159
  for entity in entities:
160
  sequence = clean_sequence(entity.get("sequence", ""))
 
175
  raise gr.Error("Please enter at least one protein sequence.")
176
 
177
  total_residues = sum(len(s) for s in chain_sequences)
 
 
 
 
 
 
178
 
179
  progress(0.1, desc="Moving model to GPU...")
180
  model.to("cuda")
 
215
  return viewer_html, " \n".join(summary_lines), pdb_path
216
 
217
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  with gr.Blocks(title="Foldy — ESMFold2 Protein Structure Prediction") as demo:
219
  gr.Markdown(
220
  f"""
 
233
  example1_btn = gr.Button("Load: Ubiquitin", size="sm")
234
  example2_btn = gr.Button("Load: CAP protein", size="sm")
235
 
236
+ # Empty placeholder, positioned here (above the settings/fold/download components
237
+ # below); filled in further down, once fold_btn etc. exist for render_entities to
238
+ # close over — see the ``with rows_container:`` block after this gr.Row().
239
+ rows_container = gr.Column()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
 
241
  with gr.Accordion("Advanced settings", open=False):
242
  num_loops = gr.Slider(1, 3, value=1, step=1, label="Recycling loops")
 
250
  with gr.Column(scale=2):
251
  structure_output = gr.HTML(label="Predicted structure")
252
 
253
+ example1_btn.click(lambda: load_example(EXAMPLE_UBIQUITIN), outputs=entities_state)
254
+ example2_btn.click(lambda: load_example(EXAMPLE_CAP), outputs=entities_state)
255
+
256
+ with rows_container:
257
+
258
+ @gr.render(inputs=entities_state)
259
+ def render_entities(entities):
260
+ etype_boxes, copies_boxes, seq_boxes, remove_btns = [], [], [], []
261
+ for idx, entity in enumerate(entities):
262
+ with gr.Row():
263
+ etype = gr.Dropdown(ENTITY_TYPES, value=entity["type"], label="Type", scale=2)
264
+ copies = gr.Number(
265
+ value=entity["copies"],
266
+ label="Copies",
267
+ precision=0,
268
+ minimum=1,
269
+ step=1,
270
+ scale=1,
271
+ )
272
+ seq = gr.Textbox(
273
+ value=entity["sequence"],
274
+ label="Sequence",
275
+ lines=4,
276
+ max_lines=4,
277
+ scale=6,
278
+ placeholder="Amino acid sequence...",
279
+ )
280
+ remove_btn = gr.Button("×", scale=0, min_width=40, visible=len(entities) > 1)
281
+ etype_boxes.append(etype)
282
+ copies_boxes.append(copies)
283
+ seq_boxes.append(seq)
284
+ remove_btns.append(remove_btn)
285
+
286
+ # No change/input handlers on the row fields themselves: Add/Remove/Fold all read
287
+ # the live, currently-displayed value of every row directly (below), so nothing
288
+ # depends on a change event having already round-tripped to the server first.
289
+ live_inputs = [*etype_boxes, *copies_boxes, *seq_boxes]
290
+
291
+ add_btn.click(add_entity_live, inputs=live_inputs, outputs=entities_state)
292
+ for idx, remove_btn in enumerate(remove_btns):
293
+ remove_btn.click(
294
+ lambda *vals, i=idx, n=len(entities): remove_entity_live(
295
+ vals[:n], vals[n : 2 * n], vals[2 * n :], i
296
+ ),
297
+ inputs=live_inputs,
298
+ outputs=entities_state,
299
+ )
300
+ fold_btn.click(
301
+ fn=fold_from_rows,
302
+ inputs=[*live_inputs, num_loops, num_sampling_steps],
303
+ outputs=[structure_output, summary_output, pdb_output],
304
+ )
305
 
306
  if __name__ == "__main__":
307
  demo.queue(max_size=20).launch(theme=gr.themes.Soft())