Spaces:
Running on Zero
Running on Zero
| """ | |
| 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 | |
| # --------------------------------------------------------------------------- | |
| 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)}" | |
| 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 = """ | |
| <div style=" | |
| display: flex; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| gap: 7px; | |
| margin: 8px 0 18px 0; | |
| "> | |
| <a href="https://github.com/DavuluriLab/DeepVRegulome" | |
| target="_blank" | |
| rel="noopener noreferrer"> | |
| <img | |
| src="https://img.shields.io/badge/GitHub-Repo-181717?logo=github" | |
| alt="GitHub Repository" | |
| > | |
| </a> | |
| <a href="https://huggingface.co/duttaprat/DeepVRegulome" | |
| target="_blank" | |
| rel="noopener noreferrer"> | |
| <img | |
| src="https://img.shields.io/badge/%F0%9F%A4%97-Models-yellow" | |
| alt="Hugging Face Models" | |
| > | |
| </a> | |
| <a href="https://pypi.org/project/deepvregulome/" | |
| target="_blank" | |
| rel="noopener noreferrer"> | |
| <img | |
| src="https://img.shields.io/pypi/v/deepvregulome?color=blue" | |
| alt="PyPI Version" | |
| > | |
| </a> | |
| <a href="https://pepy.tech/projects/deepvregulome" | |
| target="_blank" | |
| rel="noopener noreferrer"> | |
| <img | |
| src="https://static.pepy.tech/personalized-badge/deepvregulome?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads" | |
| alt="PyPI Downloads" | |
| > | |
| </a> | |
| <a href="https://arxiv.org/abs/2511.09026" | |
| target="_blank" | |
| rel="noopener noreferrer"> | |
| <img | |
| src="https://img.shields.io/badge/arXiv-2511.09026-b31b1b" | |
| alt="arXiv Paper" | |
| > | |
| </a> | |
| <a href="https://deepvregulome.streamlit.app" | |
| target="_blank" | |
| rel="noopener noreferrer"> | |
| <img | |
| src="https://img.shields.io/badge/Full%20App-Streamlit-ff4b4b" | |
| alt="Full Streamlit Application" | |
| > | |
| </a> | |
| <a href="https://creativecommons.org/licenses/by-nc/4.0/" | |
| target="_blank" | |
| rel="noopener noreferrer"> | |
| <img | |
| src="https://img.shields.io/badge/license-CC--BY--NC--4.0-green" | |
| alt="CC BY-NC 4.0 License" | |
| > | |
| </a> | |
| </div> | |
| """ | |
| 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() | |