Spaces:
Running on Zero
Running on Zero
Minjun Kang commited on
Commit Β·
62a01bd
1
Parent(s): e15df09
update feature
Browse files- __pycache__/examples.cpython-310.pyc +0 -0
- __pycache__/t5_utils.cpython-310.pyc +0 -0
- app.py +52 -92
- assets/ascl1.h5 +3 -0
- assets/atn1.h5 +3 -0
- assets/fbll1.h5 +3 -0
- assets/foxp2.h5 +3 -0
- assets/semg2.h5 +3 -0
- assets/sgta.h5 +3 -0
- assets/ss18.h5 +3 -0
- assets/synuclein.h5 +3 -0
- assets/ubqln4.h5 +3 -0
- assets/ubqln_mut10.h5 +3 -0
- assets/ubqln_mut20.h5 +3 -0
- assets/ubqln_mut30.h5 +3 -0
- examples.py +214 -0
- preprocess/add_example.py +173 -0
- preprocess/extract_example_feat.py +38 -0
- requirements.txt +1 -0
- t5_utils.py +108 -0
__pycache__/examples.cpython-310.pyc
ADDED
|
Binary file (7.99 kB). View file
|
|
|
__pycache__/t5_utils.cpython-310.pyc
ADDED
|
Binary file (3.26 kB). View file
|
|
|
app.py
CHANGED
|
@@ -9,7 +9,6 @@ Pipeline:
|
|
| 9 |
(4) Tab 2 β Condition Screening: pick one condition to vary, fix the rest β plot
|
| 10 |
"""
|
| 11 |
|
| 12 |
-
import re
|
| 13 |
import sys
|
| 14 |
import warnings
|
| 15 |
from pathlib import Path
|
|
@@ -20,11 +19,23 @@ import matplotlib
|
|
| 20 |
matplotlib.use("Agg")
|
| 21 |
import matplotlib.pyplot as plt
|
| 22 |
import joblib
|
| 23 |
-
import torch
|
| 24 |
import gradio as gr
|
| 25 |
import spaces
|
| 26 |
from huggingface_hub import snapshot_download
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
warnings.filterwarnings("ignore")
|
| 29 |
|
| 30 |
# ββ Paths βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -36,7 +47,6 @@ BASE_DIR = Path(__file__).parent
|
|
| 36 |
# at module import (app startup, plain CPU context) β before any @spaces.GPU
|
| 37 |
# call β so extract_t5_feature() never blocks on a network download while
|
| 38 |
# holding a ZeroGPU allocation (which has a short time budget).
|
| 39 |
-
T5_REPO_ID = "Rostlab/prot_t5_xl_half_uniref50-enc"
|
| 40 |
snapshot_download(T5_REPO_ID)
|
| 41 |
|
| 42 |
# ββ Physical constants (from preprocess/misc.py) ββββββββββββββββββββββββββββββ
|
|
@@ -69,23 +79,10 @@ SCREEN_LABELS = {
|
|
| 69 |
VALID_AA = set("ACDEFGHIKLMNPQRSTVWY")
|
| 70 |
ALLOW_AA = VALID_AA | set("XBJOUZ")
|
| 71 |
|
| 72 |
-
# Ξ±-synuclein (used as built-in example)
|
| 73 |
-
EXAMPLE_SEQ = (
|
| 74 |
-
"MDVFMKGLSKAKEGVVAAAEKTKQGVAEAAGKTKEGVLYVGSKTKEGVVHGVATVAEKTK"
|
| 75 |
-
"EQVTNVGGAVVTGVTAVAQKTVEGAGSIAAATGFVKKDQLGKNEEGAPQEGILEDMPVDP"
|
| 76 |
-
"DNEAYEMPSEEGYQDYEPEA"
|
| 77 |
-
)
|
| 78 |
-
|
| 79 |
# ββ Lazy-loaded singletons ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 80 |
-
_t5_tokenizer = None
|
| 81 |
-
_t5_model = None
|
| 82 |
_llps_model = None
|
| 83 |
|
| 84 |
|
| 85 |
-
def get_device() -> str:
|
| 86 |
-
return "cuda" if torch.cuda.is_available() else "cpu"
|
| 87 |
-
|
| 88 |
-
|
| 89 |
def load_llps_model():
|
| 90 |
global _llps_model
|
| 91 |
if _llps_model is None:
|
|
@@ -103,78 +100,15 @@ def load_llps_model():
|
|
| 103 |
return _llps_model
|
| 104 |
|
| 105 |
|
| 106 |
-
def load_t5():
|
| 107 |
-
global _t5_tokenizer, _t5_model
|
| 108 |
-
if _t5_model is None:
|
| 109 |
-
from transformers import AutoTokenizer, T5EncoderModel
|
| 110 |
-
import transformers.utils.import_utils as _hf_utils
|
| 111 |
-
|
| 112 |
-
# Rostlab/prot_t5_xl_half_uniref50-enc is only available as .bin (no safetensors).
|
| 113 |
-
# transformers 5.x blocks torch.load on torch < 2.6 due to CVE-2025-32434.
|
| 114 |
-
# We bypass that gate for this specific trusted checkpoint from the official
|
| 115 |
-
# HuggingFace Hub. The check lives in two places β import_utils AND the
|
| 116 |
-
# locally-imported name in modeling_utils β so both must be patched.
|
| 117 |
-
import transformers.modeling_utils as _modeling_utils
|
| 118 |
-
|
| 119 |
-
_noop = lambda: None
|
| 120 |
-
_orig_hf = _hf_utils.check_torch_load_is_safe
|
| 121 |
-
_orig_mdl = _modeling_utils.check_torch_load_is_safe
|
| 122 |
-
|
| 123 |
-
_hf_utils.check_torch_load_is_safe = _noop
|
| 124 |
-
_modeling_utils.check_torch_load_is_safe = _noop
|
| 125 |
-
|
| 126 |
-
try:
|
| 127 |
-
# Already fetched by the module-level snapshot_download() above, so
|
| 128 |
-
# ZeroGPU calls never hit the network here β a cold download would
|
| 129 |
-
# blow past the GPU-allocation time budget and get aborted.
|
| 130 |
-
# local_files_only=True enforces that guarantee.
|
| 131 |
-
repo_id = T5_REPO_ID
|
| 132 |
-
dev = get_device()
|
| 133 |
-
dtype = torch.float16 if dev == "cuda" else torch.float32
|
| 134 |
-
|
| 135 |
-
_t5_tokenizer = AutoTokenizer.from_pretrained(
|
| 136 |
-
repo_id, do_lower_case=False, local_files_only=True
|
| 137 |
-
)
|
| 138 |
-
_t5_model = (
|
| 139 |
-
T5EncoderModel.from_pretrained(
|
| 140 |
-
repo_id, torch_dtype=dtype, local_files_only=True
|
| 141 |
-
)
|
| 142 |
-
.to(dev)
|
| 143 |
-
.eval()
|
| 144 |
-
)
|
| 145 |
-
_t5_model.requires_grad_(False)
|
| 146 |
-
finally:
|
| 147 |
-
_hf_utils.check_torch_load_is_safe = _orig_hf # always restore
|
| 148 |
-
_modeling_utils.check_torch_load_is_safe = _orig_mdl
|
| 149 |
-
|
| 150 |
-
return _t5_tokenizer, _t5_model
|
| 151 |
-
|
| 152 |
-
|
| 153 |
# ββ Feature extraction ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
@spaces.GPU
|
| 155 |
def extract_t5_feature(sequence: str) -> np.ndarray:
|
| 156 |
-
|
| 157 |
-
Return mean-pooled ProtT5-XL embedding for a single sequence.
|
| 158 |
-
Output shape: (1024,) β matches the feature dimension LLPSense was trained on.
|
| 159 |
-
"""
|
| 160 |
-
tok, mdl = load_t5()
|
| 161 |
-
dev = get_device()
|
| 162 |
-
|
| 163 |
-
# Replace ambiguous residues with X (same as original pipeline)
|
| 164 |
-
clean = re.sub(r"[UZOB]", "X", sequence.strip().upper())
|
| 165 |
-
spaced = " ".join(list(clean))
|
| 166 |
-
|
| 167 |
-
enc = tok([spaced], add_special_tokens=True, padding="longest", return_tensors="pt")
|
| 168 |
-
input_ids = enc["input_ids"].to(dev)
|
| 169 |
-
attention_mask = enc["attention_mask"].to(dev)
|
| 170 |
-
|
| 171 |
-
with torch.no_grad():
|
| 172 |
-
hidden = mdl(input_ids=input_ids, attention_mask=attention_mask).last_hidden_state
|
| 173 |
-
# Mean-pool over sequence positions (ignoring padding tokens)
|
| 174 |
-
feat = (hidden * attention_mask[..., None]).sum(dim=1) / \
|
| 175 |
-
attention_mask.sum(dim=1, keepdim=True)
|
| 176 |
-
|
| 177 |
-
return feat[0].float().cpu().numpy() # (1024,)
|
| 178 |
|
| 179 |
|
| 180 |
# ββ Condition vector builder ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -344,7 +278,14 @@ def cb_extract(sequence: str):
|
|
| 344 |
yield None, _SPINNER_HTML, None, "", None, ""
|
| 345 |
|
| 346 |
try:
|
| 347 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 348 |
yield feat, _status_ok(len(seq), feat.shape[0]), None, "", None, ""
|
| 349 |
except Exception as e:
|
| 350 |
yield None, _status_err(str(e)), None, "", None, ""
|
|
@@ -509,14 +450,21 @@ with gr.Blocks(
|
|
| 509 |
label="Amino Acid Sequence (1-letter code)",
|
| 510 |
placeholder="Paste your protein sequence here (e.g. MDVFMKGLSKβ¦)",
|
| 511 |
lines=5,
|
| 512 |
-
value=
|
| 513 |
elem_id="seq-input",
|
| 514 |
)
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 520 |
|
| 521 |
with gr.Group():
|
| 522 |
gr.Markdown("## Step 2 β Extract ProtT5 Feature")
|
|
@@ -675,6 +623,18 @@ with gr.Blocks(
|
|
| 675 |
outputs=[feat_state, extract_status, pred_plot, pred_text, screen_plot, screen_text],
|
| 676 |
)
|
| 677 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 678 |
# ββ Footer ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 679 |
gr.Markdown("""
|
| 680 |
---
|
|
|
|
| 9 |
(4) Tab 2 β Condition Screening: pick one condition to vary, fix the rest β plot
|
| 10 |
"""
|
| 11 |
|
|
|
|
| 12 |
import sys
|
| 13 |
import warnings
|
| 14 |
from pathlib import Path
|
|
|
|
| 19 |
matplotlib.use("Agg")
|
| 20 |
import matplotlib.pyplot as plt
|
| 21 |
import joblib
|
|
|
|
| 22 |
import gradio as gr
|
| 23 |
import spaces
|
| 24 |
from huggingface_hub import snapshot_download
|
| 25 |
|
| 26 |
+
from examples import EXAMPLES, feature_path, find_example_by_seq
|
| 27 |
+
|
| 28 |
+
# Only surface examples that actually have a pre-computed T5 feature cached
|
| 29 |
+
# in assets/ β an entry added via preprocess/add_example.py but not yet run
|
| 30 |
+
# through preprocess/extract_example_feat.py would otherwise show up in the
|
| 31 |
+
# picker and silently fall back to a slow/GPU extraction on first click.
|
| 32 |
+
AVAILABLE_EXAMPLES = [example for example in EXAMPLES if feature_path(example["id"]).exists()]
|
| 33 |
+
from t5_utils import (
|
| 34 |
+
T5_REPO_ID,
|
| 35 |
+
extract_t5_feature as _extract_t5_feature_core,
|
| 36 |
+
read_feature_h5,
|
| 37 |
+
)
|
| 38 |
+
|
| 39 |
warnings.filterwarnings("ignore")
|
| 40 |
|
| 41 |
# ββ Paths βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 47 |
# at module import (app startup, plain CPU context) β before any @spaces.GPU
|
| 48 |
# call β so extract_t5_feature() never blocks on a network download while
|
| 49 |
# holding a ZeroGPU allocation (which has a short time budget).
|
|
|
|
| 50 |
snapshot_download(T5_REPO_ID)
|
| 51 |
|
| 52 |
# ββ Physical constants (from preprocess/misc.py) ββββββββββββββββββββββββββββββ
|
|
|
|
| 79 |
VALID_AA = set("ACDEFGHIKLMNPQRSTVWY")
|
| 80 |
ALLOW_AA = VALID_AA | set("XBJOUZ")
|
| 81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
# ββ Lazy-loaded singletons ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
|
|
|
| 83 |
_llps_model = None
|
| 84 |
|
| 85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
def load_llps_model():
|
| 87 |
global _llps_model
|
| 88 |
if _llps_model is None:
|
|
|
|
| 100 |
return _llps_model
|
| 101 |
|
| 102 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
# ββ Feature extraction ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 104 |
+
# The actual tokenize/forward/mean-pool logic lives in t5_utils.py, shared
|
| 105 |
+
# with preprocess/extract_example_feat.py so the offline-cached example
|
| 106 |
+
# features in assets/ always match what this would compute live. Only the
|
| 107 |
+
# @spaces.GPU wrapping (ZeroGPU allocation) is app-specific, and cb_extract()
|
| 108 |
+
# below skips calling this entirely when a cached feature is available.
|
| 109 |
@spaces.GPU
|
| 110 |
def extract_t5_feature(sequence: str) -> np.ndarray:
|
| 111 |
+
return _extract_t5_feature_core(sequence)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
|
| 113 |
|
| 114 |
# ββ Condition vector builder ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 278 |
yield None, _SPINNER_HTML, None, "", None, ""
|
| 279 |
|
| 280 |
try:
|
| 281 |
+
# Known example sequence with a pre-computed feature? Skip the T5
|
| 282 |
+
# model/GPU call entirely and load it straight from assets/.
|
| 283 |
+
example = find_example_by_seq(seq)
|
| 284 |
+
cached_path = feature_path(example["id"]) if example else None
|
| 285 |
+
if cached_path and cached_path.exists():
|
| 286 |
+
feat = read_feature_h5(cached_path)
|
| 287 |
+
else:
|
| 288 |
+
feat = extract_t5_feature(seq)
|
| 289 |
yield feat, _status_ok(len(seq), feat.shape[0]), None, "", None, ""
|
| 290 |
except Exception as e:
|
| 291 |
yield None, _status_err(str(e)), None, "", None, ""
|
|
|
|
| 450 |
label="Amino Acid Sequence (1-letter code)",
|
| 451 |
placeholder="Paste your protein sequence here (e.g. MDVFMKGLSKβ¦)",
|
| 452 |
lines=5,
|
| 453 |
+
value=AVAILABLE_EXAMPLES[0]["seq"] if AVAILABLE_EXAMPLES else "",
|
| 454 |
elem_id="seq-input",
|
| 455 |
)
|
| 456 |
+
# Clicking an example fills seq_box; the matching Step-2 extraction
|
| 457 |
+
# is chained onto it further down (once the Step-3 output components
|
| 458 |
+
# exist) so selecting an example runs extraction automatically.
|
| 459 |
+
# Skipped entirely if no example has a cached feature yet.
|
| 460 |
+
example_picker = None
|
| 461 |
+
if AVAILABLE_EXAMPLES:
|
| 462 |
+
with gr.Accordion("π§ͺ Examples (We provide preprocessed T5 feature)", open=False):
|
| 463 |
+
example_picker = gr.Examples(
|
| 464 |
+
examples=[[example["seq"]] for example in AVAILABLE_EXAMPLES],
|
| 465 |
+
example_labels=[example["name"] for example in AVAILABLE_EXAMPLES],
|
| 466 |
+
inputs=[seq_box],
|
| 467 |
+
)
|
| 468 |
|
| 469 |
with gr.Group():
|
| 470 |
gr.Markdown("## Step 2 β Extract ProtT5 Feature")
|
|
|
|
| 623 |
outputs=[feat_state, extract_status, pred_plot, pred_text, screen_plot, screen_text],
|
| 624 |
)
|
| 625 |
|
| 626 |
+
# Selecting a built-in example runs extraction automatically (no manual
|
| 627 |
+
# Step-2 click needed) β cache-hit examples resolve near-instantly since
|
| 628 |
+
# cb_extract reads their pre-computed feature from assets/ instead of
|
| 629 |
+
# calling the T5 model. example_picker is None when no example has a
|
| 630 |
+
# cached feature yet (see AVAILABLE_EXAMPLES above).
|
| 631 |
+
if example_picker is not None:
|
| 632 |
+
example_picker.load_input_event.then(
|
| 633 |
+
fn=cb_extract,
|
| 634 |
+
inputs=[seq_box],
|
| 635 |
+
outputs=[feat_state, extract_status, pred_plot, pred_text, screen_plot, screen_text],
|
| 636 |
+
)
|
| 637 |
+
|
| 638 |
# ββ Footer ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 639 |
gr.Markdown("""
|
| 640 |
---
|
assets/ascl1.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fec1ca3caebb001a1ec821900f7cb57c3b748ea6754b47a264a45cdbf600e88f
|
| 3 |
+
size 6160
|
assets/atn1.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fd29669387a1dae9268a8e75381ad6899f96908c403b16aa409abe3d7262402a
|
| 3 |
+
size 6157
|
assets/fbll1.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3be5535f596b4ff93013c21cddd8fcef9e3bcdde5391f42bfb26fb670b7d4157
|
| 3 |
+
size 6157
|
assets/foxp2.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:46cd534b6b9fadac71a887ea7ca9b02eb2616c9a89ee9babbfd09aa467288653
|
| 3 |
+
size 6174
|
assets/semg2.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:330004af079a2bf62cd36951258c0414da4a1e1f5acc9259e31dc520a0b60975
|
| 3 |
+
size 6180
|
assets/sgta.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:17d0cacc822200a1f62353f97dfe7bc9be6920abf2c99fd1e504f269cb50ae96
|
| 3 |
+
size 6164
|
assets/ss18.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dabf03dddeb0e3cc6b45564a1978cd2a7d92befaf5f452dfc1dbbf39b1ab500b
|
| 3 |
+
size 6156
|
assets/synuclein.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fff3a1909b4c47d3ee84fd3e0601c9ca5dd1ce1cfc66df1f0a1066087beec7c5
|
| 3 |
+
size 6149
|
assets/ubqln4.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4e81867866cb676c5c6cab266a887711cc96227ce7ebbd06258db7fd413d07b2
|
| 3 |
+
size 6151
|
assets/ubqln_mut10.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6bac2120b88b2f792579f8ac36da89b2d36a40f8677d48689facca9550df5b2e
|
| 3 |
+
size 6144
|
assets/ubqln_mut20.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:588896444e29efdd1066719561ced11fcdf9d7149632a356051d55775d55b88c
|
| 3 |
+
size 6146
|
assets/ubqln_mut30.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:229cfa12dc1810d93e96da3058aba885c9efdaa0970cf852e36f088b8d9ab21c
|
| 3 |
+
size 6152
|
examples.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Built-in example sequences for the LLPSense demo.
|
| 2 |
+
|
| 3 |
+
Shared by app.py (UI + on-the-fly extraction fallback) and
|
| 4 |
+
preprocess/extract_example_feat.py (offline pre-computation), so both stay
|
| 5 |
+
in sync on which sequences have a cached ProtT5 feature under assets/.
|
| 6 |
+
|
| 7 |
+
To add another example, just append a dict with a unique "id" here, then
|
| 8 |
+
run `python preprocess/extract_example_feat.py` to populate its cached
|
| 9 |
+
feature in assets/ before deploying.
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
ASSETS_DIR = Path(__file__).parent / "assets"
|
| 15 |
+
|
| 16 |
+
EXAMPLES = [
|
| 17 |
+
{
|
| 18 |
+
"id": "synuclein",
|
| 19 |
+
"name": "Ξ±-Synuclein",
|
| 20 |
+
"seq": (
|
| 21 |
+
"MDVFMKGLSKAKEGVVAAAEKTKQGVAEAAGKTKEGVLYVGSKTKEGVVHGVATVAEKTK"
|
| 22 |
+
"EQVTNVGGAVVTGVTAVAQKTVEGAGSIAAATGFVKKDQLGKNEEGAPQEGILEDMPVDP"
|
| 23 |
+
"DNEAYEMPSEEGYQDYEPEA"
|
| 24 |
+
),
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
"id": "ubqln4",
|
| 28 |
+
"name": "UBQLN-4",
|
| 29 |
+
"seq": (
|
| 30 |
+
"MAEPSGAETRPPIRVTVKTPKDKEEIVICDRASVKEFKEEISRRFKAQQDQLVLIFAGKI"
|
| 31 |
+
"LKDGDTLNQHGIKDGLTVHLVIKTPQKAQDPAAATASSPSTPDPASAPSTTPASPATPAQ"
|
| 32 |
+
"PSTSGSASSDAGSGSRRSSGGGPSPGAGEGSPSATASILSGFGGILGLGSLGLGSANFME"
|
| 33 |
+
"LQQQMQRQLMSNPEMLSQIMENPLVQDMMSNPDLMRHMIMANPQMQQLMERNPEISHMLN"
|
| 34 |
+
"NPELMRQTMELARNPAMMQEMMRNQDRALSNLESIPGGYNALRRMYTDIQEPMFSAAREQ"
|
| 35 |
+
"FGNNPFSSLAGNSDSSSSQPLRTENREPLPNPWSPSPPTSQAPGSGGEGTGGSGTSQVHP"
|
| 36 |
+
"TVSNPFGINAASLGSGMFNSPEMQALLQQISENPQLMQNVISAPYMRSMMQTLAQNPDFA"
|
| 37 |
+
"AQMMVNVPLFAGNPQLQEQLRLQLPVFLQQMQNPESLSILTNPRAMQALLQIQQGLQTLQ"
|
| 38 |
+
"TEAPGLVPSLGSFGISRTPAPSAGSNAGSTPEAPTSSPATPATSSPTGASSAQQQLMQQM"
|
| 39 |
+
"IQLLAGSGNSQVQTPEVRFQQQLEQLNSMGFINREANLQALIATGGDINAAIERLLGSQL"
|
| 40 |
+
"S"
|
| 41 |
+
),
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
"id": 'ascl1',
|
| 45 |
+
"name": 'ASCL1',
|
| 46 |
+
"seq": (
|
| 47 |
+
"MESSAKMESGGAGQQPQPQPQQPFLPPAACFFATAAAAAAAAAAAAAQSAQQQQQQQQQQ"
|
| 48 |
+
"QQAPQLRPAADGQPSGGGHKSAPKQVKRQRSSSPELMRCKRRLNFSGFGYSLPQQQPAAV"
|
| 49 |
+
"ARRNERERNRVKLVNLGFATLREHVPNGAANKKMSKVETLRSAVEYIRALQQLLDEHDAV"
|
| 50 |
+
"SAAFQAGVLSPTISPNYSNDLNSMAGSPVSSYSSDEGSYDPLSPEEQELLDFTNWF"
|
| 51 |
+
),
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
"id": 'atn1',
|
| 55 |
+
"name": 'ATN1',
|
| 56 |
+
"seq": (
|
| 57 |
+
"MKTRQNKDSMSMRSGRKKEAPGPREELRSRGRASPGGVSTSSSDGKAEKSRQTAKKARVE"
|
| 58 |
+
"EASTPKVNKQGRSEEISESESEETNAPKKTKTEQELPRPQSPSDLDSLDGRSLNDDGSSD"
|
| 59 |
+
"PRDIDQDNRSTSPSIYSPGSVENDSDSSSGLSQGPARPYHPPPLFPPSPQPPDSTPRQPE"
|
| 60 |
+
"ASFEPHPSVTPTGYHAPMEPPTSRMFQAPPGAPPPHPQLYPGGTGGVLSGPPMGPKGGGA"
|
| 61 |
+
"ASSVGGPNGGKQHPPPTTPISVSSSGASGAPPTKPPTTPVGGGNLPSAPPPANFPHVTPN"
|
| 62 |
+
"LPPPPALRPLNNASASPPGLGAQPLPGHLPSPHAMGQGMGGLPPGPEKGPTLAPSPHSLP"
|
| 63 |
+
"PASSSAPAPPMRFPYSSSSSSSAAASSSSSSSSSSASPFPASQALPSYPHSFPPPTSLSV"
|
| 64 |
+
"SNQPPKYTQPSLPSQAVWSQGPPPPPPYGRLLANSNAHPGPFPPSTGAQSTAHPPVSTHH"
|
| 65 |
+
"HHHQQQQQQQQQQQQQQQQQQQHHGNSGPPPPGAFPHPLEGGSSHHAHPYAMSPSLGSLR"
|
| 66 |
+
"PYPPGPAHLPPPHSQVSYSQAGPNGPPVSSSSNSSSSTSQGSYPCSHPSPSQGPQGAPYP"
|
| 67 |
+
"FPPVPTVTTSSATLSTVIATVASSPAGYKTASPPGPPPYGKRAPSPGAYKTATPPGYKPG"
|
| 68 |
+
"SPPSFRTGTPPGYRGTSPPAGPGTFKPGSPTVGPGPLPPAGPSGLPSLPPPPAAPASGPP"
|
| 69 |
+
"LSATQIKQEPAEEYETPESPVPPARSPSPPPKVVDVPSHASQSARFNKHLDRGFNSCARS"
|
| 70 |
+
"DLYFVPLEGSKLAKKRADLVEKVRREAEQRAREEKEREREREREKEREREKERELERSVK"
|
| 71 |
+
"LAQEGRAPVECPSLGPVPHRPPFEPGSAVATVPPYLGPDTPALRTLSEYARPHVMSPGNR"
|
| 72 |
+
"NHPFYVPLGAVDPGLLGYNVPALYSSDPAAREREREARERDLRDRLKPGFEVKPSELEPL"
|
| 73 |
+
"HGVPGPGLDPFPRHGGLALQPGPPGLHPFPFHPSLGPLERERLALAAGPALRPDMSYAER"
|
| 74 |
+
"LAAERQHAERVAALGNDPLARLQMLNVTPHHHQHSHIHSHLHLHQQDAIHAASASVHPLI"
|
| 75 |
+
"DPLASGSHLTRIPYPAGTLPNPLLPHPLHENEVLRHQLFAAPYRDLPASLSAPMSAAHQL"
|
| 76 |
+
"QAMHAQSAELQRLALEQQQWLHAHHPLHSVPLPAQEDYYSHLKKESDKPL"
|
| 77 |
+
),
|
| 78 |
+
},
|
| 79 |
+
{
|
| 80 |
+
"id": 'fbll1',
|
| 81 |
+
"name": 'FBLL1',
|
| 82 |
+
"seq": (
|
| 83 |
+
"MKSAASSRGGGGGGRGGGGWGSWGGGRGGGGGAGKGGGGDGGGQGGKGGFGARARGFGGG"
|
| 84 |
+
"GRGRGRGGGDGKDRGGGGQRRGGVAKSKSRRRKGAMVVSVEPHRHEGVFIYRGAEDALVT"
|
| 85 |
+
"LNMVPGQSVYGERRVTVTEGGVKQEYRTWNPFRSKLAAAILGGVDQIHIKPKSKVLYLGA"
|
| 86 |
+
"ASGTTVSHVSDIIGPDGLVYAVEFSHRAGRDLVNVAKKRTNIIPVLEDARHPLKYRMLIG"
|
| 87 |
+
"MVDVIFADVAQPDQSRIVALNAHTFLRNGGHFLISIKANCIDSTASAEAVFASEVRKLQQ"
|
| 88 |
+
"ENLKPQEQLTLEPYERDHAVVVGVYRPLPKSSSK"
|
| 89 |
+
),
|
| 90 |
+
},
|
| 91 |
+
{
|
| 92 |
+
"id": 'foxp2',
|
| 93 |
+
"name": 'FOXP2',
|
| 94 |
+
"seq": (
|
| 95 |
+
"MMQESATETISNSSMNQNGMSTLSSQLDAGSRDGRSSGDTSSEVSTVELLHLQQQQALQA"
|
| 96 |
+
"ARQLLLQQQTSGLKSPKSSDKQRPLQVPVSVAMMTPQVITPQQMQQILQQQVLSPQQLQA"
|
| 97 |
+
"LLQQQQAVMLQQQQLQEFYKKQQEQLHLQLLQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ"
|
| 98 |
+
"QQQQQQQQQQQHPGKQAKEQQQQQQQQQQLAAQQLVFQQQLLQMQQLQQQQHLLSLQRQG"
|
| 99 |
+
"LISIPPGQAALPVQSLPQAGLSPAEIQQLWKEVTGVHSMEDNGIKHGGLDLTTNNSSSTT"
|
| 100 |
+
"SSNTSKASPPITHHSIVNGQSSVLSARRDSSSHEETGASHTLYGHGVCKWPGCESICEDF"
|
| 101 |
+
"GQFLKHLNNEHALDDRSTAQCRVQMQVVQQLEIQLSKERERLQAMMTHLHMRPSEPKPSP"
|
| 102 |
+
"KPLNLVSSVTMSKNMLETSPQSLPQTPTTPTAPVTPITQGPSVITPASVPNVGAIRRRHS"
|
| 103 |
+
"DKYNIPMSSEIAPNYEFYKNADVRPPFTYATLIRQAIMESSDRQLTLNEIYSWFTRTFAY"
|
| 104 |
+
"FRRNAATWKNAVRHNLSLHKCFVRVENVKGAVWTVDEVEYQKRRSQKITGSPTLVKNIPT"
|
| 105 |
+
"SLGYGAALNASLQAALAESSLPLLSNPGLINNASSGLLQAVHEDLNGSLDHIDSNGNSSP"
|
| 106 |
+
"GCSPQPHIHSIHVKEEPVIAEDEDCPMSLVTTANHSPELEDDREIEEEPLSEDLE"
|
| 107 |
+
),
|
| 108 |
+
},
|
| 109 |
+
{
|
| 110 |
+
"id": 'semg2',
|
| 111 |
+
"name": 'SEMG2',
|
| 112 |
+
"seq": (
|
| 113 |
+
"MKSIILFVLSLLLILEKQAAVMGQKGGSKGQLPSGSSQFPHGQKGQHYFGQKDQQHTKSK"
|
| 114 |
+
"GSFSIQHTYHVDINDHDWTRKSQQYDLNALHKATKSKQHLGGSQQLLNYKQEGRDHDKSK"
|
| 115 |
+
"GHFHMIVIHHKGGQAHHGTQNPSQDQGNSPSGKGLSSQCSNTEKRLWVHGLSKEQASASG"
|
| 116 |
+
"AQKGRTQGGSQSSYVLQTEELVVNKQQRETKNSHQNKGHYQNVVDVREEHSSKLQTSLHP"
|
| 117 |
+
"AHQDRLQHGPKDIFTTQDELLVYNKNQHQTKNLSQDQEHGRKAHKISYPSSRTEERQLHH"
|
| 118 |
+
"GEKSVQKDVSKGSISIQTEEKIHGKSQNQVTIHSQDQEHGHKENKISYQSSSTEERHLNC"
|
| 119 |
+
"GEKGIQKGVSKGSISIQTEEQIHGKSQNQVRIPSQAQEYGHKENKISYQSSSTEERRLNS"
|
| 120 |
+
"GEKDVQKGVSKGSISIQTEEKIHGKSQNQVTIPSQDQEHGHKENKMSYQSSSTEERRLNY"
|
| 121 |
+
"GGKSTQKDVSQSSISFQIEKLVEGKSQIQTPNPNQDQWSGQNAKGKSGQSADSKQDLLSH"
|
| 122 |
+
"EQKGRYKQESSESHNIVITEHEVAQDDHLTQQYNEDRNPIST"
|
| 123 |
+
),
|
| 124 |
+
},
|
| 125 |
+
{
|
| 126 |
+
"id": 'sgta',
|
| 127 |
+
"name": 'SGTA',
|
| 128 |
+
"seq": (
|
| 129 |
+
"MDNKKRLAYAIIQFLHDQLRHGGLSSDAQESLEVAIQCLETAFGVTVEDSDLALPQTLPE"
|
| 130 |
+
"IFEAAATGKEMPQDLRSPARTPPSEEDSAEAERLKTEGNEQMKVENFEAAVHFYGKAIEL"
|
| 131 |
+
"NPANAVYFCNRAAAYSKLGNYAGAVQDCERAICIDPAYSKAYGRMGLALSSLNKHVEAVA"
|
| 132 |
+
"YYKKALELDPDNETYKSNLKIAELKLREAPSPTGGVGSFDIAGLLNNPGFMSMASNLMNN"
|
| 133 |
+
"PQIQQLMSGMISGGNNPLGTPGTSPSQNDLASLIQAGQQFAQQMQQQNPELIEQLRSQIR"
|
| 134 |
+
"SRTPSASNDDQQE"
|
| 135 |
+
),
|
| 136 |
+
},
|
| 137 |
+
{
|
| 138 |
+
"id": 'ss18',
|
| 139 |
+
"name": 'SS18',
|
| 140 |
+
"seq": (
|
| 141 |
+
"MSVAFAAPRQRGKGEITPAAIQKMLDDNNHLIQCIMDSQNKGKTSECSQYQQMLHTNLVY"
|
| 142 |
+
"LATIADSNQNMQSLLPAPPTQNMPMGPGGMNQSGPPPPPRSHNMPSDGMVGGGPPAPHMQ"
|
| 143 |
+
"NQMNGQMPGPNHMPMQGPGPNQLNMTNSSMNMPSSSHGSMGGYNHSVPSSQSMPVQNQMT"
|
| 144 |
+
"MSQGQPMGNYGPRPNMSMQPNQGPMMHQQPPSQQYNMPQGGGQHYQGQQPPMGMMGQVNQ"
|
| 145 |
+
"GNHMMGQRQIPPYRPPQQGPPQQYSGQEDYYGDQYSHGGQGPPEGMNQQYYPDGHNDYGY"
|
| 146 |
+
"QQPSYPEQGYDRPYEDSSQHYYEGGNSQYGQQQDAYQGPPPQQGYPPQQQQYPGQQGYPG"
|
| 147 |
+
"QQQGYGPSQGGPGPQYPNYPQGQGQQYGGYRPTQPGPPQPPQQRPYGYDQGQYGNYQQ"
|
| 148 |
+
),
|
| 149 |
+
},
|
| 150 |
+
{
|
| 151 |
+
"id": 'ubqln_mut10',
|
| 152 |
+
"name": 'UBQLN_mut10',
|
| 153 |
+
"seq": (
|
| 154 |
+
"MAEPSGAETRPPIRVTVKTPKDKEEIVICDRASVKEFKEEISRRFKAQQDQLVLIFAGKI"
|
| 155 |
+
"LKDGDTLNQHGIKDGLTVHLVIKTPQKAQDPAAATASSPSTPDPASAPSTTPASPATPAQ"
|
| 156 |
+
"PSTSGSASSDAGSGSRRSSGGGPSPGAGEGSPSATASILSGFGGILGLGSLGLGSANFME"
|
| 157 |
+
"LQQQMQRQLMSNPEMLSQICENPLVQDMMSNPDLMRHCCMANPQYQQLCERNPEISHMLN"
|
| 158 |
+
"NPELMRQTMELARNPAMMQEMMRNQDRALSNCESIPGGYNALRRMYTDIQEPMFSAAREQ"
|
| 159 |
+
"FGNNPFSSLAGNSDSSSSQPLRTENREPLPNPWSPSPPTSQAPGSGGEGTGGSGTSQVHP"
|
| 160 |
+
"TVSNPFGINAASLGSGMCNSPEMQALLQQISENCQLMQNVISAPYMRSMMQTLAQNPDFA"
|
| 161 |
+
"AQMMVNVPLFAGNPQLQEQLRLQLPVFLQQCQNPESLSILTNPRAMQALLQIQQGLQTLQ"
|
| 162 |
+
"TEAPGLVPSLGSFGISRTPAPSAGSNAGSTPEAPTSSPATPATSSPTGASSAQQQLMQQM"
|
| 163 |
+
"IQLLAGSGNSQVQTPEVRFQQQLEQLNSCGFINREANLQALIATGGDINAAIERLLGSQL"
|
| 164 |
+
"S"
|
| 165 |
+
),
|
| 166 |
+
},
|
| 167 |
+
{
|
| 168 |
+
"id": 'ubqln_mut20',
|
| 169 |
+
"name": 'UBQLN_mut20',
|
| 170 |
+
"seq": (
|
| 171 |
+
"MAEPSGAETNPNIRVTVKTPKDKEEIVICDIASVKEFKEEISRRFKAQQDQLVLIFAGKD"
|
| 172 |
+
"LKDGDTLNQHGIKDGETYHLVIKTPQKAQDPAAATASSPSTPDPASAPSTTPASPATPAQ"
|
| 173 |
+
"PSTSGSASSDAGSGSRRSSGGGPSPGAGEGSPSATASILSGFGGILGLGSLGLGSANFME"
|
| 174 |
+
"LQQQMQRQLMSNPEMLSQICENPLVQDMMSNPDLMRHCCMANPQYQQLCERNPEISHMLN"
|
| 175 |
+
"NPELMRQTMELARNPAMMQEMMRNQDRALSNCESIPGGYNALRRMYTDIQEPMFSAAREQ"
|
| 176 |
+
"FGNNPDSSLAGNSDSSSSQPLRTENREPLPNPWSPSPPTSQAPGSGGEGTGGSGTSQVHP"
|
| 177 |
+
"TVSNPFGINAASLGSGMCNSPEMQALLQQISENCQLMQNVISAPKMRSMMQTLAQNPDFA"
|
| 178 |
+
"AQMMVNVPLFAGNPQLQEQLRLQLPVFLQQCQNPESLSIDTNPRAMQALLQIQQGLQTLQ"
|
| 179 |
+
"TEAPGLVPSLGSFGISRTPAPSAGSNAGSTPEAPTSSPATPATSSPTGASSAQQQLKQQM"
|
| 180 |
+
"IQLLAGSGNSQVQTPEVRFQQQLEQLNSCGFINREANLQALIATGGDINAAIERLLGSQL"
|
| 181 |
+
"S"
|
| 182 |
+
),
|
| 183 |
+
},
|
| 184 |
+
{
|
| 185 |
+
"id": 'ubqln_mut30',
|
| 186 |
+
"name": 'UBQLN_mut30',
|
| 187 |
+
"seq": (
|
| 188 |
+
"MAEPSGAETNPNIRVTVKTPKDKEEIVICDIASVKEFKEEISRRFKAQQDQLVLIFAGKD"
|
| 189 |
+
"DKDGDTDNQHGIKDGETYHLVIKTPQKAQDPAAATASSPSTPDPASAPSTTPASPATPAQ"
|
| 190 |
+
"PSTSGSASSDAGSGSRRSSGGGPSPGAGEGSPSATASILSGFGGILGLGSLGLGSANFME"
|
| 191 |
+
"LQQQMQRQLMSNPEMLSQPCENLLVQDMMSNPDLMRHCCMANPQYQQLCERNPEISHMLN"
|
| 192 |
+
"NPELMRQPMELARNPAMMQEMMRNQDRALSNCESIPGGYNADRRDYTDIQEPMFSAAREQ"
|
| 193 |
+
"FGNNPDSSLAGNSDSSSSQPLRTENREPLPNPWSPSPPTSQAPGSGGEGTGGSGTSQVHP"
|
| 194 |
+
"TVSNPFGINAASLGSGMCNSPEMQALLQQISENCQLMQNVISAPKMRSMMQTLAQNPDFA"
|
| 195 |
+
"AQMMVNVPLFAGNPQLQEQLRLQLPVFLQQCQNPESLSIDTNPRAMQALLQIQQGLQTLQ"
|
| 196 |
+
"TEAPGLVPSLGSFGISRTPAPSAGSNAGSTPEAPTSSPATPATSSPTGASSAQQQLKQQM"
|
| 197 |
+
"IQLLAGSGNSQVQTPEVRFQQQLEQLNSCGPINREANLQALPATGGDINADIERLLGSQL"
|
| 198 |
+
"S"
|
| 199 |
+
),
|
| 200 |
+
},
|
| 201 |
+
]
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
def feature_path(example_id: str) -> Path:
|
| 205 |
+
return ASSETS_DIR / f"{example_id}.h5"
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def find_example_by_seq(seq: str):
|
| 209 |
+
"""Return the EXAMPLES entry whose sequence matches `seq`, or None."""
|
| 210 |
+
norm = seq.strip().upper()
|
| 211 |
+
for example in EXAMPLES:
|
| 212 |
+
if example["seq"].strip().upper() == norm:
|
| 213 |
+
return example
|
| 214 |
+
return None
|
preprocess/add_example.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Add example sequence(s) to examples.py from a raw-data Excel file.
|
| 2 |
+
|
| 3 |
+
The training-data Excel files (e.g. LLPSense/data/raw/nonPSPex/ASCL1.xlsx)
|
| 4 |
+
carry a "Protein name" column and a sequence column (spelled either
|
| 5 |
+
"Sequence" or "Seqence" across files) alongside condition columns this demo
|
| 6 |
+
doesn't need. Some files hold one protein across several condition rows
|
| 7 |
+
(e.g. ASCL1.xlsx); others hold several distinct proteins, one per row, each
|
| 8 |
+
with its own name/sequence (e.g. UBQLN4_mut.xlsx's UBQLN_mut10/20/30). This
|
| 9 |
+
script groups rows by protein name and appends one EXAMPLES entry per
|
| 10 |
+
distinct (name, sequence) pair found in the file.
|
| 11 |
+
|
| 12 |
+
Requires pandas + openpyxl (not part of requirements.txt β those are only
|
| 13 |
+
needed to run this offline script, not the deployed app):
|
| 14 |
+
pip install pandas openpyxl
|
| 15 |
+
|
| 16 |
+
Usage:
|
| 17 |
+
python preprocess/add_example.py /path/to/ASCL1.xlsx
|
| 18 |
+
python preprocess/add_example.py /path/to/ASCL1.xlsx --id ascl1 --name "ASCL1"
|
| 19 |
+
python preprocess/add_example.py /path/to/UBQLN4_mut.xlsx # adds all 3 mutants
|
| 20 |
+
|
| 21 |
+
--id/--name only apply when the file contains a single protein.
|
| 22 |
+
|
| 23 |
+
After adding, run preprocess/extract_example_feat.py to cache the T5 feature(s).
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
import argparse
|
| 27 |
+
import re
|
| 28 |
+
import sys
|
| 29 |
+
from pathlib import Path
|
| 30 |
+
|
| 31 |
+
import pandas as pd
|
| 32 |
+
|
| 33 |
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
| 34 |
+
from examples import EXAMPLES # noqa: E402
|
| 35 |
+
|
| 36 |
+
EXAMPLES_FILE = Path(__file__).resolve().parent.parent / "examples.py"
|
| 37 |
+
VALID_AA = set("ACDEFGHIKLMNPQRSTVWY")
|
| 38 |
+
ALLOW_AA = VALID_AA | set("XBJOUZ")
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def _find_col(df, *keywords):
|
| 42 |
+
for col in df.columns:
|
| 43 |
+
low = col.lower()
|
| 44 |
+
if all(keyword in low for keyword in keywords):
|
| 45 |
+
return col
|
| 46 |
+
return None
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def slugify(name: str) -> str:
|
| 50 |
+
slug = re.sub(r"[^a-z0-9]+", "_", name.strip().lower()).strip("_")
|
| 51 |
+
if not slug:
|
| 52 |
+
raise ValueError(f"Could not derive an id from name: {name!r}")
|
| 53 |
+
return slug
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def read_proteins(xlsx_path: Path) -> list[tuple[str, str]]:
|
| 57 |
+
"""Return one (name, sequence) pair per distinct protein in the file.
|
| 58 |
+
|
| 59 |
+
Rows are grouped by protein name β a file may repeat the same protein
|
| 60 |
+
across several condition rows (ASCL1.xlsx: 1 protein, several conditions)
|
| 61 |
+
or list several distinct proteins one per row (UBQLN4_mut.xlsx: 3
|
| 62 |
+
mutants). Either shape yields one entry per unique protein name here.
|
| 63 |
+
"""
|
| 64 |
+
df = pd.read_excel(xlsx_path, engine="openpyxl")
|
| 65 |
+
|
| 66 |
+
name_col = _find_col(df, "protein", "name")
|
| 67 |
+
seq_col = _find_col(df, "seq")
|
| 68 |
+
if name_col is None or seq_col is None:
|
| 69 |
+
raise ValueError(
|
| 70 |
+
f"{xlsx_path} is missing a protein-name or sequence column "
|
| 71 |
+
f"(found columns: {df.columns.tolist()})"
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
df = df[[name_col, seq_col]].dropna()
|
| 75 |
+
if df.empty:
|
| 76 |
+
raise ValueError(f"{xlsx_path} has no rows with both a name and a sequence")
|
| 77 |
+
|
| 78 |
+
proteins = []
|
| 79 |
+
for name, group in df.groupby(name_col, sort=False):
|
| 80 |
+
seqs = group[seq_col].astype(str).str.strip().str.upper().unique()
|
| 81 |
+
if len(seqs) != 1:
|
| 82 |
+
raise ValueError(
|
| 83 |
+
f"Protein '{name}' in {xlsx_path} has {len(seqs)} distinct "
|
| 84 |
+
f"sequences across its rows β expected exactly one"
|
| 85 |
+
)
|
| 86 |
+
proteins.append((str(name).strip(), seqs[0]))
|
| 87 |
+
return proteins
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def format_seq_literal(seq: str, width: int = 60) -> str:
|
| 91 |
+
lines = [seq[i:i + width] for i in range(0, len(seq), width)]
|
| 92 |
+
body = "\n".join(f' "{line}"' for line in lines)
|
| 93 |
+
return f"(\n{body}\n )"
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def append_example(entry: dict) -> None:
|
| 97 |
+
text = EXAMPLES_FILE.read_text()
|
| 98 |
+
match = re.search(r"(EXAMPLES\s*=\s*\[)(.*?)(\n\])", text, flags=re.DOTALL)
|
| 99 |
+
if not match:
|
| 100 |
+
raise RuntimeError(f"Could not find EXAMPLES list in {EXAMPLES_FILE}")
|
| 101 |
+
|
| 102 |
+
# The existing last entry may or may not have a trailing comma (both
|
| 103 |
+
# forms occur in practice) β normalize to one before appending.
|
| 104 |
+
body = match.group(2).rstrip()
|
| 105 |
+
if body and not body.endswith(","):
|
| 106 |
+
body += ","
|
| 107 |
+
|
| 108 |
+
new_block = (
|
| 109 |
+
"\n {\n"
|
| 110 |
+
f' "id": {entry["id"]!r},\n'
|
| 111 |
+
f' "name": {entry["name"]!r},\n'
|
| 112 |
+
f' "seq": {format_seq_literal(entry["seq"])},\n'
|
| 113 |
+
" },"
|
| 114 |
+
)
|
| 115 |
+
updated = text[:match.start(2)] + body + new_block + text[match.end(2):]
|
| 116 |
+
EXAMPLES_FILE.write_text(updated)
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def main():
|
| 120 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 121 |
+
parser.add_argument("xlsx_path", type=Path)
|
| 122 |
+
parser.add_argument(
|
| 123 |
+
"--id", type=str, default=None,
|
| 124 |
+
help="override the generated example id (only valid for single-protein files)",
|
| 125 |
+
)
|
| 126 |
+
parser.add_argument(
|
| 127 |
+
"--name", type=str, default=None,
|
| 128 |
+
help="override the display name (only valid for single-protein files)",
|
| 129 |
+
)
|
| 130 |
+
args = parser.parse_args()
|
| 131 |
+
|
| 132 |
+
proteins = read_proteins(args.xlsx_path)
|
| 133 |
+
|
| 134 |
+
if (args.id or args.name) and len(proteins) > 1:
|
| 135 |
+
parser.error(
|
| 136 |
+
f"{args.xlsx_path} contains {len(proteins)} distinct proteins β "
|
| 137 |
+
"--id/--name can only be used with a single-protein file"
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
# Track ids seen so far in this run too, so two proteins in the same
|
| 141 |
+
# file that happen to slugify to the same id are still caught.
|
| 142 |
+
known_ids = {example["id"] for example in EXAMPLES}
|
| 143 |
+
added = 0
|
| 144 |
+
|
| 145 |
+
for name, seq in proteins:
|
| 146 |
+
example_id = args.id or slugify(name)
|
| 147 |
+
display_name = args.name or name
|
| 148 |
+
|
| 149 |
+
invalid = set(seq) - ALLOW_AA
|
| 150 |
+
if invalid:
|
| 151 |
+
print(
|
| 152 |
+
f"Warning: '{name}' sequence contains unexpected characters: "
|
| 153 |
+
f"{''.join(sorted(invalid))}",
|
| 154 |
+
file=sys.stderr,
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
if example_id in known_ids:
|
| 158 |
+
print(f"'{example_id}' already exists in EXAMPLES β skipping.")
|
| 159 |
+
continue
|
| 160 |
+
|
| 161 |
+
append_example({"id": example_id, "name": display_name, "seq": seq})
|
| 162 |
+
known_ids.add(example_id)
|
| 163 |
+
added += 1
|
| 164 |
+
print(f"Added '{example_id}' ({display_name}, {len(seq)} aa) to {EXAMPLES_FILE}")
|
| 165 |
+
|
| 166 |
+
if added:
|
| 167 |
+
print("Run `python preprocess/extract_example_feat.py` to cache the T5 feature(s).")
|
| 168 |
+
else:
|
| 169 |
+
print("No new examples added.")
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
if __name__ == "__main__":
|
| 173 |
+
main()
|
preprocess/extract_example_feat.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Pre-extract ProtT5-XL features for the built-in example sequences.
|
| 2 |
+
|
| 3 |
+
Mirrors the offline pre-computation step in the training pipeline
|
| 4 |
+
(LLPSense/preprocess/extract_feat.py): run this once so the Gradio app never
|
| 5 |
+
has to hit the T5 model / GPU for a sequence it already knows about (see
|
| 6 |
+
`examples.find_example_by_seq` and `cb_extract` in app.py). This matters
|
| 7 |
+
most on the ZeroGPU-backed Space, where every GPU call consumes quota.
|
| 8 |
+
|
| 9 |
+
Usage:
|
| 10 |
+
python preprocess/extract_example_feat.py
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import sys
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
|
| 16 |
+
from huggingface_hub import snapshot_download
|
| 17 |
+
from tqdm import tqdm
|
| 18 |
+
|
| 19 |
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
| 20 |
+
|
| 21 |
+
from examples import EXAMPLES, feature_path, ASSETS_DIR # noqa: E402
|
| 22 |
+
from t5_utils import T5_REPO_ID, extract_t5_feature, write_feature_h5 # noqa: E402
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def main():
|
| 26 |
+
snapshot_download(T5_REPO_ID)
|
| 27 |
+
ASSETS_DIR.mkdir(parents=True, exist_ok=True)
|
| 28 |
+
|
| 29 |
+
for example in tqdm(EXAMPLES, desc="Extracting example T5 features"):
|
| 30 |
+
out_path = feature_path(example["id"])
|
| 31 |
+
if out_path.exists():
|
| 32 |
+
continue
|
| 33 |
+
feat = extract_t5_feature(example["seq"])
|
| 34 |
+
write_feature_h5(out_path, feat)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
if __name__ == "__main__":
|
| 38 |
+
main()
|
requirements.txt
CHANGED
|
@@ -8,6 +8,7 @@ sentencepiece # ProtT5 tokenizer backend
|
|
| 8 |
xgboost==2.0.3
|
| 9 |
scikit-learn
|
| 10 |
joblib
|
|
|
|
| 11 |
|
| 12 |
# Gradio demo
|
| 13 |
gradio>=4.0.0
|
|
|
|
| 8 |
xgboost==2.0.3
|
| 9 |
scikit-learn
|
| 10 |
joblib
|
| 11 |
+
h5py # cached example T5 features in assets/
|
| 12 |
|
| 13 |
# Gradio demo
|
| 14 |
gradio>=4.0.0
|
t5_utils.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Shared ProtT5-XL loading / feature-extraction / h5 I/O helpers.
|
| 2 |
+
|
| 3 |
+
Used by both app.py (Gradio callbacks, wrapped in @spaces.GPU) and
|
| 4 |
+
preprocess/extract_example_feat.py (offline pre-computation of the built-in
|
| 5 |
+
example sequences' features into assets/). Keeping this logic in one place
|
| 6 |
+
means the cached features in assets/ are guaranteed to match what the app
|
| 7 |
+
would compute on the fly.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import re
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import h5py
|
| 14 |
+
import numpy as np
|
| 15 |
+
import torch
|
| 16 |
+
|
| 17 |
+
T5_REPO_ID = "Rostlab/prot_t5_xl_half_uniref50-enc"
|
| 18 |
+
|
| 19 |
+
_t5_tokenizer = None
|
| 20 |
+
_t5_model = None
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def get_device() -> str:
|
| 24 |
+
return "cuda" if torch.cuda.is_available() else "cpu"
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def load_t5():
|
| 28 |
+
global _t5_tokenizer, _t5_model
|
| 29 |
+
if _t5_model is None:
|
| 30 |
+
from transformers import AutoTokenizer, T5EncoderModel
|
| 31 |
+
import transformers.utils.import_utils as _hf_utils
|
| 32 |
+
import transformers.modeling_utils as _modeling_utils
|
| 33 |
+
|
| 34 |
+
# Rostlab/prot_t5_xl_half_uniref50-enc is only available as .bin (no
|
| 35 |
+
# safetensors). transformers 5.x blocks torch.load on torch < 2.6 due
|
| 36 |
+
# to CVE-2025-32434. We bypass that gate for this specific trusted
|
| 37 |
+
# checkpoint from the official HuggingFace Hub. The check lives in
|
| 38 |
+
# two places β import_utils AND the locally-imported name in
|
| 39 |
+
# modeling_utils β so both must be patched.
|
| 40 |
+
_noop = lambda: None
|
| 41 |
+
_orig_hf = _hf_utils.check_torch_load_is_safe
|
| 42 |
+
_orig_mdl = _modeling_utils.check_torch_load_is_safe
|
| 43 |
+
|
| 44 |
+
_hf_utils.check_torch_load_is_safe = _noop
|
| 45 |
+
_modeling_utils.check_torch_load_is_safe = _noop
|
| 46 |
+
|
| 47 |
+
try:
|
| 48 |
+
repo_id = T5_REPO_ID
|
| 49 |
+
dev = get_device()
|
| 50 |
+
dtype = torch.float16 if dev == "cuda" else torch.float32
|
| 51 |
+
|
| 52 |
+
_t5_tokenizer = AutoTokenizer.from_pretrained(
|
| 53 |
+
repo_id, do_lower_case=False, local_files_only=True
|
| 54 |
+
)
|
| 55 |
+
_t5_model = (
|
| 56 |
+
T5EncoderModel.from_pretrained(
|
| 57 |
+
repo_id, torch_dtype=dtype, local_files_only=True
|
| 58 |
+
)
|
| 59 |
+
.to(dev)
|
| 60 |
+
.eval()
|
| 61 |
+
)
|
| 62 |
+
_t5_model.requires_grad_(False)
|
| 63 |
+
finally:
|
| 64 |
+
_hf_utils.check_torch_load_is_safe = _orig_hf
|
| 65 |
+
_modeling_utils.check_torch_load_is_safe = _orig_mdl
|
| 66 |
+
|
| 67 |
+
return _t5_tokenizer, _t5_model
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def extract_t5_feature(sequence: str) -> np.ndarray:
|
| 71 |
+
"""
|
| 72 |
+
Return mean-pooled ProtT5-XL embedding for a single sequence.
|
| 73 |
+
Output shape: (1024,) β matches the feature dimension LLPSense was trained on.
|
| 74 |
+
"""
|
| 75 |
+
tok, mdl = load_t5()
|
| 76 |
+
dev = get_device()
|
| 77 |
+
|
| 78 |
+
# Replace ambiguous residues with X (same as original pipeline)
|
| 79 |
+
clean = re.sub(r"[UZOB]", "X", sequence.strip().upper())
|
| 80 |
+
spaced = " ".join(list(clean))
|
| 81 |
+
|
| 82 |
+
enc = tok([spaced], add_special_tokens=True, padding="longest", return_tensors="pt")
|
| 83 |
+
input_ids = enc["input_ids"].to(dev)
|
| 84 |
+
attention_mask = enc["attention_mask"].to(dev)
|
| 85 |
+
|
| 86 |
+
with torch.no_grad():
|
| 87 |
+
hidden = mdl(input_ids=input_ids, attention_mask=attention_mask).last_hidden_state
|
| 88 |
+
# Mean-pool over sequence positions (ignoring padding tokens)
|
| 89 |
+
feat = (hidden * attention_mask[..., None]).sum(dim=1) / \
|
| 90 |
+
attention_mask.sum(dim=1, keepdim=True)
|
| 91 |
+
|
| 92 |
+
return feat[0].float().cpu().numpy() # (1024,)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
# ββ h5 cache I/O ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 96 |
+
FEATURE_TAG = "protein_feat"
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def write_feature_h5(filepath, feat: np.ndarray, tag: str = FEATURE_TAG) -> None:
|
| 100 |
+
filepath = Path(filepath)
|
| 101 |
+
filepath.parent.mkdir(parents=True, exist_ok=True)
|
| 102 |
+
with h5py.File(filepath, "w") as f:
|
| 103 |
+
f.create_dataset(tag, data=feat, dtype="f", compression="gzip")
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def read_feature_h5(filepath, tag: str = FEATURE_TAG) -> np.ndarray:
|
| 107 |
+
with h5py.File(filepath, "r") as f:
|
| 108 |
+
return f[tag][:]
|