richiam commited on
Commit
2b85448
Β·
verified Β·
1 Parent(s): 1d9de5d

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +271 -2
app.py CHANGED
@@ -1389,6 +1389,61 @@ def evaluation_tab():
1389
  ], fluid=True, className="pt-3")
1390
 
1391
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1392
  def contact_tab():
1393
  return dbc.Container([
1394
  html.H3("Research Team", className="mt-3 mb-4"),
@@ -1748,8 +1803,9 @@ app.layout = dbc.Container([
1748
  dbc.Tabs([
1749
  dbc.Tab(label="Extraction Data", tab_id="tab-proteins"),
1750
  dbc.Tab(label="Clustering Explorer",tab_id="tab-clustering"),
1751
- dbc.Tab(label="Grid Metrics", tab_id="tab-grid-metrics"),
1752
- dbc.Tab(label="Evaluation Results", tab_id="tab-evaluation"),
 
1753
  dbc.Tab(label="Extraction Pipeline Instructions", tab_id="tab-pipeline"),
1754
  dbc.Tab(label="README", tab_id="tab-readme"),
1755
  dbc.Tab(label="Contact", tab_id="tab-contact"),
@@ -1773,6 +1829,8 @@ def render_tab(tab):
1773
  return clustering_tab()
1774
  elif tab == "tab-grid-metrics":
1775
  return grid_metrics_tab()
 
 
1776
  elif tab == "tab-evaluation":
1777
  return evaluation_tab()
1778
  elif tab == "tab-pipeline":
@@ -2734,6 +2792,217 @@ def update_metrics_table(model, min_val, threshold):
2734
  )
2735
 
2736
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2737
  @app.callback(
2738
  Output("metrics-heatmap", "figure"),
2739
  Output("metrics-model-compare", "figure"),
 
1389
  ], fluid=True, className="pt-3")
1390
 
1391
 
1392
+ def protocol_configs_tab():
1393
+ _PC_FIELDS = ["expression_strain", "inducer", "medium_name",
1394
+ "plasmid", "lysis_buffer", "elution_buffer"]
1395
+ _PC_LABELS = {
1396
+ "expression_strain": "Expression host",
1397
+ "inducer": "Inducer",
1398
+ "medium_name": "Growth medium",
1399
+ "plasmid": "Plasmid",
1400
+ "lysis_buffer": "Lysis buffer",
1401
+ "elution_buffer": "Elution buffer",
1402
+ }
1403
+ topn_opts = [{"label": f"Top {n} clusters / field", "value": n}
1404
+ for n in [10, 20, 30, 50]]
1405
+ return dbc.Container([
1406
+ html.H3("Protocol Configurations", className="mt-3 mb-1"),
1407
+ html.P(
1408
+ "Most common multi-field protocol combinations derived from BioSimCSE clusters. "
1409
+ "Each line in the flow diagram represents proteins sharing the same cluster "
1410
+ "assignments across fields.",
1411
+ className="text-muted mb-3",
1412
+ ),
1413
+ dbc.Row([
1414
+ make_dropdown("Model", "pc-dd-model", MODELS,
1415
+ value="kamalkraj__BioSimCSE-BioLinkBERT-BASE"
1416
+ if "kamalkraj__BioSimCSE-BioLinkBERT-BASE" in MODELS
1417
+ else (MODELS[0] if MODELS else None)),
1418
+ make_dropdown("Min community size", "pc-dd-min", MINS,
1419
+ value="2" if "2" in MINS else (MINS[0] if MINS else None)),
1420
+ make_dropdown("Threshold", "pc-dd-threshold", THRESHOLDS,
1421
+ value="0.9" if "0.9" in THRESHOLDS else (THRESHOLDS[0] if THRESHOLDS else None)),
1422
+ dbc.Col([
1423
+ html.Label("Clusters per field", className="fw-semibold small mb-1"),
1424
+ dcc.Dropdown(
1425
+ id="pc-dd-topn",
1426
+ options=topn_opts,
1427
+ value=20,
1428
+ clearable=False,
1429
+ style={"fontSize": "13px"},
1430
+ ),
1431
+ ]),
1432
+ ], className="mb-3 g-2"),
1433
+ dcc.Loading(
1434
+ dcc.Graph(id="pc-parcats-graph", style={"height": "560px"}),
1435
+ type="circle", color="#1a73e8",
1436
+ ),
1437
+ html.H5("Top configurations", className="mt-4 mb-2"),
1438
+ html.P(
1439
+ "Cluster labels are grouped into top-level categories (BL21, IPTG, LB, pET…) "
1440
+ "to aggregate across fine-grained t=0.9 clusters. % is relative to all proteins with 4 core fields.",
1441
+ className="text-muted small mb-2",
1442
+ ),
1443
+ dcc.Loading(html.Div(id="pc-config-table"), type="circle", color="#1a73e8"),
1444
+ ], fluid=True, className="pt-3")
1445
+
1446
+
1447
  def contact_tab():
1448
  return dbc.Container([
1449
  html.H3("Research Team", className="mt-3 mb-4"),
 
1803
  dbc.Tabs([
1804
  dbc.Tab(label="Extraction Data", tab_id="tab-proteins"),
1805
  dbc.Tab(label="Clustering Explorer",tab_id="tab-clustering"),
1806
+ dbc.Tab(label="Grid Metrics", tab_id="tab-grid-metrics"),
1807
+ dbc.Tab(label="Protocol Configurations",tab_id="tab-protocol-configs"),
1808
+ dbc.Tab(label="Evaluation Results", tab_id="tab-evaluation"),
1809
  dbc.Tab(label="Extraction Pipeline Instructions", tab_id="tab-pipeline"),
1810
  dbc.Tab(label="README", tab_id="tab-readme"),
1811
  dbc.Tab(label="Contact", tab_id="tab-contact"),
 
1829
  return clustering_tab()
1830
  elif tab == "tab-grid-metrics":
1831
  return grid_metrics_tab()
1832
+ elif tab == "tab-protocol-configs":
1833
+ return protocol_configs_tab()
1834
  elif tab == "tab-evaluation":
1835
  return evaluation_tab()
1836
  elif tab == "tab-pipeline":
 
2792
  )
2793
 
2794
 
2795
+ _PC_PROTOCOL_FIELDS = ["expression_strain", "inducer", "medium_name",
2796
+ "plasmid", "lysis_buffer", "elution_buffer"]
2797
+ _PC_FIELD_LABELS = {
2798
+ "expression_strain": "Expression host",
2799
+ "inducer": "Inducer",
2800
+ "medium_name": "Growth medium",
2801
+ "plasmid": "Plasmid",
2802
+ "lysis_buffer": "Lysis buffer",
2803
+ "elution_buffer": "Elution buffer",
2804
+ }
2805
+ _PC_COLORS = [
2806
+ "#1a73e8", "#4db8ff", "#80ccff", "#1aa85c", "#f4a55a",
2807
+ "#e8711a", "#c0392b", "#8e1ae8", "#2ecc71", "#adb5bd",
2808
+ "#dee2e6",
2809
+ ]
2810
+
2811
+
2812
+ def _pc_build_wide(all_fields_df, top_n):
2813
+ """Pivot ALL_FIELDS into wide format, keeping top_n clusters per field."""
2814
+ sub = all_fields_df[all_fields_df["field"].isin(_PC_PROTOCOL_FIELDS)].copy()
2815
+ rows = []
2816
+ for field in _PC_PROTOCOL_FIELDS:
2817
+ fd = sub[sub["field"] == field]
2818
+ clustered = fd[fd["cluster_id"] != -1]
2819
+ # Top N clusters by size
2820
+ top_ids = (
2821
+ clustered.groupby("cluster_id").size()
2822
+ .sort_values(ascending=False)
2823
+ .head(top_n).index
2824
+ )
2825
+ def label_row(r):
2826
+ if r["cluster_id"] == -1:
2827
+ return None
2828
+ if r["cluster_id"] in top_ids:
2829
+ lbl = r["cluster_label_short"]
2830
+ return lbl[:40] + "…" if len(lbl) > 40 else lbl
2831
+ return "Other clusters"
2832
+ fd = fd.copy()
2833
+ fd["cat"] = fd.apply(label_row, axis=1)
2834
+ rows.append(fd[["key", "protein_index", "cat"]].rename(columns={"cat": field}))
2835
+
2836
+ wide = rows[0]
2837
+ for r in rows[1:]:
2838
+ wide = wide.merge(r, on=["key", "protein_index"], how="outer")
2839
+ return wide
2840
+
2841
+
2842
+ @app.callback(
2843
+ Output("pc-parcats-graph", "figure"),
2844
+ Output("pc-config-table", "children"),
2845
+ Input("pc-dd-model", "value"),
2846
+ Input("pc-dd-min", "value"),
2847
+ Input("pc-dd-threshold", "value"),
2848
+ Input("pc-dd-topn", "value"),
2849
+ )
2850
+ def update_protocol_configs(model, min_val, threshold, top_n):
2851
+ if not all([model, min_val, threshold, top_n]):
2852
+ empty = go.Figure()
2853
+ empty.update_layout(paper_bgcolor="white",
2854
+ annotations=[dict(text="Select model, min and threshold.",
2855
+ showarrow=False, font=dict(size=14))])
2856
+ return empty, html.Div()
2857
+
2858
+ adf = _load_cluster_csv(model, min_val, threshold)
2859
+ if adf is None:
2860
+ empty = go.Figure()
2861
+ empty.update_layout(paper_bgcolor="white",
2862
+ annotations=[dict(text="Data file not found.",
2863
+ showarrow=False, font=dict(size=14))])
2864
+ return empty, html.Div("Data file not found.", className="text-muted small")
2865
+
2866
+ wide = _pc_build_wide(adf, top_n)
2867
+ core = _PC_PROTOCOL_FIELDS[:4] # host, inducer, medium, plasmid
2868
+ w4 = wide.dropna(subset=core)
2869
+
2870
+ # ── Parcats figure ──────────────────────────────────────────────────────
2871
+ dims = []
2872
+ for field in _PC_PROTOCOL_FIELDS:
2873
+ if field not in wide.columns:
2874
+ continue
2875
+ col = w4[field].fillna("N/A") if field in w4.columns else None
2876
+ if col is None:
2877
+ continue
2878
+ from collections import Counter as _Ctr
2879
+ cnt = _Ctr(col.tolist())
2880
+ cat_order = [c for c, _ in cnt.most_common() if c != "N/A"] + ["N/A"]
2881
+ dims.append(go.parcats.Dimension(
2882
+ values=col.tolist(),
2883
+ label=_PC_FIELD_LABELS.get(field, field),
2884
+ categoryorder="array",
2885
+ categoryarray=cat_order,
2886
+ ))
2887
+
2888
+ # Color by expression host category index
2889
+ host_col = w4["expression_strain"].fillna("N/A").tolist()
2890
+ all_hosts = list(dict.fromkeys(host_col))
2891
+ host_idx = {h: i for i, h in enumerate(all_hosts)}
2892
+ color_vals = [host_idx.get(h, 0) for h in host_col]
2893
+ n_hosts = max(len(all_hosts), 1)
2894
+ colorscale = [[i / max(n_hosts - 1, 1), _PC_COLORS[i % len(_PC_COLORS)]]
2895
+ for i in range(n_hosts)]
2896
+
2897
+ fig = go.Figure(go.Parcats(
2898
+ dimensions=dims,
2899
+ line=dict(color=color_vals, colorscale=colorscale, shape="hspline"),
2900
+ labelfont=dict(size=12, family="Arial"),
2901
+ tickfont=dict(size=10, family="Arial"),
2902
+ arrangement="freeform",
2903
+ hoverinfo="count+probability",
2904
+ ))
2905
+ short_model = model.split("__")[-1] if "__" in model else model
2906
+ fig.update_layout(
2907
+ title=dict(
2908
+ text=f"Protocol configuration flows β€” {short_model} t={threshold} min={min_val}",
2909
+ font=dict(size=13, family="Arial"), x=0.5,
2910
+ ),
2911
+ paper_bgcolor="white",
2912
+ font=dict(family="Arial", size=11),
2913
+ margin=dict(l=60, r=60, t=60, b=40),
2914
+ )
2915
+
2916
+ # ── Top-config table β€” category-level grouping ──────────────────────────
2917
+ # Map fine-grained cluster labels to readable top-level categories so
2918
+ # combinations survive the specificity of t=0.9 clusters.
2919
+ def _grp(field, label):
2920
+ if not label or label == "Other clusters":
2921
+ return None
2922
+ sl = str(label).lower()
2923
+ if field == "expression_strain":
2924
+ if "bl21" in sl: return "BL21(DE3)"
2925
+ if "rosetta" in sl: return "Rosetta"
2926
+ if "c41" in sl or "c43" in sl: return "C41/C43"
2927
+ if "hek" in sl or "cho" in sl or "293" in sl: return "Human/CHO"
2928
+ if "sf9" in sl or "sf21" in sl: return "Insect (Sf9)"
2929
+ if "yeast" in sl or "pichia" in sl: return "Yeast"
2930
+ return "Other E. coli"
2931
+ if field == "inducer":
2932
+ if "not mentioned" in sl: return "IPTG (implied)"
2933
+ if "iptg" in sl: return "IPTG"
2934
+ if "arabinose" in sl: return "Arabinose"
2935
+ return "Other inducer"
2936
+ if field == "medium_name":
2937
+ if re.search(r"\blb\b|luria.bertani|luria broth", sl): return "LB"
2938
+ if "terrific" in sl: return "TB"
2939
+ if "2xyt" in sl or "2x yt" in sl: return "2xYT"
2940
+ if "minimal" in sl or "m9" in sl: return "Minimal"
2941
+ return "Other medium"
2942
+ if field == "plasmid":
2943
+ if re.search(r"\bpet", sl): return "pET"
2944
+ if "pgex" in sl: return "pGEX"
2945
+ if "pqe" in sl: return "pQE"
2946
+ if "pmal" in sl: return "pMAL"
2947
+ return "Other plasmid"
2948
+ if field == "lysis_buffer":
2949
+ if re.search(r"\btris\b", sl): return "Tris"
2950
+ if "pbs" in sl: return "PBS"
2951
+ if "hepes" in sl: return "HEPES"
2952
+ if re.search(r"\bphosphate\b", sl): return "Phosphate"
2953
+ return "Other lysis"
2954
+ if field == "elution_buffer":
2955
+ if "imidazole" in sl: return "Imidazole"
2956
+ if "glutathione" in sl: return "Glutathione"
2957
+ if "maltose" in sl: return "Maltose"
2958
+ return "Other elution"
2959
+ return label
2960
+
2961
+ # Build category table from all clustered entries (not just top-N)
2962
+ # to get meaningful combination counts.
2963
+ sub_all = adf[adf["field"].isin(core) & (adf["cluster_id"] != -1)].copy()
2964
+ sub_all["cat"] = sub_all.apply(
2965
+ lambda r: _grp(r["field"], r["cluster_label_short"]), axis=1
2966
+ )
2967
+ sub_all = sub_all[sub_all["cat"].notna()]
2968
+ wide_cat = sub_all.pivot_table(
2969
+ index=["key", "protein_index"], columns="field",
2970
+ values="cat", aggfunc="first"
2971
+ ).reset_index()
2972
+ w4_grp = wide_cat.dropna(subset=core)
2973
+ from collections import Counter as _Ctr
2974
+ combos = _Ctr(tuple(r) for r in w4_grp[core].itertuples(index=False))
2975
+ total = len(w4_grp) # proteins with all 4 fields in a named cluster
2976
+
2977
+ table_rows = []
2978
+ for rank, (combo, cnt) in enumerate(combos.most_common(20), 1):
2979
+ row = {"Rank": rank, "Count": cnt, "%": f"{cnt/total*100:.1f}%"}
2980
+ for field, val in zip(core, combo):
2981
+ row[_PC_FIELD_LABELS[field]] = val
2982
+ table_rows.append(row)
2983
+
2984
+ cols = ["Rank", "Count", "%"] + [_PC_FIELD_LABELS[f] for f in core]
2985
+ table = dash_table.DataTable(
2986
+ columns=[{"name": c, "id": c} for c in cols],
2987
+ data=table_rows,
2988
+ sort_action="native",
2989
+ style_table={"overflowX": "auto"},
2990
+ style_cell={"fontSize": "12px", "padding": "5px 10px", "textAlign": "left",
2991
+ "maxWidth": "220px", "overflow": "hidden", "textOverflow": "ellipsis"},
2992
+ style_header={"fontWeight": "bold", "backgroundColor": "#f8f9fa"},
2993
+ style_data_conditional=[
2994
+ {"if": {"row_index": "odd"}, "backgroundColor": "#f8f9fa"},
2995
+ {"if": {"row_index": 0}, "backgroundColor": "#e8f0fe", "fontWeight": "600"},
2996
+ ],
2997
+ tooltip_data=[
2998
+ {c: {"value": str(row.get(c, "")), "type": "markdown"} for c in cols}
2999
+ for row in table_rows
3000
+ ],
3001
+ tooltip_duration=None,
3002
+ )
3003
+ return fig, table
3004
+
3005
+
3006
  @app.callback(
3007
  Output("metrics-heatmap", "figure"),
3008
  Output("metrics-model-compare", "figure"),