amitpande74 commited on
Commit
eaa8a05
1 Parent(s): 55ffc43

Update to 118K tissue-curated model (kNN demo mode)

Browse files
app.py CHANGED
@@ -7,17 +7,11 @@ Upload a gene expression matrix -> get UBERON tissue classification + embeddings
7
  v3 (May 2026): Trained on 118,263 tissue-curated samples from TCGA, GTEx, ARCHS4
8
  (cell lines excluded, classes balanced). 42 UBERON tissues, 94.9% balanced accuracy.
9
 
10
- Deployment note:
11
- The full model weights (vae_tissue.final_model.pth, ~8 GB) exceed the
12
- Hugging Face free-tier storage limit and are NOT shipped with this Space.
13
- When the .pth is absent, the app runs in demo mode: uploaded samples are
14
- projected with TruncatedSVD and classified by kNN against the pre-computed
15
- 118K reference embeddings. When the .pth IS present (e.g. running locally),
16
- the app uses the trained VAE encoder for exact inference. Both modes use the
17
- same 118K reference embeddings; only the encoding of new uploads differs.
18
-
19
- Run locally (full VAE inference, needs the .pth in model/):
20
- streamlit run app.py
21
 
22
  Author: Amit Pande, MDC Berlin/BIMSB
23
  """
@@ -30,8 +24,7 @@ from pathlib import Path
30
  from sklearn.neighbors import KNeighborsClassifier
31
  from collections import Counter
32
 
33
- # -- Config --
34
- MODEL_DIR = Path("model")
35
  K = 5
36
  LATENT_DIM = 121
37
 
@@ -43,7 +36,6 @@ st.markdown(
43
  "across **42 UBERON tissue categories** (94.9% balanced accuracy, 121-dim latent space)."
44
  )
45
 
46
- # -- Load model (optional) + artifacts + reference embeddings --
47
  @st.cache_resource
48
  def load_all():
49
  art = joblib.load(MODEL_DIR / "vae_tissue.artifacts.joblib")
@@ -68,7 +60,6 @@ def load_all():
68
  knn = KNeighborsClassifier(n_neighbors=K, metric='cosine', n_jobs=-1)
69
  knn.fit(ref_emb.values, ref_clin['uberon_tissue'].values)
70
 
71
- # Load the full VAE only if the weights are present (local / paid Space).
72
  model = None
73
  pth = MODEL_DIR / "vae_tissue.final_model.pth"
74
  if pth.exists():
@@ -77,7 +68,7 @@ def load_all():
77
  model = torch.load(pth, map_location='cpu', weights_only=False)
78
  model.eval()
79
  except Exception as ex:
80
- st.sidebar.warning(f"Model weights present but not loaded ({ex}); using kNN demo mode.")
81
  model = None
82
 
83
  return model, gene_list, scaler, knn, ref_emb, ref_clin
@@ -96,15 +87,13 @@ try:
96
  "鈩癸笍 Running in **demo mode**. The 118K reference embeddings are used as-is; "
97
  "uploaded samples are projected with TruncatedSVD and classified by kNN. "
98
  "Full VAE encoding of new samples requires the model weights "
99
- "(vae_tissue.final_model.pth), which are deposited at Zenodo and are too large "
100
- "for the free Hugging Face tier."
101
  )
102
  except Exception as e:
103
  st.error(f"Load error: {e}")
104
- st.info("Place reference files in `model/` (embeddings_*.csv, *_clin.csv, vae_tissue.artifacts.joblib).")
105
  st.stop()
106
 
107
- # -- Helpers --
108
  def orient_matrix(df):
109
  genes_in_cols = len(set(df.columns) & set(gene_list))
110
  genes_in_rows = len(set(df.index) & set(gene_list))
@@ -112,11 +101,10 @@ def orient_matrix(df):
112
  df = df.T
113
  return df
114
 
115
- def embed_and_classify(df_aligned_scaled):
116
- """Return (embeddings, pred_labels, max_probs). Uses VAE if available, else SVD+kNN."""
117
  if model is not None:
118
  import torch
119
- X_t = torch.tensor(df_aligned_scaled, dtype=torch.float32)
120
  with torch.no_grad():
121
  h = model.encoders[0](X_t)
122
  mu = model.FC_mean(h[0])
@@ -133,10 +121,10 @@ def embed_and_classify(df_aligned_scaled):
133
  max_probs = probs.numpy().max(axis=1)
134
  else:
135
  from sklearn.decomposition import TruncatedSVD
136
- n_comp = min(LATENT_DIM, df_aligned_scaled.shape[0] - 1, df_aligned_scaled.shape[1])
137
  n_comp = max(n_comp, 1)
138
  svd = TruncatedSVD(n_components=n_comp, random_state=42)
139
- emb_reduced = svd.fit_transform(df_aligned_scaled)
140
  if emb_reduced.shape[1] < LATENT_DIM:
141
  pad = np.zeros((emb_reduced.shape[0], LATENT_DIM - emb_reduced.shape[1]))
142
  emb = np.concatenate([emb_reduced, pad], axis=1)
@@ -146,14 +134,13 @@ def embed_and_classify(df_aligned_scaled):
146
  max_probs = np.full(len(pred_labels), np.nan)
147
  return emb, pred_labels, max_probs
148
 
149
- # -- File upload --
150
  st.markdown("---")
151
  col1, col2 = st.columns([2, 1])
152
  with col1:
153
  uploaded = st.file_uploader(
154
  "Upload CSV/TSV (genes 脳 samples or samples 脳 genes)",
155
  type=["csv", "tsv", "txt"],
156
- help="HGNC gene symbols. Log2-transformed expression values (TPM, RPKM, or counts).")
157
  with col2:
158
  st.markdown(f"""
159
  **Expected input:**
@@ -227,4 +214,4 @@ st.caption(
227
  "Flexynesis Tissue VAE (v3) 路 Akalin Lab, MDC Berlin/BIMSB 路 "
228
  "118,263 samples 路 42 UBERON tissues 路 94.9% balanced accuracy 路 "
229
  "github.com/BIMSBbioinfo/flexynesis"
230
- )
 
7
  v3 (May 2026): Trained on 118,263 tissue-curated samples from TCGA, GTEx, ARCHS4
8
  (cell lines excluded, classes balanced). 42 UBERON tissues, 94.9% balanced accuracy.
9
 
10
+ When the full model weights (vae_tissue.final_model.pth, ~8 GB) are absent
11
+ (Hugging Face free tier), the app runs in demo mode: uploaded samples are
12
+ projected with TruncatedSVD and classified by kNN against the pre-computed
13
+ 118K reference embeddings. When the .pth IS present (local), the trained VAE
14
+ encoder is used for exact inference.
 
 
 
 
 
 
15
 
16
  Author: Amit Pande, MDC Berlin/BIMSB
17
  """
 
24
  from sklearn.neighbors import KNeighborsClassifier
25
  from collections import Counter
26
 
27
+ MODEL_DIR = Path(".")
 
28
  K = 5
29
  LATENT_DIM = 121
30
 
 
36
  "across **42 UBERON tissue categories** (94.9% balanced accuracy, 121-dim latent space)."
37
  )
38
 
 
39
  @st.cache_resource
40
  def load_all():
41
  art = joblib.load(MODEL_DIR / "vae_tissue.artifacts.joblib")
 
60
  knn = KNeighborsClassifier(n_neighbors=K, metric='cosine', n_jobs=-1)
61
  knn.fit(ref_emb.values, ref_clin['uberon_tissue'].values)
62
 
 
63
  model = None
64
  pth = MODEL_DIR / "vae_tissue.final_model.pth"
65
  if pth.exists():
 
68
  model = torch.load(pth, map_location='cpu', weights_only=False)
69
  model.eval()
70
  except Exception as ex:
71
+ st.sidebar.warning(f"Weights present but not loaded ({ex}); using kNN demo mode.")
72
  model = None
73
 
74
  return model, gene_list, scaler, knn, ref_emb, ref_clin
 
87
  "鈩癸笍 Running in **demo mode**. The 118K reference embeddings are used as-is; "
88
  "uploaded samples are projected with TruncatedSVD and classified by kNN. "
89
  "Full VAE encoding of new samples requires the model weights "
90
+ "(vae_tissue.final_model.pth), deposited at Zenodo and too large for the free tier."
 
91
  )
92
  except Exception as e:
93
  st.error(f"Load error: {e}")
94
+ st.info("Place reference files in repo root (embeddings_*.csv, *_clin.csv, vae_tissue.artifacts.joblib).")
95
  st.stop()
96
 
 
97
  def orient_matrix(df):
98
  genes_in_cols = len(set(df.columns) & set(gene_list))
99
  genes_in_rows = len(set(df.index) & set(gene_list))
 
101
  df = df.T
102
  return df
103
 
104
+ def embed_and_classify(X_scaled):
 
105
  if model is not None:
106
  import torch
107
+ X_t = torch.tensor(X_scaled, dtype=torch.float32)
108
  with torch.no_grad():
109
  h = model.encoders[0](X_t)
110
  mu = model.FC_mean(h[0])
 
121
  max_probs = probs.numpy().max(axis=1)
122
  else:
123
  from sklearn.decomposition import TruncatedSVD
124
+ n_comp = min(LATENT_DIM, X_scaled.shape[0] - 1, X_scaled.shape[1])
125
  n_comp = max(n_comp, 1)
126
  svd = TruncatedSVD(n_components=n_comp, random_state=42)
127
+ emb_reduced = svd.fit_transform(X_scaled)
128
  if emb_reduced.shape[1] < LATENT_DIM:
129
  pad = np.zeros((emb_reduced.shape[0], LATENT_DIM - emb_reduced.shape[1]))
130
  emb = np.concatenate([emb_reduced, pad], axis=1)
 
134
  max_probs = np.full(len(pred_labels), np.nan)
135
  return emb, pred_labels, max_probs
136
 
 
137
  st.markdown("---")
138
  col1, col2 = st.columns([2, 1])
139
  with col1:
140
  uploaded = st.file_uploader(
141
  "Upload CSV/TSV (genes 脳 samples or samples 脳 genes)",
142
  type=["csv", "tsv", "txt"],
143
+ help="HGNC gene symbols. Log2-transformed expression values.")
144
  with col2:
145
  st.markdown(f"""
146
  **Expected input:**
 
214
  "Flexynesis Tissue VAE (v3) 路 Akalin Lab, MDC Berlin/BIMSB 路 "
215
  "118,263 samples 路 42 UBERON tissues 路 94.9% balanced accuracy 路 "
216
  "github.com/BIMSBbioinfo/flexynesis"
217
+ )
embeddings_test.csv CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:65c933c42a2e665bcbf8f37ee08e812446b29a6c084f4f36cde3e3f8af2402e2
3
- size 14762761
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b8353267526ba6fe1b189650a11eb9918282fa9537515f9893a74195263ef688
3
+ size 37219853
embeddings_train.csv CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:8effbf1939159417c5c3b217cb6321449149c1d1809b68270c3dcee4ab43a088
3
- size 83634269
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:917d5c6677790a8725edaf78a589fa0c8cc675019548b2add49778f6edaeb2cc
3
+ size 155547606
test_clin.csv CHANGED
The diff for this file is too large to render. See raw diff
 
train_clin.csv CHANGED
The diff for this file is too large to render. See raw diff
 
vae_tissue.artifacts.joblib CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:9a2ca19cf98acb56bfa9d5b75c41cc912a293586954d178914da1286b86bd0b4
3
- size 523817
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2bafdb85f7da1e7976055e42329dcaf8d228cb15217692b6be9a36de5de0c29c
3
+ size 658142