Biocoder09 commited on
Commit
ecd5b53
·
verified ·
1 Parent(s): 2493619

Upload 14 files

Browse files
Dockerfile ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ ENV PORT=8080
11
+
12
+ CMD ["sh", "-c", "uvicorn app:app --host 0.0.0.0 --port $PORT"]
README.md CHANGED
@@ -1,12 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
- title: Canloc
3
- emoji: 🏢
4
- colorFrom: indigo
5
- colorTo: green
6
- sdk: docker
7
- pinned: false
8
- license: apache-2.0
9
- short_description: web server for subcellular location of candida
 
 
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🧬 CANLoc — Protein Subcellular Localization Predictor
2
+
3
+ CANLoc is a production-ready machine learning web application for predicting the **subcellular localization of proteins** directly from amino acid sequences.
4
+ It provides accurate, fast, and interpretable predictions through a modern deep-learning–assisted pipeline and an interactive web interface.
5
+
6
+ ---
7
+
8
+ ## 🔬 Model Overview
9
+
10
+ CANLoc combines:
11
+
12
+ - **ESM2 (Transformer-based protein language model)**
13
+ Used for extracting rich sequence embeddings without alignment.
14
+
15
+ - **Mean pooling of residue embeddings**
16
+ Produces fixed-length feature vectors.
17
+
18
+ - **XGBoost classifier**
19
+ Trained on curated protein datasets for robust multiclass prediction.
20
+
21
+ ### Predicted Classes
22
+ - Cytoplasm
23
+ - Nucleus
24
+ - Membrane
25
+ - Mitochondria
26
+
27
+ Each prediction includes **class probabilities** and **confidence visualization.**
28
+
29
  ---
30
+
31
+ ## 📊 Features
32
+
33
+ - Single sequence prediction
34
+ - Batch prediction via FASTA file upload
35
+ - Probability bar chart and radar plot
36
+ - Confidence-based interpretation
37
+ - Clean, responsive bioinformatics-style UI
38
+ - Dockerized for reproducible deployment
39
+ - FastAPI backend + modern frontend
40
+
41
  ---
42
 
43
+ ## 🧪 Input Formats
44
+
45
+ ### Single Sequence
46
+ Paste a raw amino acid sequence: MVKFKKYGIP...
47
+
48
+
49
+ ### FASTA File
50
+ Upload a standard FASTA file with one or multiple sequences:
51
+ sp|P25296|CANB_YEAST
52
+ MSLIHPDTAKYPFKFEPF...
53
+
54
+
55
+ ---
56
+
57
+ ## 📈 Output Interpretation
58
+
59
+ - **Predicted Location**
60
+ The most probable subcellular class.
61
+
62
+ - **Class Probabilities**
63
+ Displayed as percentages for all four classes.
64
+
65
+ - **Confidence Levels**
66
+ - High: ≥ 75%
67
+ - Medium: 60–75%
68
+ - Low: < 60% (interpret with caution)
69
+
70
+ ---
71
+
72
+ ## ⚙️ Evaluation & Validation
73
+
74
+ The model was evaluated using:
75
+ - Train/test split
76
+ - 10-fold stratified cross-validation
77
+ - Precision, recall, F1-score
78
+ - Sensitivity and specificity analysis
79
+ - ROC curves per class
80
+
81
+ These evaluations confirm CANLoc’s reliability for academic/research workflows..
82
+
83
+
84
+ ---
85
+
86
+ ## 🚀 Deployment
87
+
88
+ CANLoc is containerized and deployed using **Docker** and **Railway**.
89
+
90
+ ## 📄 License
91
+ This project is licensed under the Apache License 2.0.
92
+
93
+ >Free for academic and commercial use
94
+ >Includes patent protection
95
+ >No restrictions on deployment or modification
96
+
97
+ See the LICENSE file for details.
98
+
99
+
100
+ ## 📬 Contact
101
+
102
+ For questions, bug report or feedback:
103
+ majidkhan>jssmsc@gmail.com
104
+
105
+ ## 📌 Citation
106
+
107
+ If you use CANLoc in academic work, please cite appropriately.
108
+
app.py ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ================================
2
+ # GLOBAL WARNING SUPPRESSION
3
+ # ================================
4
+ import warnings
5
+ warnings.filterwarnings("ignore", category=UserWarning)
6
+ warnings.filterwarnings("ignore", category=FutureWarning)
7
+ warnings.filterwarnings("ignore", category=RuntimeWarning)
8
+ warnings.filterwarnings("ignore", category=DeprecationWarning)
9
+ warnings.simplefilter("ignore")
10
+
11
+ # ================================
12
+ # IMPORTS
13
+ # ================================
14
+ import json
15
+ import pickle
16
+ import numpy as np
17
+ import torch
18
+ from io import StringIO
19
+ from Bio import SeqIO
20
+
21
+ from fastapi import FastAPI, Request, UploadFile, File
22
+ from fastapi.staticfiles import StaticFiles
23
+ from fastapi.responses import HTMLResponse
24
+ from fastapi.templating import Jinja2Templates
25
+
26
+ from transformers import AutoTokenizer, AutoModel
27
+
28
+ # ================================
29
+ # FASTAPI INIT + MOUNTS
30
+ # ================================
31
+ app = FastAPI()
32
+
33
+ app.mount("/static", StaticFiles(directory="static"), name="static")
34
+ templates = Jinja2Templates(directory="templates")
35
+
36
+ # ================================
37
+ # LOAD MODEL + TOKENIZER
38
+ # ================================
39
+ DEVICE = torch.device("cpu")
40
+
41
+ tokenizer = AutoTokenizer.from_pretrained("facebook/esm2_t30_150M_UR50D")
42
+ esm_model = AutoModel.from_pretrained("facebook/esm2_t30_150M_UR50D").to(DEVICE)
43
+ esm_model.eval()
44
+
45
+ with open("model.pkl", "rb") as f:
46
+ classifier = pickle.load(f)
47
+
48
+ with open("label_map.json", "r") as f:
49
+ LABEL_MAP = json.load(f)
50
+
51
+ INV_LABEL_MAP = {v: k for k, v in LABEL_MAP.items()}
52
+
53
+ # ================================
54
+ # ESM2 EMBEDDING FUNCTION
55
+ # ================================
56
+ def embed_sequence(seq: str) -> np.ndarray:
57
+ seq = seq.strip()
58
+ inputs = tokenizer(seq, return_tensors="pt", add_special_tokens=True)
59
+ inputs = {k: v.to(DEVICE) for k, v in inputs.items()}
60
+
61
+ with torch.no_grad():
62
+ outputs = esm_model(**inputs)
63
+
64
+ token_emb = outputs.last_hidden_state.squeeze(0)
65
+ mean_emb = token_emb[1:-1].mean(dim=0)
66
+ return mean_emb.cpu().numpy().reshape(1, -1)
67
+
68
+ # ================================
69
+ # PREDICT ONE SEQUENCE
70
+ # ================================
71
+ def run_single_prediction(seq: str):
72
+ emb = embed_sequence(seq)
73
+ probs = classifier.predict_proba(emb)[0]
74
+ pred_class = int(np.argmax(probs))
75
+ pred_label = INV_LABEL_MAP[pred_class]
76
+
77
+ return {
78
+ "prediction_label": pred_label,
79
+ "probabilities": {INV_LABEL_MAP[i]: float(p) for i, p in enumerate(probs)}
80
+ }
81
+
82
+ # ================================
83
+ # PREDICT FASTA FILE
84
+ # ================================
85
+ def run_fasta_prediction(content: str):
86
+ results = []
87
+ handle = StringIO(content)
88
+
89
+ for record in SeqIO.parse(handle, "fasta"):
90
+ seq = str(record.seq).strip()
91
+ if not seq:
92
+ continue
93
+
94
+ emb = embed_sequence(seq)
95
+ probs = classifier.predict_proba(emb)[0]
96
+ pred_class = int(np.argmax(probs))
97
+ pred_label = INV_LABEL_MAP[pred_class]
98
+
99
+ results.append({
100
+ "sequence": record.id,
101
+ "length": len(seq),
102
+ "prediction_label": pred_label,
103
+ "probabilities": {INV_LABEL_MAP[i]: float(p) for i, p in enumerate(probs)}
104
+ })
105
+
106
+ return {"results": results}
107
+
108
+ # ================================
109
+ # PAGE ROUTES
110
+ # ================================
111
+ @app.get("/", response_class=HTMLResponse)
112
+ async def home(request: Request):
113
+ return templates.TemplateResponse("index.html", {"request": request})
114
+
115
+ @app.get("/about", response_class=HTMLResponse)
116
+ async def about(request: Request):
117
+ return templates.TemplateResponse("about.html", {"request": request})
118
+
119
+ @app.get("/help", response_class=HTMLResponse)
120
+ async def help_page(request: Request):
121
+ return templates.TemplateResponse("help.html", {"request": request})
122
+
123
+ @app.get("/contact", response_class=HTMLResponse)
124
+ async def contact(request: Request):
125
+ return templates.TemplateResponse("contact.html", {"request": request})
126
+
127
+ # ================================
128
+ # API: UNIVERSAL SEQUENCE PREDICTION
129
+ # ================================
130
+ @app.post("/api/predict_sequence")
131
+ async def api_predict_sequence(request: Request):
132
+ # 1. Try JSON
133
+ try:
134
+ data = await request.json()
135
+ if "sequence" in data:
136
+ return run_single_prediction(data["sequence"])
137
+ except Exception:
138
+ pass
139
+
140
+ # 2. Try FormData
141
+ try:
142
+ form = await request.form()
143
+ if "sequence" in form:
144
+ return run_single_prediction(form["sequence"])
145
+ except Exception:
146
+ pass
147
+
148
+ return {"error": "No sequence provided"}
149
+
150
+ # ================================
151
+ # API: FASTA PREDICTION
152
+ # ================================
153
+ @app.post("/api/predict_fasta")
154
+ async def api_predict_fasta(file: UploadFile = File(...)):
155
+ raw = await file.read()
156
+ content = raw.decode("utf-8", errors="ignore")
157
+ return run_fasta_prediction(content)
label_map.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "cytoplasm": 0,
3
+ "membrane": 1,
4
+ "mitochondria": 2,
5
+ "nucleus": 3
6
+ }
model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d4f315be4d8d5aa0da874e85326215db418ebe391a40b020dfb061dcb06996c7
3
+ size 1879414
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ numpy<2
2
+ torch==2.2.2
3
+ transformers==4.37.2
4
+ tokenizers==0.15.2
5
+ scikit-learn==1.3.2
6
+ xgboost==2.0.3
7
+ pydantic==1.10.14
8
+ python-multipart
9
+ jinja2
10
+ biopython
11
+ fastapi
12
+ uvicorn
static/protein_hero.svg ADDED
static/script.js ADDED
@@ -0,0 +1,389 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ let barChart = null;
2
+ let radarChart = null;
3
+ let bigBarChartObj = null;
4
+ let bigRadarChartObj = null;
5
+ let fastaResults = [];
6
+
7
+ const API_BASE = ""; // same origin
8
+
9
+ //------------------ Safe Fetch ------------------
10
+ async function safeFetchJSON(url, options = {}) {
11
+ const res = await fetch(url, options);
12
+ const text = await res.text();
13
+
14
+ let data;
15
+ try {
16
+ data = JSON.parse(text);
17
+ } catch {
18
+ throw new Error(text || `HTTP ${res.status}`);
19
+ }
20
+ if (!res.ok) {
21
+ throw new Error(data.error || `HTTP ${res.status}`);
22
+ }
23
+ return data;
24
+ }
25
+
26
+ // ------------------ INIT ------------------
27
+ document.addEventListener("DOMContentLoaded", () => {
28
+ // Tabs
29
+ const tabSeq = document.getElementById("tab-seq");
30
+ const tabFasta = document.getElementById("tab-fasta");
31
+ const panelSeq = document.getElementById("panel-seq");
32
+ const panelFasta = document.getElementById("panel-fasta");
33
+
34
+ if (tabSeq && tabFasta && panelSeq && panelFasta) {
35
+ tabSeq.addEventListener("click", () => {
36
+ tabSeq.classList.add("active");
37
+ tabFasta.classList.remove("active");
38
+ panelSeq.classList.remove("hidden");
39
+ panelFasta.classList.add("hidden");
40
+ });
41
+
42
+ tabFasta.addEventListener("click", () => {
43
+ tabFasta.classList.add("active");
44
+ tabSeq.classList.remove("active");
45
+ panelFasta.classList.remove("hidden");
46
+ panelSeq.classList.add("hidden");
47
+ });
48
+ }
49
+
50
+ // Buttons
51
+ const predictBtn = document.getElementById("predictBtn");
52
+ if (predictBtn) predictBtn.addEventListener("click", predictSequence);
53
+
54
+ const predictFastaBtn = document.getElementById("predictFastaBtn");
55
+ if (predictFastaBtn) predictFastaBtn.addEventListener("click", predictFasta);
56
+
57
+ // FASTA modal close events
58
+ const fastaBackdrop = document.getElementById("fastaModalBackdrop");
59
+ const fastaClose = document.getElementById("fastaModalClose");
60
+ if (fastaBackdrop) fastaBackdrop.addEventListener("click", closeFastaModal);
61
+ if (fastaClose) fastaClose.addEventListener("click", closeFastaModal);
62
+
63
+ // Big chart click
64
+ const barCanvas = document.getElementById("barChart");
65
+ const radarCanvas = document.getElementById("radarChart");
66
+ if (barCanvas) barCanvas.addEventListener("click", openBigBar);
67
+ if (radarCanvas) radarCanvas.addEventListener("click", openBigRadar);
68
+ });
69
+
70
+ // ------------------ HELPERS ------------------
71
+ function validateSequence(seq) {
72
+ const s = seq.trim().toUpperCase();
73
+ if (!s) return [false, "Sequence is empty."];
74
+ const validAA = /^[ACDEFGHIKLMNPQRSTVWYUBZX*]+$/;
75
+ if (!validAA.test(s)) return [false, "Invalid characters in sequence."];
76
+ if (s.length < 15) return [false, "Sequence too short (min 15 AA)."];
77
+ return [true, null];
78
+ }
79
+
80
+ // Capitalize class names
81
+ function pretty(name) {
82
+ return name.charAt(0).toUpperCase() + name.slice(1);
83
+ }
84
+
85
+ // Probability severity
86
+ function getConfidenceMeta(maxProb) {
87
+ if (maxProb >= 0.75) return { color: "#4ade80", text: "High confidence" };
88
+ if (maxProb >= 0.6) return { color: "#facc15", text: "Medium confidence" };
89
+ return { color: "#f87171", text: "Low confidence – interpret cautiously" };
90
+ }
91
+
92
+ // Bar chart colors
93
+ function buildBarColors(values) {
94
+ const max = Math.max(...values);
95
+ return values.map(v =>
96
+ v === max ? "rgba(45,212,191,0.95)" : "rgba(166,184,184,0.9)"
97
+ );
98
+ }
99
+
100
+ // Probability list builder
101
+ function updateProbList(container, probs) {
102
+ if (!container) return;
103
+ container.innerHTML = "";
104
+ Object.entries(probs).forEach(([k, v]) => {
105
+ const div = document.createElement("div");
106
+ div.className = "prob-item";
107
+ const left = document.createElement("span");
108
+ left.textContent = pretty(k); // CAPITALIZED
109
+ const right = document.createElement("span");
110
+ right.textContent = (v * 100).toFixed(2) + "%"; // PERCENT
111
+ div.appendChild(left);
112
+ div.appendChild(right);
113
+ container.appendChild(div);
114
+ });
115
+ }
116
+
117
+ // ------------------ CHART DRAWING ------------------
118
+ function drawCharts(classLabels, rawValues) {
119
+ const barCanvas = document.getElementById("barChart");
120
+ const radarCanvas = document.getElementById("radarChart");
121
+ if (!barCanvas || !radarCanvas) return;
122
+
123
+ const labels = classLabels.map(pretty); // CAPITALIZE
124
+ const values = rawValues;
125
+
126
+ if (barChart) barChart.destroy();
127
+ if (radarChart) radarChart.destroy();
128
+
129
+ // Bar Chart
130
+ barChart = new Chart(barCanvas.getContext("2d"), {
131
+ type: "bar",
132
+ data: {
133
+ labels,
134
+ datasets: [
135
+ {
136
+ data: values,
137
+ backgroundColor: buildBarColors(values),
138
+ borderRadius: 6
139
+ }
140
+ ]
141
+ },
142
+ options: {
143
+ responsive: true,
144
+ plugins: {
145
+ legend: { display: false },
146
+ tooltip: {
147
+ callbacks: {
148
+ label: ctx => (ctx.parsed.y * 100).toFixed(2) + "%" // PERCENT
149
+ }
150
+ }
151
+ },
152
+ scales: {
153
+ y: { beginAtZero: true, max: 1 },
154
+ x: { ticks: { color: "#e5e7eb" } }
155
+ }
156
+ }
157
+ });
158
+
159
+ // Radar Chart
160
+ radarChart = new Chart(radarCanvas.getContext("2d"), {
161
+ type: "radar",
162
+ data: {
163
+ labels,
164
+ datasets: [
165
+ {
166
+ data: values,
167
+ backgroundColor: "rgba(45,212,191,0.18)",
168
+ borderColor: "rgba(45,212,191,0.9)",
169
+ borderWidth: 2
170
+ }
171
+ ]
172
+ },
173
+ options: {
174
+ responsive: true,
175
+ maintainAspectRatio: false,
176
+ plugins: {
177
+ legend: { display: false },
178
+ tooltip: {
179
+ callbacks: {
180
+ label: ctx => (ctx.parsed.r * 100).toFixed(2) + "%" // PERCENT
181
+ }
182
+ }
183
+ },
184
+ scales: {
185
+ r: {
186
+ beginAtZero: true,
187
+ max: 1,
188
+ grid: { color: "rgba(55,65,81,0.4)" },
189
+ angleLines: { color: "rgba(55,65,81,0.4)" },
190
+ pointLabels: { color: "#e5e7eb", font: { size: 16 } },
191
+ ticks: { display: false }
192
+ }
193
+ }
194
+ }
195
+ });
196
+ }
197
+
198
+ // ------------------ SINGLE SEQUENCE ------------------
199
+ async function predictSequence() {
200
+ const seqInput = document.getElementById("sequenceInput");
201
+ const seq = seqInput.value.trim();
202
+
203
+ const [ok, err] = validateSequence(seq);
204
+ if (!ok) return alert(err);
205
+
206
+ const loading = document.getElementById("loadingSeq");
207
+ const resultCard = document.getElementById("seqResultCard");
208
+ const resultHeader = document.getElementById("seqResultHeader");
209
+ const warningEl = document.getElementById("seqConfidenceWarning");
210
+ const probList = document.getElementById("seqProbList");
211
+
212
+ loading.classList.remove("hidden");
213
+ resultCard.classList.add("hidden");
214
+
215
+ try {
216
+ const res = await fetch(`${API_BASE}/api/predict_sequence`, {
217
+ method: "POST",
218
+ headers: { "Content-Type": "application/json" },
219
+ body: JSON.stringify({ sequence: seq })
220
+ });
221
+ const data = await res.json();
222
+ loading.classList.add("hidden");
223
+
224
+ if (!res.ok || data.error) return alert(data.error || "Prediction failed.");
225
+
226
+ const label = pretty(data.prediction_label); // CAPITALIZED
227
+ const probs = data.probabilities || {};
228
+ const labels = Object.keys(probs);
229
+ const values = labels.map(k => probs[k]);
230
+
231
+ const maxProb = Math.max(...values);
232
+ const meta = getConfidenceMeta(maxProb);
233
+
234
+ resultHeader.innerHTML = `<span style="color:${meta.color}">Predicted Location: ${label}</span>`;
235
+ warningEl.textContent = meta.text;
236
+ warningEl.style.color = meta.color;
237
+
238
+ updateProbList(probList, probs);
239
+ drawCharts(labels, values);
240
+ resultCard.classList.remove("hidden");
241
+
242
+ } catch (e) {
243
+ loading.classList.add("hidden");
244
+ alert("Server error: " + e);
245
+ }
246
+ }
247
+
248
+ // ------------------ FASTA ------------------
249
+ async function predictFasta() {
250
+ const fileInput = document.getElementById("fastaFile");
251
+ const file = fileInput.files[0];
252
+
253
+ if (!file) return alert("Choose a FASTA file.");
254
+
255
+ const loading = document.getElementById("loadingFasta");
256
+ const wrapper = document.getElementById("fastaResultsWrapper");
257
+ const tbody = document.getElementById("fastaTableBody");
258
+
259
+ loading.classList.remove("hidden");
260
+ wrapper.classList.add("hidden");
261
+ tbody.innerHTML = "";
262
+ fastaResults = [];
263
+
264
+ try {
265
+ const form = new FormData();
266
+ form.append("file", file);
267
+
268
+ const res = await fetch(`${API_BASE}/api/predict_fasta`, {
269
+ method: "POST",
270
+ body: form
271
+ });
272
+ const data = await res.json();
273
+
274
+ loading.classList.add("hidden");
275
+
276
+ if (!res.ok || data.error) return alert(data.error || "FASTA read error.");
277
+
278
+ fastaResults = data.results;
279
+ renderFastaTable();
280
+ wrapper.classList.remove("hidden");
281
+
282
+ } catch (e) {
283
+ loading.classList.add("hidden");
284
+ alert("Server error: " + e);
285
+ }
286
+ }
287
+
288
+ function renderFastaTable() {
289
+ const tbody = document.getElementById("fastaTableBody");
290
+ tbody.innerHTML = "";
291
+
292
+ fastaResults.forEach((r, idx) => {
293
+ const probs = r.probabilities || {};
294
+ const maxProb = Math.max(...Object.values(probs));
295
+
296
+ const tr = document.createElement("tr");
297
+ tr.dataset.index = idx;
298
+ tr.innerHTML = `
299
+ <td>${idx + 1}</td>
300
+ <td>${r.sequence}</td>
301
+ <td>${r.length}</td>
302
+ <td>${pretty(r.prediction_label)}</td>
303
+ <td>${(maxProb * 100).toFixed(2)}%</td>
304
+ `;
305
+ tr.addEventListener("click", () => openFastaModal(idx));
306
+ tbody.appendChild(tr);
307
+ });
308
+ }
309
+
310
+ // FASTA modal (no charts)
311
+ function openFastaModal(i) {
312
+ const r = fastaResults[i];
313
+ if (!r) return;
314
+
315
+ document.getElementById("fastaModalTitle").textContent =
316
+ `Sequence: ${r.sequence}`;
317
+
318
+ document.getElementById("fastaModalMeta").textContent =
319
+ `Length: ${r.length} | Predicted: ${pretty(r.prediction_label)}`;
320
+
321
+ updateProbList(document.getElementById("fastaProbList"), r.probabilities);
322
+
323
+ document.getElementById("fastaModal").classList.remove("hidden");
324
+ }
325
+
326
+ function closeFastaModal() {
327
+ document.getElementById("fastaModal").classList.add("hidden");
328
+ }
329
+
330
+ // ------------------ BIG CHART MODALS ------------------
331
+ function openBigBar() {
332
+ if (!barChart) return;
333
+ const modal = document.getElementById("bigBarModal");
334
+ const ctx = document.getElementById("bigBarChart").getContext("2d");
335
+
336
+ if (bigBarChartObj) bigBarChartObj.destroy();
337
+
338
+ bigBarChartObj = new Chart(ctx, {
339
+ type: barChart.config.type,
340
+ data: barChart.config.data,
341
+ options: {
342
+ responsive: false,
343
+ animation: false,
344
+ scales: {
345
+ y: { beginAtZero: true, max: 1 },
346
+ x: { ticks: { color: "#e5e7eb" } }
347
+ }
348
+ }
349
+ });
350
+
351
+ modal.classList.remove("hidden");
352
+ }
353
+
354
+ function closeBigBar() {
355
+ document.getElementById("bigBarModal").classList.add("hidden");
356
+ }
357
+
358
+ function openBigRadar() {
359
+ if (!radarChart) return;
360
+ const modal = document.getElementById("bigRadarModal");
361
+ const ctx = document.getElementById("bigRadarChart").getContext("2d");
362
+
363
+ if (bigRadarChartObj) bigRadarChartObj.destroy();
364
+
365
+ bigRadarChartObj = new Chart(ctx, {
366
+ type: radarChart.config.type,
367
+ data: radarChart.config.data,
368
+ options: {
369
+ responsive: false,
370
+ animation: false,
371
+ scales: {
372
+ r: {
373
+ beginAtZero: true,
374
+ max: 1,
375
+ grid: { color: "rgba(55,65,81,0.4)" },
376
+ angleLines: { color: "rgba(55,65,81,0.4)" },
377
+ pointLabels: { color: "#e4e4e4ff", font: { size: 18 } },
378
+ ticks: { display: false }
379
+ }
380
+ }
381
+ }
382
+ });
383
+
384
+ modal.classList.remove("hidden");
385
+ }
386
+
387
+ function closeBigRadar() {
388
+ document.getElementById("bigRadarModal").classList.add("hidden");
389
+ }
static/style.css ADDED
@@ -0,0 +1,436 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root{
2
+ --navy:#04293a;
3
+ --navy-dark:#02151a;
4
+ --turq:#2dd4bf;
5
+ --turq-light:#5ffbf1;
6
+ --text:#e6f7f5;
7
+ --muted:#a6b8b8;
8
+ --card-bg:rgba(255,255,255,0.02);
9
+ --border:rgba(255,255,255,0.04);
10
+ --glass:rgba(255,255,255,0.03);
11
+ }
12
+
13
+ /* RESET */
14
+ *{
15
+ margin:0;
16
+ padding:0;
17
+ box-sizing:border-box;
18
+ font-family:Inter, system-ui, Roboto, "Helvetica Neue", Arial;
19
+ }
20
+ body{
21
+ background:linear-gradient(180deg, var(--navy-dark), #041a20);
22
+ color:var(--text);
23
+ -webkit-font-smoothing:antialiased;
24
+ }
25
+
26
+ /* NAVBAR */
27
+ .nav.fixed{
28
+ position:fixed;
29
+ top:0; left:0; right:0;
30
+ z-index:100;
31
+ background:rgba(4,41,58,0.93);
32
+ backdrop-filter:blur(8px);
33
+ border-bottom:1px solid var(--border);
34
+ }
35
+ .nav-inner{
36
+ max-width:1180px;
37
+ margin:auto;
38
+ padding:12px 16px;
39
+ display:flex;
40
+ justify-content:space-between;
41
+ align-items:center;
42
+ }
43
+ .nav-left{
44
+ display:flex;
45
+ align-items:center;
46
+ }
47
+ .logo-circle{
48
+ width:42px;
49
+ height:42px;
50
+ border-radius:50%;
51
+ background:linear-gradient(135deg, var(--turq), #0be8c4);
52
+ display:flex;
53
+ justify-content:center;
54
+ align-items:center;
55
+ font-size:20px;
56
+ }
57
+ .logo-text{
58
+ margin-left:10px;
59
+ }
60
+ .logo-title{
61
+ font-weight:700;
62
+ font-size:20px;
63
+ }
64
+ .logo-sub{
65
+ color:var(--muted);
66
+ font-size:12px;
67
+ margin-top:2px;
68
+ }
69
+ .nav-links{
70
+ display:flex;
71
+ gap:20px;
72
+ }
73
+ .nav-link{
74
+ text-decoration:none;
75
+ color:var(--muted);
76
+ font-size:14px;
77
+ padding:6px 10px;
78
+ border-radius:8px;
79
+ }
80
+ .nav-link:hover{
81
+ color:var(--turq);
82
+ background:rgba(255,255,255,0.03);
83
+ }
84
+
85
+ /* HERO (SVG background) */
86
+ .hero{
87
+ height:260px;
88
+ display:flex;
89
+ align-items:center;
90
+ justify-content:center;
91
+ position:relative;
92
+ overflow:hidden;
93
+ margin-top:70px;
94
+ background:linear-gradient(180deg, var(--navy-dark), #042029);
95
+ }
96
+ .hero-network{
97
+ position:absolute;
98
+ inset:0;
99
+ background-image:url("/static/protein_hero.svg");
100
+ background-size:cover;
101
+ background-position:center;
102
+ opacity:0.42;
103
+ filter:drop-shadow(0 0 35px var(--turq));
104
+ pointer-events:none;
105
+ }
106
+ .hero-inner{
107
+ position:relative;
108
+ z-index:2;
109
+ text-align:center;
110
+ }
111
+ .hero-title{
112
+ font-size:42px;
113
+ font-weight:700;
114
+ line-height:1.1;
115
+ text-shadow:0 0 8px rgba(20,184,166,0.55);
116
+ }
117
+ .hero-title span{
118
+ color:var(--turq);
119
+ }
120
+ .hero-sub{
121
+ color:var(--muted);
122
+ margin-top:10px;
123
+ font-size:15px;
124
+ max-width:850px;
125
+ margin:auto;
126
+ }
127
+
128
+ /* MAIN LAYOUT */
129
+ .main-wrap{
130
+ max-width:1100px;
131
+ margin:20px auto 60px auto;
132
+ padding:0 16px;
133
+ }
134
+ .card{
135
+ background:var(--card-bg);
136
+ border:1px solid var(--border);
137
+ border-radius:14px;
138
+ padding:20px;
139
+ box-shadow:0 10px 30px rgba(0,0,0,0.5);
140
+ margin-bottom:20px;
141
+ }
142
+
143
+ /* TABS */
144
+ .tabs{
145
+ display:flex;
146
+ gap:10px;
147
+ margin-bottom:16px;
148
+ }
149
+ .tab{
150
+ padding:8px 16px;
151
+ border-radius:999px;
152
+ border:1px solid var(--border);
153
+ background:transparent;
154
+ color:var(--muted);
155
+ cursor:pointer;
156
+ }
157
+ .tab.active{
158
+ background:linear-gradient(90deg, var(--turq), #24d6b3);
159
+ color:#05323a;
160
+ font-weight:700;
161
+ }
162
+ .panel{
163
+ margin-top:10px;
164
+ }
165
+
166
+ /* TEXTAREA + UPLOAD */
167
+ .label{
168
+ color:var(--muted);
169
+ font-size:13px;
170
+ margin-bottom:6px;
171
+ display:block;
172
+ }
173
+ textarea{
174
+ width:100%;
175
+ min-height:160px;
176
+ background:var(--glass);
177
+ border:1px solid var(--border);
178
+ border-radius:10px;
179
+ padding:12px;
180
+ color:var(--text);
181
+ resize:vertical;
182
+ }
183
+ input[type=file]{
184
+ color:var(--muted);
185
+ }
186
+ .row{
187
+ margin-top:12px;
188
+ display:flex;
189
+ align-items:center;
190
+ gap:12px;
191
+ }
192
+ .btn{
193
+ background:linear-gradient(90deg, var(--turq), #25cdb6);
194
+ color:#032b2a;
195
+ border:none;
196
+ font-weight:700;
197
+ padding:10px 18px;
198
+ border-radius:999px;
199
+ cursor:pointer;
200
+ }
201
+ .btn:hover{
202
+ filter:brightness(1.08);
203
+ }
204
+ .loading{
205
+ font-size:13px;
206
+ color:var(--muted);
207
+ }
208
+
209
+ /* RESULT CARD */
210
+ .result-card{
211
+ background:rgba(255,255,255,0.02);
212
+ margin-top:20px;
213
+ padding:18px;
214
+ border-radius:12px;
215
+ border:1px solid var(--border);
216
+ }
217
+ .result-header{
218
+ font-size:20px;
219
+ font-weight:700;
220
+ }
221
+ .warning-text{
222
+ color:var(--muted);
223
+ margin-top:6px;
224
+ }
225
+
226
+ /* PROBABILITIES */
227
+ .prob-box{
228
+ margin-top:14px;
229
+ }
230
+ .prob-list{
231
+ display:grid;
232
+ grid-template-columns:repeat(auto-fit, minmax(150px,1fr));
233
+ gap:10px;
234
+ }
235
+ .prob-item{
236
+ background:rgba(255,255,255,0.02);
237
+ padding:8px;
238
+ border-radius:8px;
239
+ border:1px solid var(--border);
240
+ display:flex;
241
+ justify-content:space-between;
242
+ font-size:14px;
243
+ }
244
+
245
+ /* CHARTS */
246
+ .chart-title {
247
+ text-align: center;
248
+ font-weight: 700;
249
+ margin-bottom: 8px;
250
+ }
251
+
252
+ .charts-row{
253
+ display:flex;
254
+ gap:16px;
255
+ flex-wrap:wrap;
256
+ margin-top:14px;
257
+ }
258
+ .chart-card{
259
+ flex:1 1 300px;
260
+ background:rgba(255,255,255,0.01);
261
+ border:1px solid var(--border);
262
+ padding:12px;
263
+ border-radius:10px;
264
+ }
265
+ canvas{
266
+ width:100%!important;
267
+ height:240px!important;
268
+ }
269
+
270
+ /* FASTA TABLE */
271
+ .fasta-wrapper{
272
+ margin-top:18px;
273
+ }
274
+ .small-text{
275
+ color:var(--muted);
276
+ font-size:13px;
277
+ margin-bottom:8px;
278
+ }
279
+ .table-container{
280
+ margin-top:8px;
281
+ border-radius:12px;
282
+ overflow:hidden;
283
+ border:1px solid var(--border);
284
+ }
285
+ .results-table{
286
+ width:100%;
287
+ border-collapse:collapse;
288
+ font-size:14px;
289
+ }
290
+ .results-table th,
291
+ .results-table td{
292
+ padding:10px;
293
+ border-bottom:1px solid rgba(255,255,255,0.04);
294
+ color:var(--muted);
295
+ }
296
+ .results-table tbody tr{
297
+ cursor:pointer;
298
+ }
299
+ .results-table tbody tr:hover{
300
+ background:rgba(255,255,255,0.04);
301
+ }
302
+
303
+ /* FASTA MODAL (no charts) */
304
+ .modal{
305
+ position:fixed;
306
+ inset:0;
307
+ display:flex;
308
+ justify-content:center;
309
+ align-items:center;
310
+ z-index:200;
311
+ }
312
+ .modal-backdrop{
313
+ position:absolute;
314
+ inset:0;
315
+ background:rgba(0,0,0,0.7);
316
+ }
317
+ .modal-content{
318
+ position:relative;
319
+ z-index:2;
320
+ background:linear-gradient(180deg,#041e21,#022326);
321
+ border:1px solid var(--border);
322
+ padding:16px;
323
+ border-radius:14px;
324
+ width:92%;
325
+ max-width:600px;
326
+ }
327
+ .modal-close{
328
+ position:absolute;
329
+ top:8px;
330
+ right:10px;
331
+ background:transparent;
332
+ border:none;
333
+ font-size:22px;
334
+ color:var(--muted);
335
+ cursor:pointer;
336
+ }
337
+ .modal-meta{
338
+ color:var(--muted);
339
+ font-size:13px;
340
+ margin-bottom:8px;
341
+ }
342
+
343
+ /* BIG CHART MODALS */
344
+ .chart-modal{
345
+ position:fixed;
346
+ inset:0;
347
+ background:rgba(0,0,0,0.75);
348
+ display:flex;
349
+ justify-content:center;
350
+ align-items:center;
351
+ z-index:3000;
352
+ }
353
+ .chart-modal-content{
354
+ position:relative;
355
+ background:#0f172a;
356
+ padding:20px;
357
+ border-radius:10px;
358
+ }
359
+ .close-big-chart{
360
+ position:absolute;
361
+ top:10px;
362
+ right:15px;
363
+ font-size:26px;
364
+ cursor:pointer;
365
+ color:white;
366
+ }
367
+
368
+ /* FOOTER */
369
+ .footer{
370
+ text-align:center;
371
+ padding:18px 0;
372
+ color:var(--muted);
373
+ border-top:1px solid var(--border);
374
+ margin-top:40px;
375
+ }
376
+
377
+ /* FASTA TABLE ALIGNMENT */
378
+ /* Targets the table you already render: .results-table */
379
+ .results-table {
380
+ width: 100%;
381
+ border-collapse: collapse;
382
+ table-layout: fixed; /* force column widths to be respected */
383
+ font-size: 14px;
384
+ }
385
+
386
+ .results-table th,
387
+ .results-table td {
388
+ text-align: center;
389
+ padding: 10px;
390
+ white-space: nowrap;
391
+ overflow: hidden;
392
+ text-overflow: ellipsis;
393
+ }
394
+
395
+ /* set sensible column widths (adjust numbers if needed) */
396
+ .results-table th:nth-child(1),
397
+ .results-table td:nth-child(1) { width: 48px; } /* # */
398
+ .results-table th:nth-child(2),
399
+ .results-table td:nth-child(2) { width: 360px; } /* Sequence ID */
400
+ .results-table th:nth-child(3),
401
+ .results-table td:nth-child(3) { width: 110px; } /* Length */
402
+ .results-table th:nth-child(4),
403
+ .results-table td:nth-child(4) { width: 180px; } /* Predicted Location */
404
+ .results-table th:nth-child(5),
405
+ .results-table td:nth-child(5) { width: 120px; } /* Max Confidence */
406
+
407
+ /* visual niceties */
408
+ .results-table thead th {
409
+ background: rgba(10, 61, 98, 0.95);
410
+ color: #fff;
411
+ font-weight: 600;
412
+ }
413
+ .results-table tbody tr:hover {
414
+ background: rgba(255,255,255,0.02);
415
+ }
416
+
417
+
418
+ /* UTILITIES */
419
+ .hidden{
420
+ display:none;
421
+ }
422
+
423
+ /* RESPONSIVE */
424
+ @media(max-width:860px){
425
+ .nav-inner{
426
+ flex-direction:column;
427
+ align-items:flex-start;
428
+ gap:6px;
429
+ }
430
+ .hero-title{
431
+ font-size:32px;
432
+ }
433
+ canvas{
434
+ height:220px!important;
435
+ }
436
+ }
templates/about.html ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+
4
+ <div class="main-wrap">
5
+ <section class="card">
6
+ <h2>About CANLoc</h2>
7
+
8
+ <p>
9
+ CANLoc is a machine-learning system designed to predict the subcellular
10
+ localization of proteins directly from the protein sequence. It combines
11
+ transformer-based embeddings from the <b>ESM2</b> model
12
+ with an optimized <b>XGBoost</b> classifier trained on curated
13
+ protein datasets.
14
+ </p>
15
+
16
+ <h3>Performance & Evaluation</h3>
17
+
18
+ <p>
19
+ CANLoc achieves high accuracy, precision, recall, and F1-scores across all
20
+ classes. We additionally validate the model using:
21
+ </p>
22
+
23
+ <ul>
24
+ <li>Train/test split evaluation</li>
25
+ <li>10-fold stratified cross-validation</li>
26
+ <li>ROC curves for each class</li>
27
+ <li>Sensitivity and specificity analysis</li>
28
+ </ul>
29
+
30
+ <p>
31
+ These evaluations confirm that CANLoc predictions are reliable for academic
32
+ and research workflows.
33
+ </p>
34
+
35
+ <h3>Intended Use</h3>
36
+
37
+ <p>
38
+ CANLoc is designed for:
39
+ </p>
40
+
41
+ <ul>
42
+ <li>Functional protein studies</li>
43
+ <li>Localization-oriented drug delivery strategy</li>
44
+ </ul>
45
+
46
+ <h3>Model Strengths</h3>
47
+ <ul>
48
+ <li>Fast and scalable for single or batch prediction</li>
49
+ <li>Transformer embeddings provide rich biological context</li>
50
+ <li>High accuracy with interpretable confidence metrics</li>
51
+ <li>No alignment or preprocessing required beyond the raw sequence</li>
52
+ </ul>
53
+
54
+ <h3>Limitations</h3>
55
+ <ul>
56
+ <li>Performance depends on sequence length and quality</li>
57
+ <li>Ambiguous sequences may reduce confidence</li>
58
+ <li>Designed for four major classes only</li>
59
+ </ul>
60
+
61
+ <p>
62
+ CANLoc represents a balance between modern deep learning and classical machine
63
+ learning methods, producing a system that is both <b>reliable</b> and
64
+ <b>lightweight enough to deploy</b> in real-world web applications.
65
+ </p>
66
+
67
+ </section>
68
+ </div>
69
+
70
+ {% endblock %}
templates/base.html ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>CANLoc – Protein Subcellular Localization Predictor</title>
6
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
7
+ <link rel="stylesheet" href="/static/style.css" />
8
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
9
+ <script src="/static/script.js" defer></script>
10
+ </head>
11
+ <body>
12
+ <!-- Fixed Navbar -->
13
+ <header class="nav fixed">
14
+ <div class="nav-inner">
15
+ <div class="nav-left">
16
+ <div class="logo-circle">🧬</div>
17
+ <div class="logo-text">
18
+ <div class="logo-title">Welcome to CANLoc</div>
19
+ <div class="logo-sub">A web server for predicting subcellular localization of protein</div>
20
+ </div>
21
+ </div>
22
+ <nav class="nav-links">
23
+ <a href="/" class="nav-link">Home</a>
24
+ <a href="/about" class="nav-link">About</a>
25
+ <a href="/help" class="nav-link">Help</a>
26
+ <a href="/contact" class="nav-link">Contact</a>
27
+ </nav>
28
+ </div>
29
+ </header>
30
+
31
+ <!-- Hero with neon SVG background -->
32
+ <section class="hero">
33
+ <div class="hero-network"></div>
34
+ <div class="hero-inner">
35
+ <h1 class="hero-title">
36
+ CANLoc<br>
37
+ </h1>
38
+ <p class="hero-sub">
39
+ Subcellular localization predictor of Candida.
40
+ </p>
41
+ </div>
42
+ </section>
43
+
44
+ {% block content %}{% endblock %}
45
+
46
+ <footer class="footer">
47
+ CANLoc – v1.0 Research/Academic Use Only.
48
+ </footer>
49
+ </body>
50
+ </html>
templates/contact.html ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+
4
+ <section class="section-card">
5
+ <h2>Contact for feedback or bug reports</h2>
6
+
7
+ <table style="width:100%; max-width:900px; margin:auto; border-collapse:collapse;">
8
+ <tr>
9
+ <!-- Supervisor column -->
10
+ <td style="width:50%; vertical-align: top; padding:10px;">
11
+ <h3>Dr. SM JAIMOHAN</h3>
12
+ <p><strong>Project Planning & Supervision</strong></p>
13
+ <p>
14
+ Senior Scientist<br>
15
+ Advanced Materials Laboratory<br>
16
+ CSIR-CLRI<br>
17
+ Adyar, Chennai 600020<br>
18
+ TN, India
19
+ </p>
20
+ <p>
21
+ Email:
22
+ <a>
23
+ jai_clri@csir.res.in
24
+ </a>
25
+ </p>
26
+ </td>
27
+
28
+ <!-- Developer column -->
29
+ <td style="width:50%; vertical-align: top; padding:10px;">
30
+ <h3>Mr. MAJID KHAN, M.Sc Bioinformatics</h3>
31
+ <p><strong>Developer</strong></p>
32
+ <p>
33
+ Workflow designer<br>
34
+ Architecture behind the Prediction Webserver,
35
+ responsible for system design, implementation, and optimisation.
36
+ </p>
37
+ <p>
38
+ Advanced Materials Laboratory<br>
39
+ CSIR-CLRI<br>
40
+ Chennai 600020
41
+ </p>
42
+ <p>
43
+ Email:
44
+ <a>
45
+ majidkhan.jssmsc@gmail.com
46
+ </a>
47
+ </p>
48
+ </td>
49
+ </tr>
50
+ </table>
51
+ </section>
52
+
53
+ {% endblock %}
templates/help.html ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+
4
+ <div class="main-wrap">
5
+ <section class="card">
6
+ <h2>Help &amp; Usage</h2>
7
+
8
+ <h3>Single Sequence</h3>
9
+ <ul>
10
+ <li>Paste a protein sequence using single-letter amino acid code.</li>
11
+ <li>Remove spaces, numbers, headers, or other characters.</li>
12
+ <li>Minimum length: 15 amino acids.</li>
13
+ </ul>
14
+
15
+ <h3>FASTA File</h3>
16
+ <ul>
17
+ <li>For Multiple sequences, upload a valid FASTA file (.fa, .fasta).</li>
18
+ <li>Each entry will be analyzed individually.</li>
19
+ <li>Refer to a row in the results table to see probabilities for that sequence.</li>
20
+ </ul>
21
+
22
+ <h3>Interpreting Predictions</h3>
23
+ <ul>
24
+ <li><b>Predicted Location</b>: class with the highest probability.</li>
25
+ <li><b>Confidence Bar Chart</b>: probability of each class.</li>
26
+ <li><b>Radar Plot</b>: comparative view of probabilities across all classes.</li>
27
+ <li>Use medium/low confidence predictions cautiously and combine with biological context.</li>
28
+ </ul>
29
+ </section>
30
+ </div>
31
+
32
+ {% endblock %}
templates/index.html ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+
4
+ <main class="main-wrap">
5
+ <section class="card">
6
+ <div class="tabs">
7
+ <button id="tab-seq" class="tab active">Single Sequence</button>
8
+ <button id="tab-fasta" class="tab">Multiple Sequences</button>
9
+ </div>
10
+
11
+ <!-- Single sequence panel -->
12
+ <div id="panel-seq" class="panel">
13
+ <label class="label">Protein Sequence</label>
14
+ <textarea id="sequenceInput" placeholder="Paste protein sequence"></textarea>
15
+ <div class="row">
16
+ <button id="predictBtn" class="btn">Predict Localization</button>
17
+ <div id="loadingSeq" class="loading hidden">Running prediction…</div>
18
+ </div>
19
+
20
+ <div id="seqResultCard" class="result-card hidden">
21
+ <div id="seqResultHeader" class="result-header"></div>
22
+ <div id="seqConfidenceWarning" class="warning-text"></div>
23
+
24
+ <div class="charts-row">
25
+ <div class="chart-card">
26
+ <h4 class="chart-title">Confidence Bar Chart</h4>
27
+ <canvas id="barChart"></canvas>
28
+ </div>
29
+ <div class="chart-card">
30
+ <h4 class="chart-title">Radar Plot</h4>
31
+ <canvas id="radarChart"></canvas>
32
+ </div>
33
+ </div>
34
+
35
+ <div class="prob-box">
36
+ <h4>Class Probabilities</h4>
37
+ <div id="seqProbList" class="prob-list"></div>
38
+ </div>
39
+ </div>
40
+ </div>
41
+
42
+ <!-- FASTA panel -->
43
+ <div id="panel-fasta" class="panel hidden">
44
+ <label class="label">Upload FASTA file</label>
45
+ <input id="fastaFile" type="file" accept=".fa,.fasta" />
46
+ <div class="row">
47
+ <button id="predictFastaBtn" class="btn">Predict from FASTA</button>
48
+ <div id="loadingFasta" class="loading hidden">Running batch prediction…</div>
49
+ </div>
50
+
51
+ <div id="fastaResultsWrapper" class="fasta-wrapper hidden">
52
+ <h3>FASTA Predictions</h3>
53
+ <p class="small-text"> Refer to the row for probabilities of the sequence.</p>
54
+ <div class="table-container">
55
+ <table class="results-table">
56
+ <thead>
57
+ <tr>
58
+ <th>#</th>
59
+ <th>Sequence ID</th>
60
+ <th>Length</th>
61
+ <th>Predicted Location</th>
62
+ <th>Max Confidence</th>
63
+ </tr>
64
+ </thead>
65
+ <tbody id="fastaTableBody"></tbody>
66
+ </table>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </section>
71
+ </main>
72
+
73
+ <!-- FASTA detail modal (NO graphs, only details) -->
74
+ <div id="fastaModal" class="modal hidden">
75
+ <div class="modal-backdrop" id="fastaModalBackdrop"></div>
76
+ <div class="modal-content">
77
+ <button id="fastaModalClose" class="modal-close">&times;</button>
78
+ <h3 id="fastaModalTitle">Sequence details</h3>
79
+ <p id="fastaModalMeta" class="modal-meta"></p>
80
+
81
+ <div class="prob-box">
82
+ <h4>Class Probabilities</h4>
83
+ <div id="fastaProbList" class="prob-list"></div>
84
+ </div>
85
+ </div>
86
+ </div>
87
+
88
+ <!-- BIG BAR CHART MODAL -->
89
+ <div id="bigBarModal" class="chart-modal hidden">
90
+ <div class="chart-modal-content">
91
+ <span class="close-big-chart" onclick="closeBigBar()">&times;</span>
92
+ <canvas id="bigBarChart" width="700" height="700"></canvas>
93
+ </div>
94
+ </div>
95
+
96
+ <!-- BIG RADAR CHART MODAL -->
97
+ <div id="bigRadarModal" class="chart-modal hidden">
98
+ <div class="chart-modal-content">
99
+ <span class="close-big-chart" onclick="closeBigRadar()">&times;</span>
100
+ <canvas id="bigRadarChart" width="700" height="700"></canvas>
101
+ </div>
102
+ </div>
103
+
104
+ {% endblock %}