DeepVRegulome / app.py
duttaprat's picture
Update app.py
e93a0cf verified
Raw
History Blame Contribute Delete
18.3 kB
"""
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 = """
<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()