wpk2005 commited on
Commit
c44aa7d
·
verified ·
1 Parent(s): 4ad1da9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +168 -129
app.py CHANGED
@@ -9,6 +9,8 @@ import pickle
9
  import hashlib
10
  import time
11
  import threading
 
 
12
 
13
  # ===== 页面设置 =====
14
  st.set_page_config(page_title="多肽虚拟筛选工具", layout="wide")
@@ -16,27 +18,58 @@ st.title("多肽虚拟筛选工具")
16
 
17
  # ===== 防休眠机制 =====
18
  def keep_alive():
19
- """每隔一段时间在后台执行一次轻量操作,防止应用被云平台冻结"""
20
  while True:
21
- time.sleep(300) # 每5分钟
22
  try:
23
  _ = st.session_state.get("_", None)
24
  except Exception:
25
  pass
26
 
27
- # 启动后台保活线程
28
  if "alive_thread" not in st.session_state:
29
  alive_thread = threading.Thread(target=keep_alive, daemon=True)
30
  alive_thread.start()
31
  st.session_state.alive_thread = True
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  # ===== 侧边栏参数 =====
34
  st.sidebar.header("筛选参数")
35
 
36
- drug = st.sidebar.text_input("靶点序列(参照肽)", value="HAEGTFTSDV")
 
 
 
 
 
37
 
38
- min_len = st.sidebar.number_input("最小长度", value=10, min_value=5, max_value=50)
39
- max_len = st.sidebar.number_input("最大长度", value=12, min_value=5, max_value=50)
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
  cut_rule = st.sidebar.selectbox(
42
  "酶切规则",
@@ -50,7 +83,7 @@ cut_rule = st.sidebar.selectbox(
50
  "中性蛋白酶 (FLIV)",
51
  "碱性蛋白酶 (KRFLIV)",
52
  ],
53
- index=0,
54
  )
55
  cut_map = {
56
  "胰蛋白酶 (KR)": "KR",
@@ -64,9 +97,8 @@ cut_map = {
64
  }
65
  CUT = cut_map[cut_rule]
66
 
67
- top_n = st.sidebar.number_input("输出Top几", value=10, min_value=5, max_value=100)
68
-
69
- content_threshold = st.sidebar.number_input("蛋白含量阈值(%)", value=10, min_value=0, max_value=100)
70
 
71
  # ===== 生成缓存文件名 =====
72
  def get_cache_key():
@@ -188,123 +220,130 @@ def get_content(src_name):
188
  # ===== 主界面 =====
189
  st.subheader("开始筛选")
190
 
191
- if st.button("开始筛选", type="primary"):
192
- # 加载模型
193
- tok, mod = load_model()
194
-
195
- # 拉取蛋白数据(优先读本地缓存)
196
- food_cache_file = "food_cache.pkl"
197
- if os.path.exists(food_cache_file):
198
- with open(food_cache_file, "rb") as f:
199
- FOOD = pickle.load(f)
200
- st.success(f"已从缓存加载 {len(FOOD)} 条蛋白序列")
201
- else:
202
- with st.spinner("正在从UniProt下载食用蛋白序列..."):
203
- FOOD = fetch_proteins()
204
- with open(food_cache_file, "wb") as f:
205
- pickle.dump(FOOD, f)
206
- st.success(f"已获取并缓存 {len(FOOD)} 条蛋白序列")
207
-
208
- # 虚拟酶切
209
- allp = []
210
- for name, seq in FOOD.items():
211
- for p in cut(seq, CUT, min_len, max_len):
212
- allp.append({"src": name, "p": p})
213
-
214
- # 去重(按物种简称 + 肽段序列)
215
- seen = set()
216
- unip = []
217
- for x in allp:
218
- species_short = (
219
- x["src"].split("(")[-1].split(")")[0].strip()
220
- if "(" in x["src"]
221
- else x["src"]
222
- )
223
- key = (species_short, x["p"])
224
- if key not in seen:
225
- seen.add(key)
226
- unip.append(x)
227
- st.info(f"酶切并去重后共 {len(unip)} 条候选肽")
228
-
229
- # AI打分(智能缓存)
230
- cache_key = get_cache_key()
231
- score_cache_file = f"score_cache_{cache_key}.pkl"
232
-
233
- if os.path.exists(score_cache_file):
234
- with open(score_cache_file, "rb") as f:
235
- res = pickle.load(f)
236
- st.success(f"已从缓存加载 {len(res)} 条打分结果")
237
- else:
238
- progress_bar = st.progress(0)
239
- status_text = st.empty()
240
- v_drug = vec(drug, tok, mod)
241
- res = []
242
- total = len(unip)
243
- for idx, x in enumerate(unip):
244
- v_p = vec(x["p"], tok, mod)
245
- s = sim(v_p, v_drug)
246
- res.append({"src": x["src"], "p": x["p"], "score": round(s, 4)})
247
- if idx % 50 == 0:
248
- progress_bar.progress(min(idx / total, 1.0))
249
- status_text.text(f"打分进度: {idx}/{total}")
250
- progress_bar.progress(1.0)
251
- status_text.text(f"打分完成: {total}/{total}")
252
- with open(score_cache_file, "wb") as f:
253
- pickle.dump(res, f)
254
- st.success(f"已完成打分并缓存")
255
-
256
- # 含量标注
257
- for x in res:
258
- x["content_pct"] = get_content(x["src"])
259
-
260
- # 过滤
261
- filtered_res = [x for x in res if x["content_pct"] >= content_threshold]
262
- if filtered_res:
263
- res = filtered_res
264
-
265
- # 排序
266
- res.sort(key=lambda x: x["score"], reverse=True)
267
-
268
- # 显示Top结果(限制显示行数,避免内存溢出,索引从1开始)
269
- st.subheader("结构相似度排名")
270
- df_top = pd.DataFrame(res[:top_n])
271
- df_top.index = range(1, len(df_top) + 1)
272
- show_cols = ["src", "p", "score"]
273
- if "content_pct" in df_top.columns:
274
- show_cols.append("content_pct")
275
- max_display_rows = 100
276
- st.caption(f"显示前 {min(top_n, max_display_rows)} 条结果(共 {top_n} 条)")
277
- st.dataframe(df_top[show_cols].head(max_display_rows), width='stretch')
278
-
279
- # 下载按钮
280
- csv_top = df_top.to_csv(index=False).encode("utf-8-sig")
281
- st.download_button("下载Top结果CSV", csv_top, "out_top.csv", "text/csv")
282
-
283
- df_all = pd.DataFrame(res)
284
- csv_all = df_all.to_csv(index=False).encode("utf-8-sig")
285
- st.download_button("下载全部结果CSV", csv_all, "out_all.csv", "text/csv")
286
-
287
- # 物种统计(全部候选肽,索引从1开始)
288
- st.subheader("物种贡献统计(全部候选肽)")
289
- species_counts = {}
290
- for x in res:
291
- s = (
292
- x["src"].split("(")[-1].split(")")[0].strip()
293
- if "(" in x["src"]
294
- else "unknown"
 
 
 
 
 
 
295
  )
296
- species_counts[s] = species_counts.get(s, 0) + 1
297
- species_df = pd.DataFrame(
298
- list(species_counts.items()), columns=["物种", "候选肽数量"]
299
- )
300
- species_df = species_df.sort_values("候选肽数量", ascending=False)
301
- species_df = species_df.reset_index(drop=True)
302
- species_df.index = range(1, len(species_df) + 1)
303
-
304
- col1, col2 = st.columns(2)
305
- with col1:
306
- st.dataframe(species_df.head(20), width='stretch')
307
- with col2:
308
- st.bar_chart(species_df.set_index("物种"))
309
-
310
- st.success("筛选完成!")
 
 
9
  import hashlib
10
  import time
11
  import threading
12
+ import re
13
+ from datetime import datetime
14
 
15
  # ===== 页面设置 =====
16
  st.set_page_config(page_title="多肽虚拟筛选工具", layout="wide")
 
18
 
19
  # ===== 防休眠机制 =====
20
  def keep_alive():
 
21
  while True:
22
+ time.sleep(300)
23
  try:
24
  _ = st.session_state.get("_", None)
25
  except Exception:
26
  pass
27
 
 
28
  if "alive_thread" not in st.session_state:
29
  alive_thread = threading.Thread(target=keep_alive, daemon=True)
30
  alive_thread.start()
31
  st.session_state.alive_thread = True
32
 
33
+ # ===== 默认参数 =====
34
+ DEFAULTS = {
35
+ "drug": "HAEGTFTSDV",
36
+ "min_len": 10,
37
+ "max_len": 12,
38
+ "cut_rule": "胰蛋白酶 (KR)",
39
+ "top_n": 10,
40
+ "content_threshold": 10,
41
+ }
42
+
43
+ # 初始化 session_state
44
+ for key, value in DEFAULTS.items():
45
+ if key not in st.session_state:
46
+ st.session_state[key] = value
47
+
48
  # ===== 侧边栏参数 =====
49
  st.sidebar.header("筛选参数")
50
 
51
+ # 重置按钮(放在最上面)
52
+ def reset_params():
53
+ for key, value in DEFAULTS.items():
54
+ st.session_state[key] = value
55
+
56
+ st.sidebar.button("🔄 重置为默认参数", on_click=reset_params, use_container_width=True)
57
 
58
+ drug = st.sidebar.text_input(
59
+ "靶点序列(参照肽)",
60
+ key="drug",
61
+ help="请输入20种天然氨基酸字母序列(如:HAEGTFTSDV)"
62
+ )
63
+
64
+ # 靶点输入实时校验
65
+ if drug:
66
+ if re.fullmatch(r'[ACDEFGHIKLMNPQRSTVWYacdefghiklmnpqrstvwy]+', drug):
67
+ st.sidebar.success("✅ 序列格式正确")
68
+ else:
69
+ st.sidebar.error("❌ 序列包含非法字符,仅限20种天然氨基酸字母")
70
+
71
+ min_len = st.sidebar.number_input("最小长度", min_value=5, max_value=50, key="min_len")
72
+ max_len = st.sidebar.number_input("最大长度", min_value=5, max_value=50, key="max_len")
73
 
74
  cut_rule = st.sidebar.selectbox(
75
  "酶切规则",
 
83
  "中性蛋白酶 (FLIV)",
84
  "碱性蛋白酶 (KRFLIV)",
85
  ],
86
+ key="cut_rule",
87
  )
88
  cut_map = {
89
  "胰蛋白酶 (KR)": "KR",
 
97
  }
98
  CUT = cut_map[cut_rule]
99
 
100
+ top_n = st.sidebar.number_input("输出Top几", min_value=5, max_value=100, key="top_n")
101
+ content_threshold = st.sidebar.number_input("蛋白含量阈值(%)", min_value=0, max_value=100, key="content_threshold")
 
102
 
103
  # ===== 生成缓存文件名 =====
104
  def get_cache_key():
 
220
  # ===== 主界面 =====
221
  st.subheader("开始筛选")
222
 
223
+ # 按钮在计算中禁用
224
+ if st.button("🚀 开始筛选", type="primary", disabled=st.session_state.get("running", False)):
225
+ # 输入校验
226
+ if not re.fullmatch(r'[ACDEFGHIKLMNPQRSTVWYacdefghiklmnpqrstvwy]+', drug):
227
+ st.error("靶点序列包含非法字符,请输入有效的氨基酸序列(仅限20种天然氨基酸字母)")
228
+ st.stop()
229
+
230
+ st.session_state.running = True
231
+
232
+ filter_params = f"靶点:{drug.upper()} | 酶:{cut_rule.split('(')[0].strip()} | 长度:{min_len}-{max_len} | 时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
233
+
234
+ try:
235
+ tok, mod = load_model()
236
+
237
+ food_cache_file = "food_cache.pkl"
238
+ if os.path.exists(food_cache_file):
239
+ with open(food_cache_file, "rb") as f:
240
+ FOOD = pickle.load(f)
241
+ st.success(f"已从缓存加载 {len(FOOD)} 条蛋白序列")
242
+ else:
243
+ with st.spinner("正在从UniProt下载食用蛋白序列..."):
244
+ FOOD = fetch_proteins()
245
+ with open(food_cache_file, "wb") as f:
246
+ pickle.dump(FOOD, f)
247
+ st.success(f"已获取并缓存 {len(FOOD)} 条蛋白序列")
248
+
249
+ allp = []
250
+ for name, seq in FOOD.items():
251
+ for p in cut(seq, CUT, min_len, max_len):
252
+ allp.append({"src": name, "p": p})
253
+
254
+ seen = set()
255
+ unip = []
256
+ for x in allp:
257
+ species_short = (
258
+ x["src"].split("(")[-1].split(")")[0].strip()
259
+ if "(" in x["src"]
260
+ else x["src"]
261
+ )
262
+ key = (species_short, x["p"])
263
+ if key not in seen:
264
+ seen.add(key)
265
+ unip.append(x)
266
+ st.info(f"酶切并去重后共 {len(unip)} 条候选肽")
267
+
268
+ cache_key = get_cache_key()
269
+ score_cache_file = f"score_cache_{cache_key}.pkl"
270
+
271
+ if os.path.exists(score_cache_file):
272
+ with open(score_cache_file, "rb") as f:
273
+ res = pickle.load(f)
274
+ st.success(f"已从缓存加载 {len(res)} 条打分结果")
275
+ else:
276
+ progress_bar = st.progress(0)
277
+ status_text = st.empty()
278
+ v_drug = vec(drug.upper(), tok, mod)
279
+ res = []
280
+ total = len(unip)
281
+ for idx, x in enumerate(unip):
282
+ v_p = vec(x["p"], tok, mod)
283
+ s = sim(v_p, v_drug)
284
+ res.append({"src": x["src"], "p": x["p"], "score": round(s, 4)})
285
+ if idx % 50 == 0:
286
+ progress_bar.progress(min(idx / total, 1.0))
287
+ status_text.text(f"打分进度: {idx}/{total}")
288
+ progress_bar.progress(1.0)
289
+ status_text.text(f"打分完成: {total}/{total}")
290
+ with open(score_cache_file, "wb") as f:
291
+ pickle.dump(res, f)
292
+ st.success(f"已完成打分并缓存")
293
+
294
+ for x in res:
295
+ x["content_pct"] = get_content(x["src"])
296
+
297
+ filtered_res = [x for x in res if x["content_pct"] >= content_threshold]
298
+ if filtered_res:
299
+ res = filtered_res
300
+
301
+ res.sort(key=lambda x: x["score"], reverse=True)
302
+
303
+ st.caption(f"📋 {filter_params}")
304
+
305
+ st.subheader("结构相似度排名")
306
+ df_top = pd.DataFrame(res[:top_n])
307
+ df_top.index = range(1, len(df_top) + 1)
308
+ show_cols = ["src", "p", "score"]
309
+ if "content_pct" in df_top.columns:
310
+ show_cols.append("content_pct")
311
+ max_display_rows = 100
312
+ st.caption(f"显示前 {min(top_n, max_display_rows)} 条结果(共 {top_n} 条)")
313
+ st.dataframe(df_top[show_cols].head(max_display_rows), width='stretch')
314
+
315
+ csv_top = df_top.to_csv(index=False).encode("utf-8-sig")
316
+ st.download_button("下载Top结果CSV", csv_top, "out_top.csv", "text/csv")
317
+
318
+ df_all = pd.DataFrame(res)
319
+ csv_all = df_all.to_csv(index=False).encode("utf-8-sig")
320
+ st.download_button("下载全部结果CSV", csv_all, "out_all.csv", "text/csv")
321
+
322
+ st.subheader("物种贡献统计(全部候选肽)")
323
+ species_counts = {}
324
+ for x in res:
325
+ s = (
326
+ x["src"].split("(")[-1].split(")")[0].strip()
327
+ if "(" in x["src"]
328
+ else "unknown"
329
+ )
330
+ species_counts[s] = species_counts.get(s, 0) + 1
331
+ species_df = pd.DataFrame(
332
+ list(species_counts.items()), columns=["物种", "候选肽数量"]
333
  )
334
+ species_df = species_df.sort_values("候选肽数量", ascending=False)
335
+ species_df = species_df.reset_index(drop=True)
336
+ species_df.index = range(1, len(species_df) + 1)
337
+ total_count = species_df["候选肽数量"].sum()
338
+ species_df["占比(%)"] = (species_df["候选肽数量"] / total_count * 100).round(1)
339
+
340
+ col1, col2 = st.columns(2)
341
+ with col1:
342
+ st.dataframe(species_df[["物种", "候选肽数量", "占比(%)"]].head(20), width='stretch')
343
+ with col2:
344
+ st.bar_chart(species_df.set_index("物种")["候选肽数量"])
345
+
346
+ st.success("筛选完成!")
347
+
348
+ finally:
349
+ st.session_state.running = False