amitpande74 commited on
Commit
4f18e56
·
verified ·
1 Parent(s): 6cbdeb7

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +203 -0
app.py ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Flexynesis Tissue VAE – Web Application (Demo)
4
+ ===============================================
5
+ Tissue classification via kNN on pre-computed latent embeddings.
6
+ Full model inference available when vae_tissue.final_model.pth is present.
7
+
8
+ Author: Amit Pande, MDC Berlin/BIMSB
9
+ """
10
+
11
+ import streamlit as st
12
+ import pandas as pd
13
+ import numpy as np
14
+ import joblib
15
+ from pathlib import Path
16
+ from sklearn.neighbors import KNeighborsClassifier
17
+ from collections import Counter
18
+
19
+ MODEL_DIR = Path("model")
20
+ K = 5
21
+
22
+ st.set_page_config(page_title="Flexynesis Tissue VAE", page_icon="🧬", layout="wide")
23
+ st.title("🧬 Flexynesis Tissue VAE")
24
+ st.markdown(
25
+ "Upload a bulk RNA-seq gene expression matrix to classify tissue-of-origin "
26
+ "using a supervised VAE trained on **75,619 samples** from TCGA, GTEx, DepMap, and ARCHS4 "
27
+ "across **43 UBERON tissue categories** (90.7% balanced accuracy, 121-dim latent space)."
28
+ )
29
+
30
+ @st.cache_resource
31
+ def load_all():
32
+ art = joblib.load(MODEL_DIR / "vae_tissue.artifacts.joblib")
33
+ gene_list = list(art['feature_lists']['gex'])
34
+ scaler = art['transforms']['gex']
35
+
36
+ train_emb = pd.read_csv(MODEL_DIR / "embeddings_train.csv", index_col=0)
37
+ test_emb = pd.read_csv(MODEL_DIR / "embeddings_test.csv", index_col=0)
38
+ train_clin = pd.read_csv(MODEL_DIR / "train_clin.csv", index_col=0)
39
+ test_clin = pd.read_csv(MODEL_DIR / "test_clin.csv", index_col=0)
40
+
41
+ all_emb = pd.concat([train_emb, test_emb])
42
+ all_clin = pd.concat([train_clin, test_clin])
43
+ idx = all_emb.index.intersection(all_clin.index)
44
+ ref_emb = all_emb.loc[idx]
45
+ ref_clin = all_clin.loc[idx]
46
+ mask = (ref_clin['uberon_tissue'].notna() &
47
+ ~ref_clin['uberon_tissue'].isin(['unknown','other','unmapped','nan','']))
48
+ ref_emb = ref_emb[mask]
49
+ ref_clin = ref_clin[mask]
50
+
51
+ knn = KNeighborsClassifier(n_neighbors=K, metric='cosine', n_jobs=-1)
52
+ knn.fit(ref_emb.values, ref_clin['uberon_tissue'].values)
53
+
54
+ # Try loading full VAE model (optional — needed for new sample encoding)
55
+ model = None
56
+ pth = MODEL_DIR / "vae_tissue.final_model.pth"
57
+ if pth.exists():
58
+ try:
59
+ import torch
60
+ model = torch.load(pth, map_location='cpu', weights_only=False)
61
+ model.eval()
62
+ except Exception as ex:
63
+ st.sidebar.warning(f"Model .pth not loaded: {ex}. Running in kNN-only mode.")
64
+
65
+ return model, gene_list, scaler, knn, ref_emb, ref_clin
66
+
67
+ try:
68
+ model, gene_list, scaler, knn_ref, ref_emb, ref_clin = load_all()
69
+ mode_str = "Full VAE + kNN" if model is not None else "kNN on pre-computed embeddings"
70
+ st.success(
71
+ f"✅ {len(gene_list):,} genes · "
72
+ f"{len(ref_emb):,} reference samples · "
73
+ f"{ref_clin['uberon_tissue'].nunique()} tissues · "
74
+ f"Mode: **{mode_str}**"
75
+ )
76
+ if model is None:
77
+ st.info(
78
+ "ℹ️ Running in **demo mode** — tissue classification uses kNN on "
79
+ "pre-computed training embeddings. For full VAE encoding of new samples, "
80
+ "the model weights file (vae_tissue.final_model.pth) is required."
81
+ )
82
+ except Exception as e:
83
+ st.error(f"Load error: {e}")
84
+ st.stop()
85
+
86
+ def orient_matrix(df):
87
+ if len(set(df.index) & set(gene_list)) > len(set(df.columns) & set(gene_list)):
88
+ df = df.T
89
+ return df
90
+
91
+ def classify(df):
92
+ aligned = pd.DataFrame(0.0, index=df.index, columns=gene_list)
93
+ common = [g for g in gene_list if g in df.columns]
94
+ aligned[common] = df[common].values
95
+ aligned = aligned.fillna(0)
96
+ X_scaled = scaler.transform(aligned.values)
97
+
98
+ if model is not None:
99
+ import torch
100
+ X_t = torch.tensor(X_scaled, dtype=torch.float32)
101
+ with torch.no_grad():
102
+ h = model.encoders[0](X_t)
103
+ mu = model.FC_mean(h[0])
104
+ logits = model.MLPs['uberon_tissue'](mu)
105
+ lmap = model.dataset.label_mappings['uberon_tissue']
106
+ n2i = {v: k for k, v in lmap.items()}
107
+ ni = n2i.get('nan', None)
108
+ if ni is not None:
109
+ logits[:, ni] = -1e9
110
+ probs = torch.softmax(logits, dim=1)
111
+ pred_idx = logits.argmax(dim=1).numpy()
112
+ emb = mu.numpy()
113
+ pred_labels = [lmap[int(i)] for i in pred_idx]
114
+ max_probs = probs.numpy().max(axis=1)
115
+ else:
116
+ # Demo mode: scale → kNN classify on expression space directly
117
+ pred_labels = knn_ref.predict(X_scaled).tolist()
118
+ max_probs = np.ones(len(pred_labels)) * float('nan')
119
+ emb = X_scaled[:, :121] # truncate for download
120
+
121
+ return emb, pred_labels, max_probs
122
+
123
+ # ── Upload ──
124
+ st.markdown("---")
125
+ col1, col2 = st.columns([2, 1])
126
+ with col1:
127
+ uploaded = st.file_uploader(
128
+ "Upload CSV/TSV (genes × samples or samples × genes)",
129
+ type=["csv", "tsv", "txt"],
130
+ help="HGNC gene symbols. Log2-transformed values (TPM, RPKM, or counts).")
131
+ with col2:
132
+ st.markdown(f"""
133
+ **Expected input:**
134
+ - HGNC gene symbols
135
+ - {len(gene_list):,} genes used by model
136
+ - Log2-transformed expression
137
+ """)
138
+
139
+ if uploaded:
140
+ sep = '\t' if uploaded.name.endswith(('.tsv', '.txt')) else ','
141
+ df = pd.read_csv(uploaded, index_col=0, sep=sep)
142
+ st.write(f"**Uploaded:** {df.shape[0]:,} × {df.shape[1]:,}")
143
+ st.dataframe(df.iloc[:5, :5], use_container_width=True)
144
+
145
+ if st.button("🚀 Classify Tissues", type="primary"):
146
+ with st.spinner("Processing..."):
147
+ df = orient_matrix(df)
148
+ overlap = len(set(df.columns) & set(gene_list))
149
+ st.write(f"Gene overlap: **{overlap:,}/{len(gene_list):,}** ({100*overlap/len(gene_list):.1f}%)")
150
+ if overlap < 1000:
151
+ st.warning("Low gene overlap — results may be unreliable.")
152
+
153
+ emb, pred_labels, max_probs = classify(df)
154
+
155
+ distances, indices = knn_ref.kneighbors(
156
+ emb if model is not None else
157
+ scaler.transform(
158
+ pd.DataFrame(0.0, index=df.index, columns=gene_list)
159
+ .assign(**{g: df[g] for g in gene_list if g in df.columns})
160
+ .fillna(0).values)[:, :121])
161
+ breakdowns = []
162
+ for i in range(len(emb)):
163
+ src = Counter(ref_clin.iloc[indices[i]]['source'].values)
164
+ breakdowns.append('; '.join(f"{s}:{c}" for s, c in src.most_common()))
165
+
166
+ st.markdown("---")
167
+ st.subheader("📊 Results")
168
+ conf_col = [f"{p:.1%}" if not np.isnan(p) else "kNN" for p in max_probs]
169
+ results = pd.DataFrame({
170
+ 'Sample': df.index,
171
+ 'Tissue': pred_labels,
172
+ 'Confidence': conf_col,
173
+ 'kNN Dist': distances.mean(axis=1).round(4),
174
+ 'Sources': breakdowns,
175
+ })
176
+ st.dataframe(results, use_container_width=True, height=400)
177
+
178
+ col1, col2 = st.columns(2)
179
+ with col1:
180
+ st.subheader("Tissue Distribution")
181
+ st.bar_chart(pd.Series(pred_labels).value_counts())
182
+ with col2:
183
+ st.subheader("Confidence Distribution")
184
+ conf_vals = [p if not np.isnan(p) else 0 for p in max_probs]
185
+ st.bar_chart(pd.DataFrame({'Confidence': conf_vals}, index=df.index))
186
+
187
+ st.markdown("---")
188
+ c1, c2 = st.columns(2)
189
+ with c1:
190
+ emb_df = pd.DataFrame(emb, index=df.index,
191
+ columns=[f"z{i}" for i in range(emb.shape[1])])
192
+ st.download_button("📥 Embeddings (CSV)", emb_df.to_csv(),
193
+ "flexynesis_embeddings.csv", "text/csv")
194
+ with c2:
195
+ st.download_button("📥 Classifications (CSV)", results.to_csv(index=False),
196
+ "flexynesis_classifications.csv", "text/csv")
197
+
198
+ st.markdown("---")
199
+ st.caption(
200
+ "Flexynesis Tissue VAE · Akalin Lab, MDC Berlin/BIMSB · "
201
+ "75,619 samples · 43 UBERON tissues · 90.7% balanced accuracy · "
202
+ "github.com/BIMSBbioinfo/flexynesis"
203
+ )