snehasis19 commited on
Commit
0d9bbd4
·
verified ·
1 Parent(s): 751bb66

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +284 -0
app.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # patched_app.py
2
+ import os
3
+ import io
4
+ import csv
5
+ import subprocess
6
+ import pandas as pd
7
+ import joblib
8
+ import numpy as np
9
+ import torch
10
+ import esm
11
+ import logging
12
+ from flask import Flask, request, render_template, send_file, session, redirect, url_for
13
+
14
+ # logging
15
+ logging.basicConfig(
16
+ level=logging.INFO,
17
+ format="%(asctime)s %(levelname)s %(message)s",
18
+ handlers=[logging.FileHandler("app.log"), logging.StreamHandler()]
19
+ )
20
+
21
+ # ---------- Paths ----------
22
+ BASE_DIR = r"C:\pythonProject"
23
+ MODEL_PATH = os.path.join(BASE_DIR, "best_model_LR.sav")
24
+ ENCODER_PATH = os.path.join(BASE_DIR, "label_encoder.pkl")
25
+
26
+ BLAST_PATH = r"C:\Program Files\NCBI\blast-2.16.0+\bin\blastp.exe"
27
+ BLAST_DB = r"C:\pythonProject\pathway_db" # must be BLAST DB prefix
28
+ MAPPING_FILE = r"C:\pythonProject\pathway_map.csv"
29
+ BITSCORE_THRESHOLD = 80.0
30
+ CONFIDENCE_THRESHOLD = 0.7 # threshold for accepting gene family predictions
31
+
32
+ RESULT_CSV = os.path.join(os.getcwd(), "combined_results.csv")
33
+
34
+ # ---------- Load Models ----------
35
+ model = None
36
+ encoder = None
37
+ esm_model = None
38
+ batch_converter = None
39
+
40
+ try:
41
+ if os.path.exists(MODEL_PATH):
42
+ model = joblib.load(MODEL_PATH)
43
+ logging.info("LR model loaded successfully.")
44
+ except Exception:
45
+ logging.exception("Failed to load LR model")
46
+
47
+ try:
48
+ if os.path.exists(ENCODER_PATH):
49
+ encoder = joblib.load(ENCODER_PATH)
50
+ logging.info("Label encoder loaded.")
51
+ except Exception:
52
+ logging.exception("Failed to load label encoder")
53
+
54
+ try:
55
+ esm_model, alphabet = esm.pretrained.load_model_and_alphabet("esm2_t6_8M_UR50D")
56
+ esm_model.eval()
57
+ esm_model = esm_model.to("cpu")
58
+ batch_converter = alphabet.get_batch_converter()
59
+ logging.info("ESM2 model loaded and set to CPU.")
60
+ except Exception:
61
+ logging.exception("Failed to load ESM model")
62
+
63
+ # ---------- Functions ----------
64
+ def esm2_320_embed(sequence):
65
+ if esm_model is None or batch_converter is None:
66
+ raise RuntimeError("ESM model not available")
67
+ batch_labels, batch_strs, batch_tokens = batch_converter([("seq1", sequence)])
68
+ with torch.no_grad():
69
+ results = esm_model(batch_tokens, repr_layers=[6], return_contacts=False)
70
+ token_representations = results["representations"][6]
71
+ seq_repr = token_representations[0, 1:-1].detach().cpu().numpy()
72
+ return seq_repr.mean(axis=0)
73
+
74
+ def run_blast_and_get_dataframe(temp_fasta, blast_output):
75
+ cmd = [
76
+ BLAST_PATH,
77
+ "-query", temp_fasta,
78
+ "-db", BLAST_DB,
79
+ "-out", blast_output,
80
+ "-outfmt", "6 qseqid sseqid pident length evalue bitscore"
81
+ ]
82
+ proc = subprocess.run(cmd, capture_output=True, text=True)
83
+ if proc.returncode != 0:
84
+ logging.error("BLAST error:\n%s", proc.stderr)
85
+ if not os.path.exists(blast_output) or os.path.getsize(blast_output) == 0:
86
+ return pd.DataFrame(columns=["qseqid","sseqid","pident","length","evalue","bitscore"])
87
+ cols = ["qseqid", "sseqid", "pident", "length", "evalue", "bitscore"]
88
+ try:
89
+ df = pd.read_csv(blast_output, sep="\t", names=cols)
90
+ df["bitscore"] = df["bitscore"].astype(float)
91
+ return df
92
+ except Exception:
93
+ logging.exception("Failed to read BLAST output")
94
+ return pd.DataFrame(columns=cols)
95
+
96
+ # ---------- Flask app ----------
97
+ app = Flask(__name__)
98
+ app.secret_key = 'your-secret-key-here' # Required for session handling
99
+ latest_predictions = []
100
+
101
+ @app.after_request
102
+ def add_cache_control(response):
103
+ """Add cache control headers to prevent caching"""
104
+ response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
105
+ response.headers["Pragma"] = "no-cache"
106
+ response.headers["Expires"] = "0"
107
+ return response
108
+
109
+ @app.route("/", methods=["GET", "POST"])
110
+ def index():
111
+ global latest_predictions
112
+ latest_predictions = []
113
+ predictions = []
114
+
115
+ if request.method == "POST":
116
+ sequences = []
117
+ headers = []
118
+
119
+ # --- Parse uploaded file ---
120
+ uploaded_file = request.files.get("fasta_file")
121
+ if uploaded_file and uploaded_file.filename != "":
122
+ seq = ""
123
+ header = ""
124
+ for line in uploaded_file:
125
+ line = line.decode().strip()
126
+ if not line:
127
+ continue
128
+ if line.startswith(">"):
129
+ if seq:
130
+ sequences.append(seq)
131
+ headers.append(header)
132
+ seq = ""
133
+ header = line[1:] # keep full header
134
+ else:
135
+ seq += line
136
+ if seq:
137
+ sequences.append(seq)
138
+ headers.append(header)
139
+
140
+ # --- Parse textarea ---
141
+ sequence_text = request.form.get("sequence_text", "").strip()
142
+ if sequence_text:
143
+ seq = ""
144
+ header = ""
145
+ for line in sequence_text.splitlines():
146
+ line = line.strip()
147
+ if not line:
148
+ continue
149
+ if line.startswith(">"):
150
+ if seq:
151
+ sequences.append(seq)
152
+ headers.append(header)
153
+ seq = ""
154
+ header = line[1:] # keep full header
155
+ else:
156
+ seq += line
157
+ if seq:
158
+ sequences.append(seq)
159
+ headers.append(header if header else "sequence_from_text")
160
+
161
+ if not sequences:
162
+ return "⚠️ No valid sequences provided."
163
+
164
+ # --- Gene family prediction with confidence check ---
165
+ try:
166
+ features = [esm2_320_embed(seq) for seq in sequences]
167
+ features = np.array(features)
168
+ gene_preds = []
169
+ if model is None:
170
+ gene_preds = ["Unknown"] * len(sequences)
171
+ else:
172
+ probs = model.predict_proba(features)
173
+ max_probs = probs.max(axis=1)
174
+ pred_indices = probs.argmax(axis=1)
175
+ for idx, prob in zip(pred_indices, max_probs):
176
+ if prob >= CONFIDENCE_THRESHOLD:
177
+ gene = encoder.inverse_transform([idx])[0] if encoder else str(idx)
178
+ else:
179
+ gene = "Unknown"
180
+ gene_preds.append(gene)
181
+ except Exception:
182
+ logging.exception("Gene family prediction failed")
183
+ gene_preds = ["Unknown"] * len(sequences)
184
+
185
+ # --- Save temp fasta with BLAST-safe headers ---
186
+ temp_fasta = os.path.join(os.getcwd(), "temp_input.fasta")
187
+ header_map = {}
188
+ with open(temp_fasta, "w") as f:
189
+ for h, s in zip(headers, sequences):
190
+ safe_h = h.replace(" ", "_")
191
+ header_map[safe_h] = h
192
+ f.write(f">{safe_h}\n{s}\n")
193
+
194
+ # --- Run BLAST ---
195
+ blast_output = os.path.join(os.getcwd(), "blast_results.txt")
196
+ blast_df = run_blast_and_get_dataframe(temp_fasta, blast_output)
197
+
198
+ # --- Load mapping file ---
199
+ map_df = pd.DataFrame()
200
+ if os.path.exists(MAPPING_FILE):
201
+ try:
202
+ map_df = pd.read_csv(MAPPING_FILE, dtype=str)
203
+ except Exception:
204
+ logging.exception("Failed to read mapping file")
205
+
206
+ # --- Pick best hits ---
207
+ combined_results = []
208
+ if not blast_df.empty:
209
+ top_hits_idx = blast_df.groupby("qseqid")["bitscore"].idxmax()
210
+ top_hits = blast_df.loc[top_hits_idx]
211
+ else:
212
+ top_hits = pd.DataFrame(columns=blast_df.columns)
213
+
214
+ # --- Build results ---
215
+ for i, orig_header in enumerate(headers):
216
+ safe_h = orig_header.replace(" ", "_")
217
+ top_hit_row = top_hits[top_hits["qseqid"] == safe_h]
218
+
219
+ if not top_hit_row.empty:
220
+ row = top_hit_row.iloc[0]
221
+ top_hit = row["sseqid"]
222
+ bitscore = float(row["bitscore"])
223
+ pathways = "No Pathways Found"
224
+ if not map_df.empty and "Entry" in map_df.columns and "Pathways" in map_df.columns:
225
+ paths = map_df.loc[map_df["Entry"] == top_hit, "Pathways"]
226
+ if (not paths.empty) and bitscore >= BITSCORE_THRESHOLD:
227
+ pathways = paths.values[0]
228
+ else:
229
+ top_hit = "No Hit"
230
+ bitscore = 0.0
231
+ pathways = "No Pathways Found"
232
+
233
+ gene_family = gene_preds[i] if i < len(gene_preds) else "Unknown"
234
+ combined_results.append([orig_header, top_hit, bitscore, pathways, gene_family])
235
+
236
+ # --- Save results ---
237
+ result_df = pd.DataFrame(
238
+ combined_results,
239
+ columns=["Query/Header", "Top Hit", "Bitscore", "Pathways", "Predicted Gene Family"]
240
+ )
241
+ result_df.to_csv(RESULT_CSV, index=False)
242
+ latest_predictions = combined_results
243
+
244
+ # Store results in session and redirect to results page
245
+ session['predictions'] = combined_results
246
+ session['result_summary'] = {
247
+ 'total_sequences': len(sequences),
248
+ 'successful_predictions': len([p for p in gene_preds if p != "Unknown"]),
249
+ 'total_pathways_found': len([r for r in combined_results if r[3] != "No Pathways Found"])
250
+ }
251
+
252
+ return redirect(url_for('results'))
253
+
254
+ return render_template("index.html", predictions=[])
255
+
256
+ @app.route("/download_csv")
257
+ def download_csv():
258
+ global latest_predictions
259
+ if not latest_predictions:
260
+ return "⚠️ No predictions to download."
261
+ output = io.StringIO()
262
+ writer = csv.writer(output)
263
+ writer.writerow(["Query/Header", "Top Hit", "Bitscore", "Pathways", "Predicted Gene Family"])
264
+ writer.writerows(latest_predictions)
265
+ output.seek(0)
266
+ return send_file(
267
+ io.BytesIO(output.getvalue().encode()),
268
+ mimetype="text/csv",
269
+ as_attachment=True,
270
+ download_name="combined_predictions.csv"
271
+ )
272
+
273
+ @app.route("/results")
274
+ def results():
275
+ predictions = session.get('predictions', [])
276
+ summary = session.get('result_summary', {})
277
+
278
+ if not predictions:
279
+ return redirect(url_for('index'))
280
+
281
+ return render_template("results.html", predictions=predictions, summary=summary)
282
+
283
+ if __name__ == "__main__":
284
+ app.run(debug=True)