Imported from sahuno/igv-reports-skill (
SKILL.md). Install upstream withnpx skills add sahuno/igv-reports-skill. Copyright stays with the author.
igv-reports
This skill builds self-contained HTML genomic-region reports with
igv-reports (create_report).
Each report is a single browseable HTML containing the igv.js viewer plus
embedded data slices for every region. No server, no internet, no IGV
install needed at view time.
The skill has three entry points:
- build — one-shot: sites BED + BAM(s) ± VCF → HTML.
- cohort — multi-sample driver from a samplesheet → per-sample HTMLs + index.
- prep-track — utility: convert plain-gzip GFF/GTF/BED.gz into a bgzip + tabix-indexed track that igv-reports can load.
What this skill is (and is not)
This skill is a driver layer on top of the upstream igv-reports
Python package by the IGV team
(github.com/igvteam/igv-reports).
The naming is unavoidable — both share the igv-reports name.
| Component | Source | Role |
|---|---|---|
create_report CLI |
upstream PyPI package igv-reports |
does the actual HTML rendering |
scripts/build_igvreports.py |
this skill | wraps create_report with default-track resolution, cohort/samplesheet mode, SIF auto-detect |
scripts/verify_{report,cohort,anchors}.py |
this skill | post-render structural + content audits (not in upstream) |
scripts/prep_track.sh |
this skill | bgzip+tabix utility for annotation tracks |
The skill is not on PyPI — it's a directory of scripts. Use it by either
cloning this repo or copying scripts/ next to your data.
Off-MSKCC quickstart
Defaults assume MSKCC HPC (lab SIF, databases_config.yaml, /data1/greenbab
bind). Anywhere else:
# 1. Install the UPSTREAM igv-reports package (provides `create_report`).
# Use -U to pick up the latest fixes; skill requires >=1.16.0.
pip install -U 'igv-reports>=1.16.0'
# 2. Get this skill's wrapper scripts (one of):
# - clone: git clone https://github.com/sahuno/igv-reports-skill.git
# cd igv-reports-skill
# - or copy scripts/ next to your project
# 3. Run the wrapper, bypassing the lab databases YAML with explicit paths:
python scripts/build_igvreports.py \
--genome hg38 \
--sites sites.hg38.bed \
--bam tumor.bam normal.bam \
--fasta /path/to/hg38.fa \
--no-default-tracks \
--extra-track /path/to/your_cpg_islands.bed.gz \
--extra-track /path/to/gencode.v47.annotation.gff3.gz \
--output report.hg38.html
If you only need raw create_report (no cohort mode, no verifiers, no
auto-tracks), skip the skill entirely and use upstream directly —
see igvteam/igv-reports docs.
Environment overrides (all optional):
| Var | Effect |
|---|---|
IGV_REPORTS_DB_CONFIG |
Path to your own databases YAML (same schema as the lab's) |
IGV_REPORTS_SIF |
Path to your own igv-reports apptainer SIF |
SAMTOOLS_SIF_DEFAULT |
Path to your own samtools SIF (verifier only) |
IGV_REPORTS_BIND |
Colon-separated bind paths for singularity (default /data1/greenbab). Empty string disables binding. |
Driver flags --fasta and --no-default-tracks let you skip the databases
YAML entirely without setting any env var. --no-apptainer forces the
PATH create_report path even on a SLURM node. The hermetic tests/unit/
suite runs anywhere with pytest + Python ≥ 3.10.
When to use which entry point
| User request | Entry point |
|---|---|
| "Make an HTML for these 5 SV breakpoints in tumor.bam" | build |
| "Give me one HTML per patient for the cohort integration calls" | cohort |
| "create_report fails with 'not BGZF' on this gencode" | prep-track |
Defaults (locked in)
- Tracks always loaded, top-to-bottom in the viewer:
- CpG islands (BED, plain or bgzipped)
- Gencode full annotation (GFF3.gz, transcripts + exons + CDS + UTRs, NOT a gene-level-only file)
- RepeatMasker (BED.gz, bgzipped + tabix-indexed) Plus the user's BAM(s), VCF, and any extra tracks they pass.
--flanking 300bp on either side of each site (good for SV breakpoints and point variants alike). Override per call if needed.--standaloneso the HTML is offline-viewable.- Output filename includes the genome tag — e.g.
cohort.hg38.html— to passenforce-genome-tag.sh. - Reference FASTA is resolved from
databases_config.yaml:/data1/greenbab/users/ahunos/apps/llm_configs/claude/profiles/databases/databases_config.yaml(lab default; override with$IGV_REPORTS_DB_CONFIGor--no-default-tracks+--fasta). Supported genome IDs:hg38,mm10,mm39,t2t_CHM13v2_plusY,GRCh37. - Per-genome default track availability is recorded in
references/databases_config_paths.md— read it before assembling tracks so the skill doesn't try to load a track that doesn't exist for the selected genome (e.g., mm39 has no rmsk in our database).
Sites BED format (critical)
igv-reports' BED parser reads fields by position and trips on a header
row (ValueError: invalid literal for int() with base 10: 'start'). Always
emit a plain headerless 4-column BED:
chr start end name
chr2 25227855 25342590 DNMT3A_full_gene
Tab-separated. The name becomes the row label in the report's variant
table — make it specific enough to identify the site after deduping.
By default create_report shows only the chr/start/end position columns
in the clickable table. To surface the name (or any extra columns from
a 5+ column BED), pass --info-columns <colname> to the driver:
python scripts/build_igvreports.py ... --info-columns name
python scripts/build_igvreports.py ... --info-columns gene_name,score
Column names are matched by header (so a #chrom\tstart\tend\tname\tscore
header works). For positional BED without a header, the convention is the
4th column = name, 5th = score, 6th = strand.
The project's enforce-genome-tag.sh hook requires a genome tag in the BED
filename: use sites.hg38.bed, not sites.bed.
--type for BED-style sites
When the sites input is a BED (not a VCF), pass --type mutation to
create_report (or the driver). This gives the right viewer behavior at
each row — one locus per row, no split-screen, table on top. Without it,
some BED layouts trigger create_report's split-screen junction view by
heuristic. Use --type variant for VCF sites, or omit for create_report's
auto-detection (only safe with a VCF).
python scripts/build_igvreports.py ... --type mutation --info-columns name
Pitfalls (the skill should encode and/or detect these)
| Symptom | Root cause | Fix |
|---|---|---|
ValueError: invalid literal for int() on first row |
Header row in sites BED | Strip header — plain BED |
UnicodeDecodeError: byte 0x8b reading a track |
igv-reports reading bgzip as text | Filename must end .gff3.gz / .bed.gz AND be true bgzip (check with file <name> for "extra field") |
tabix: not BGZF |
Track was plain-gzipped, not bgzipped | Run prep-track entry point |
tabix: out of order while indexing |
GFF/GTF/BED records not pos-sorted within chr | prep-track does sort -k1,1 -k4,4n before bgzip |
| Annotation track empty in viewer | Tabix returns no rows in displayed window — often correct biology (e.g., CGI-distal site). Confirm with tabix file region |
|
Genome ID lookup fails with --genome hg38 |
igv.js bundled IDs require internet at view + render time. Use --fasta /path/to/local.fa instead (always works offline) |
Full pitfalls + create_report flag reference in references/best_practices.md.
How to run — quick recipe
Activate the snakemake conda env first; create_report lives there:
source /home/ahunos/miniforge3/etc/profile.d/conda.sh
conda activate snakemake
Then call the bundled driver script (use scripts/ relative to the repo
root, or an absolute path to wherever the skill is checked out):
python scripts/build_igvreports.py \
--sites results/run/inputs/sites.hg38.bed \
--bam tumor.bam normal.bam \
--vcf calls.vcf \
--genome hg38 \
--output results/run/reports/cohort.hg38.html
The driver:
- Resolves the genome's CpG / gencode / rmsk paths from
databases_config.yaml(skipping any that don't exist for the chosen genome). - Validates the sites BED is headerless and that all rows have
start < end. - Calls
create_reportwith--flanking 300 --standalone. - Writes a logs/ entry capturing the full command, the flanking value, the per-region embedded data sizes, and the resolved track list — required by the project's analysis-script audit-trail expectations.
For multi-sample cohorts, use --samplesheet samplesheet.tsv instead of
--bam/--vcf. Samplesheet format: sample, bam_tumor, bam_normal, vcf, sites_bed.
The driver emits one HTML per sample plus a top-level index.html that lists
all samples with links. Pass --jobs N to build the per-sample HTMLs in
parallel via ThreadPoolExecutor (each create_report call is I/O-bound on
BAM slicing, so threading scales well; --jobs 6 for a 6-patient cohort
roughly 1/Nx wall-clock vs sequential). Default is --jobs 1. Layout matches
the ATLL viral-integration reference implementation:
results/<run>/
├── inputs/<sample>/sites.<genome>.bed
├── reports/<sample>.<genome>.html
├── reports/index.html
└── logs/run_<timestamp>.log
prep-track — fixing a non-bgzip track
If a GFF3/GTF/BED.gz is plain-gzip rather than bgzip, igv-reports fails silently or with an obscure error. Two modes:
In-place (with .bak.original_gzip backup) — replaces the original:
bash scripts/prep_track.sh /path/to/track.gff3.gz
Sibling file (non-destructive — original untouched) — write the
bgzipped+indexed track to a new path. Use this when other pipelines point
at the original .gff3.gz and you can't risk a brief window where the
file is replaced:
bash scripts/prep_track.sh /path/to/track.gff3.gz \
--out /path/to/track.bgz.gff3.gz
The script (both modes):
- Backs up the original to
<name>.bak.original_gzip(in-place mode only). gunzip -cs the file.- Sorts by
chrthen numericpos(sort -k1,1 -k4,4n). (Gencode delivers records interleaved by feature type at the same locus — tabix requires pos-sorted.) bgzips to target.tabix -p <gff|gtf|bed>s.- Verifies a sample tabix query returns rows.
Run from any env that has htslib's bgzip + tabix on PATH.
Diagnostic — file <name> for distinguishing the two formats:
- Plain gzip:
gzip compressed data, from Unix, original size <N> - bgzip:
Blocked GNU Zip Format (BGZF; gzipped file with extra field)
The extra field keyword is the bgzip giveaway.
When generating an answer.md / run.sh for the user
The driver script (build_igvreports.py) deliberately abstracts the
underlying create_report flags — it sets --standalone, --fasta, the
--flanking 300 default, and the YAML-resolved annotation tracks
internally so the user doesn't have to remember them. That abstraction is
good for ergonomics but bad for auditability: a reviewer reading the
answer.md later can't see what flags are actually being invoked without
opening the driver source.
To keep both: when you produce a runnable command for the user, also include a code block titled "Equivalent direct create_report invocation" that shows the fully-expanded command with all flags and resolved track paths inline. The user should see the wrapper command they're going to run AND the underlying command it expands to. Example:
## Run
```bash
python build_igvreports.py --genome mm10 --sites peaks.mm10.bed \\
--bam ./data/ip.bam ./data/input.bam \\
--output reports/peaks_qc.mm10.html
```
### Equivalent direct create_report invocation
```bash
create_report peaks.mm10.bed \\
--fasta /data1/greenbab/database/mm10/mm10.fa \\
--flanking 300 --standalone \\
--tracks ./data/ip.bam ./data/input.bam \\
/data1/greenbab/database/mm10/mm10_CpGIslands.bed \\
/data1/greenbab/database/mm10/annotations/gencode.vM25.annotation.gtf.gz \\
/data1/greenbab/database/RepeatMaskerDB/.../rmsk_all_repeats_mm10.bed.gz \\
--title "ChIP-seq peak QC (mm10) — IP vs Input" \\
--output reports/peaks_qc.mm10.html
```
This costs you ~10 lines and gives the reviewer a full audit trail. For cohort runs, show the expanded form for ONE representative sample only — the others differ only in BAM/VCF paths.
Post-render verification
scripts/verify_report.py parses a built HTML and confirms it actually
contains what its inputs declared. Six checks: html_exists,
html_min_size, region_count (tableJson rows == sites BED rows),
region_coords (each BED row finds a matching (chrom, start+1, end[, name])
in tableJson — BED is 0-based, the HTML stores 1-based start), region_sessions
(sessionDictionary has one entry per row), and tracks_present (every
name from --track-config or every basename from positional --tracks
appears in the decoded igv.js session's tracks[].name list).
python scripts/verify_report.py \
--html results/<run>/reports/sample.hg38.html \
--sites results/<run>/inputs/sites.hg38.bed \
--track-config results/<run>/inputs/tracks.json \
--min-size-mb 1.0 \
--out results/<run>/reports/sample.verify.tsv \
--fail-on-fail
Output is a TSV with columns check / status / observed / expected / details
(also printed to stdout). With --fail-on-fail, exits nonzero if any check
is FAIL — wire this into Snakemake / CI so the pipeline gates on render
quality, not just on create_report's exit code.
NOTE: --standalone replaces every track URL with an inlined data: URL
after slicing, so URL paths are unrecoverable from the embedded session.
The check matches on track NAMES (which --standalone preserves) — for
--track-config JSON pass meaningful names; positional --tracks mode
uses basenames.
Cohort-level verification (verify_cohort.py)
The per-sample verifier above confirms each HTML is internally consistent
but cannot tell whether sample-1's HTML accidentally embeds sample-2's BAM
(e.g., samplesheet typo, copy-paste, tumor/normal slot swap). For cohort
runs, scripts/verify_cohort.py adds five cross-sample checks:
| Check | What it asserts |
|---|---|
cohort_html_coverage (global) |
Each samplesheet row has exactly one HTML; flags missing + extras |
sample_tracks_match (per-sample) |
Each HTML's session contains every BAM/VCF basename declared in THAT row |
no_cross_sample_contamination (per-sample) |
Each HTML contains no basename that belongs to a DIFFERENT row's track columns (default tracks from databases_config.yaml are allow-listed) |
sample_id_embedded (per-sample) |
The sample column value appears in the HTML's <title> or filename |
index_consistency (global) |
index.html links exactly the samplesheet sample set; each target exists and is non-empty |
Auto-invoked by default at the end of build_igvreports.py --samplesheet
cohort runs. Disable with --no-verify; gate the pipeline with
--fail-on-fail. Standalone invocation:
python scripts/verify_cohort.py \
--samplesheet samplesheet.tsv \
--reports-dir results/<run>/reports/ \
--genome hg38 \
--out results/<run>/reports/cohort_verify.tsv \
--summary results/<run>/reports/cohort_verify.summary.md \
--fail-on-fail
The TSV adds a sample column on top of the per-sample verify schema, with
"*" for cohort-global rows. The markdown rollup (--summary) groups
PASS/FAIL counts by check + lists every failure inline.
Worked regression: tests/integration/cohort_verify/scenarios.sh builds a
3-sample cohort and asserts each of four corruption scenarios (missing
HTML, sample swap, index drift, truncated HTML) triggers the expected
check FAILs.
Content verification (verify_anchors.py) — opt-in, slow
verify_cohort.py proves the HTML says the right thing. It can NOT
confirm the embedded BAM slice contains the data it claims to. Two
failure modes slip past structural checks:
- Sample swap with matching basename — the cohort loop wired the wrong
BAM into
sample_1's build, but the swapped BAM'sPath.stemhappens to match whatsample_1's row declared (or two files in different dirs share a basename). Track name passes; slice content is wrong. - Silent empty slice — region rendered, but the slice has 0 reads
(failed
samtools index, source BAM corruption, coords outside coverage).
scripts/verify_anchors.py closes the gap by re-running samtools view -c
against both the source BAM (at generate time) and the embedded slice (at
verify time), then comparing counts. Two-mode workflow:
# 1. After the cohort renders cleanly, freeze the read counts as a regression fixture.
python scripts/verify_anchors.py generate \
--samplesheet samplesheet.tsv \
--sites sites.hg38.bed \
--out anchors.hg38.tsv
# 2. Re-verify any time after — works against a fresh build of the same inputs,
# or to audit an existing HTML for unexpected content drift.
python scripts/verify_anchors.py verify-cohort \
--samplesheet samplesheet.tsv \
--reports-dir results/<run>/reports/ \
--genome hg38 \
--anchors anchors.hg38.tsv \
--out results/<run>/reports/cohort_verify_anchors.tsv \
--fail-on-fail
Or chained into the build driver:
# Freeze anchors at build time:
python scripts/build_igvreports.py --samplesheet ... --anchors-mode generate \
--anchors anchors.hg38.tsv
# Verify a later build against frozen anchors:
python scripts/build_igvreports.py --samplesheet ... --anchors-mode verify \
--anchors anchors.hg38.tsv --fail-on-fail
Anchors TSV schema (#-prefixed header per lab BED convention):
#sample track_name track_type chrom start end expected tolerance min max notes
track_type is one of:
bam—expectedis the count fromsamtools view -c -F 1536against the source BAM at generate time, and the same count against the embedded BAM slice at verify time. Default when the column is absent (backwards compat — pre-2026-05-19 anchor files keep working).bedgraph—expectedis the number of data rows in the source bedGraph overlapping the region (CpG count for methylation data, peak count for ChIP coverage). Verify-time count comes from the wig/bedGraph slice embedded by igv-reports in the HTML — gzip-decoded in-memory, no samtools needed.
bedGraph tracks come from the samplesheet's extra_tracks column.
Anchors for them are generated automatically alongside BAM anchors when
you run verify_anchors.py generate against a samplesheet that includes
bedGraph entries (e.g. *.5mC.bedgraph, *.5hmC.bg, plain or .gz).
tolerance is a ratio (default 5%). min/max are absolute bounds that
override tolerance when set — useful for known-positive sites like
"this integration must have ≥20 reads" or "this promoter must have ≥10 CpGs".
samtools is resolved in this order: --samtools-sif PATH → $SAMTOOLS_SIF
→ /data1/greenbab/users/ahunos/apps/containers/samtools_v1.23.1.sif →
PATH samtools. SIF preferred per rules/apptainer_vs_conda.md.
bedGraph anchors don't require samtools.
Why this matters for methylation viewers: the silent-failure mode for
methylation reports is "region rendered, slice has 0 CpGs" — an empty
bedGraph slice because the source had no calls in that window, or
because the slice extraction silently dropped them. Pure structural
verification (verify_cohort.py) confirms the bedGraph track is in the
HTML but can't tell whether it's empty. The bedgraph-anchor mode closes
this gap.
Why opt-in and not default: the verify step shells out to samtools per (sample × region) and indexes each slice — ~1 s/anchor. For a 6-sample cohort × 50 regions that's ~5 min on top of the structural verify (which runs in seconds). Reach for this when sample swap or content regression is a real concern; the structural verifier is sufficient for routine builds.
Worked regression: tests/integration/anchor_verify/scenarios.sh builds a
2-sample cohort and asserts each of four content scenarios (tolerance
violation, min-bound violation, corrupted slice, missing anchor) triggers
the expected PASS / FAIL / SKIP outcome.
Triage interpretation (interpret_reports.py) — human-first reading surface
After verify-cohort produces a checks TSV, interpret_reports.py rolls it
up into a single cohort-wide interpretation.md: a summary table (sample ×
verdict counts) then per-sample sections with a per-region verdict.
python scripts/interpret_reports.py \
--checks results/<run>/reports/cohort_verify_anchors.tsv \
--out results/<run>/reports/interpretation.md
Per-region verdict, derived solely from the anchor results (no new thresholds):
PASS— every non-SKIP anchor for the region passedFAIL— every non-SKIP anchor failedREVIEW— mixed pass/fail across the region's tracksUNVERIFIED— every anchor was SKIP (region not rendered, or no tracks matched) — distinct from FAIL: nothing was checked
Regions sort FAIL-first (FAIL → REVIEW → UNVERIFIED → PASS) so the ones
needing attention sit on top of each sample section; FAIL/REVIEW/UNVERIFIED
get a per-track breakdown (observed vs expected + the reason), PASS collapses
to one line. If the checks TSV is missing or empty, a fallback file points
the reader at the HTML instead. It's a reporting tool — it always exits 0 on a
successful write; verify_anchors --fail-on-fail remains the CI gate.
Worked regression: tests/integration/interpret/scenarios.sh renders a
synthetic cohort and asserts the summary totals, FAIL-first ordering, the
malformed-input exit-2 path, and the missing-input fallback.
Output and workflow logging
Every run logs to logs/run_<YYYYMMDD_HHMMSS>.log next to the reports dir.
The log captures:
- Resolved track paths (per genome, after databases_config.yaml lookup).
- The exact
create_reportcommand. - The flanking value used (default 300 bp — this is the value that's baked into all the embedded data slices, so audit trails depend on it).
- Per-region embedded data sizes (extracted post-render so the user can see which regions inflated the HTML).
- Total HTML size.
This satisfies CLAUDE.md §"Logging and Audit Trail" — every run is reproducible from the log alone.
Track choice nuances
For gencode on hg38, the default points at
gencode.v47.annotation.gff3.gz (full annotation, bgzip + tabix). This
gives transcript models with exons / CDS / UTRs. The gene-level-only
companion (gencode.v47.genes.annotation.sorted.gff3.gz) renders only
solid gene boxes and is fine for high-zoom views, but the full annotation
is the right default for read-level inspection at integration / fusion /
SV junctions.
For mouse genomes, databases_config.yaml ships .gtf.gz paths instead.
GTFs work in igv-reports if bgzip + tabix-indexed; prep-track converts
plain-gzip GTFs the same way it does GFF3s.
For T2T-CHM13, only the FASTA + GTF + CGI are indexed in our DB; rmsk is absent and is auto-skipped by the driver. The variant table will load without rmsk; flag this in the run log.
Common-case examples
The examples/ directory has runnable templates:
single_sample.sh— one BAM + one VCF + a sites BED → one HTML.cohort_samplesheet.sh— TSV-driven multi-sample run.prep_track_demo.sh— convert a plain-gzip gencode to bgzip+tabix.methylation_ont/— ONT 5mC/5hmC viewer (BAM withcolorBy: basemod2- per-sample bedGraph at fixed y-axis 0..100). End-to-end worked example with pre-sliced data; recipe.md explains the slots.
These are reference implementations; copy and edit them for new runs rather than starting from scratch.
Tests
Three-layer suite under tests/, orchestrated by tests/run_all.sh:
| Layer | What it covers | Runtime | Needs |
|---|---|---|---|
unit (tests/unit/) |
parser layer of verify_report.py + verify_anchors.py — TSV loading, status decision, session-entry locator, balanced-brace JSON extractor, decode round-trip — all with synthetic inputs |
~1 s | pytest |
smoke (tests/smoke/) |
samtools_count / samtools_index / full slice-decode-and-count round-trip against the committed tests/fixtures/tiny_colo829.hg38.bam (457 KB, sliced from public ONT COLO829 release) |
~3 s | pytest + samtools (SIF or PATH) |
integration (tests/integration/) |
end-to-end: build a 2-/3-sample cohort, structural verify, anchor verify, run 4 corruption scenarios per verifier | ~7 min cold, ~30 s cached | full cohort BAMs (lab default OR IGV_REPORTS_TEST_BAM_{1,2,3} env override). SKIPs with exit 77 if neither is available |
bash tests/run_all.sh # all three layers
bash tests/run_all.sh --unit-only # ~1 s — fastest feedback loop
bash tests/run_all.sh --no-integration # ~12 s — works on any machine
bash tests/run_all.sh --integration-only
The fixture provenance + regeneration recipe live in tests/fixtures/README.md. Anchor counts the smoke layer expects (chr2=5, chr7=9) are the contract — any fixture regeneration that changes them must also update the smoke test constants.
ONT methylation viewers (specialized path)
For per-read 5mC/5hmC visualization the positional --tracks API does
not work — you need named tracks with colorBy: "basemod2" on the BAMs
and min: 0, max: 100 on the bedGraph tracks (cross-sample y-axis lock,
see rules/igv.md). Use the --track-config <json> passthrough:
# 1. Write a YAML spec listing samples (see tracks_spec.example.yaml).
# 2. Generate tracks.json with the right defaults baked in:
python scripts/generate_tracks_json.py \
--spec tracks_spec.yaml --run-dir results/<run>/ \
--out results/<run>/tracks.json
# 3. Build the report:
python scripts/build_igvreports.py \
--sites results/<run>/sites.hg38.bed \
--track-config results/<run>/tracks.json \
--genome hg38 --flanking 0 \
--type mutation --info-columns name \
--output results/<run>/methylation_report.hg38.html
Annotation shortcuts in the YAML
The default --tracks path (SV/variant viewers) auto-resolves CpG islands,
gencode, and RepeatMasker from databases_config.yaml when you pass
--genome hg38. On the --track-config (methylation) path you used to
have to hand-paste those paths into the YAML. As of the methylation-polish
round, you can use a default: shortcut for the same resolution:
genome: hg38
annotation:
# SHORTCUT — resolved from databases_config.yaml for the genome above.
# Gets an Okabe-Ito color + sensible displayMode you can override per entry.
- default: gencode
- default: cgi
- default: repmasker
- default: epdnew_coding # hg38 only
- default: epdnew_noncoding # hg38 only
# Mix with EXPLICIT entries when needed (e.g. a pre-sliced custom track):
- name: "My custom peak set"
url: peaks/promoter_slices.bed
format: bed
Valid default: keys: cgi, gencode, repmasker, epdnew_coding,
epdnew_noncoding. Mixing both forms is supported; order is preserved.
Override the canned name/color/displayMode per entry by adding the
field alongside default:. The shortcut needs a top-level genome: in
the spec and reads databases_config.yaml (resolution path same as the
SV viewer driver — points at $IGV_REPORTS_DB_CONFIG if set, else the
lab default; override per-run with --db-config PATH).
Key methylation-specific defaults:
--flanking 0(sites BED already encodes the window — promoter/gene span).--info-columns name(surface the BEDnamecolumn in the variant table).--type mutation(one-locus view per row; not split-screen).- bedGraph not bigwig — igv-reports cannot slice
.bwdirectly.
When --track-config is set the driver bypasses the auto-resolved
default annotation tracks (CGI / gencode / rmsk) and the --bam /
--vcf / --extra-track flags — the JSON is the source of truth.
Build annotation slices into the JSON instead.
--apptainer is auto-detected: the driver flips to the dedicated
igv-reports 1.16.0 SIF (/data1/greenbab/users/ahunos/apps/containers/igv-reports_1.16.0.sif,
83 MB, pulled from Galaxy depot) when SLURM_JOB_ID is in the
environment — i.e. running on a compute node where the NFS conda
cold-start tax matters (rules/apptainer_vs_conda.md). On the login
node it stays on the conda env. Override with --apptainer /
--no-apptainer; the decision lands in the run log.
Full recipe and rationale: references/methylation_ont.md. Worked
example with real data: examples/methylation_ont/.
Exporting HTML and PNG side-by-side (--also-png)
The HTML report is the deep-dive view; sometimes you also need static
PNGs you can email, drop in a Slack channel, or paste into slides. The
driver's --also-png flag invokes the sister igver skill against the
same sites BED and same track list that drove create_report, so
both artifacts cover identical regions with matching content.
python scripts/build_igvreports.py \
--samplesheet samplesheet.tsv \
--genome hg38 \
--output-dir results/run/reports/ \
--jobs 6 \
--also-png \
--png-dpi 600 --png-display-mode collapse
Output layout per sample:
results/run/reports/
├── <sample>.hg38.html # interactive
├── png_<sample>.hg38/
│ ├── igver_regions.bed # flanked BED with UIDs (igver -r)
│ ├── igver_input.txt # track paths, one per line (igver -i)
│ ├── manifest.tsv # bridge: BED row ↔ PNG ↔ HTML row
│ └── png/
│ ├── chr1-100-500.alpha.png # one PNG per region
│ └── chr2-0-700.beta.png
└── index.html
How consistency is guaranteed — five levers
- Single sites BED with
--flankingbaked in. The driver writesigver_regions.bedwithstart − flankingandend + flankingalready applied (clamped to 0 on the low side); igver sees the same coordinates create_report's igv.js viewer slices to. - Single resolved track list. On the default (positional) path the
exact
[BAMs, VCF, extras, defaults]list passed tocreate_reportis also written toigver_input.txt. On the--track-configpath the local-pathurl:entries from the JSON are extracted (http(s) URLs are skipped — igver can't consume them). - Matched display mode. Default is
--png-display-mode collapseto line up with the HTML'sBAM_DEFAULTS displayMode: COLLAPSED. Override toexpandfor per-read SV inspection on both artifacts. - UID-based filenames. The BED's
namecolumn (auto-assignedregion_<idx>when missing) becomes both the HTML table label (via--info-columns name) and the PNG filename suffix (<chr-start-end>.<uid>.png). A user finds the same region in either artifact by the same string. manifest.tsvaudit trail. Per-sample TSV with columns:bed_row_idx, uid, chrom, start_orig, end_orig, start_flanked, end_flanked, region, png_path, html_path, html_table_row. One row per region in BED order.verify_cohort.pyreads this to run three PNG-side checks (count matches, exist + non-empty, html-row contiguity).
Resolution of the igver invocation
Order, first match wins:
--igver-cmd '...'(split on whitespace — supportsapptainer exec ... igver).$IGVER_CMDenv var (same shape).igveron PATH.- Lab apptainer SIF at
/data1/greenbab/software/images/igver_latest.sif(only if reachable).
If none resolve, the build exits before invoking create_report so you don't pay the HTML cost before finding out PNGs are unavailable.
Methylation caveat (bigwig vs bedGraph)
The HTML methylation path uses bedGraph tracks (igv.js consumes
those directly); igver's per-read methylation view uses BAMs with
--color-by BASE_MODIFICATION, and igver's cross-sample comparison view
uses bigwig tracks. Content can be made identical only if both
formats trace back to the same modkit pileup output (modkit bedmethyl tobigwig of the same bedGraph). The driver's --also-png passes the
JSON's url: entries through verbatim, so if your YAML lists bedGraphs
they'll go to igver as-is — igver will render them but the result may
look different from the HTML's color-coded per-read view. For
publication-quality methylation PNGs, supply a parallel tracks.json
that lists bigwigs and run igver separately (see rules/igv.md).
For SV/variant viewers this caveat doesn't apply — both render the identical BAMs and the result is content-equivalent.
Cross-artifact verification
The driver runs an inline existence check right after igver returns:
walks each expected PNG path (<chr>-<start>-<end>.<uid>.<ext> derived
from the manifest) and fails the build with an actionable message if
any are missing or zero-byte. This catches igver's documented
silent-exit-0 failure mode (egg-link install without the IGV Java
binary; see rules/igv.md) — proc.returncode != 0 alone misses it.
In addition, verify_cohort.py then runs three checks per sample:
| Check | Catches |
|---|---|
png_count_matches_bed |
partial igver run (SIGKILL mid-batch), stale manifest from a previous build, filename collisions |
pngs_exist_and_nonempty |
empty IGV screenshots (< 10 KB threshold; useful screenshots are typically ≥ 50 KB) |
png_html_row_alignment |
manifest rows referencing a different HTML, html_table_row not contiguous 1..N |
--png-min-size-kb 5.0 lowers the threshold if you have legitimate
no-data regions where igver produces a near-empty PNG.
Credits
The HTML rendering engine here is not this skill — it is the upstream
igv-reports package
(create_report) maintained by the IGV team at the Broad Institute (MIT,
© 2018-2019 The Broad Institute and The Regents of the University of
California). The rendered HTML embeds igv.js from the same team. This
skill is a driver + verifier layer on top.
When producing outputs that will end up in a publication, cite the igv.js paper: Robinson, J. T., Thorvaldsdóttir, H., Turner, D., & Mesirov, J. P. (2023). igv.js: an embeddable JavaScript implementation of the Integrative Genomics Viewer (IGV). Bioinformatics, 39(1), btac830. doi:10.1093/bioinformatics/btac830.
Full attribution, the upstream MIT license verbatim, the reuse-vs-add
table, and BibTeX entries live in CREDITS.md.
See also
references/best_practices.md— full create_report flag reference, format gotchas, performance notes. Read this if a run fails in a way not listed in the Pitfalls table above.references/databases_config_paths.md— per-genome track availability matrix and exact YAML keys. Read this when adding a new genome or diagnosing a missing-track warning.references/methylation_ont.md— ONT 5mC/5hmC cheat-sheet (colorBy, min:0/max:100, flanking=0, bedGraph vs bigwig, EPDnew lookup).scripts/build_igvreports.py— the driver. Reads--samplesheetor--bam/--vcfdirect-args, resolves tracks, validates the sites BED, writes the HTMLs and the run log. Supports--track-config <json>passthrough for fully-styled track sets.scripts/generate_tracks_json.py— YAML spec → tracks.json with ONT-methylation defaults baked in (colorBy=basemod2, min:0/max:100, group-paired Okabe-Ito colors).scripts/verify_report.py— post-render structural verifier; parses the HTML's embedded tableJson + sessionDictionary, confirms region count / coordinates / track names match the inputs. Emits a verify.tsv and gates on--fail-on-fail.scripts/verify_cohort.py— cohort-level verifier; layered on top of verify_report's per-sample checks, adds cross-sample contamination scanning + index.html / sample-id consistency. Auto-invoked at the end ofbuild_igvreports.py --samplesheet; standalone-runnable too.scripts/verify_anchors.py— content verifier; samtools-counts the embedded BAM slices and compares to anchors frozen from the source BAMs at build time. Catches sample swaps that share basenames and silent empty slices. Opt-in via--anchors-mode generate|verifyon the build driver; slow (~1 s/anchor). See SKILL.md content-verification section.scripts/prep_track.sh— gunzip → sort → bgzip → tabix utility.igv-screenshotsskill — the static PNG/PDF/SVG counterpart based on igver. Use it instead of this one when the deliverable is a publication-quality figure rather than a clickable viewer.- Reference implementation:
/data1/greenbab/projects/ont/Project_17424/results/20260503_hg38plusHTLV1EBV_cohort_integration_igvreports/— 6-patient ATLL cohort viral-integration HTMLs + DNMT3A sanity check; this skill was extracted from that work.