Geonomic commited on
Commit
e9d9f3f
·
verified ·
1 Parent(s): f4f9533

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +167 -0
app.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import os
3
+ import re
4
+ import requests
5
+ import torch
6
+ import joblib
7
+ import numpy as np
8
+ import torch.nn.functional as F
9
+ from Bio.Blast import NCBIWWW
10
+ from Bio.Blast import NCBIXML
11
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification, AutoModel
12
+
13
+ # Configure the web page styling
14
+ st.set_page_config(page_title="The Genomic Oracle", page_icon="🧬", layout="wide")
15
+
16
+ # ==========================================
17
+ # 1. LOAD AI MODELS (CACHED IN RAM)
18
+ # ==========================================
19
+ @st.cache_resource(show_spinner="Booting up the Oracle Network...")
20
+ def load_oracle_brains():
21
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
22
+
23
+ clf = joblib.load("coding_classifier_universal.joblib")
24
+
25
+ tokenizer = AutoTokenizer.from_pretrained("DNABERT_Local", trust_remote_code=True)
26
+ model_base = AutoModel.from_pretrained("DNABERT_Local", trust_remote_code=True).to(device)
27
+ model_base.eval()
28
+
29
+ model_promoter = AutoModelForSequenceClassification.from_pretrained("llm_promoter_classifier_v2", trust_remote_code=True).to(device)
30
+ model_promoter.eval()
31
+
32
+ return device, tokenizer, clf, model_base, model_promoter
33
+
34
+ device, tokenizer, clf_coding, model_base, model_promoter = load_oracle_brains()
35
+
36
+ # ==========================================
37
+ # 2. BIOINFORMATICS PIPELINE
38
+ # ==========================================
39
+ def analyze_sequence(dna_sequence):
40
+ clean_seq = "".join(dna_sequence.split()).upper()
41
+ inputs = tokenizer([clean_seq], return_tensors="pt", max_length=300, truncation=True, padding=True)
42
+ inputs = {k: v.to(device) for k, v in inputs.items()}
43
+
44
+ with torch.no_grad():
45
+ out_base = model_base(**inputs)
46
+ mask = inputs["attention_mask"].unsqueeze(-1)
47
+ embedding = (out_base[0] * mask).sum(dim=1) / mask.sum(dim=1).clamp(min=1)
48
+ vector = embedding.float().cpu().numpy()
49
+
50
+ p_coding = clf_coding.predict_proba(vector)[0][1]
51
+
52
+ if p_coding >= 0.60:
53
+ return "GENE (Protein-Coding DNA)", p_coding, {"Level 1 (Gene)": p_coding, "Level 2 (Promoter)": "Skipped", "Level 3 (Intergenic)": "Skipped"}
54
+
55
+ with torch.no_grad():
56
+ outputs_promoter = model_promoter(**inputs)
57
+ logits = outputs_promoter.logits
58
+ probs = F.softmax(logits, dim=-1)
59
+ p_promoter = probs[0][0].item()
60
+
61
+ if p_promoter >= 0.50:
62
+ return "PROMOTER (Regulatory DNA)", p_promoter, {"Level 1 (Gene)": p_coding, "Level 2 (Promoter)": p_promoter, "Level 3 (Intergenic)": 1 - p_promoter}
63
+
64
+ return "INTERGENIC (Non-Functional Junk DNA)", 1 - p_promoter, {"Level 1 (Gene)": p_coding, "Level 2 (Promoter)": p_promoter, "Level 3 (Intergenic)": 1 - p_promoter}
65
+
66
+ def get_genomic_context(sequence, feature_type):
67
+ try:
68
+ result_handle = NCBIWWW.qblast("blastn", "nt", sequence, entrez_query="Homo sapiens[Organism] AND biomol_genomic[PROP]", hitlist_size=1)
69
+ blast_record = NCBIXML.read(result_handle)
70
+ except Exception as e:
71
+ return {"error": f"BLAST Connection Error: {e}"}
72
+
73
+ if not blast_record.alignments:
74
+ return {"error": "No human genome match found for this sequence."}
75
+
76
+ alignment = blast_record.alignments[0]
77
+ hsp = alignment.hsps[0]
78
+ accession = alignment.accession
79
+ full_title = alignment.title.split('|')[-1].strip()
80
+
81
+ chrom_match = re.search(r"chromosome\s([0-9XYMT]+)", alignment.title, re.IGNORECASE)
82
+ chrom = chrom_match.group(1) if chrom_match else None
83
+ location_string = f"Chromosome {chrom}" if chrom else f"Accession {accession} | {full_title}"
84
+
85
+ start = hsp.sbjct_start
86
+ end = hsp.sbjct_end
87
+ is_forward = (start < end)
88
+ strand_txt = "Forward (+)" if is_forward else "Reverse (-)"
89
+
90
+ if chrom is None:
91
+ return {"location": location_string, "start": start, "end": end, "strand": strand_txt, "metadata": "BLAST returned a localized record without a chromosome. Ensembl mapping skipped."}
92
+
93
+ search_start = min(start, end) if feature_type == "CODING" else (end if is_forward else max(1, end - 15000))
94
+ search_end = max(start, end) if feature_type == "CODING" else (end + 15000 if is_forward else end)
95
+
96
+ try:
97
+ response = requests.get(f"https://rest.ensembl.org/overlap/region/human/{chrom}:{search_start}-{search_end}?feature=gene", headers={"Content-Type": "application/json"})
98
+ response.raise_for_status()
99
+ genes = response.json()
100
+ except Exception as e:
101
+ return {"location": location_string, "start": start, "end": end, "strand": strand_txt, "metadata": f"Ensembl mapping unavailable: {e}"}
102
+
103
+ if not genes:
104
+ gene_desc = "No annotated genes found in this specific region."
105
+ else:
106
+ if feature_type == "PROMOTER":
107
+ genes.sort(key=lambda x: min(abs(x['start'] - end), abs(x['end'] - end)))
108
+ top_gene = genes[0]
109
+ name = top_gene.get('external_name', 'Unknown')
110
+ biotype = top_gene.get('biotype', 'Unknown').replace('_', ' ').title()
111
+ desc = top_gene.get('description', 'No description available.').split(' [')[0]
112
+ gene_desc = f"Matches Gene: {name} | Type: {biotype} | Function: {desc}" if feature_type == "CODING" else f"Regulates Downstream Gene: {name} | Type: {biotype} | Function: {desc}"
113
+
114
+ return {"location": location_string, "start": start, "end": end, "strand": strand_txt, "metadata": gene_desc}
115
+
116
+ # ==========================================
117
+ # 3. STREAMLIT USER INTERFACE
118
+ # ==========================================
119
+ st.title("🧬 The Genomic Oracle")
120
+ st.markdown("### A Deep Learning Cascade for DNA Sequence Classification")
121
+ st.info("💡 **Tip:** Sequences longer than 50 base pairs yield significantly more accurate biological predictions.")
122
+
123
+ user_input = st.text_area("Enter DNA Sequence:", height=150)
124
+
125
+ run_mapping = st.checkbox("Query NCBI BLAST for spatial mapping (Takes 1-3 minutes)")
126
+
127
+ if st.button("Initialize Deep Scan", type="primary"):
128
+ if len(user_input) < 10:
129
+ st.error("Sequence too short! Please provide at least 10 base pairs.")
130
+ else:
131
+ with st.spinner("Analyzing spatial attention tensors..."):
132
+ label, conf, raw_scores = analyze_sequence(user_input)
133
+
134
+ st.success("Analysis Complete!")
135
+
136
+ col1, col2 = st.columns(2)
137
+ with col1:
138
+ st.subheader("Classification")
139
+ st.write(f"**{label}**")
140
+ st.write(f"**Confidence:** {conf:.2%}")
141
+
142
+ with col2:
143
+ st.subheader("Internal Statistics")
144
+ st.write(f"- Level 1 (Coding): {raw_scores['Level 1 (Gene)'] if isinstance(raw_scores['Level 1 (Gene)'], str) else f'{raw_scores['Level 1 (Gene)']:.2%}'}")
145
+ st.write(f"- Level 2 (Promoter): {raw_scores['Level 2 (Promoter)'] if isinstance(raw_scores['Level 2 (Promoter)'], str) else f'{raw_scores['Level 2 (Promoter)']:.2%}'}")
146
+ st.write(f"- Level 3 (Intergenic): {raw_scores['Level 3 (Intergenic)'] if isinstance(raw_scores['Level 3 (Intergenic)'], str) else f'{raw_scores['Level 3 (Intergenic)']:.2%}'}")
147
+
148
+ st.divider()
149
+
150
+ if run_mapping:
151
+ if "Junk" in label:
152
+ st.warning("Spatial mapping bypassed. Sequence classified as non-functional noise.")
153
+ else:
154
+ with st.spinner("Querying NCBI and Ensembl databases..."):
155
+ context_type = "CODING" if "Coding" in label else "PROMOTER"
156
+ context = get_genomic_context(user_input, context_type)
157
+
158
+ st.subheader("Final Mapping Report")
159
+ if "error" in context:
160
+ st.error(context['error'])
161
+ elif "location" in context:
162
+ st.write(f"**Location:** {context['location']}")
163
+ st.write(f"**Strand:** {context['strand']}")
164
+ st.write(f"**Coordinates:** {context['start']:,} - {context['end']:,}")
165
+ st.write(f"**Notes:** {context['metadata']}")
166
+ else:
167
+ st.error("Could not map sequence.")