""" DeepVRegulome: Variant Effect Prediction Demo ============================================== Interactive demo for 462 fine-tuned DNABERT models predicting regulatory element activity and variant effects on transcription factor binding. Each model load triggers a HuggingFace download event. """ import gradio as gr import torch import math import random import spaces from transformers import AutoTokenizer, AutoModelForSequenceClassification # --------------------------------------------------------------------------- # Constants # --------------------------------------------------------------------------- HF_REPO = "duttaprat/DeepVRegulome" SEQ_LEN = 301 KMER = 6 TF_MODELS = [ "AEBP2","AGO1","AGO2","AHR","ARHGAP35","ARID1B","ARID2","ARID4B","ASH1L", "ATF2","ATF3","ATF4","ATF7","ATM","BACH1","BATF","BCL11A","BCL11B","BCL3", "BCL6","BCOR","BHLHE40","BRCA1","C11orf30","CBFA2T2","CBFA2T3","CBFB", "CBX1","CC2D1A","CDC5L","CEBPA","CEBPB","CEBPG","CEBPZ","CHD2","CREB1", "CREB3L1","CREM","CTBP2","CTCFL","DACH1","DEAF1","DEK","DIDO1","DMAP1", "DRAP1","E2F1","E2F4","E2F6","E2F7","E4F1","EBF1","EED","EGR1","EGR2", "ELF1","ELF3","ELF4","ELK1","EP400","ERF","ESRRA","ETS1","ETV1","ETV5", "FEZF1","FIP1L1","FOS","FOSL1","FOSL2","FOXA1","FOXA2","FOXA3","FOXM1", "FOXP1","FOXP2","FUS","GABPB1","GATA1","GATA2","GATA3","GATA4","GATAD1", "GATAD2A","GFI1B","GLI2","GLI4","GLIS1","GLIS2","GMEB1","GMEB2","GTF2B", "GTF2F1","HBP1","HCFC1","HDAC6","HES2","HHEX","HIC1","HLF","HMBOX1", "HMG20A","HMG20B","HMGXB4","HNF1A","HNF4A","HNF4G","HNRNPH1","HNRNPK", "HNRNPL","HNRNPLL","HOMEZ","HSF1","IKZF2","IKZF3","IKZF5","INSM2","IRF1", "IRF2","IRF3","IRF4","IRF5","JUNB","JUND","KAT2A","KAT2B","KAT8","KDM3A", "KDM4A","KDM4B","KDM5A","KDM5B","KDM6A","KLF1","KLF10","KLF13","KLF16", "KLF17","KLF4","KLF5","KLF6","KLF7","KLF8","KLF9","KMT2B","L3MBTL2", "LARP7","LCORL","MAFF","MAFG","MAFK","MAX","MAZ","MBD1","MBD2","MCM2", "MCM3","MEF2A","MEF2B","MEF2C","MEIS2","MGA","MIER2","MIER3","MITF", "MIXL1","MLX","MNT","MTA1","MTA2","MXD3","MXD4","MXI1","MYB","MYC", "MYNN","MYRF","MZF1","NANOG","NCOA1","NEUROD1","NFE2","NFE2L1","NFE2L2", "NFIA","NFIB","NFIC","NFIL3","NFKBIZ","NFYA","NFYB","NFYC","NKRF","NONO", "NR2C1","NR2C2","NR2F1","NR2F2","NR2F6","NR3C1","NRF1","OSR2","OVOL3", "PATZ1","PAX5","PBX2","PBX3","PCBP1","PCBP2","PHB2","PHF20","PHF21A", "PHF5A","PHF8","PKNOX1","PLRG1","POU2F2","POU5F1","PPARG","PRDM10", "PRDM15","PRDM4","PROX1","PRPF4","PSIP1","RAD51","RARG","RARB","RBAK", "RBBP5","RBM14","RBM22","RBM25","RBM34","RCOR1","RCOR2","RELA","REST", "RFX1","RFX3","RFX5","RFXANK","RFXAP","RNF2","RREB1","RUNX1","RUNX2", "SAFB","SAP130","SAP30","SCRT1","SETDB1","SFPQ","SIN3B","SIRT6","SIX1", "SIX4","SKIL","SMAD1","SMAD3","SMAD4","SMAD5","SNAI2","SNIP1","SOX13", "SOX15","SOX2","SOX3","SOX4","SOX6","SOX9","SP1","SP110","SP140L","SP2", "SP4","SP5","SPI1","SREBF1","SREBF2","SRSF3","SRSF7","SS18","STAT5B", "TAF1","TAF15","TAF7","TAL1","TBL1XR1","TBR1","TBX1","TBX21","TBX3", "TCF12","TCF3","TCF7L2","TEAD1","TEAD2","TEAD4","TFAP2A","TFAP2C", "TFAP4","TFDP1","TFDP2","TFE3","THAP1","THAP11","THRA","THRB","TRIM22", "TRIM24","TRIM28","TSC22D4","UBTF","USF1","USF2","WRNIP1","XBP1","YBX1", "YY1","YY2","ZBED1","ZBED4","ZBED5","ZBTB1","ZBTB10","ZBTB11","ZBTB14", "ZBTB2","ZBTB21","ZBTB25","ZBTB26","ZBTB33","ZBTB40","ZBTB44","ZBTB49", "ZBTB7A","ZBTB7B","ZBTB8A","ZC3H11A","ZC3H4","ZC3H8","ZCCHC11","ZFHX2", "ZFP1","ZFP14","ZFP28","ZFP3","ZFP30","ZFP36L2","ZFP41","ZFP62","ZFP64", "ZFP82","ZFP91","ZGPAT","ZHX1","ZHX2","ZKSCAN1","ZKSCAN5","ZKSCAN8", "ZMIZ1","ZMYM3","ZNF12","ZNF131","ZNF134","ZNF135","ZNF140","ZNF142", "ZNF143","ZNF148","ZNF184","ZNF189","ZNF197","ZNF205","ZNF207","ZNF215", "ZNF217","ZNF219","ZNF22","ZNF224","ZNF232","ZNF239","ZNF24","ZNF253", "ZNF25","ZNF263","ZNF264","ZNF274","ZNF280A","ZNF280D","ZNF281","ZNF282", "ZNF296","ZNF316","ZNF317","ZNF318","ZNF319","ZNF331","ZNF335","ZNF337", "ZNF33A","ZNF33B","ZNF341","ZNF350","ZNF362","ZNF382","ZNF383","ZNF384", "ZNF395","ZNF407","ZNF414","ZNF416","ZNF419","ZNF423","ZNF425","ZNF426", "ZNF44","ZNF444","ZNF446","ZNF449","ZNF460","ZNF496","ZNF501","ZNF507", "ZNF510","ZNF511","ZNF512","ZNF512B","ZNF513","ZNF514","ZNF516","ZNF518A", "ZNF521","ZNF524","ZNF547","ZNF548","ZNF554","ZNF556","ZNF557","ZNF558", "ZNF569","ZNF574","ZNF576","ZNF579","ZNF580","ZNF584","ZNF589","ZNF592", "ZNF597","ZNF607","ZNF609","ZNF610","ZNF614","ZNF639","ZNF644","ZNF654", "ZNF655","ZNF660","ZNF672","ZNF687","ZNF691","ZNF700","ZNF710","ZNF713", "ZNF720","ZNF737","ZNF740","ZNF746","ZNF761","ZNF766","ZNF768","ZNF770", "ZNF775","ZNF777","ZNF778","ZNF782","ZNF784","ZNF786","ZNF788","ZNF79", "ZNF800","ZNF83","ZNF830","ZNF837","ZNF839","ZNF883","ZNF891","ZSCAN16", "ZSCAN20","ZSCAN22","ZSCAN29","ZSCAN31","ZSCAN4","ZSCAN5A","ZXDB", ] HISTONE_MODELS = ["H2AK9ac", "H3K23me2", "H3K9me1", "H4K12ac"] ALL_MODELS = TF_MODELS + HISTONE_MODELS # Popular models to feature at the top of the dropdown FEATURED = [ "CREB1", "EGR1", "ELF1", "FOXA1", "GATA1", "GATA3", "HNF4A", "MYC", "NANOG", "SP1", "CTCFL", ] # --------------------------------------------------------------------------- # Helper functions # --------------------------------------------------------------------------- def to_kmer(seq: str, k: int = KMER) -> str: """Convert DNA sequence to k-mer representation.""" seq = seq.upper().strip() return " ".join(seq[i:i+k] for i in range(len(seq) - k + 1)) def random_dna(length: int = SEQ_LEN) -> str: """Generate a random DNA sequence.""" return "".join(random.choices("ACGT", k=length)) # Global cache: holds one model + tokenizer at a time _cache = {"name": None, "model": None, "tokenizer": None} def load_model(model_name: str): """Load a model from HuggingFace Hub (cached after first load).""" if _cache["name"] == model_name: return _cache["model"], _cache["tokenizer"] subfolder = f"models/{model_name}" tokenizer = AutoTokenizer.from_pretrained( HF_REPO, subfolder=subfolder, trust_remote_code=False, ) model = AutoModelForSequenceClassification.from_pretrained( HF_REPO, subfolder=subfolder, trust_remote_code=False, ) model.eval() model.to("cuda") # Replace cache (free previous model memory) _cache["name"] = model_name _cache["model"] = model _cache["tokenizer"] = tokenizer return model, tokenizer def predict_binding(model, tokenizer, seq: str) -> float: """Return binding probability for a single sequence.""" device = next(model.parameters()).device kmer_seq = to_kmer(seq) inputs = tokenizer( kmer_seq, return_tensors="pt", max_length=512, truncation=True, padding=True, ) inputs = {k: v.to(device) for k, v in inputs.items()} with torch.no_grad(): logits = model(**inputs).logits prob = torch.softmax(logits, dim=-1)[0][1].item() return prob # --------------------------------------------------------------------------- # Gradio callbacks # --------------------------------------------------------------------------- @spaces.GPU def run_binding_prediction(model_name: str, sequence: str): """Predict TF binding probability for a single sequence.""" sequence = sequence.upper().strip().replace(" ", "").replace("\n", "") if not model_name: return "Please select a model." if not sequence: return "Please enter a DNA sequence." if len(sequence) < 20: return f"Sequence too short ({len(sequence)}bp). Provide at least 20bp." invalid = set(sequence) - set("ACGTN") if invalid: return f"Invalid characters: {invalid}. Only A, C, G, T, N allowed." try: model, tokenizer = load_model(model_name) prob = predict_binding(model, tokenizer, sequence) label = "Bound" if prob >= 0.5 else "Unbound" return ( f"Model: {model_name}\n" f"Sequence length: {len(sequence)}bp\n" f"Binding probability: {prob:.4f}\n" f"Prediction: {label}" ) except Exception as e: return f"Error: {str(e)}" @spaces.GPU def run_variant_scoring(model_name: str, ref_seq: str, alt_seq: str): """Score a variant by comparing REF and ALT sequences.""" ref_seq = ref_seq.upper().strip().replace(" ", "").replace("\n", "") alt_seq = alt_seq.upper().strip().replace(" ", "").replace("\n", "") if not model_name: return "Please select a model." if not ref_seq or not alt_seq: return "Please enter both REF and ALT sequences." for name, seq in [("REF", ref_seq), ("ALT", alt_seq)]: if len(seq) < 20: return f"{name} sequence too short ({len(seq)}bp)." invalid = set(seq) - set("ACGTN") if invalid: return f"Invalid characters in {name}: {invalid}" try: model, tokenizer = load_model(model_name) prob_ref = predict_binding(model, tokenizer, ref_seq) prob_alt = predict_binding(model, tokenizer, alt_seq) eps = 1e-7 lo_ref = math.log((prob_ref + eps) / (1 - prob_ref + eps)) lo_alt = math.log((prob_alt + eps) / (1 - prob_alt + eps)) delta = lo_alt - lo_ref disrupted = abs(delta) > 2.0 return ( f"Model: {model_name}\n" f"REF binding probability: {prob_ref:.4f}\n" f"ALT binding probability: {prob_alt:.4f}\n" f"Log-odds (REF): {lo_ref:.4f}\n" f"Log-odds (ALT): {lo_alt:.4f}\n" f"Delta log-odds: {delta:.4f}\n" f"Disrupted (|delta| > 2.0): {'Yes' if disrupted else 'No'}" ) except Exception as e: return f"Error: {str(e)}" def generate_example_seq(): """Generate a random 301bp DNA sequence for testing.""" return random_dna(SEQ_LEN) def generate_variant_pair(): """Generate a REF/ALT pair (single nucleotide change at center).""" ref = list(random_dna(SEQ_LEN)) alt = ref.copy() mid = SEQ_LEN // 2 bases = [b for b in "ACGT" if b != ref[mid]] alt[mid] = random.choice(bases) return "".join(ref), "".join(alt) # --------------------------------------------------------------------------- # Build the Gradio interface # --------------------------------------------------------------------------- # Reorder model list: featured first, then the rest featured_set = set(FEATURED) model_choices = ( [f"★ {m}" for m in FEATURED] + ["---"] + [m for m in ALL_MODELS if m not in featured_set] ) def clean_model_name(name: str) -> str: """Strip the star prefix from featured models.""" return name.replace("★ ", "").strip() def binding_wrapper(model_name, sequence): return run_binding_prediction(clean_model_name(model_name), sequence) def variant_wrapper(model_name, ref_seq, alt_seq): return run_variant_scoring(clean_model_name(model_name), ref_seq, alt_seq) BADGES_HTML = """
GitHub Repository Hugging Face Models PyPI Version PyPI Downloads arXiv Paper Full Streamlit Application CC BY-NC 4.0 License
""" with gr.Blocks( title="DeepVRegulome", theme=gr.themes.Base( primary_hue=gr.themes.colors.emerald, font=("Inter", "system-ui", "sans-serif"), ), ) as demo: gr.Markdown( """ # DeepVRegulome **464 fine-tuned DNABERT models for regulatory variant-effect prediction** Predict transcription-factor binding and score regulatory variant effects using models trained on ENCODE ChIP-seq data. Each prediction dynamically loads the selected model from [duttaprat/DeepVRegulome](https://huggingface.co/duttaprat/DeepVRegulome). DeepVRegulome includes **458 transcription-factor models**, **4 histone-modification models**, **1 splice-acceptor model**, and **1 splice-donor model**. """ ) gr.HTML(BADGES_HTML) with gr.Tab("Binding Prediction"): gr.Markdown( """ Predict the probability that a transcription factor or regulatory protein binds a given DNA sequence. Enter a standard **301 bp DNA sequence**. Shorter or longer sequences may also be processed. """ ) with gr.Row(): with gr.Column(scale=1): model_dd = gr.Dropdown( choices=[c for c in model_choices if c != "---"], value="★ CREB1", label="Select Model (★ = featured)", filterable=True, ) seq_input = gr.Textbox( label="DNA Sequence", placeholder="Paste a DNA sequence containing A, C, G, and T...", lines=4, ) with gr.Row(): example_btn = gr.Button( "Random 301 bp Sequence", size="sm", ) predict_btn = gr.Button( "Predict", variant="primary", ) with gr.Column(scale=1): output_box = gr.Textbox( label="Prediction Result", lines=6, interactive=False, ) example_btn.click( fn=generate_example_seq, outputs=seq_input, ) predict_btn.click( fn=binding_wrapper, inputs=[model_dd, seq_input], outputs=output_box, ) with gr.Tab("Variant Effect Scoring"): gr.Markdown( """ Estimate the effect of a genomic variant by comparing predicted regulatory activity for the reference and alternate sequences. The delta log-odds score quantifies the predicted change in binding. A larger absolute score indicates a stronger predicted regulatory effect. """ ) with gr.Row(): with gr.Column(scale=1): model_dd2 = gr.Dropdown( choices=[c for c in model_choices if c != "---"], value="★ CREB1", label="Select Model (★ = featured)", filterable=True, ) ref_input = gr.Textbox( label="REF Sequence", placeholder="Paste the reference DNA sequence...", lines=3, ) alt_input = gr.Textbox( label="ALT Sequence", placeholder="Paste the alternate DNA sequence containing the variant...", lines=3, ) with gr.Row(): var_example_btn = gr.Button( "Random REF/ALT Pair", size="sm", ) score_btn = gr.Button( "Score Variant", variant="primary", ) with gr.Column(scale=1): var_output = gr.Textbox( label="Variant Effect Result", lines=8, interactive=False, ) def fill_variant_pair(): ref, alt = generate_variant_pair() return ref, alt var_example_btn.click( fn=fill_variant_pair, outputs=[ref_input, alt_input], ) score_btn.click( fn=variant_wrapper, inputs=[model_dd2, ref_input, alt_input], outputs=var_output, ) gr.Markdown( """ --- **Citation:** Dutta, Obusan, Sathian, and Davuluri. *DeepVRegulome: Deep Learning Predicts Functional Impact of Short Genomic Variants on the Human Regulome with Application to Cancer.* [arXiv:2511.09026](https://arxiv.org/abs/2511.09026) (2025). Predictions are computational and should be interpreted alongside experimental, clinical, and population-level evidence. """ ) if __name__ == "__main__": demo.launch()