
Alphagenome Single Variant Analysis
- 1.3k installs
- 2.6k repo stars
- Updated July 7, 2026
- google-deepmind/science-skills
alphagenome-single-variant-analysis is a genomic ML skill that runs AlphaGenome single-variant effect predictions and matplotlib visualizations for developers who need variant impact scores inside Python agent workflows.
About
alphagenome-single-variant-analysis is a DeepMind science skill that teaches coding agents to run single-variant genomic effect predictions with the pip package alphagenome. The workflow imports genome, track_data, transcript, ism, dna_client, variant_scorers, and plot_components, initializes a DNA client from a dotenv-loaded API key, and produces pandas DataFrames plus matplotlib plots for variant interpretation. Developers reach for alphagenome-single-variant-analysis when they need in silico variant scoring, in silico mutagenesis (ISM), or locus visualization without hand-rolling AlphaGenome API calls. The skill targets bioinformatics and computational biology engineers embedding AlphaGenome into reproducible agent pipelines.
- Official Python client for AlphaGenome single-variant scoring
- Includes genome.Interval, genome.Variant, ISM interpretation, and track data utilities
- Automatic API key loading via dotenv from agent config directory
- Built-in visualization components with matplotlib integration
- DNA model client with gRPC endpoint configuration for gdmscience.googleapis.com
Alphagenome Single Variant Analysis by the numbers
- 1,253 all-time installs (skills.sh)
- +167 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #266 of 2,064 Data Science & ML skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/google-deepmind/science-skills --skill alphagenome-single-variant-analysisAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.3k |
|---|---|
| repo stars | ★ 2.6k |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 7, 2026 |
| Repository | google-deepmind/science-skills ↗ |
How do you score single genomic variants with AlphaGenome?
Run single-variant genomic effect predictions and visualizations using DeepMind’s AlphaGenome model inside agentic workflows.
Who is it for?
Bioinformatics developers who already have variant coordinates and an AlphaGenome API key and want agent-guided prediction runs.
Skip if: Developers who need bulk VCF pipelines, clinical reporting, or variant analysis without Python and the alphagenome package installed.
When should I use this skill?
The user asks to predict variant effects, run ISM, or visualize a genomic locus with AlphaGenome inside an agent workflow.
What you get
Variant effect scores, track DataFrames, ISM results, and matplotlib genomic plots
- variant score tables
- genomic track plots
- ISM interpretation output
By the numbers
- Uses the pip package alphagenome with 6 core import modules documented in the skill
Files
Variant Analysis using AlphaGenome
Prerequisites
1. `uv`: Read the uv skill and follow its Setup instructions to ensure uv is installed and on PATH. 2. User Notification: If LICENSE_NOTIFICATION.txt does not already exist in this skill directory then (1) prominently notify the user to check the terms at https://deepmind.google.com/science/alphagenome/, then (2) create the file recording the notification text and timestamp. 3. `.env` file: Make sure the .env file exists in your home directory. Create one if it does not exist. 4. `ALPHAGENOME_API_KEY`: This skill requires an API key to function. You must ask the user for an API key if this skill looks relevant to their request and you do not have one in .env. The user can sign up at https://deepmind.google.com/science/alphagenome/. Do NOT ask the user to paste their key into the chat (this would leak the key into the agent's context). Instead, explain that a key is necessary to use AlphaGenome and give the user this command substituting `ENV_FILE` with the resolved literal path to the `.env` file:
printf "Enter AlphaGenome API key (typing hidden): " && read -s key && echo && echo "ALPHAGENOME_API_KEY=$key" >> "ENV_FILE" && echo "Saved."The scripts load credentials automatically via dotenv. NEVER read, print, or inspect the .env file or its variables (e.g. no cat, grep, echo, printenv, or os.environ.get on keys). Credentials must stay out of the agent's context.
When running in sandbox, dotenv.load_dotenv() will be a no-op, and instead the sandbox will read credentials and inject them directly.
Core Rules
- NEVER run `python3` or `python3 -c` directly. The system Python does not
necessarily have pandas, numpy, and other key dependencies. ALWAYS use uv run to run ALL Python code — including scripts, ad-hoc analysis files, and one-liners. Do not attempt to pip install or create new venvs — uv manages an isolated environment automatically.
- Offline Only: NEVER use external APIs (e.g., MyGene.info, Ensembl REST)
for gene/transcript lookup. Use lookup_gene_info.py with the local GTF. If it fails, fix the environment/paths, do not switch to external APIs.
- API Key is required:
ALPHAGENOME_API_KEYmust be set before running
any script (in sandbox, credentials are injected automatically).
- Notification: If this skill is used, ensure this is mentioned in the
output.
- Report Format: Always use the templates in
docs/report-templates.md
for generating analysis reports, and ensure to include the table of top hits from the discovery scan.
Environment Setup & Troubleshooting
Python Environment
All scripts must be executed using uv run, which manages an isolated virtual environment with the correct dependencies via uv.
uv run <script_name> [args...]For ad-hoc scripts (e.g., inline analysis code saved to a temp file), pass the full path instead of a short name:
uv run --project $SKILL_DIR /tmp/my_analysis.py --arg1 val1[!NOTE] The first invocation resolves and installs dependencies (~10s).
Subsequent runs use the cached environment and start instantly. The cache
lives in ~/.cache/uv/.Common Issues
- Column Names:
tidy_scoresand metadata often usegene_name(not
gene_symbol) and output_type (not modality). Always inspect df.columns before filtering.
- Large Genes: Genes > 500kb (e.g.,
USH2A) break thewhole_geneview.
Use --view detail or manual regional windows instead.
- Sashimi Strand Error:
plot_components.Sashimidoes NOT accept a
strand argument directly. Filter input tracks instead.
- KeyError: 'ontology_curie': Not all tracks have
ontology_curie. Check
track.metadata.columns before filtering.
- Python Path: If
exec: "python": executable file not foundoccurs,
ensure you are using uv run instead of bare python/python3.
- NotImplementedError (pandas): "iLocation based boolean indexing on an
integer type is not available". This occurs when using boolean masks with .iloc on integer-indexed DataFrames in newer pandas versions. Fix: Convert boolean masks to integer indices using np.flatnonzero(mask).
- GTF Feather Case Sensitivity: The AlphaGenome GTF Feather file uses
Capitalized column names (Feature, Start, End, Strand) unlike standard GTF files. Always check df.columns if getting KeyErrors.
- `score_variant` ontology filtering:
score_variantdoes NOT accept
ontology_terms as an argument. You must filter the returned AnnData objects manually by inspecting adata.var columns. In contrast, predict_variant DOES accept ontology_terms directly.
- Sashimi Zoom Logic: To ensure "skipping" arcs are visible, expand the
zoom to include the flanking exons rather than relying on junction overlap alone.
- Junction Scores: Raw
Junctionobjects frompredictionmay be simple
Intervals. Use junction_data.get_junctions_to_plot(predictions=..., name=...) to retrieve objects with the .k (abundance/score) attribute.
- `uv` Not Found: If
exec: uv: not found, follow the installation
instructions in Prerequisites.
- Registry Authentication Error (401): If
uvfails with 401 Unauthorized
for a private registry, set UV_INDEX_URL=https://pypi.org/simple before running the script.
References
- alphagenome-api.md — API reference and code
patterns
- interpretation-guide.md — Interpretation
guide, score magnitude rules, ISM, and checklist.
- report-templates.md — Full report templates
- `scripts/visualize_variant_effects.py`
— Single-variant visualization template (Ref/Alt comparisons, Splicing).
- Splicing Zoom Strategy: Uses a Hybrid Approach for optimal
visibility: 1. Base Interval: Variant +/- 1 downstream and upstream exon (Structural Context). 2. Junction Expansion: Expands to include the full span of any significant splicing junction (e.g., exon skipping events that span multiple exons). 3. Anchor Enforcement: Ensures the exons anchoring these long junctions are fully visible. Lesson: Simple fixed windows (e.g., 2kb) or nearest-exon logic often fail for skipping events. Always use the observed junction data to drive zoom levels.
- `examples/splicing/` — Splicing analysis examples
- `examples/model_limitation_RNU4ATAC/`
— ncRNA structure limitation case study
- `examples/polyadenylation_HBA2/` — 3'
UTR / Polyadenylation case study
- `examples/regulatory/` — Regulatory variant
examples
- `examples/negative_result_GATA4/` —
Negative results (mathematical artefact)
- `examples/negative_result_TGFB3/` —
Negative results (proxies)
- `scripts/lookup_gene_info.py` — Gene &
transcript lookup
- `scripts/resolve_ontology_terms.py` —
Ontology term resolution (UBERON/CL IDs)
--------------------------------------------------------------------------------
Code Patterns
Broad Discovery Scan
Use score_variant across differential scorers only to discover unexpected tissue effects.
from alphagenome.models import dna_client
from alphagenome.models import variant_scorers
from alphagenome.data import genome
import os
import pandas as pd
# Setup API Key and Client
dna_model = dna_client.create(api_key=os.environ.get('ALPHAGENOME_API_KEY'),
address='dns:///gdmscience.googleapis.com:443')
# Define Variant (example)
variant_str = "chr2:1234:A>C"
chrom, pos_str, ref_alt = variant_str.split(':')
ref, alt = ref_alt.split('>')
pos = int(pos_str)
# Use supported sequence length (e.g., 2**20 for optimal performance)
SEQ_LENGTH = 2**20
interval = genome.Interval(chrom, pos - SEQ_LENGTH // 2, pos + SEQ_LENGTH // 2)
variant = genome.Variant(chrom, pos, ref, alt)
scorers = [
variant_scorers.RECOMMENDED_VARIANT_SCORERS[m]
for m in variant_scorers.RECOMMENDED_VARIANT_SCORERS
if "ACTIVE" not in m and "CAGE" not in m and "PROCAP" not in m
]
print(f"Scoring variant {variant_str}...")
scores_list = dna_model.score_variant(interval=interval, variant=variant, variant_scorers=scorers)
# Process and Display Results
all_dfs = []
for score_adata in scores_list:
df = variant_scorers.tidy_scores([score_adata], match_gene_strand=True)
if df is not None:
all_dfs.append(df)
if all_dfs:
df = pd.concat(all_dfs)
significant = df[df['quantile_score'].abs() > 0.995]
ranked = significant.sort_values('raw_score', key=abs, ascending=False)
print("Top Significant Hits:")
print(ranked[['biosample_name', 'gene_name', 'output_type', 'quantile_score', 'raw_score']])Extended Search for Disease-Relevant Tissues
# Define keywords based on disease context
disease_keywords = ["liver", "hepatocyte"]
# Filter for any match
mask = df['biosample_name'].str.contains('|'.join(disease_keywords), case=False, na=False)
relevant_hits = df[mask].sort_values('raw_score', key=abs, ascending=False)
print(f"\n--- Extended Analysis (Keywords: {disease_keywords}) ---")
print(relevant_hits.head(20)[['biosample_name', 'output_type', 'raw_score', 'quantile_score']])Workflow Checklist
Variant Analysis Progress:
- [ ] Step 0: Review Golden Examples (MANDATORY)
- [ ] Step 1: Create Output Folder and Setup
- [ ] Step 2: Parse User Query & Research
- [ ] Step 3: Resolve Tissues & Modalities
- [ ] Step 4: Visualize & Save Plots
- [ ] Step 5: Analyze Predictions (view plots, no code). MANDATORY: Read [interpretation-guide.md](docs/interpretation-guide.md) before interpreting results.
- [ ] Step 6: Write Report, save it as `report.md` (MANDATORY)
- [ ] Step 7: Self-Critique (view `report.md` to verify links & claims)
- [ ] Step 8: Make artifact out of `report.md`--------------------------------------------------------------------------------
Multi-Variant Workflow
If multiple variants are specified, spawn sub-agents to run each variant analysis and then synthesize each report.md into a single report.
Script Reference
| Script | Purpose |
|---|---|
lookup_gene_info | Comprehensive gene and transcript lookup using |
: : GTF data : | resolve_ontology_terms | Biological terms → UBERON/CL/EFO IDs | | visualize_variant_effects | REF/ALT visualization (expression, regulatory, | : : splicing) : | analyze_ism | In-Silico Mutagenesis SeqLogo generation | | interpret_splicing | Quantitative splicing analysis (delta scores, | : : junctions) : | visualize_genome_tracks | Genomic track visualization for a region |
AlphaGenome API Reference
Pip package: alphagenome
Setup and Imports
Standard imports for AlphaGenome workflows:
from alphagenome.data import gene_annotation
from alphagenome.data import genome
from alphagenome.data import track_data
from alphagenome.data import transcript as transcript_utils
from alphagenome.interpretation import ism
from alphagenome.models import dna_client
from alphagenome.models import variant_scorers
from alphagenome.visualization import plot_components
import matplotlib.pyplot as plt
import pandas as pdClient Initialization
The API key is automatically loaded by dotenv from the .env file in the agent configuration dir.
To initialize a client:
from alphagenome.models import dna_client
dna_model = dna_client.create(
api_key=os.environ.get('ALPHAGENOME_API_KEY'),
address='dns:///gdmscience.googleapis.com:443',
)Core Data Types
genome.Interval
0-based half-open interval (includes start, excludes end).
interval = genome.Interval(chromosome='chr1', start=1_000, end=1_010)
interval.center() # Returns center position (int)
interval.width # Returns 10
interval.resize(100) # Resizes around center
interval.overlaps(other_interval)
interval.contains(other_interval)
interval.intersect(other_interval)genome.Variant
Position is 1-based (VCF-compatible).
variant = genome.Variant(
chromosome='chr22',
position=36201698, # 1-based!
reference_bases='A',
alternate_bases='C',
)
# Get interval around variant
interval = variant.reference_interval.resize(dna_client.SEQUENCE_LENGTH_1MB)Predictions
Predict from DNA Sequence
output = dna_model.predict_sequence(
sequence='GATTACA'.center(dna_client.SEQUENCE_LENGTH_1MB, 'N'),
requested_outputs=[dna_client.OutputType.DNASE],
ontology_terms=['UBERON:0002048'], # Lung
)
# Access predictions
print(output.dnase.values.shape) # (sequence_length, num_tracks)
print(output.dnase.metadata) # Track metadata DataFramePredict from Genome Interval
interval = genome.Interval('chr1', 1000000, 1000001)
interval = interval.resize(dna_client.SEQUENCE_LENGTH_1MB)
output = dna_model.predict_interval(
interval=interval,
requested_outputs=[dna_client.OutputType.RNA_SEQ],
ontology_terms=['UBERON:0001114'], # Right liver lobe
)Mouse Predictions
Specify organism=dna_client.Organism.MUS_MUSCULUS for mouse models.
output = dna_model.predict_sequence(
...,
organism=dna_client.Organism.MUS_MUSCULUS,
)Variant Analysis
Predict Variant Effects (Raw Tracks)
Compare predictions for Reference (REF) vs Alternate (ALT) alleles.
variant_output = dna_model.predict_variant(
interval=interval,
variant=variant,
requested_outputs=[dna_client.OutputType.RNA_SEQ],
ontology_terms=['UBERON:0001157'], # Colon - Transverse
)
ref_tracks = variant_output.reference.rna_seq
alt_tracks = variant_output.alternate.rna_seqScore Variants (Aggregated Scores)
Get aggregated scores using recommended scorers.
scorer = variant_scorers.RECOMMENDED_VARIANT_SCORERS['RNA_SEQ']
variant_scores_list = dna_model.score_variant(
interval=interval,
variant=variant,
variant_scorers=[scorer],
)
scores = variant_scores_list[0]
# Tidy scores to DataFrame
df = variant_scorers.tidy_scores([scores], match_gene_strand=True)
print(df[['gene_symbol', 'raw_score', 'quantile_score']])Available recommended scorers: ATAC, CAGE, DNASE, PROCAP, RNA_SEQ, CHIP_TF, CHIP_HISTONE, SPLICE_SITES, SPLICE_SITE_USAGE, SPLICE_JUNCTIONS, POLYADENYLATION, CONTACT_MAPS
Batch Variant Scoring
# Parse variants from VCF-like DataFrame
for _, row in vcf_df.iterrows():
variant = genome.Variant(
chromosome=str(row.CHROM),
position=int(row.POS),
reference_bases=row.REF,
alternate_bases=row.ALT,
)
interval = variant.reference_interval.resize(
dna_client.SEQUENCE_LENGTH_1MB
)
scores = dna_model.score_variant(
interval=interval,
variant=variant,
variant_scorers=list(
variant_scorers.RECOMMENDED_VARIANT_SCORERS.values()
),
)In Silico Mutagenesis (ISM)
Systematically mutate a region to find important motifs.
from alphagenome.interpretation import ism
sequence_interval = genome.Interval(
'chr20', 3_753_000, 3_753_400
).resize(dna_client.SEQUENCE_LENGTH_16KB)
ism_interval = sequence_interval.resize(256) # Mutate center 256bp
scorer = variant_scorers.CenterMaskScorer(
requested_output=dna_client.OutputType.DNASE,
width=501,
aggregation_type=variant_scorers.AggregationType.DIFF_MEAN,
)
variant_scores = dna_model.score_ism_variants(
interval=sequence_interval,
ism_interval=ism_interval,
variant_scorers=[scorer],
)TrackData Operations
Properties
tdata = output.dnase
tdata.values # numpy array (sequence_length, num_tracks)
tdata.metadata # pandas DataFrame with track info
tdata.resolution # bp per position
tdata.interval # genome.IntervalFiltering by Strand
pos_tracks = tdata.filter_to_positive_strand()
neg_tracks = tdata.filter_to_negative_strand()
unstranded = tdata.filter_to_unstranded()Filtering by Track Name
track1_tdata = tdata.select_tracks_by_name(names='track1')Filtering by Metadata (Manual)
mask = tracks.metadata['histone_mark'] == 'H3K27ac'
filtered_tracks = track_data.TrackData(
values=tracks.values[:, mask],
metadata=tracks.metadata[mask],
resolution=tracks.resolution,
interval=tracks.interval,
uns=tracks.uns,
)Slicing
# By position indices
tdata.slice_by_positions(start=2, end=4)
# By genomic interval
tdata.slice_by_interval(
genome.Interval(chromosome='chr1', start=1_002, end=1_004)
)Resizing
tdata.resize(width=2) # Crop to center 2 positions
tdata.resize(width=8) # Pad with zerosResolution Conversion
tdata.change_resolution(resolution=128) # Downsample
tdata.change_resolution(resolution=1) # UpsampleTrack Metadata Reference
| Modality | Key Column | Example Values |
|---|---|---|
CHIP_HISTONE | histone_mark | H3K27ac, H3K4me3, H3K27me3 |
CHIP_TF | target | CTCF, JUND, POLR2A |
RNA_SEQ, CAGE | strand | +, - |
| All | ontology_curie | UBERON:0002107, EFO:0001187 |
| All | biosample_name | liver, HepG2 |
[!CAUTION]CHIP_HISTONEuseshistone_mark, NOTtarget.targetis for
CHIP_TF.Visualization
plot_components.plot(
components=[
plot_components.TranscriptAnnotation(transcripts),
plot_components.Tracks(output.rna_seq),
plot_components.OverlaidTracks(
tdata={'REF': ref_tracks, 'ALT': alt_tracks},
colors={'REF': 'dimgrey', 'ALT': 'red'},
),
],
interval=interval,
annotations=[plot_components.VariantAnnotation([variant], alpha=0.8)],
)
plt.show()[!CAUTION]VariantAnnotationmust be inannotations=, NOTcomponents.
Putting it in components causes `AttributeError: 'VariantAnnotation' object
has no attribute 'num_axes'`.
Get Human-Readable Tissue Names
tissue = tracks.metadata[
tracks.metadata['ontology_curie'] == ontology_id
]['biosample_name'].iloc[0]
plt.title(f"{gene_symbol} - {tissue} - {modality.name}")Gene Annotations (GTF)
gtf = pd.read_feather(
'https://storage.googleapis.com/alphagenome/reference/gencode/'
'hg38/gencode.v46.annotation.gtf.gz.feather'
)
# Filter for MANE Select transcripts
gtf_transcripts = gene_annotation.filter_protein_coding(gtf)
gtf_transcripts = gene_annotation.filter_to_mane_select_transcript(
gtf_transcripts
)
# Get stranded interval for a gene
interval = gene_annotation.get_gene_interval(gtf, gene_symbol='CYP2B6')GTF feather uses capitalized column names for core fields:
| Correct | Incorrect |
|---|---|
Feature | feature |
Chromosome | seqname |
Start | start |
End | end |
Strand | strand |
Other columns (gene_name, gene_id, gene_type) remain lowercase.
Best Practices
1. Interval Resizing: Always resize to a supported length before prediction. Use dna_client.SUPPORTED_SEQUENCE_LENGTHS.keys() for options.
2. Efficient Predictions: Always specify requested_outputs and ontology_terms to reduce compute and data transfer.
3. Variant Scoring: Use tidy_scores(..., match_gene_strand=True) to filter irrelevant strand matches. quantile_score allows comparison across different scorers.
4. ISM: Expensive (scores 3 variants per position). Use shorter context intervals (16KB) and narrower mutation regions.
5. Saving Figures: Use plt.savefig('plot.png', bbox_inches='tight') to prevent cut-off labels.
Common Pitfalls
OverlaidTracks has no title argument
# Wrong:
plot_components.OverlaidTracks(..., title="My Title") # ERROR!
# Correct:
plot_components.plot([plot_components.OverlaidTracks(...)], interval=...)
plt.suptitle("My Title")TrackData has no subset() method
Use manual filtering with boolean masks on metadata, or use select_tracks_by_name() for name-based selection.
uv run fails
Clear the cached environment and retry: uv cache clean && uv run <script_name>. If the issue persists, check that pyproject.toml exists in the skill root and contains alphagenome>=0.6.1 in dependencies.
Client initialization needs correct address
Always use the production address for API access: address='dns:///gdmscience.googleapis.com:443'
--------------------------------------------------------------------------------
Output Types
Defined as dna_client.OutputType, used in requested_outputs:
| OutputType | Description |
|---|---|
ATAC | ATAC-seq: chromatin accessibility |
CAGE | Cap Analysis of Gene Expression |
DNASE | DNase I hypersensitive sites: chromatin accessibility |
RNA_SEQ | RNA sequencing: gene expression |
CHIP_HISTONE | ChIP-seq: histone modifications |
CHIP_TF | ChIP-seq: transcription factor binding |
SPLICE_SITES | Donor and acceptor splice sites |
SPLICE_SITE_USAGE | Fraction of time each splice site is used |
SPLICE_JUNCTIONS | Split read RNA-seq counts for each junction |
CONTACT_MAPS | 3D DNA-DNA contact probabilities |
PROCAP | Precision Run-On sequencing and capping |
--------------------------------------------------------------------------------
Variant Scoring Details
Gene Expression (RNA-seq)
Quantifies impact on overall gene transcript abundance.
- Comparison: Predicted RNA coverage between REF and ALT alleles.
- Mask: Exons for a gene of interest.
- Aggregation: Log-fold change: `log(mean(ALT) + 0.001) - log(mean(REF) +
0.001)`.
Polyadenylation Site (PAS) Usage
Captures variant's impact on RNA isoform production (paQTLs).
- Mask: Local 400bp windows around 3' cleavage junctions.
- Aggregation: Maximum absolute log-fold change of isoform ratios
(distal/proximal PAS usage).
TSS Activity (CAGE, PRO-cap)
Quantifies local changes at TSSs.
- Mask: Local 501bp window centered at variant.
- Aggregation:
log2[(sum(ALT) + 1) / (sum(REF) + 1)].
Chromatin Accessibility (ATAC-seq, DNase-seq)
- Mask: Local 501bp window centered at variant.
- Aggregation:
log2[(sum(ALT) + 1) / (sum(REF) + 1)].
Transcription Factor Binding (ChIP-TF)
- Mask: Local 501bp window centered at variant.
- Aggregation:
log2[(sum(ALT) + 1) / (sum(REF) + 1)].
Histone Modifications (ChIP-Histone)
- Mask: Local 2001bp window centered at variant.
- Aggregation:
log2[(sum(ALT) + 1) / (sum(REF) + 1)].
Splicing (Splice Sites)
Changes in class assignment probabilities (acceptor, donor) across gene body.
- Aggregation:
max(|ALT - REF|)across gene body.
Splicing (Splice Site Usage)
Changes in fraction of splice site usage.
- Aggregation:
max(|ALT - REF|)across gene body.
Splicing (Splice Junctions)
Changes in predicted RNA-seq reads spanning junctions.
- Aggregation:
max(|log(ALT) - log(REF)|)across splice site pairs.
3D Genome Contact (Contact Maps)
Local contact disruption.
- Mask: Local 1MB window centered at variant.
- Aggregation: Mean absolute difference of contact frequencies for
variant-containing bin.
Active Allele Scorers
Capture absolute activity level (not REF/ALT difference): max(aggregated_signal(ALT), aggregated_signal(REF)) over masked region.
Quantile Scores
Quantile scores are empirical percentile ranks vs common variants (MAF>0.01 in gnomAD v3). A quantile of 0.99 means the score is at the 99th percentile. Maximum value is ±0.999990 (~300K variant background). For signed scorers, quantiles are linearly mapped to [-1, 1] to preserve directionality.
Practical rule: Use quantile as significance indicator; use raw_score for magnitude comparison within the same scorer.
Example: Model Limitation (RNA Secondary Structure)
Variant: chr2:121530927:G>A Gene: RNU4ATAC (U4atac snRNA) Phenotype: Roifman Syndrome Mechanism: RNA Secondary Structure / Stability (Post-Transcriptional)
Why this is a Critical Example
This analysis illustrates a specific Blind Spot of the model:
1. The Signal: The variant has High Quantiles (0.998) but Low Raw Scores (~0.01). 2. The Reality: A high quantile with a near-zero raw score typically reflects low variance in the model's background predictions for that track—it is a statistical artifact, not a meaningful biological signal. Furthermore, the model does not simulate the physical folding of the RNA molecule. 3. True Mechanism: Variants in snRNAs often affect secondary structure/folding, preventing proper spliceosome assembly. This is a post-transcriptional physics problem, not a DNA-to-RNA transcription problem.
Key Takeaway
AlphaGenome is a DNA-to-Expression model, not an RNA Folding model.
- If the agent is analyzing an ncRNA (snRNA, tRNA, rRNA), it must
acknowledge that AlphaGenome predicts transcription from DNA, but does not simulate post-transcriptional RNA folding or secondary structure stability.
- Strict Rule: A pattern of High Quantile + Low Raw Score should be
reported as "No Significant Molecular Effect Predicted by AlphaGenome". Do not invent proxy mechanisms (e.g., "structural importance") based on statistical artifacts.
- Do not over-interpret "Regulatory" scores for mechanisms that occur after
transcription. The true pathogenic mechanism (e.g., RNA folding defect) is likely invisible to the model.
Variant Analysis Report: chr2:121530927:G>A (RNU4ATAC)
Summary
The variant chr2:121530927:G>A is located in the *RNU4ATAC gene, an snRNA associated with Roifman syndrome. AlphaGenome analysis identified a "High Quantile" signal in RNA-seq models (e.g., Skeletal Muscle). However, the absolute magnitude of this change (Raw Score ~0.01) is negligible. This pattern of high quantiles and near-zero raw scores is typically a statistical artifact driven by low variance in the model's background predictions for that track. Furthermore, the true disease mechanism of RNU4ATAC variants involves post-transcriptional RNA folding and secondary structure stability, which AlphaGenome does not* simulate. Therefore, no significant molecular effect is predicted by the model.
Genomic Context
- Variant: chr2:121530927:G>A
- Overlapping Gene: RNU4ATAC (U4atac snRNA)
- Disease Association: Roifman Syndrome
- Mechanism: RNA Secondary Structure / Stability
1. Top Discovery Hits (Statistical Artifact)
High quantiles but negligible raw scores indicate lack of strong functional driver.
| Tissue | Ontology | Modality | Raw | Quant | Effect |
|---|---|---|---|---|---|
| Skel. Muscle | UBERON:0001134 | RNA_SEQ | 0.012 | 0.998 | Artifact |
| Brain Cortex | UBERON:0001851 | RNA_SEQ | 0.010 | 0.996 | Artifact |
| Liver | UBERON:0002107 | RNA_SEQ | 0.009 | 0.995 | Artifact |
| Adipose | UBERON:0001013 | RNA_SEQ | -0.011 | 0.994 | Artifact |
Observations:
- Magnitude: Raw scores are ~0.01, well below the threshold of biological
relevance.
- Quantile Inflation: The high quantiles likely reflect low variance in
the model background, where even tiny deviations are "rare" but not impactful.
--------------------------------------------------------------------------------
Plots and Visual Analysis
Whole-Gene Expression View
!Whole-Gene View
Visual Observation:
- Overview: The macro-scale expression profile of RNU4ATAC remains
virtually identical between REF and ALT.
Regulatory Effects: Skeletal Muscle
!Regulatory Effects
Visual Observation:
- Broader Context: The broader regulatory context shows no loss or gain of
distinct promoter or enhancer peaks.
Detail View (+/- 50bp)
!Detail View
Visual Observation (Zoomed):
- Variant Site: Zooming in reveals that the absolute magnitude of change
predicted by the model is negligible, visually confirming that the "High Quantile" flag represents noise rather than a major disruption.
Comparative ISM Analysis
!ISM SeqLogo
Interpretation:
- Motif Stability: The ISM matrix lacks the characteristic strong,
isolated motif disruption that defines definitive regulatory variants, further supporting the "statistical artifact" diagnosis.
--------------------------------------------------------------------------------
Conclusion
AlphaGenome predicts no significant molecular effect for chr2:121530927:G>A in RNU4ATAC. The "significant" quantile hits reported by the model are statistical artifacts tracking near-zero raw score variations. Because RNU4ATAC is an snRNA, the true disease mechanism likely involves macromolecular folding defects post-transcription. AlphaGenome is a DNA-to-Expression model and cannot simulate RNA folding.
Recommendation: Assess predictions with RNA secondary structure tooling; do not rely on proxy regulatory scores from AlphaGenome.
Example: True Negative Result (GATA4 / VSD)
Variant: chr8:11703860:G>T Phenotype: Ventricular Septal Defect (Alleged) Verdict: Likely Benign / No Functional Effect
Why this is a good example
This analysis demonstrates a True Negative result where:
1. Statistical Artefacts: The Discovery Scan reported high quantiles (~0.99) for Heart RNA-seq, which can be misleading. 2. Magnitude Check: The Raw Scores were negligible (~0.01 or <1% change), revealing the "significance" was likely due to low variance in the model background rather than true biological impact. 3. Visual Confirmation:
- Whole-Gene Plot: Shows identical REF/ALT expression profiles.
- Detail Plot: Shows preserved chromatin accessibility (DNASE) at the
variant site.
- ISM: Shows minimal motif disruption logic.
Key Takeaway
Always verify statistically significant hits (High Quantile) with Magnitude (Raw Score) and Visual Inspection. If the raw score is low and the plot shows no change, the Quantile is a false alarm.
Variant Analysis Report: chr8:11703860:G>T
Summary
The variant chr8:11703860:G>T is implicated in Ventricular Septal Defect (VSD) and located in/near the * GATA4 gene. AlphaGenome analysis predicted no significant molecular impact* in Heart tissues. While some RNA-seq scores achieve high statistical quantiles (0.99), the absolute magnitude of change is negligible (<1% change, raw score ~ -0.01), suggesting these are likely background noise rather than functional biological effects. No evidence of splicing disruption, promoter loss, or enhanceosome destabilization was found in the modeled tissues.
Genomic Context
- Variant: chr8:11703860:G>T
- Overlapping Gene: GATA4
- Disease Association: Ventricular Septal Defect (VSD)
1. Top Discovery Hits (Heart Assessment)
High quantiles but negligible raw scores indicate lack of strong functional driver.
| Tissue | Ontology | Mode | Raw | Quant | Bio Effect |
|---|---|---|---|---|---|
| Heart L Vent. | UBERON:0002084 | RNA | -0.009 | -0.99 | Negligible |
| Heart | UBERON:0000948 | RNA | -0.010 | -0.99 | Negligible |
| Heart R Vent. | UBERON:0002080 | RNA | -0.009 | -0.99 | Negligible |
| Neuronal Stem Cell | CL:0000047 | RNA | 0.019 | 0.99 | Negligible |
Observations:
- Magnitude: Raw scores are ~0.01 (approx 0.7% change), which is typically
below the threshold of biological relevance.
- Quantile Inflation: The high quantiles likely reflect the tight
regulation of GATA4 in the model background, where even tiny deviations are "rare" but not necessarily impactful.
--------------------------------------------------------------------------------
Plots and Visual Analysis
Regulatory Effects: Heart
!Heart Regulatory Effects
Visual Observation:
- Overview: The REF and ALT tracks appear identical. No loss of DNASE
peaks or H3K27ac marks is visible at the variant site.
Whole-Gene Expression View
!Whole-Gene RNA-seq
Visual Observation:
- Expression: The RNA-seq coverage across the entire GATA4 gene is
visually indistinguishable between REF (Blue) and ALT (Red). There is no evidence of global downregulation or isoform loss.
Detail View (+/- 50bp)
!Detail View
Visual Observation (Zoomed):
- Variant Site: At the exact variant position, the chromatin accessibility
(DNASE) and transcription factor binding signals remain unchanged.
Comparative ISM Analysis (Regulatory)
!ISM SeqLogo
Interpretation:
- Motif Stability: The ISM analysis shows a complex consensus motif but
minimal disruption scoring (Top score: 0.030). The G>T change does not significantly alter the binding potential of the predicted factors.
--------------------------------------------------------------------------------
Conclusion
AlphaGenome does not support a standard loss-of-function or regulatory disruption mechanism for chr8:11703860:G>T in GATA4 based on adult/fetal heart models. The variant may be: 1. Non-functional (if VSD association is weak/conflicting). 2. Act via a mechanism not modeled (e.g., protein-coding missense change affecting protein structure, which this tool does not score). 3. developmentally specific to a timeframe not captured in the training data.
Recommendation: Assess protein-coding effect (missense?) and structural variation potential.
Example: True Negative Result (TGFB3 / ARVC)
Variant: chr14:75958692:G>A Gene: TGFB3 Phenotype: Arrhythmogenic Right Ventricular Cardiomyopathy (ARVC) Verdict: Likely Benign Regulatory Effect / Protein-Coding Mechanism?
Why this is a good example
This analysis demonstrates a True Negative for Regulatory Disruption where:
1. Low Signal: Discovery scores were universally low (<0.1), unlike the GATA4 example which had high quantiles but low magnitude. Here, both were low. 2. Visual Confirmation:
- Whole-Gene Plot: Shows identical expression profiles.
- Detail Plot: Shows stable chromatin.
- ISM: Empty matrices for ATAC, indicating no sensitive enhancer logic
at this site.
Key Takeaway
When a known disease gene (TGFB3) shows zero regulatory impact in relevant tissue models (Heart), consider:
1. Protein-Coding Effect: Is it a missense variant? (AlphaGenome only scores regulatory potential). 2. Missing Context: Is it a cryptic splicing event not captured? (Splicing scores were also low here). 3. Benign: It might just be a benign variant in a disease gene.
Variant Analysis Report: chr14:75958692:G>A
Summary
The variant chr14:75958692:G>A is implicated in Arrhythmogenic Right Ventricular Cardiomyopathy (ARVC) and located in the * TGFB3 gene (associated with Loeys-Dietz syndrome and arrhythmogenic phenotypes). AlphaGenome analysis predicted no significant molecular impact* in Heart tissues. All discovery scores were low (<0.1), and visual inspection of RNA-seq and regulatory tracks showed no discernible difference between REF and ALT alleles. This suggests the variant may be likely to have no effect, act via a protein-coding mechanism (missense/synonymous) not modeled here, or affect a context not captured in the current heart models.
Genomic Context
- Variant: chr14:75958692:G>A
- Overlapping Gene: TGFB3
- Disease Association: ARVC / Loeys-Dietz Syndrome
1. Top Discovery Hits (Negative Result)
No tissue showed significant regulatory disruption.
| Tissue | Ontology | Modality | Raw | Quant | Effect |
|---|---|---|---|---|---|
| Heart | UBERON:0000948 | RNA_SEQ | 0.09 | 0.50 | Negligible |
| B Cell | CL:0000236 | ATAC | 0.05 | 0.85 | Negligible |
| Heart R Vent. | UBERON:0002080 | SPLICE | 0.02 | 0.77 | Negligible |
Observations:
- Magnitude: Raw scores are consistently near zero.
- Quantile: Even the "best" hits are well below the significance threshold
(0.995).
--------------------------------------------------------------------------------
Plots and Visual Analysis
Regulatory Effects: Heart
!Heart Regulatory Effects
Visual Observation:
- Overview: The REF and ALT tracks are visually identical. No loss of
promoter activity or enhancer signal is observed at the variant site.
Whole-Gene Expression View
!Whole-Gene RNA-seq
Visual Observation:
- Expression: The RNA-seq coverage across TGFB3 is indistinguishable
between REF and ALT, confirming no global expression change.
Detail View (+/- 50bp)
!Detail View
Visual Observation (Zoomed):
- Variant Site: High-resolution view confirms the stability of the local
chromatin environment (DNASE/ATAC).
Comparative ISM Analysis
!Heart ISM SeqLogo
Interpretation:
- Motif Stability: The ISM analysis yielded low scores (max 0.15) and no
clear motif disruption. The ATAC ISM matrix was empty, indicating no sensitive enhancer elements were detected at this position in the model.
--------------------------------------------------------------------------------
Conclusion
AlphaGenome does not predict a regulatory function for chr14:75958692:G>A in TGFB3 using Heart models. The variant is likely non-regulatory (check for missense/coding effects) or likely to have no effect in this context.
Recommendation: Verify protein-coding status.
Example: Polyadenylation Signal Disruption (HBA2 / Alpha-Thalassemia)
Variant: chr16:173692:A>G Gene: HBA2 (3' UTR) Phenotype: Hemoglobin H Disease / Alpha-Thalassemia Mechanism: Disruption of PolyA Signal (`AATAAA` -> `AATAAG`)
Why this is a good example
This analysis demonstrates a textbook 3' End Processing Defect:
1. Discovery Signal: The strongest hits (+1.07) are in Splice Junctions tracks (K562). In the 3' UTR context, this indicates failure to terminate or read-through, rather than splicing of introns. 2. ISM Smoking Gun: The ISM analysis for RNA-seq/Splicing unequivocally identifies the `AATAAA` hexamer as the critical motif (Score 2.45) which is destroyed by the variant. 3. Visuals: The Regulatory plots show the disruption at the transcript end.
Key Takeaway
When analyzing 3' UTR variants:
- Look for Splice Junction scores (indicating read-through).
- Look for RNA-seq changes (stability).
- Use ISM to check for
AATAAAorATTAAAmotifs. This is often the
"smoking gun" for regulatory pathology in 3' UTRs.
Variant Analysis Report: chr16:173692:A>G (Hemoglobin H)
Summary
The variant chr16:173692:A>G is a pathogenic mutation in the 3' UTR of * HBA2 (Hemoglobin Subunit Alpha 2), causative for Hemoglobin H Disease (Alpha-Thalassemia). AlphaGenome analysis definitively identifies the molecular mechanism as the disruption of the canonical Polyadenylation Signal (`AATAAA`)*. This is evidenced by a severe perturbation of transcript processing in Erythroid models (K562), where the variant causes a massive increase in aberrant "Splice Junction" scores (+1.07), likely reflecting transcriptional read-through or failure to terminate. ISM analysis confirms the destruction of the critical AATAAA motif.
Genomic Context
- Variant: chr16:173692:A>G
- Overlapping Gene: HBA2 (3' UTR)
- Disease Association: Hemoglobin H Disease / Alpha-Thalassemia
1. Top Discovery Hits (Processing Defect)
High scores in "Splice Junctions" for a 3' UTR variant indicate processing failure.
| Tissue | Ontology | Modality | Raw | Quant | Effect |
|---|---|---|---|---|---|
| K562 | EFO:0002067 | SPLICE_JUNC | +1.07 | 0.998 | PolyA Failure |
| K562 | EFO:0002067 | SPLICE_JUNC | +0.51 | 0.998 | PolyA Failure |
| K562 | EFO:0002067 | SPLICE_JUNC | +0.44 | 0.997 | PolyA Failure |
Observations:
- Mechanism: The consistent high "Splicing" scores in the 3' UTR are
characteristic of PolyA signal loss, where the transcription machinery fails to terminate/cleave, reading through into downstream regions or utilizing cryptic sites.
--------------------------------------------------------------------------------
Plots and Visual Analysis
Whole-Gene Expression View
!Whole-Gene RNA-seq
Visual Observation:
- Context: The plot shows the HBA2 locus. While global expression levels
may appear similar, the structure of the 3' end is likely altered (see ISM).
Regulatory Effects: K562
!K562 Regulatory Effects
Visual Observation:
- 3' UTR Disruption: The variant lies in the 3' region (
AATAAA). The
high splicing score suggests the model detects a structural change in the transcript boundaries here.
ISM Analysis: The "Smoking Gun"
!K562 ISM SeqLogo
Interpretation:
- Motif Disruption: The ISM analysis cleanly identifies the `AATAAA`
hexamer as the most critical motif at this position (Score 2.45).
- Variant Effect: The
A>Gmutation destroys this canonical PolyA signal,
which is the textbook mechanism for this form of Alpha-Thalassemia.
--------------------------------------------------------------------------------
Conclusion
The variant chr16:173692:A>G is a classic Regulatory Mutation that abolishes the * HBA2 Polyadenylation Signal (`AATAAA`)*. AlphaGenome correctly predicts this with high confidence in Erythroid models, flagging it as a major processing defect (+1.07 Score). This leads to unstable mRNA and alpha-globin deficiency.
Analysis Report: chr11:116837649:T>G (APOA1)
1. Variant Context
- Variant:
chr11:116837649:T>G - Gene: APOA1 (Apolipoprotein A-I)
- Location: Promoter Region (27 bp upstream of TSS on negative
strand).
- Disease: Hypoalphalipoproteinemia (HDL Deficiency).
- Tissues: Liver is the primary tissue for APOA1 expression.
2. Molecular Mechanism Hypothesis
The variant is located in the proximal promoter of APOA1. The AlphaGenome model predicts a Regulatory effect leading to altered expression.
3. Predicted Effects & Tissue Specificity
Primary Findings
| Tissue | Modality | Raw | Quant | Interpretation |
|---|---|---|---|---|
| Heart (L Vent.) | RNA-seq | -0.99 | 0.99998 | Strongest |
: : : : : disruption : | Liver | RNA-seq | +0.14 | 0.999 | Significant |
Tissue Specificity Comparison
- Top Discovery Hit vs Disease-Relevant Tissue: The strongest signal is
actually in the Heart (Left Ventricle, Cardiac Septum), where the model predicts a very strong quantile score. The Liver signal, while significant, is lower in magnitude.
- Interpretation:
- APOA1 is expressed in multiple tissues. The variant likely disrupts a
broadly active regulatory element (promoter).
- The Heart signal suggests the variant has a **strong regulatory
potential in cardiac tissue. While Hypoalphalipoproteinemia is a metabolic (Liver) disease, this finding raises the possibility of subclinical cardiac effects** or simply reflects that the promoter is more "active/sensitive" in the model's Heart context.
- Result: We prioritize the Liver effect for the disease phenotype
(HDL deficiency), but acknowledge the Heart effect as the strongest molecular signal.
4. Visualizations
Liver (Expression/Regulation)
Clinical Target !Liver Effects
- Observation: Increased expression (+0.14) and local chromatin changes.
Heart (Left Ventricle)
Top Discovery Hit !Heart Effects
- Observation: Strong regulatory disruption. Note the specific track
changes compared to Liver.
5. Motif Analysis (ISM)
Heart (Left Ventricle) - RNA-seq
!Heart ISM
- Motif Analysis: The SeqLogo shows the specific nucleotides driving the
high score. A tall letter at the center (variant position) indicates direct motif disruption.
Liver - RNA-seq
!Liver ISM
- Motif Analysis: Comparison with Heart shows whether the same or
different motifs are active.
6. Conclusion
The variant chr11:116837649:T>G is a functional PROMOTER variant. 1. Clinical Impact: It alters APOA1 regulation in the Liver, consistent with Hypoalphalipoproteinemia. 2. Molecular Insight: The effect is broad, with the strongest regulatory signals observed in Heart tissue, suggesting a pleiotropic effect on the APOA1 promoter. 3. Mechanism: Disruption of TF binding leading to expression changes (Gain in Liver, potential Loss/Change in Heart), supported by ISM analysis showing motif sensitivity at the variant locus.
Regulatory Analysis Examples
This directory contains verified examples of successful regulatory variant analyses (Promoters, Enhancers).
Examples
1. Promoter Variant (Expression gain/loss) - APOA1
- Report: apoa1_promoter/report.md
- Plots:
- Liver Tracks (Clinical)
- Heart Tracks (Top Hit)
- Liver ISM
- Key Features:
- Promoter Zoom: Minimal zoom (~200bp) to show local chromatin
changes.
- Modality Integration: Concordant RNA-seq and DNASE/ChIP changes.
- Tissue Specificity: Comparing Clinical Target (Liver) vs Top
Discovery Hit (Heart).
- ISM: Identifying disrupted motifs.
Best Practices
- Local Zoom: Use tight 200bp windows for DNASE/ChIP to see local shape.
- Separate Plots: Do not combine RNA-seq (gene-scale) and DNASE
(local-scale) on the same X-axis unless they align perfectly; separate files are often cleaner.
- ISM: Always run ISM for strong regulatory hits to identify the
transcription factor involved.
Variant Analysis Report: COL6A2 chr21:46126238:G>C
1. Summary
The variant chr21:46126238:G>C in COL6A2 is predicted to have a critical impact on splicing in Aorta and other vascular tissues. The substitution abolishes the canonical splice donor site (Score decrease > 14) and activates a refined cryptic splice donor site 60bp downstream (Score increase > 14). This results in a 60bp exon extension, which leads to the in-frame insertion of 20 amino acids into the COL6A2 protein. The effect is highly significant (Quantile Score > 0.99999) across multiple vascular tissues, aligning with the gene's function in connective tissue structure.
2. Visual Analysis (Aorta)
!Aorta Variant Effects
Interpretation:
- Junction Tracks (Sashimi):
- Ref (Blue): Canonical donor usage is dominant.
- Alt (Red): Canonical donor is lost (0 reads). A new cryptic junction
appears 60bp downstream (high read count).
- Splice Sites:
- Confirms the loss of the canonical donor site at
46126238and the gain
of a strong cryptic donor site at 46126298.
- Net Effect: 60bp extension of the exon.
3. Genomic Context & Mechanism
- Gene: COL6A2 (Collagen Type VI Alpha 2 Chain)
- Strand: Plus (+)
- Mechanism: Cryptic Splice Donor Activation causing Exon Extension
1. Canonical Loss: The G>C mutation disrupts the consensus sequence of the canonical splice donor at chr21:46126237. 2. Cryptic Gain: This forces the spliceosome to utilize a cryptic donor site located 60bp downstream at chr21:46126297. 3. Consequence: The exon is extended by 60 base pairs. 4. Protein Impact: 60bp / 3 = 20 codons. This is an in-frame insertion of 20 amino acids, potentially disrupting the triple-helical domain or other structural properties of COL6A2.
4. Conclusion
The chr21:46126238:G>C variant in COL6A2 disrupts the canonical splice donor and activates a cryptic site 60bp downstream. This drives a 20-amino acid in-frame insertion, likely destabilizing the collagen triple helix—a well-established mechanism for COL6A-related muscular disorders.
Variant Analysis Report: DLG1 chr3:197081044:TACTC>T
1. Summary
The variant chr3:197081044:TACTC>T (a 4bp deletion) in DLG1 is predicted to cause significant exon skipping in Tibial Artery and other vascular tissues. The deletion is located intronic, 6bp downstream of a canonical splice donor site. This disruption abolishes the usage of the canonical donor, forcing the splicing machinery to skip the adjacent exon entirely. This effect is highly significant (Quantile Score > 0.99999) and is consistent across multiple arterial tissues (Coronary, Umbilical).
2. Visual Analysis (Tibial Artery)
!Tibial Artery Variant Effects
Interpretation:
- Junction Tracks (Sashimi):
- Ref (Blue): Canonical splicing inclusion of the exon. Two arcs
connect the exon to its upstream and downstream neighbors.
- Alt (Red): The canonical arcs are lost. A single new arc
connects the upstream donor directly to the downstream acceptor, skipping the exon completely.
- Mechanism:
- The variant deletes 4bp (
ACTC) from the intronic region relative to
the donor site (positions +2 to +5 or similar relative to the splice site boundary).
- This disrupts the recognition of the donor site by the spliceosome (U1
snRNP).
- Result: The exon is skipped.
3. Genomic Context & Mechanism
- Gene: DLG1 (Discs Large MAGUK Scaffold Protein 1)
- Strand: Minus (-)
- Variant Location: Intronic, near the 5' Splice Site (Donor) of an exon.
- Molecular Consequence: Disruption of the splice donor consensus sequence
-> Exon Skipping.
4. Conclusion
The 4bp deletion chr3:197081044:TACTC>T destroys a canonical splice donor site in DLG1, leading to the skipping of the associated exon in tibial artery tissue. This is a robust, high-confidence prediction validated by deep learning models.
Splicing Analysis Examples
This directory contains verified examples of successful splicing analyses and reports.
Examples
1. Exon Skipping (Donor Disruption) - DLG1
- Report: dlg1_report.md
- Plot: dlg1_exon_skipping.png
- Key Feature: Hybrid Zoom. Notice how the plot zooms out (~9kb) to
show the full skipping event and its anchor exons, rather than just the variant's immediate vicinity. This is critical for visualizing skipping.
2. Exon Extension (Cryptic Donor) - COL6A2
- Report: col6a2_report.md
- Plot: col6a2_exon_extension.png
- Key Feature: Site Shift. The Sashimi arcs and Splice Site tracks
clearly show the donor site moving 60bp downstream.
Best Practices Checklist
- Deduplication: Ensure redundant RNA-seq tracks (Total vs PolyA) are
filtered.
- Raw Counts: Sashimi plots should show raw integer counts (not
normalized) for clarity.
- Strand Aware: Always verify the gene strand and filter tracks
accordingly.
- Dynamic Zoom: Use the "Hybrid Span" logic (Junctions + Anchors) for
skipping events, but tighter zooms for local shifts (extensions).
Interpretation Guide
This guide covers biological interpretation, signal patterns, motif analysis, and the pre-report reasoning checklist. Read this before writing any report.
--------------------------------------------------------------------------------
Signal Patterns Reference
Use this table to identify mechanisms from model outputs.
Expression & Regulatory
Some example mechanisms (non-exhaustive, use your reasoning ability):
| Mechanism | Location | Key Signals | ISM Check |
|---|---|---|---|
| **TF Binding | Promoter/TSS or | DNASE loss (narrow | Disrupted TF |
: Loss : Enhancer : peak) + RNA-seq : motif (Strong : : : : loss : REF) : | TF Binding | Promoter/TSS or | DNASE gain (narrow | Created TF motif | : Site Creation : Enhancer : peak) + RNA-seq : (Strong ALT) : : : : gain : : | Enhancer | Distal | DNASE loss | Disrupted TF | : Disruption : : (distal) + RNA-seq : motif (Strong : : : : loss (linked : REF) : : : : gene). : : : : : H3K27ac/p300 loss : : : : : confirms. : : | Enhancer | Distal | DNASE gain | Created TF motif | : Creation : : (distal) + RNA-seq : (Strong ALT) : : : : gain (linked : : : : : gene). : : : : : H3K27ac/p300 gain : : : : : confirms. : : | TSS Shift | 5' UTR | RNA-seq shape | — | : : : change (5' end) or : : : : : CAGE peak shift : : | uORF | 5' UTR | Context-dependent: | — | : Creation : : may show weak : : : : : Expression score. : : : : : Verify ATG : : : : : creation in 5' : : : : : UTR. : : | mRNA | 3' UTR (Note: | RNA-seq loss | — | : Stability : Model does not : WITHOUT : : : : explicitly model : chromatin/splicing : : : : this, unlikely to : defects. Possible : : : : reliably pick up) : miRNA binding site : : : : : alteration. : : | Statistical | Any | High Quantile | Empty/noise | : Artifact** : (low-expression : (>0.999) + Zero : : : : genes) : Plot Difference. : : : : : Caused by variance : : : : : stabilization in : : : : : low-signal : : : : : regions. : :
Splicing
| Mechanism | Key Signals | Junction Evidence |
|---|---|---|
| Exon Skipping | Low Splice Site at | Junction connects |
: : junction + new junctions : flanking exons, bypassing : : : spanning skipped exon : variant : | Intron Retention | Low Splice Site + high | — | : : RNA-seq in intron : : : : (read-through) : : | Cryptic Exon | New Splice Site (high | Junction starts/ends near | : (Pseudo-exon) : score) + new junctions : variant : : : flanking cryptic exon : : | Exon Extension | New donor/acceptor near | Junction shifted by N bp | : : existing site : from canonical : | PolyA Signal Loss | 3' UTR variant: high | AATAAA motif in ISM | : : Splice Junctions : : : : (read-through) + RNA-seq : : : : loss : :
Splicing Interpretation Rules
- Specific mechanisms only: Do NOT write "likely exon skipping or intron
retention" if data allows you to distinguish. Use the junction coordinates.
- Proxy signals: If Expression Loss (-0.999) >>> Splicing Disruption
(0.99), the primary mechanism is likely transcriptional (promoter/enhancer loss), not splicing. Exception: clearly aberrant new splice junction.
- Complex outcome: If multiple new cryptic sites compete with canonical
site, describe as "complex outcome of new candidate sites."
--------------------------------------------------------------------------------
Tissue and Location Verification
- Verify Tissue Lineage: Both top hits and disease-relevant tissues are
valuable. Do NOT ignore unexpected top hits (e.g., "Mesenchymal Stem Cell" for an erythroid gene), as they may reveal regulatory potential. However, you MUST also search for and include the expected disease-relevant tissues (e.g., K562/Erythroblast) to ensure the report directly addresses the clinical context.
- Verify Tissue Relevance:
- Match Disease to Organ System: If the disease is "Cardiomyopathy",
you MUST use "heart", "atrium", "ventricle", or "cardiomyocyte".
- Avoid Generic Proxies: Do NOT use "Smooth Muscle Cell" for Heart
(Cardiac Muscle != Smooth Muscle). Do NOT use "Fibroblast" for Brain.
- Search Strategy: If a specific cell type query (e.g.,
"cardiomyocyte") yields 0 hits, search for the organ (e.g., "heart", "cardiac"). While you should report unexpected top hits in other tissues, ensure you also include the relevant organ/tissue to address the clinical context.
- Verify Location (GTF Overlap): Do NOT guess "Promoter" vs "5' UTR".
Check the GTF coordinates. A variant at -30 might be in the 5' UTR if the TSS is upstream. Use lookup_gene_info.py --coord='chr:pos' or bedtools intersect to confirm.
--------------------------------------------------------------------------------
Score Interpretation
Raw Score vs Quantile Score
- Raw score: Effect magnitude, scorer-specific scale. Use for comparing
tissues WITHIN the same scorer only.
- Quantile score: Percentile rank vs common variants. Use to assess
significance. Saturates at ±0.999990.
- See variant-scoring-info section in alphagenome-api.md
for mathematical details.
Magnitude Rules
[!NOTE] Disclaimer: The interpretation of raw scores depends heavily on
the specific modality and assay type. The thresholds below are general rules
of thumb, primarily derived from RNA-seq data, and should not be treated as
absolute rules. Always validate quantitative scores by visually inspecting the
plotted tracks.
- < 0.1: Typically indicates No Significant Effect or background
noise, even if the quantile score is high.
- 0.1 - 0.5: Often represents a subtle or weak effect. Report as a
potential subtle change.
- 0.5 - 1.0: Suggests a moderate effect (roughly corresponding to a 1.4-2x
fold change in RNA-seq).
- > 1.0: Strong effect (generally >2x fold change for RNA-seq).
- RNA-seq raw_score ≈ log2 fold-change: -4 ≈ 16-fold reduction, -1 ≈
2-fold reduction, -0.35 ≈ 1.27-fold reduction.
- NO percentage interpretation: Raw scores are NOT percentages. Do not
interpret "0.09" as "9%".
- Relative Magnitude: Use relative comparisons (e.g., "low magnitude
compared to TERT's 1.5") or qualitative terms (e.g., "marginal effect").
- Always cite the raw score in reports (e.g., "Raw Score -1.89").
High Quantile + Low Raw Score
This is the most common trap. A quantile of 0.99+ with |raw_score| < 0.1 is effectively NO MOLECULAR EFFECT. This occurs in low-expression genes where variance stabilization inflates quantiles. Report as "No Significant Effect" and explain the statistical artifact.
Negative Results & Scientific Integrity
[!CRITICAL] Most variants are Benign. Do not "stretch" to find a mechanism
where none exists.
- Value of Negative Results: Reporting "AlphaGenome predicts NO molecular
effect" is a valuable scientific finding.
- Strict Anti-Speculation: Do not invent mechanisms (e.g., "cryptic splice
site", "enhancer disruption") unless explicitly shown by the model. If distinct evidence is missing, use broad terms like "predicted splicing disruption".
- Occupancy ≠ Disruption: A variant landing in an active enhancer (e.g.,
H3K27ac peak present) or promoter (e.g. before gene TSS) does not imply disruption. If REF and ALT tracks are identical and scores are near zero, the variant has no predicted effect. Do not claim disruption based on location alone.
- Model Limitations: AlphaGenome is a DNA-to-molecular phenotype model but
has limited scope of modeled molecular phenotypes. For example, it does NOT model:
- Post-Transcriptional RNA Biology: Beyond splicing (e.g., RNA
secondary structure folding, macromolecular assembly, transport).
- miRNA Processing: Due to low abundance or short length of
transcripts.
- Protein Coding Effects: Missense, nonsense, or frameshift mutations
affecting protein structure or function.
- Developmental Specificity: Effects restricted to specific
developmental timeframes not represented in the training data.
- Environmental or Dynamic Contexts: Effects triggered by specific
external stimuli or dynamic cell states not captured by the static biosample profiles.
--------------------------------------------------------------------------------
ISM (In-Silico Mutagenesis) Interpretation
Reading SeqLogo Plots
- Tall letters at variant position: Mutation directly disrupts an
important motif.
- Positive height = activating when present; Negative = repressive.
- REF vs ALT: Strong REF letter → ALT disrupts binding site. Strong ALT
letter → ALT creates new binding site.
Motif Identification Rules
Use your base knowledge about sequences (semantic knowledge) to identify motifs when available. For well-known canonical sequences, you can identify directly:
| Motif | Sequence | Context Required |
|---|---|---|
| TATA Box | TATAAA / TTTATA | Verify in Promoter |
| PolyA Signal | AATAAA / TTTATT | Verify in 3' UTR |
| Splice Donor | GT | Verify at intron boundary |
| Splice Acceptor | AG | Verify at intron boundary |
| E-box (MYC/MAX) | CACGTG | — |
| GRE (Glucocorticoid) | TGTTCT | — |
For less obvious sequences: use your knowledge of TF motifs, and if no confident match is found, report the consensus sequence with "resembles" or "potential" qualifier (e.g., "Disrupted motif resembling GATA consensus").
Reverse Complement
ALWAYS check the reverse complement. The model sees double-stranded DNA, so the motif may be on the minus strand (e.g., AATAAA ↔ TTTATT).
Negative ISM
If the ISM plot shows only tiny bars (<0.1 height) or random noise, report "No specific motif disruption identified." Do not force a match.
--------------------------------------------------------------------------------
Model Limitations
AlphaGenome predicts transcription and splicing from DNA sequence. It does NOT model:
- miRNA processing (low abundance / short length)
- RNA secondary structure (e.g., 3' UTR selenocysteine insertion
sequences, snRNA stem-loops)
- Protein folding or stability (missense effects)
- Catalytic activity (variant may produce stable but non-functional RNA)
- Developmental timing or stress-response contexts
Rules:
- If the gene is a non-coding RNA (snRNA, tRNA, rRNA) and you see High
Quantile + Low Raw Score: report "No Significant Effect" and state the structural limitation.
- If the known mechanism is protein stability: state that benign
regulatory scores do not rule out protein-level pathogenicity.
- If the known mechanism is enzymatic/catalytic: do not rule out
pathogenicity from neutral expression scores.
--------------------------------------------------------------------------------
Ontology Resolution Best Practices
The resolve_ontology_terms script implements smart matching. Key principles:
1. Specificity first: Prefer "Naive thymus-derived CD4-positive..." over generic "T-cell". 2. No silent fallbacks: Better to return [NOT FOUND] than silently map "Kupffer cell" → "Liver". 3. You bridge disease → tissue: The script matches terms, not diseases. You must identify that "Multiple Sclerosis" → Oligodendrocyte, T-cell, Brain, then query those terms. 4. Abbreviation handling: Common abbreviations (lv → left ventricle, huvec → human umbilical vein endothelial) are built in. 5. Conservation of specificity: A match is rejected if any substantive query token is missing from the target. Only generic stopwords ("human", "tissue", "sample") can be safely dropped. 6. Partial match flagging: If a partial match or fallback is necessary, it must be explicitly flagged as [PARTIAL MATCH] in the output.
Tissue selection for reports:
- Match disease to organ system: "Cardiomyopathy" → heart, atrium,
ventricle, cardiomyocyte.
- Avoid generic proxies: Do NOT use "Smooth Muscle Cell" for heart
(cardiac ≠ smooth), "Fibroblast" for brain.
- Search strategy: If specific query (e.g., "cardiomyocyte") yields 0
hits, search for the organ ("heart", "cardiac").
--------------------------------------------------------------------------------
Pre-Report Reasoning Checklist
Complete this BEFORE writing the report. Ground every interpretation in specific visual observations from the plots.
[!CAUTION] Do not pursue only the "obvious" answer (nearest gene,
literature-known tissue). Focus on where the model shows the strongest signal.
Ensure you have examined ALL significant scores, not just a convenient "top
5".
1. Cross-Tissue & Cross-Modality Patterns
- [ ] Analyze tissue specificity: Determine if the effect is universal or
tissue-restricted, and explain why.
- [ ] Check modality agreement: Verify if different modalities agree
within each tissue (concordant = strong evidence; discordant = complex regulation or potential model limitation).
- [ ] Identify largest effect: Pinpoint which tissue shows the largest
effect and assess its biological plausibility.
2. Hypothesis Validation
- [ ] Cite supporting plots: Explicitly state which plots support or
refute the proposed molecular mechanism.
- [ ] Cite supporting scores: Explicitly state which variant scores
support or refute the proposed molecular mechanism.
3. ISM SeqLogo
- [ ] Identify motif disruptions: Detail the motif disruptions evident
from REF vs ALT differences.
- [ ] Check consistency: Determine if disruptions are consistent across
tissues or tissue-specific.
- [ ] Explain magnitude differences: Use ISM plots to explain magnitude
differences between tissues.
4. Synthesis
- [ ] Define primary molecular mechanism: Trace the chain of events: E.g.
motif disruption → chromatin effect → transcriptional consequence → potential disease link.
- [ ] Avoid speculation: If scores are low and plots are flat, report "No
Significant Effect" instead of forcing a story.
- [ ] Address original query: Ensure the report directly answers the
user's initial question.
5. Report Readiness
- [ ] Write a narrative: Focus on telling a biological story, not just
dumping data.
- [ ] Verify references: Ensure all plot files are present and correctly
referenced.
- [ ] Embed all views: Include ISM plots, Detail Views, and Whole-Gene
Views where applicable.
- [ ] Confirm evidence: Verify that allegations of splicing have the
required splicing-related plots showing effects, expression changes have RNA-seq differences, and regulatory effects have DNASE/ChIP peak changes.
--------------------------------------------------------------------------------
Report Templates
This file provides simplified templates for reporting AlphaGenome variant analysis results. Follow the structure below to ensure all critical sections are covered.
[!IMPORTANT] DO NOT use the agent's default artifact directory for this
report. Save it as a regular file named report.md directly in the variant'soutput directory (e.g., analysis_chr1_12345_A_G/report.md) in the workspace.Use relative paths for embedded plots (filename.png).--------------------------------------------------------------------------------
1. Standard Analysis Report Template
Use this template for variants that show significant functional effects.
# Analysis Report: {variant_str} ({gene_name})
## 1. Summary of Findings
[Detailed biological narrative, not generic. State gene function, specific mechanism (e.g., "Pseudo-exon", "Enhancer Disruption"), and effect with raw scores. Quantify effect in biological terms (e.g., fold-change) rather than just citing raw scores or quantiles.]
## 2. Genomic Context
- **Variant**: {variant_str}
- **Gene**: {gene_name} ({ENSG_ID})
- **Location**: [e.g., Promoter, 5' UTR, Exon, Intron, 3' UTR]
- **Disease**: {disease_name} (if applicable)
## 3. Discovery Hits & Disease-Relevant Scores
[Table of top hits from discovery scan, limited to top 15-20. Include disease-relevant tissues even if they are not top hits.]
| Biosample Name | Gene Name | Output Type | Raw Score | Quantile Score |
| :--- | :--- | :--- | :--- | :--- |
| [Tissue] | [Gene] | [Modality] | [Score] | [Quantile] |
*Note: Discuss any disease-relevant tissues that were not in the top hits but show significant effects.*
## 4. Plots and Visual Analysis
[Embed all plots generated using standard markdown syntax: ``. Every ISM plot must have a specific interpretation caption. If visual inspection is ambiguous for AI agents, state the reliance on quantitative scores.]

*Fig 1: Main view showing [modality] tracks.*

*Fig 2: Detail view centered on variant.*

*Fig 3: ISM SeqLogo showing [motif description].*
## 5. Hypothesis Evaluation
[Explicitly state whether the hypothesis is SUPPORTED, REFUTED, or PARTIALLY SUPPORTED by the model scores.]
## 6. Primary Molecular Mechanism
[Synthesize the causal pathway: e.g., Variant disrupts X motif → causes chromatin closure → reduces expression of Y gene.]
## 7. Limitations
[State model blind spots (e.g., secondary structure, protein folding) and any unresolved questions or data gaps (e.g., missing modalities, proxy tissues used).]
## 8. Conclusion
[Narrative synthesis addressing primary finding, mechanism, biological impact, and confidence.]--------------------------------------------------------------------------------
[project]
name = "alphagenome-single-variant-analysis"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
"alphagenome>=0.6.1",
"absl-py",
"python-dotenv",
"matplotlib",
"numpy",
"pandas",
"pyarrow",
"scipy",
"seaborn",
]
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
r"""Generate ISM Sequence Logo for a variant.
Design Note:
This tool generates ISM Sequence Logos to identify disrupted motifs at the
variant position, helping explain *why* a regulatory or splicing site was
lost or gained (mechanistic cause).
The script extracts the top k-mer and prints its reverse complement.
The agent must use its own knowledge about transcription factor binding
motifs to identify candidate TFs that match the extracted motif or its
reverse complement.
Usage:
uv run analyze_ism.py --chrom=chr17 --pos=7675148 --ref=G --alt=A \
--tissue=liver --ontology=UBERON:0002107 --modality=DNASE
Examples:
uv run analyze_ism.py --chrom=chr17 --pos=7675148 --ref=G --alt=A \
--tissue=liver --ontology=UBERON:0002107 --modality=DNASE
uv run analyze_ism.py --chrom=chr21 --pos=46126238 --ref=G --alt=C \
--tissue='skeletal muscle' --ontology=CL:0002545 --modality=SPLICE_SITE_USAGE \
--gene=COL6A2
uv run analyze_ism.py --chrom=chr7 --pos=5529776 --ref=C --alt=T \
--tissue=HepG2 --ontology=EFO:0001187 --modality=CHIP_TF --gene=ACTB \
--output_dir=./ism_plots
"""
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "alphagenome",
# "numpy",
# "python-dotenv",
# ]
# ///
from __future__ import annotations
import argparse
import os
from typing import Any, Sequence
from alphagenome.data import genome
from alphagenome.interpretation import ism
from alphagenome.models import dna_client
from alphagenome.models import variant_scorers
from alphagenome.visualization import plot_components
import dotenv
import numpy as np
def _reverse_complement(seq: str) -> str:
"""Returns the reverse complement of a DNA sequence."""
return seq[::-1].translate(str.maketrans('ACGTacgt', 'TGCAtgca'))
def extract_ontology_scores(
ism_results: list[tuple[Any, ...]],
ontology_id: str,
gene_name: str | None = None,
) -> tuple[list[float], list[Any]]:
"""Extracts scores for a specific ontology ID from ISM results."""
scores_flat: list[float] = []
variants_flat: list[Any] = []
for adata, *_ in ism_results:
var_obj = adata.uns['variant']
score = 0.0
if 'ontology_curie' in adata.var.columns:
col_mask = adata.var['ontology_curie'] == ontology_id
if col_mask.any():
row_mask = slice(None) # All rows
if gene_name and 'gene_name' in adata.obs.columns:
gene_mask = adata.obs['gene_name'] == gene_name
if gene_mask.any():
row_mask = gene_mask
score = np.nanmean(adata.X[row_mask, col_mask])
else:
print(
'Info: No scores found for ontology term'
f' {ontology_id!r} in ISM result.'
)
scores_flat.append(score)
variants_flat.append(var_obj)
return scores_flat, variants_flat
def interpret_ism_matrix(
ref_ism_mat: np.ndarray,
bases: list[str],
kmer_length: int,
min_threshold: float,
) -> None:
"""Prints interpretation summary from an ISM matrix."""
print('\n--- ISM Interpretation Summary ---')
if not np.any(ref_ism_mat):
print('ISM matrix is all zeros. No relevant tracks or scores found.')
return
max_scores_per_pos = np.max(np.abs(ref_ism_mat), axis=1)
top_pos_idx = int(np.argmax(max_scores_per_pos))
top_score = ref_ism_mat[top_pos_idx, :]
center_idx = ref_ism_mat.shape[0] // 2
print(
f'Top Disrupted Position: {top_pos_idx - center_idx} (Relative to'
' Variant)'
)
print(f'Scores at Top Position: {dict(zip(bases, top_score))}')
# Use relative threshold but ensure it's at least min_threshold
threshold = max(np.max(np.abs(ref_ism_mat)) * 0.1, min_threshold)
consensus_seq: list[str] = []
for i in range(ref_ism_mat.shape[0]):
row = ref_ism_mat[i, :]
best_idx = np.argmax(np.abs(row))
if abs(row[best_idx]) > threshold:
consensus_seq.append(bases[best_idx])
else:
consensus_seq.append('.')
consensus_str = ''.join(consensus_seq)
print(f'Consensus Motif: {consensus_str}')
print(f'Reverse Compl : {_reverse_complement(consensus_str)}')
if ref_ism_mat.shape[0] >= kmer_length:
best_kmer_score = -1.0
best_kmer_seq = ''
best_kmer_start = 0
for i in range(ref_ism_mat.shape[0] - kmer_length + 1):
window = ref_ism_mat[i : i + kmer_length, :]
score = np.sum(np.max(np.abs(window), axis=1))
if score > best_kmer_score:
best_kmer_score = score
best_kmer_start = i
seq = ''
for j in range(kmer_length):
w_row = window[j, :]
seq += bases[np.argmax(np.abs(w_row))]
best_kmer_seq = seq
print(
f'Top {kmer_length}-mer: {best_kmer_seq}'
f' (Start: {best_kmer_start - center_idx},'
f' Score: {best_kmer_score:.3f})'
)
print(f'RevComp {kmer_length}-mer: {_reverse_complement(best_kmer_seq)}')
print('----------------------------------\n')
def analyze_ism(
chrom: str,
pos: int,
ref: str,
alt: str,
tissue: str,
ontology: str,
modality: str,
gene: str,
output_dir: str,
kmer_length: int,
min_threshold: float,
) -> None:
"""Runs In-Silico Mutagenesis (ISM) analysis and plots the results."""
api_key = os.environ.get('ALPHAGENOME_API_KEY')
if not api_key:
raise ValueError('ALPHAGENOME_API_KEY not found.')
print('Initializing AlphaGenome Client...')
dna_model = dna_client.create(
api_key=api_key,
address='dns:///gdmscience.googleapis.com:443',
)
variant = genome.Variant(chrom, pos, ref, alt)
print(f'Variant: {variant}')
ism_interval = variant.reference_interval.resize(32)
sequence_interval = ism_interval.resize(dna_client.SEQUENCE_LENGTH_1MB)
try:
output_type = dna_client.OutputType[modality.upper()]
except KeyError as e:
raise ValueError(
f'Unknown modality: {modality}. Valid options:'
f' {[o.name for o in dna_client.OutputType]}'
) from e
if output_type == dna_client.OutputType.SPLICE_JUNCTIONS:
raise ValueError(
'SPLICE_JUNCTIONS is NOT supported for ISM. Please use'
' SPLICE_SITE_USAGE instead.'
)
print(f'Scoring ISM for {tissue} ({output_type.name})...')
modality_key = modality.upper()
if modality_key in variant_scorers.RECOMMENDED_VARIANT_SCORERS:
ism_scorer = variant_scorers.RECOMMENDED_VARIANT_SCORERS[modality_key]
else:
raise ValueError(
f'No recommended scorer found for modality: {modality_key}. '
'Available recommended scorers: '
f'{list(variant_scorers.RECOMMENDED_VARIANT_SCORERS.keys())}'
)
print('Running ISM on REF background...')
ref_ism_results = dna_model.score_ism_variants(
interval=sequence_interval,
ism_interval=ism_interval,
variant_scorers=[ism_scorer],
)
ref_scores, ref_variants = extract_ontology_scores(
ref_ism_results, ontology, gene_name=gene
)
ref_ism_mat = ism.ism_matrix(ref_scores, variants=ref_variants)
max_score = np.max(np.abs(ref_ism_mat))
if max_score == 0:
print('WARNING: ISM matrix is empty (all zeros). Check ontology ID.')
elif max_score < min_threshold:
print(
f'WARNING: Max ISM score is very low ({max_score:.3f}), below'
f' threshold {min_threshold}. The result may not be reliable.'
)
print('Generating SeqLogo...')
fig = plot_components.plot(
[
plot_components.SeqLogo(
scores=ref_ism_mat,
scores_interval=ism_interval,
ylabel=f'ISM {tissue}\n{output_type.name}',
)
],
interval=ism_interval,
fig_width=15,
title=f'ISM Motif Analysis: {gene} {chrom}:{pos} ({tissue})',
annotations=[plot_components.VariantAnnotation([variant], alpha=0.5)],
)
safe_tissue = tissue.replace(' ', '_').replace('/', '_')
filename = os.path.join(output_dir, f'ism_{safe_tissue}_{modality}.png')
os.makedirs(output_dir, exist_ok=True)
fig.savefig(filename)
print(f'Saved ISM SeqLogo to {filename}')
bases = ['A', 'C', 'G', 'T']
interpret_ism_matrix(ref_ism_mat, bases, kmer_length, min_threshold)
def main(argv: Sequence[str] | None = None) -> None:
"""Main entry point for the ISM analysis CLI tool."""
dotenv.load_dotenv(os.path.expanduser('~/.env'))
parser = argparse.ArgumentParser(
description='Generate ISM Sequence Logo for a variant.'
)
parser.add_argument(
'--chrom',
required=True,
help='Chromosome (e.g., chr17).',
)
parser.add_argument(
'--pos', type=int, required=True, help='Position (1-based).'
)
parser.add_argument('--ref', required=True, help='Reference allele.')
parser.add_argument('--alt', required=True, help='Alternate allele.')
parser.add_argument(
'--tissue', required=True, help='Tissue name for labeling.'
)
parser.add_argument(
'--ontology', required=True, help='Ontology CURIE (e.g., UBERON:0002107).'
)
parser.add_argument(
'--modality',
required=True,
help='Output modality (e.g., DNASE, CHIP_TF).',
)
parser.add_argument(
'--gene', default='Unknown', help='Gene name for plot title.'
)
parser.add_argument('--output_dir', default='.', help='Output directory.')
parser.add_argument(
'--kmer_length',
type=int,
default=8,
help='Length of k-mer to scan for top score.',
)
parser.add_argument(
'--min_threshold',
type=float,
default=0.05,
help='Minimum absolute score threshold for motif extraction.',
)
args = parser.parse_args(argv)
analyze_ism(
args.chrom,
args.pos,
args.ref,
args.alt,
args.tissue,
args.ontology,
args.modality,
args.gene,
args.output_dir,
args.kmer_length,
args.min_threshold,
)
if __name__ == '__main__':
main()
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Generates tissue_ontology_mapping.json from the AlphaGenome API.
Usage:
uv run scripts/generate_ontology_mapping.py
Can be imported and called programmatically:
from generate_ontology_mapping import generate_mapping_file
generate_mapping_file('/path/to/output.json')
"""
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "alphagenome",
# "pandas",
# "python-dotenv",
# ]
# ///
import json
import logging
import os
import dotenv
logger = logging.getLogger(__name__)
from alphagenome.models import dna_client
import pandas as pd
def clean_list(series: pd.Series) -> list[str]:
"""Clean a pandas series to a sorted list of unique non-empty values."""
vals = set(series.dropna().astype(str))
vals = {
v
for v in vals
if v.strip() and v.lower() not in ('none', 'nan', 'null', '')
}
return sorted(list(vals))
def prune_empty(d):
"""Recursively remove empty values from nested dicts and lists."""
if not isinstance(d, (dict, list)):
return d
if isinstance(d, list):
return [
v for v in (prune_empty(v) for v in d) if v not in (None, [], {}, '')
]
if isinstance(d, dict):
return {
k: v
for k, v in ((k, prune_empty(v)) for k, v in d.items())
if v not in (None, [], {}, '')
}
def create_biological_mapping(df: pd.DataFrame) -> dict:
"""Create a mapping from ontology CURIEs to biosample metadata."""
df_proc = df.drop(
columns=['genetically_modified', 'nonzero_mean', 'name'], errors='ignore'
).copy()
df_proc['output_type'] = (
df_proc['output_type']
.astype(str)
.str.replace('OutputType.', '', regex=False)
)
mapping = {}
for curie, group in df_proc.groupby('ontology_curie'):
if pd.isna(curie):
continue
entry = {
'biosample': {
'name': group['biosample_name'].iloc[0],
'type': group['biosample_type'].iloc[0],
'life': clean_list(group['biosample_life_stage']),
'sources': clean_list(group['data_source']),
},
'molecular': {
'histones': clean_list(group['histone_mark']),
'tfs': clean_list(group['transcription_factor']),
},
'assays': {
assay: {
'tracks': clean_list(ag['output_type']),
'marks': (
clean_list(ag['histone_mark'])
+ clean_list(ag['transcription_factor'])
),
}
for assay, ag in group.groupby('Assay title')
},
}
mapping[curie] = prune_empty(entry)
return mapping
def generate_mapping_file(output_path: str) -> dict:
"""Fetches AlphaGenome metadata and writes the ontology mapping JSON.
Args:
output_path: Path to write the tissue_ontology_mapping.json file.
Returns:
The generated mapping dictionary.
Raises:
RuntimeError: If ALPHAGENOME_API_KEY is not set.
"""
api_key = os.environ.get('ALPHAGENOME_API_KEY')
if not api_key:
raise RuntimeError(
'ALPHAGENOME_API_KEY not set. '
'Ensure the `.env` file contains ALPHAGENOME_API_KEY=<key> and '
'use uv run to run this script.'
)
logger.info('Fetching output metadata from AlphaGenome API...')
dna_model = dna_client.create(api_key=api_key)
df = dna_model.output_metadata(dna_client.Organism.HOMO_SAPIENS).concatenate()
logger.info('Got %d rows.', len(df))
logger.info('Building ontology mapping...')
mapping = create_biological_mapping(df)
logger.info('Created %d entries.', len(mapping))
os.makedirs(os.path.dirname(output_path), exist_ok=True)
with open(output_path, 'w') as f:
json.dump(mapping, f, indent=2)
logger.info('Wrote mapping to %s', output_path)
return mapping
def get_tissue_ontology_mapping_path(resource_dir: str) -> str:
"""Returns the path to the tissue ontology mapping JSON file.
Args:
resource_dir: Directory where the resources are stored.
Returns:
Absolute path to tissue_ontology_mapping.json.
"""
return os.path.join(resource_dir, 'tissue_ontology_mapping.json')
def main():
dotenv.load_dotenv(os.path.expanduser('~/.env'))
script_dir = os.path.dirname(os.path.abspath(__file__))
resource_dir = os.path.join(script_dir, '..', 'resources')
generate_mapping_file(get_tissue_ontology_mapping_path(resource_dir))
if __name__ == '__main__':
main()
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Quantitative analysis of splicing effects (Splice Sites & Junctions).
This script provides high-resolution, quantitative analysis of splicing changes.
While discovery scans give summary scores, this script analyzes specific
junctions to detect events like exon skipping or cryptic splicing.
Design Note:
This tool provides quantitative analysis of specific splice junctions,
complementary to analyze_ism.py which can be used to analyze motifs affecting
splice site usage. While ISM helps identify *why* a site was lost or gained
(motif disruption), this script reveals *what* structural changes occurred
(e.g., exon skipping or cryptic junction usage). It offloads final judgment
to the agent but provides heuristic flags (e.g., GAIN/LOSS) to guide the analysis.
Usage:
uv run scripts/interpret_splicing.py --chrom=chr21 --pos=46126238 --ref=G --alt=C \
--ontology_id=CL:0002545
"""
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "alphagenome",
# "numpy",
# "pandas",
# "python-dotenv",
# ]
# ///
from __future__ import annotations
import argparse
import os
from typing import Any, Sequence
from alphagenome.data import genome
from alphagenome.models import dna_client
import dotenv
import numpy as np
import pandas as pd
API_ADDRESS = 'dns:///gdmscience.googleapis.com:443'
def get_track(obj: Any, attr: str, curie: str) -> Any:
"""Retrieves a track filtered by ontology CURIE."""
if not hasattr(obj, attr):
print(f"Warning: Object missing attribute '{attr}'.")
return None
data = getattr(obj, attr)
if hasattr(data, 'filter_by_ontology'):
return data.filter_by_ontology(curie)
mask = data.metadata['ontology_curie'] == curie
if not mask.any():
print(f"Info: No data found for ontology term '{curie}' in track '{attr}'.")
return None
return data.filter_tracks(mask.values)
def junctions_to_df(junc_track: Any) -> pd.DataFrame:
"""Converts a JunctionTrack to a DataFrame."""
if junc_track is None or len(junc_track.junctions) == 0:
return pd.DataFrame()
scores = None
if hasattr(junc_track, 'values'):
vals = junc_track.values
# Handle different shapes of values array from API
if vals.shape[0] == len(junc_track.junctions):
scores = vals.mean(axis=1)
elif len(vals.shape) > 1 and vals.shape[1] == len(junc_track.junctions):
scores = vals.mean(axis=0)
elif len(vals.shape) == 1 and len(vals) == len(junc_track.junctions):
scores = vals
if scores is None:
print('Warning: Failed to parse scores for junctions.')
return pd.DataFrame()
rows = []
for junction, score in zip(junc_track.junctions, scores):
rows.append({
'start': junction.start,
'end': junction.end,
'score': float(score),
'strand': junction.strand,
})
return pd.DataFrame(rows)
def analyze_splicing(
chrom: str,
pos: int,
ref: str,
alt: str,
ontology_id: str,
window: int,
) -> None:
"""Runs the splicing analysis for a specific variant."""
api_key = os.environ.get('ALPHAGENOME_API_KEY')
if not api_key:
raise ValueError('ALPHAGENOME_API_KEY environment variable not set.')
dna_model = dna_client.create(api_key=api_key, address=API_ADDRESS)
variant = genome.Variant(chrom, pos, ref, alt)
zoom_interval = genome.Interval(chrom, pos - window // 2, pos + window // 2)
pred_interval = zoom_interval.resize(131072)
print(
f'Quantifying splicing changes for {chrom}:{pos}:{ref}>{alt} in'
f' {ontology_id}...'
)
requested_outputs = [
dna_client.OutputType.SPLICE_JUNCTIONS,
dna_client.OutputType.SPLICE_SITE_USAGE,
dna_client.OutputType.RNA_SEQ,
]
prediction = dna_model.predict_variant(
interval=pred_interval,
variant=variant,
requested_outputs=requested_outputs,
ontology_terms=[ontology_id],
)
print('\n--- Splice Site Usage Analysis ---')
ss_ref = get_track(prediction.reference, 'splice_site_usage', ontology_id)
ss_alt = get_track(prediction.alternate, 'splice_site_usage', ontology_id)
if ss_ref and ss_alt:
start = pred_interval.start
idx = pos - start
for offset in range(-2, 3):
position = pos + offset
index = idx + offset
try:
val_ref = (
ss_ref.values[index].mean()
if ss_ref.values.ndim > 1
else ss_ref.values[index]
)
val_alt = (
ss_alt.values[index].mean()
if ss_alt.values.ndim > 1
else ss_alt.values[index]
)
diff = val_alt - val_ref
if abs(diff) > 0.05:
print(
f'Position {position} (Offset {offset}): REF={val_ref:.3f},'
f' ALT={val_alt:.3f}, Delta={diff:.3f}'
)
if val_ref > 0.5 and val_alt < 0.1:
print(f' -> Loss of strong splice site at {position}!')
elif val_ref < 0.1 and val_alt > 0.5:
print(f' -> Gain of new splice site at {position}!')
except (IndexError, AttributeError, ValueError) as e:
if not isinstance(e, IndexError):
print(f'Warning: Error during splice site diff calculation: {e}')
pass
else:
print('Splice site usage track missing.')
print('\n--- Junction Analysis ---')
junc_ref = prediction.reference.splice_junctions.filter_by_ontology(
ontology_id
)
junc_alt = prediction.alternate.splice_junctions.filter_by_ontology(
ontology_id
)
df_ref = junctions_to_df(junc_ref)
df_alt = junctions_to_df(junc_alt)
if not df_ref.empty and not df_alt.empty:
merged = pd.merge(
df_ref,
df_alt,
on=['start', 'end', 'strand'],
how='outer',
suffixes=('_REF', '_ALT'),
).fillna(0)
merged['delta'] = merged['score_ALT'] - merged['score_REF']
# Junctions overlapping variant
variant_overlapping = merged[
(merged['start'] < pos) & (merged['end'] > pos)
]
print(f'Junctions overlapping variant ({pos}):')
if variant_overlapping.empty:
print(' None found.')
else:
for _, row in variant_overlapping.iterrows():
print(
f" {int(row['start'])}-{int(row['end'])} (Strand {row['strand']}):"
f" REF={row['score_REF']:.2f}, ALT={row['score_ALT']:.2f},"
f" Delta={row['delta']:.2f}"
)
if row['score_REF'] > 5 and row['score_ALT'] < 1:
print(' -> Skipping/Loss of canonical intron (Exon skipping?)')
# Cryptic Junctions (Gain in ALT)
cryptic = merged[(merged['score_ALT'] > 5) & (merged['score_REF'] < 1)]
if not cryptic.empty:
print('\nPotential Cryptic Junctions (Gain in ALT):')
for _, row in cryptic.iterrows():
print(
f" {int(row['start'])}-{int(row['end'])}:"
f" REF={row['score_REF']:.2f} -> ALT={row['score_ALT']:.2f}"
)
print('\nTop 5 Most Changed Junctions:')
top_changes = merged.reindex(
merged['delta'].abs().sort_values(ascending=False).index
).head(5)
print(top_changes[['start', 'end', 'score_REF', 'score_ALT', 'delta']])
else:
print('No junctions found or failed to parse.')
print('\n--- RNA-seq Analysis ---')
rna_ref = get_track(prediction.reference, 'rna_seq', ontology_id)
rna_alt = get_track(prediction.alternate, 'rna_seq', ontology_id)
if rna_ref and rna_alt:
start_idx = pos - pred_interval.start - 50
end_idx = pos - pred_interval.start + 50
def safe_mean(arr: np.ndarray) -> np.ndarray:
if arr.ndim > 1:
return arr.mean(axis=1)
return arr
ref_vals = safe_mean(rna_ref.values)
alt_vals = safe_mean(rna_alt.values)
ref_window = ref_vals[start_idx:end_idx]
alt_window = alt_vals[start_idx:end_idx]
print(
f'Mean RNA Coverage (+/- 50bp): REF={ref_window.mean():.2f},'
f' ALT={alt_window.mean():.2f}'
)
if ref_window.mean() > 0:
log2fc = np.log2((alt_window.mean() + 1e-3) / (ref_window.mean() + 1e-3))
print(f'Log2 Fold Change at variant: {log2fc:.2f}')
else:
print('RNA-seq track missing.')
def main(argv: Sequence[str] | None = None) -> None:
"""Main entry point for the splicing analysis CLI tool."""
dotenv.load_dotenv(os.path.expanduser('~/.env'))
parser = argparse.ArgumentParser(
description='Quantitative analysis of splicing effects.'
)
parser.add_argument(
'--chrom', required=True, help='Chromosome (e.g., chr21).'
)
parser.add_argument(
'--pos', type=int, required=True, help='Position (1-based).'
)
parser.add_argument('--ref', required=True, help='Reference allele.')
parser.add_argument('--alt', required=True, help='Alternate allele.')
parser.add_argument(
'--ontology_id', required=True, help='Ontology CURIE (e.g., CL:0002545).'
)
parser.add_argument(
'--window',
type=int,
default=1500,
help='Window size around variant for analysis.',
)
args = parser.parse_args(argv)
analyze_splicing(
args.chrom,
args.pos,
args.ref,
args.alt,
args.ontology_id,
args.window,
)
if __name__ == '__main__':
main()
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Comprehensive gene and transcript lookup tool using GTF data.
Usage:
# 1. Lookup gene symbol or coordinate to get ID and location
uv run scripts/lookup_gene_info.py --genes='TP53,BRCA1'
uv run scripts/lookup_gene_info.py --genes='chr17:7675148'
# 2. Find genes near a coordinate
uv run scripts/lookup_gene_info.py --coord='chr17:7675148' --window=50000
# 3. List and filter transcripts for a gene
uv run scripts/lookup_gene_info.py --genes='EGFR' --transcripts --mane
"""
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "alphagenome",
# "pandas",
# "python-dotenv",
# ]
# ///
from __future__ import annotations
import argparse
import os
import re
from typing import Sequence
from alphagenome.data import gene_annotation
import dotenv
import pandas as pd
GTF_URL = (
'https://storage.googleapis.com/alphagenome/reference/gencode/'
'hg38/gencode.v46.annotation.gtf.gz.feather'
)
def load_gtf() -> pd.DataFrame:
"""Loads the GTF feather file."""
print(f'Loading GTF from {GTF_URL}...')
return pd.read_feather(GTF_URL)
def parse_gene_input(genes: list[str]) -> list[str]:
"""Parses comma-separated gene input and deduplicates."""
parsed = set()
for gene in genes:
parsed |= {x.strip() for x in gene.split(',') if x.strip()}
return sorted(parsed)
def classify_queries(queries: list[str]) -> tuple[list[str], list[str]]:
"""Classifies queries into coordinates and gene symbols."""
coords = []
symbols = []
for query in queries:
if re.match(r'^chr[0-9XYM]+:\d+', query):
coords.append(query)
else:
symbols.append(query)
return coords, symbols
def parse_coord(coord_str: str) -> tuple[str, int, int]:
"""Parses coordinate string like chr1:100-200 or chr1:150."""
chrom, pos_part = coord_str.split(':', 1)
if '-' in pos_part:
start_str, end_str = pos_part.split('-')
return chrom, int(start_str), int(end_str)
else:
pos = int(pos_part)
return chrom, pos, pos
# --- Mode 1: Gene Symbol <-> Coord Lookup (from lookup_ensg_gtf) ---
def run_gene_lookup(gtf: pd.DataFrame, genes_input: list[str]) -> None:
"""Looks up gene symbols or coordinates in GTF."""
genes_of_interest = parse_gene_input(genes_input)
coords_to_lookup, symbols_to_lookup = classify_queries(genes_of_interest)
results = []
# Handle symbols
if symbols_to_lookup:
mask = gtf['gene_name'].isin(symbols_to_lookup)
result_df = gtf[mask][
['gene_name', 'gene_id_nopatch', 'Chromosome', 'Start', 'End', 'Strand']
]
results.append(result_df)
# Handle coordinates
for coord in coords_to_lookup:
chrom, pos, _ = parse_coord(coord)
mask = (
(gtf['Chromosome'] == chrom)
& (gtf['Start'] <= pos)
& (gtf['End'] >= pos)
)
result_df = gtf[mask][
['gene_name', 'gene_id_nopatch', 'Chromosome', 'Start', 'End', 'Strand']
]
results.append(result_df)
if not results:
print('No matches found.')
return
df = pd.concat(results).drop_duplicates()
if df.empty:
print('No matches found.')
else:
print(df.to_string(index=False))
# --- Mode 2: Find Genes at Coordinate (from lookup_gene_at_coord) ---
def run_coord_search(gtf: pd.DataFrame, coord: str, window: int) -> None:
"""Finds genes near a coordinate."""
chrom, start, end = parse_coord(coord)
if start == end:
search_start = start - window
search_end = end + window
else:
search_start, search_end = start, end
print(f'\nSearching for genes at {chrom}:{search_start:,}-{search_end:,}')
print('-' * 50)
mask = (
(gtf['Chromosome'] == chrom)
& (gtf['Start'] <= search_end)
& (gtf['End'] >= search_start)
)
matching_genes = gtf[mask].copy()
if matching_genes.empty:
print('No genes found in this region.')
return
# Calculate distance if it's a single point query
if start == end:
def calc_dist(row):
if row['Start'] <= start <= row['End']:
return 0
return min(abs(row['Start'] - start), abs(row['End'] - start))
matching_genes['Distance'] = matching_genes.apply(calc_dist, axis=1)
matching_genes = matching_genes.sort_values('Distance')
cols = [
'gene_name',
'gene_id_nopatch',
'Chromosome',
'Start',
'End',
'Distance',
]
else:
matching_genes = matching_genes.sort_values('Start')
cols = ['gene_name', 'gene_id_nopatch', 'Chromosome', 'Start', 'End']
# Keep only gene features to avoid duplicate rows for exons/transcripts
feature_col = 'Feature' if 'Feature' in matching_genes.columns else 'feature'
if feature_col in matching_genes.columns:
matching_genes = matching_genes[matching_genes[feature_col] == 'gene']
if matching_genes.empty:
print(
'No gene features found (only exons/transcripts). Showing unique gene'
' names:'
)
print(matching_genes['gene_name'].unique())
else:
print(matching_genes[cols].drop_duplicates().to_string(index=False))
# --- Mode 3: List and Filter Transcripts (from lookup_transcripts) ---
def run_transcript_lookup(
gtf: pd.DataFrame,
genes_input: list[str],
mane: bool,
protein_coding: bool,
transcript_support_level: str | None,
longest: bool,
details: bool,
) -> None:
"""Lists and filters transcripts for genes."""
genes_list = parse_gene_input(genes_input)
filtered = gtf[gtf['gene_name'].isin(genes_list)].copy()
if filtered.empty:
print(f'No data found for genes: {genes_list}')
return
if mane:
print('Filtering to MANE transcripts...')
filtered = gene_annotation.filter_to_mane_select_transcript(filtered)
if protein_coding:
print('Filtering to protein coding transcripts...')
filtered = gene_annotation.filter_to_protein_coding_transcript(filtered)
if transcript_support_level:
print(
f'Filtering to Transcript Support Level: {transcript_support_level}...'
)
tsl_list = [x.strip() for x in transcript_support_level.split(',')]
if 'transcript_support_level' in filtered.columns:
filtered['tsl_clean'] = (
filtered['transcript_support_level'].astype(str).str.extract(r'^(\d)')
)
filtered = filtered[filtered['tsl_clean'].isin(tsl_list)]
else:
print(
'Warning: "transcript_support_level" column not found. Cannot filter'
' by TSL.'
)
if longest:
print('Filtering to longest transcript per gene...')
filtered = gene_annotation.filter_to_longest_transcript(filtered)
cols = ['gene_name', 'transcript_id']
if details:
for column in [
'transcript_type',
'transcript_support_level',
'Chromosome',
'Start',
'End',
]:
if column in filtered.columns:
cols.append(column)
print('\nResults:')
if 'Feature' in filtered.columns:
transcript_features = filtered[filtered['Feature'] == 'transcript']
if not transcript_features.empty:
filtered = transcript_features
print(filtered[cols].drop_duplicates().to_string(index=False))
def main(argv: Sequence[str] | None = None) -> None:
dotenv.load_dotenv(os.path.expanduser('~/.env'))
parser = argparse.ArgumentParser(
description='Lookup gene and transcript info using GTF data.'
)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
'--genes', help='Comma-separated gene symbols or coordinates to lookup.'
)
group.add_argument(
'--coord', help='Genomic coordinate for search (e.g. chr17:7675148).'
)
parser.add_argument(
'--window',
type=int,
default=50000,
help='Window size for coordinate search (default 50kb).',
)
parser.add_argument(
'--transcripts',
action='store_true',
help='List transcripts instead of gene info.',
)
# Transcript filters
parser.add_argument(
'--mane', action='store_true', help='Filter to MANE transcripts.'
)
parser.add_argument(
'--protein_coding',
action='store_true',
help='Filter to protein coding transcripts.',
)
parser.add_argument(
'--transcript_support_level',
help='Filter by Transcript Support Level (e.g. 1,2).',
)
parser.add_argument(
'--longest',
action='store_true',
help='Filter to longest transcript per gene.',
)
parser.add_argument(
'--details', action='store_true', help='Show full transcript details.'
)
args = parser.parse_args(argv)
gtf = load_gtf()
if args.coord is not None:
run_coord_search(gtf, args.coord, args.window)
elif args.genes is not None:
genes_list = [x.strip() for x in args.genes.split(',')]
if args.transcripts:
run_transcript_lookup(
gtf,
genes_list,
args.mane,
args.protein_coding,
args.transcript_support_level,
args.longest,
args.details,
)
else:
run_gene_lookup(gtf, genes_list)
else:
parser.error('Please specify either --genes or --coord. See --help.')
if __name__ == '__main__':
main()
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Resolves ontology terms by searching the AlphaGenome ontology mapping file for closest matches.
This script acts as a candidate retriever for mapping free-text queries to
AlphaGenome-compatible ontology terms. It performs a fast,
simple word-overlap search over the available tissues and returns the top
matches ranked by score.
Design Note:
This tool acts as a simple candidate retriever for ontology mapping.
It does not perform complex internal synonym mapping or semantic
rules, leaving the calling agent (or researcher) to leverage their
domain knowledge for query expansion (e.g., "cardiac" -> "heart")
and final candidate selection.
Usage:
uv run resolve_ontology_terms.py --query='liver'
Examples:
uv run resolve_ontology_terms.py --query='liver'
uv run resolve_ontology_terms.py --query='k562' --limit=5
"""
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "alphagenome",
# "pandas",
# "python-dotenv",
# ]
# ///
import argparse
import json
import os
import re
import sys
from typing import Any, Sequence
import dotenv
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
RESOURCES_DIR = os.path.join(SCRIPT_DIR, "..", "resources")
MAPPING_FILE = os.path.join(RESOURCES_DIR, "tissue_ontology_mapping.json")
def normalize_and_split(text: str) -> set[str]:
"""Lowercases and splits text into alphanumeric words of length > 2."""
text = re.sub(r"[^a-zA-Z0-9\s]", " ", text.lower())
return {t.strip() for t in text.split() if len(t.strip()) > 2}
def search_ontology(
query: str, mapping: dict[str, Any], limit: int = 10
) -> list[dict[str, Any]]:
"""Search for tissues matching the query words."""
query_words = normalize_and_split(query)
if not query_words:
return []
results = []
for curie, data in mapping.items():
name = data.get("biosample", {}).get("name", "")
# Flatten all metadata for searching
full_text = f"{curie} {name} {str(data)}"
record_words = normalize_and_split(full_text)
# Calculate intersection score
# Score is the number of query words found in the record
score = len(query_words & record_words)
# Give extra weight if the word is in the specific name
name_words = normalize_and_split(name)
score += len(query_words & name_words) * 2.0
if score > 0:
results.append({
"curie": curie,
"name": name,
"type": data.get("biosample", {}).get("type", "N/A"),
"assays": list(data.get("assays", {}).keys()),
"score": score,
})
# Sort by score descending, then by name length
# (prefer shorter, more specific names if score tied)
results.sort(key=lambda x: (x["score"], -len(x["name"])), reverse=True)
return results[:limit]
def main(argv: Sequence[str] | None = None) -> None:
dotenv.load_dotenv(os.path.expanduser("~/.env"))
parser = argparse.ArgumentParser(
description="Resolves ontology terms by searching available tracks."
)
parser.add_argument(
"--query", required=True, help="Tissue name or keyword to search for."
)
parser.add_argument(
"--limit", type=int, default=10, help="Max number of results to return."
)
args = parser.parse_args(argv)
if not os.path.exists(MAPPING_FILE):
print(
f"Error: Mapping file not found at {MAPPING_FILE}.",
file=sys.stderr,
)
print(
"Please run generate_ontology_mapping.py first to generate it.",
file=sys.stderr,
)
return
with open(MAPPING_FILE, "r") as f:
mapping = json.load(f)
results = search_ontology(args.query, mapping, args.limit)
print(f"\nSearch results for: '{args.query}'")
print(f"Query words used: {normalize_and_split(args.query)}")
print("-" * 60)
if not results:
print("No matches found.")
else:
print(
f"{'Rank':<4} | {'ID':<15} | {'Name':<40} | {'Type':<10} | {'Score':<5}"
)
print("-" * 80)
for i, res in enumerate(results):
# Truncate name if too long for table
name = res["name"][:37] + "..." if len(res["name"]) > 40 else res["name"]
print(
f"[{i+1:<2}] | {res['curie']:<15} | {name:<40} | {res['type']:<10} |"
f" {res['score']:.1f}"
)
if __name__ == "__main__":
main()
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
r"""Visualize regional model predictions.
Usage:
uv run scripts/visualize_genome_tracks.py --chrom=chr19 --start=11089363 --end=11133820 \
--ontology=UBERON:0002107 --output_dir=./region_plots
Examples:
uv run scripts/visualize_genome_tracks.py --chrom=chr19 --start=11089363 --end=11133820 \
--ontology=UBERON:0002107 --output_dir=./region_plots
uv run scripts/visualize_genome_tracks.py --chrom=chr19 --start=11089363 --end=11133820 \
--ontology=UBERON:0002107 --output_dir=./region_plots \
--zoom_genes=LDLR
uv run scripts/visualize_genome_tracks.py --chrom=chr17 --start=7661779 --end=7687538 \
--ontology=UBERON:0000310 --output_dir=./plots \
--zoom_genes=TP53
"""
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "alphagenome",
# "numpy",
# "pandas",
# "pyarrow",
# "python-dotenv",
# ]
# ///
from __future__ import annotations
import argparse
import os
from alphagenome.data import gene_annotation
from alphagenome.data import genome
from alphagenome.data import transcript as transcript_utils
from alphagenome.models import dna_client
from alphagenome.visualization import plot_components
import dotenv
import numpy as np
import pandas as pd
GTF_URL = (
'https://storage.googleapis.com/alphagenome/reference/gencode/'
'hg38/gencode.v46.annotation.gtf.gz.feather'
)
API_ADDRESS = 'dns:///gdmscience.googleapis.com:443'
def create_client() -> dna_client.DnaClient:
"""Creates an AlphaGenome DNA client."""
api_key = os.environ.get('ALPHAGENOME_API_KEY')
if not api_key:
raise ValueError('ALPHAGENOME_API_KEY environment variable not set.')
return dna_client.create(
api_key=api_key,
address=API_ADDRESS,
)
def safe_filter_tracks(track: object, mask: object) -> object | None:
"""Safely filters tracks using integer indices."""
if track is None:
return None
if isinstance(mask, (list, np.ndarray, pd.Series)):
mask = np.array(mask)
if mask.dtype == bool:
mask = np.flatnonzero(mask)
return track.filter_tracks(mask)
def filter_tracks_by_ontology(
track: object, ontology_curie: str
) -> object | None:
"""Filters tracks to those matching the given ontology CURIE."""
if track is None or track.metadata.empty:
return track
if 'ontology_curie' in track.metadata.columns:
# track.metadata is a DataFrame where each row is one track (e.g. one
# tissue/cell-type). We compare each row's ontology_curie string against
# the target to build a boolean mask selecting only matching tracks.
vals = track.metadata['ontology_curie'].astype(str).str.strip().values
target = str(ontology_curie).strip()
mask = vals == target
if np.sum(mask) == 0:
return None
return safe_filter_tracks(track, mask)
return track
def add_track_component(
components: list[object],
track: object,
ontology: str,
separate_strands: bool = False,
track_type: str = 'Track',
) -> None:
"""Filters a track by ontology and appends to the component list."""
if track is None:
return
filt = filter_tracks_by_ontology(track, ontology)
if filt is None:
return
if separate_strands and 'strand' in filt.metadata.columns:
for strand in ['+', '-']:
s_track = filt.filter_tracks(
np.flatnonzero(filt.metadata['strand'] == strand)
)
if not s_track.metadata.empty:
if len(s_track.metadata) > 5:
s_track = safe_filter_tracks(s_track, np.arange(5))
components.append(
plot_components.Tracks(
s_track, ylabel_template=f'{track_type} ({strand})'
)
)
else:
if len(filt.metadata) > 5:
filt = safe_filter_tracks(filt, np.arange(5))
components.append(
plot_components.Tracks(filt, ylabel_template=f'{track_type}')
)
def render_broad_view(
client: dna_client.DnaClient,
gtf: pd.DataFrame,
chrom: str,
start: int,
end: int,
ontology: str,
output_dir: str,
) -> None:
"""Renders the broad region overview plot."""
region = genome.Interval(chrom, start, end)
print(f'Analyzing Region: {region}')
center = (start + end) // 2
model_len = 1_048_576 # Always use 2**20 for full context
pred_start = center - (model_len // 2)
pred_end = center + (model_len // 2)
pred_interval = genome.Interval(chrom, pred_start, pred_end)
print(f'Broad Prediction Interval (Model-Compatible): {pred_interval}')
broad_outputs = [
dna_client.OutputType.RNA_SEQ,
dna_client.OutputType.ATAC,
dna_client.OutputType.DNASE,
dna_client.OutputType.CHIP_HISTONE,
dna_client.OutputType.CHIP_TF,
dna_client.OutputType.CONTACT_MAPS,
]
print('Running Broad Prediction...')
prediction = client.predict_interval(
interval=pred_interval,
requested_outputs=broad_outputs,
ontology_terms=[ontology],
)
components: list[object] = []
longest_gtf = gene_annotation.filter_to_longest_transcript(gtf)
tx_extractor = transcript_utils.TranscriptExtractor(longest_gtf)
transcripts = tx_extractor.extract(pred_interval)
if transcripts:
components.append(
plot_components.TranscriptAnnotation(
transcripts, label_name='gene_name'
)
)
ref_preds = prediction
add_track_component(
components,
ref_preds.rna_seq,
ontology,
separate_strands=True,
track_type='RNA',
)
add_track_component(components, ref_preds.atac, ontology, track_type='ATAC')
add_track_component(components, ref_preds.dnase, ontology, track_type='DNASE')
add_track_component(
components, ref_preds.chip_histone, ontology, track_type='Histone'
)
add_track_component(components, ref_preds.chip_tf, ontology, track_type='TF')
if hasattr(ref_preds, 'contact_maps') and ref_preds.contact_maps:
if hasattr(plot_components, 'ContactMaps'):
filt_cmap = filter_tracks_by_ontology(ref_preds.contact_maps, ontology)
if filt_cmap and not filt_cmap.metadata.empty:
components.append(
plot_components.ContactMaps(filt_cmap, ylabel_template='Hi-C')
)
else:
print(
'WARNING: plot_components.ContactMaps not found. Skipping Contact'
' Maps.'
)
if components:
print('Rendering Broad Plot...')
fig = plot_components.plot(components, interval=pred_interval)
fig.set_size_inches(20, len(components) * 2 + 2)
fig.savefig(
os.path.join(output_dir, 'broad_view.png'),
bbox_inches='tight',
dpi=150,
)
print(f"Saved {os.path.join(output_dir, 'broad_view.png')}")
else:
print('No components to plot for broad view.')
def render_zoom_view(
client: dna_client.DnaClient,
gtf: pd.DataFrame,
chrom: str,
gene: str,
ontology: str,
output_dir: str,
) -> None:
"""Renders a zoomed-in splicing plot for a specific gene."""
print(f'\nProcessing Zoom for {gene}...')
gene_df = gtf[gtf['gene_name'] == gene]
if gene_df.empty:
print(f'Gene {gene} not found.')
return
gene_interval = gene_annotation.get_gene_interval(gene_df, gene_symbol=gene)
padding = 2000
zoom_interval = gene_interval.resize(gene_interval.width + 2 * padding)
# Ensure at least 50kb context for small genes.
if zoom_interval.width < 50000:
zoom_interval = zoom_interval.resize(50000)
print(f'Zoom Interval (Requested): {zoom_interval}')
model_len = 1_048_576 # Always use 2**20 for full context
z_center = (zoom_interval.start + zoom_interval.end) // 2
pred_interval = genome.Interval(
chrom, z_center - (model_len // 2), z_center + (model_len // 2)
)
print(f'Prediction Interval (Model-Compatible): {pred_interval}')
zoom_outputs = [
dna_client.OutputType.RNA_SEQ,
dna_client.OutputType.SPLICE_SITES,
dna_client.OutputType.SPLICE_SITE_USAGE,
dna_client.OutputType.SPLICE_JUNCTIONS,
]
zoom_predictions = client.predict_interval(
interval=pred_interval,
requested_outputs=zoom_outputs,
ontology_terms=[ontology],
)
zoom_components: list[object] = []
longest_gtf = gene_annotation.filter_to_longest_transcript(gtf)
tx_extractor = transcript_utils.TranscriptExtractor(longest_gtf)
zoom_transcripts = tx_extractor.extract(zoom_interval)
if zoom_transcripts:
zoom_components.append(
plot_components.TranscriptAnnotation(
zoom_transcripts, label_name='gene_name'
)
)
gene_strand = gene_df['Strand'].iloc[0]
if zoom_predictions.rna_seq:
r_filt = filter_tracks_by_ontology(zoom_predictions.rna_seq, ontology)
if r_filt:
r_strand = safe_filter_tracks(
r_filt, r_filt.metadata['strand'] == gene_strand
)
if r_strand and not r_strand.metadata.empty:
zoom_components.append(
plot_components.Tracks(
r_strand, ylabel_template=f'RNA ({gene_strand})'
)
)
if zoom_predictions.splice_sites:
ss_filt = filter_tracks_by_ontology(zoom_predictions.splice_sites, ontology)
if ss_filt:
zoom_components.append(
plot_components.Tracks(ss_filt, ylabel_template='Sites')
)
if zoom_predictions.splice_site_usage:
su_filt = filter_tracks_by_ontology(
zoom_predictions.splice_site_usage, ontology
)
if su_filt:
zoom_components.append(
plot_components.Tracks(su_filt, ylabel_template='Usage')
)
if zoom_predictions.splice_junctions:
j_filt = filter_tracks_by_ontology(
zoom_predictions.splice_junctions, ontology
)
if j_filt and not j_filt.metadata.empty:
zoom_components.append(
plot_components.Sashimi(
j_filt,
ylabel_template='Junctions',
normalize_values=False,
)
)
if zoom_components:
print(f'Rendering Zoom Plot for {gene}...')
fig = plot_components.plot(zoom_components, interval=zoom_interval)
fig.set_size_inches(20, len(zoom_components) * 2)
fig.savefig(
os.path.join(
output_dir,
f'zoom_{gene}_{zoom_interval.start}-{zoom_interval.end}.png',
),
bbox_inches='tight',
dpi=150,
)
print(f'Saved zoom plot to {output_dir}')
def main(argv: list[str] | None = None) -> None:
"""Main entry point for the region visualization CLI tool."""
dotenv.load_dotenv(os.path.expanduser('~/.env'))
parser = argparse.ArgumentParser(
description='Visualize regional model predictions.'
)
parser.add_argument(
'--chrom', required=True, help='Chromosome (e.g., chr19).'
)
parser.add_argument(
'--start', type=int, required=True, help='Region start position.'
)
parser.add_argument(
'--end', type=int, required=True, help='Region end position.'
)
parser.add_argument(
'--ontology',
required=True,
help='Ontology CURIE (e.g., UBERON:0002107).',
)
parser.add_argument('--output_dir', required=True, help='Output directory.')
parser.add_argument(
'--zoom_genes', help='Comma-separated genes to zoom into.'
)
args = parser.parse_args(argv)
os.makedirs(args.output_dir, exist_ok=True)
client = create_client()
print('Loading GTF...')
gtf = pd.read_feather(GTF_URL)
render_broad_view(
client,
gtf,
args.chrom,
args.start,
args.end,
args.ontology,
args.output_dir,
)
if args.zoom_genes:
zoom_genes = [g.strip() for g in args.zoom_genes.split(',')]
for gene in zoom_genes:
render_zoom_view(
client,
gtf,
args.chrom,
gene,
args.ontology,
args.output_dir,
)
if __name__ == '__main__':
main()
Related skills
How it compares
Pick alphagenome-single-variant-analysis when you need DeepMind AlphaGenome single-variant scoring inside an agent session rather than generic genomics scripting.
FAQ
What Python package does alphagenome-single-variant-analysis use?
alphagenome-single-variant-analysis uses the pip package alphagenome, importing modules such as alphagenome.models.dna_client, alphagenome.models.variant_scorers, alphagenome.interpretation.ism, and alphagenome.visualization.plot_components for scoring and plotting.
How does AlphaGenome authentication work in this skill?
alphagenome-single-variant-analysis loads an AlphaGenome API key automatically via dotenv from a .env file in the agent configuration directory before initializing the dna_client for variant predictions.
Is Alphagenome Single Variant Analysis safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.