import spaces # MUST BE LINE 1. Fixes the "CUDA Initialized" error! import gradio as gr import os import re import requests import torch import joblib import numpy as np import torch.nn.functional as F from Bio.Blast import NCBIWWW, NCBIXML # include HF native imports for the Phenotype model from transformers import AutoTokenizer, AutoModelForSequenceClassification, AutoModel, BertTokenizer, BertForSequenceClassification, AutoConfig from huggingface_hub import hf_hub_download # =================================== # 1. LOAD AI MODELS (GLOBALLY CACHED) # =================================== print("Waking up the Genomic Oracle...\n") # A. Kadir's Gatekeeper clf_coding = joblib.load("coding_classifier_universal.joblib") # B. Base DNABERT tokenizer_base = AutoTokenizer.from_pretrained("DNABERT_Local", trust_remote_code=True) model_base = AutoModel.from_pretrained("DNABERT_Local", trust_remote_code=True, _fast_init=False) model_base.eval() # C. DNABERT-2 Promoter Model tokenizer_promoter = AutoTokenizer.from_pretrained("llm_promoter_classifier_v2", trust_remote_code=True) model_promoter = AutoModelForSequenceClassification.from_pretrained("llm_promoter_classifier_v2", trust_remote_code=True, _fast_init=False) model_promoter.eval() # D. Multi-Feature LightGBM lgbm_path = hf_hub_download(repo_id="Geonomic/Genomic-Oracle-Weights", filename="dnabert_lightgbm_model_feature_type_v2.pkl") raw_lgbm = joblib.load(lgbm_path) # If it's a dictionary, print the keys to the log and try to extract the model if isinstance(raw_lgbm, dict): print(f" DEBUG: LightGBM Dictionary Keys: {raw_lgbm.keys()}") # We will try the most common names for saved models if "model" in raw_lgbm: lightgbm_model = raw_lgbm["model"] elif "classifier" in raw_lgbm: lightgbm_model = raw_lgbm["classifier"] else: # Fallback: just grab the very first thing in the dictionary first_key = list(raw_lgbm.keys())[0] lightgbm_model = raw_lgbm[first_key] else: lightgbm_model = raw_lgbm # E. Custom Lean/Obese Phenotype BERT (Forced Native Architecture via Colab Fix) tokenizer_pheno = BertTokenizer.from_pretrained("Geonomic/Genomic-Oracle-Weights", do_lower_case=False) config_pheno = AutoConfig.from_pretrained("Geonomic/Genomic-Oracle-Weights", trust_remote_code=True) model_pheno = BertForSequenceClassification.from_pretrained("Geonomic/Genomic-Oracle-Weights", config=config_pheno, _fast_init=False) model_pheno.eval() FEATURE_DICT = { 0: "Gene/Transcript (Coding/mRNA)", 1: "Regulatory Region (Promoter/Enhancer/Silencer)", 2: "Long Non-Coding RNA (lncRNA)", 3: "Small/Transfer RNA (snRNA/miRNA/tRNA)", 4: "Repeat Region / Mobile Genetic Element", 5: "Pseudogene" } # ============================================== # 2. CORE INFERENCE ENGINE (ZeroGPU Accelerated) # ============================================== @spaces.GPU def run_deep_learning_cascade(dna_sequence): device = torch.device("cuda") # THE FINAL KEY: Teleport the CPU-locked models into the A100 GPU! model_base.to(device) model_promoter.to(device) model_pheno.to(device) clean_seq = "".join(dna_sequence.split()).upper() # --- LEVEL 1: Base Embedding & Kadir's Gatekeeper --- inputs = tokenizer_base([clean_seq], return_tensors="pt", max_length=300, truncation=True, padding=True) inputs = {k: v.to(device) for k, v in inputs.items()} with torch.no_grad(): out_base = model_base(**inputs) mask = inputs["attention_mask"].unsqueeze(-1) embedding = (out_base[0] * mask).sum(dim=1) / mask.sum(dim=1).clamp(min=1) vector = embedding.float().cpu().numpy() p_coding = clf_coding.predict_proba(vector)[0][1] is_coding = p_coding >= 0.60 raw_scores = {"Protein-Coding Probability": p_coding} # --- LEVEL 2: LightGBM Structural Classification --- lgb_prediction = int(lightgbm_model.predict(vector)[0]) structural_feature = FEATURE_DICT.get(lgb_prediction, "Unknown Region") # raw_scores["Predicted Structure"] = structural_feature # THE CONTRADICTION RESOLVER # The fine-tuned LightGBM overrides any contradiction during class assignment if lgb_prediction == 0: is_coding = True # If LightGBM had to overrule, we boost the base confidence to match its high AUROC accuracy confidence = p_coding if p_coding >= 0.50 else 0.85 else: is_coding = False confidence = (1 - p_coding) if p_coding < 0.50 else 0.85 # --- LEVEL 3: The Deep Learning Branching Logic --- summary_dict = { "Final Classification": "GENE" if is_coding else "NON-CODING", "Feature": structural_feature } # BRANCH A: Phenotype Analysis (Triggered if Coding AND is CDS/Exon) if is_coding and lgb_prediction == 0: kmers = [clean_seq[i:i+5] for i in range(len(clean_seq) - 4)] spaced_kmers = " ".join(kmers) inputs_pheno = tokenizer_pheno(spaced_kmers, return_tensors="pt", max_length=512, truncation=True).to(device) with torch.no_grad(): outputs = model_pheno(**inputs_pheno) probs = F.softmax(outputs.logits, dim=-1) prob_obese, prob_lean = probs[0][0].item(), probs[0][1].item() phenotype = "Obesity-Associated" if prob_obese > prob_lean else "Lean-Associated" summary_dict["Phenotype"] = phenotype raw_scores["Phenotype (Obese)"] = prob_obese raw_scores["Phenotype (Lean)"] = prob_lean # BRANCH B: Promoter Validation (Triggered if Non-Coding AND is Promoter/Enhancer) elif not is_coding and lgb_prediction == 1: inputs_promo = tokenizer_promoter([clean_seq], return_tensors="pt", max_length=300, truncation=True, padding=True).to(device) with torch.no_grad(): outputs = model_promoter(**inputs_promo) probs = F.softmax(outputs.logits, dim=-1) p_promoter = probs[0][0].item() validation = "High Confidence Regulatory Element" if p_promoter >= 0.50 else "Weak Regulatory Signal" summary_dict["Validation"] = validation raw_scores["Promoter Signal"] = p_promoter return summary_dict, confidence, raw_scores # =================================== # 3. SPATIAL MAPPING (NCBI / ENSEMBL) # =================================== def get_genomic_context(sequence, is_coding): feature_type = "CODING" if is_coding else "PROMOTER" try: # ask blast for 5 hits instead of 1 so we can hunt for the true chromosome result_handle = NCBIWWW.qblast( "blastn", "nt", sequence, entrez_query="Homo sapiens[Organism] AND biomol_genomic[PROP]", hitlist_size=50 ) blast_record = NCBIXML.read(result_handle) except Exception as e: return {"error": f"BLAST Connection Error: {e}"} if not blast_record.alignments: return {"error": "No human genome match found for this sequence."} # Loop through the top hits and grab the first one that is an actual Chromosome alignment = blast_record.alignments[0] # Default to the top hit chrom = None for aln in blast_record.alignments: chrom_match = re.search(r"chromosome\s([0-9XYMT]+)", aln.title, re.IGNORECASE) if chrom_match: alignment = aln chrom = chrom_match.group(1) break # We found the chromosome, stop searching! hsp = alignment.hsps[0] location_string = f"Chromosome {chrom}" if chrom else f"Accession {alignment.accession}" start, end = hsp.sbjct_start, hsp.sbjct_end is_forward = (start < end) strand_txt = "Forward (+)" if is_forward else "Reverse (-)" if chrom is None: return {"location": location_string, "start": start, "end": end, "strand": strand_txt, "metadata": "BLAST returned a localized record without a chromosome. Ensembl mapping skipped."} search_start = min(start, end) if feature_type == "CODING" else (end if is_forward else max(1, end - 15000)) search_end = max(start, end) if feature_type == "CODING" else (end + 15000 if is_forward else end) try: response = requests.get( f"https://rest.ensembl.org/overlap/region/human/{chrom}:{search_start}-{search_end}?feature=gene", headers={"Accept": "application/json"} ) response.raise_for_status() genes = response.json() except Exception as e: return {"location": location_string, "start": start, "end": end, "strand": strand_txt, "metadata": f"Ensembl mapping unavailable: {e}"} if not genes: gene_desc = "No annotated genes found in this specific region." else: if feature_type == "PROMOTER": genes.sort(key=lambda x: min(abs(x['start'] - end), abs(x['end'] - end))) top_gene = genes[0] name = top_gene.get('external_name', 'Unknown') biotype = top_gene.get('biotype', 'Unknown').replace('_', ' ').title() desc = top_gene.get('description', 'No description available.').split(' [')[0] gene_desc = f"Matches Gene: {name} | Type: {biotype} | Function: {desc}" if feature_type == "CODING" else f"Regulates Downstream Gene: {name} | Type: {biotype} | Function: {desc}" return {"location": location_string, "start": start, "end": end, "strand": strand_txt, "metadata": gene_desc} # ============================== # 4. GRADIO INTERFACE (FRONTEND) # ============================== def gradio_inference(dna_sequence, run_mapping): if len(dna_sequence.strip()) < 10: error_html = f"""