CNV across a population (1000 Genomes)
The tutorials target the JBrowse v5 beta, and the v4.3.0 release
on the download page lacks some of what they show. To install the
beta of JBrowse Web, run npm install -g @jbrowse/cli@next, then
jbrowse create jbrowse2 --branch v5.0.0-beta.8. Desktop beta builds are coming soon.
Copy number varies from person to person, and we show the whole 1000 Genomes panel at once: one heatmap row per individual, colored by how far that person strays from the diploid baseline of 2. JBrowse renders that live from per-sample BigWigs, and past a few hundred samples the per-file requests dominate, so the second half packs the values into one Zarr store.
Prerequisites
- a JBrowse instance to paste a track into (see the web quickstart, or the desktop quickstart: every file here is a URL, so Desktop needs nothing hosted)
node24 or newer, to build a Zarr store; the converter is one downloadable file that pulls two npm packages- QuicK-mer2 and a 30x alignment, to add samples of your own
Where the data comes from
QuicK-mer2 copy-number estimates over the 30x 1000 Genomes panel, from the Kidd lab at the University of Michigan (Shen and Kidd 2020).
- the sample list across 26 populations, from the lab's UCSC track hub: https://raw.githubusercontent.com/KiddLab/kmer_1KG/master/kmer-1kg.trackDb.txt
- the per-sample bigWigs, one individual's copy number in 1 kb bins, re-hosted unmodified because the lab's own download share is offline. One file per sample under its population, so HG00551 and HG00553 are https://jbrowse.org/genomes/GRCh38/1000g/kidd_lab_cnv/PUR/HG00551.qm2.CN.1k.bw and https://jbrowse.org/genomes/GRCh38/1000g/kidd_lab_cnv/PUR/HG00553.qm2.CN.1k.bw
- the same values packed into one Zarr store for the
latency comparison. This is a directory of
chunks, so it is the
urian adapter takes, not something to open in a browser: https://jbrowse.org/demos/1000g/qm2_cn_1kb.zarr
The QuicK-mer2 estimates
The QuicK-mer2 estimates come from the Kidd lab's KiddLab/kmer_1KG track hub; this page reads the lab's raw per-sample bigWigs. QuicK-mer2 counts only k-mers that occur exactly once in the reference, so its estimates are per paralog.
Load the panel as one track
The whole panel goes in as one track on hg38, so the display, clustering and color settings are declared once:
{
"type": "MultiQuantitativeTrack",
"trackId": "pur_copynumber_1000g",
"name": "PUR copy number (1000 Genomes)",
"assemblyNames": ["hg38"],
"adapter": {
"type": "MultiWiggleAdapter",
"bigWigs": [
"https://jbrowse.org/genomes/GRCh38/1000g/kidd_lab_cnv/PUR/HG00551.qm2.CN.1k.bw",
"https://jbrowse.org/genomes/GRCh38/1000g/kidd_lab_cnv/PUR/HG00553.qm2.CN.1k.bw"
]
},
"displayDefaults": {
"defaultRendering": "density",
"origin": 2,
"scales": { "y": { "domainMin": 0, "domainMax": 4 } },
"color": {
"field": "score",
"scale": "threshold",
"range": ["#2166ac", "#b2182b"]
}
}
}
jbrowse add-track-json '{
"type": "MultiQuantitativeTrack",
"trackId": "pur_copynumber_1000g",
"name": "PUR copy number (1000 Genomes)",
"assemblyNames": ["hg38"],
"adapter": {
"type": "MultiWiggleAdapter",
"bigWigs": [
"https://jbrowse.org/genomes/GRCh38/1000g/kidd_lab_cnv/PUR/HG00551.qm2.CN.1k.bw",
"https://jbrowse.org/genomes/GRCh38/1000g/kidd_lab_cnv/PUR/HG00553.qm2.CN.1k.bw"
]
},
"displayDefaults": {
"defaultRendering": "density",
"origin": 2,
"scales": { "y": { "domainMin": 0, "domainMax": 4 } },
"color": {
"field": "score",
"scale": "threshold",
"range": ["#2166ac", "#b2182b"]
}
}
}'
In JBrowse Desktop, or in any running JBrowse Web session, open a view on this track’s assembly, then File → Open track..., choose Add track from pasted JSON, and paste:
{
"type": "MultiQuantitativeTrack",
"trackId": "pur_copynumber_1000g",
"name": "PUR copy number (1000 Genomes)",
"assemblyNames": ["hg38"],
"adapter": {
"type": "MultiWiggleAdapter",
"bigWigs": [
"https://jbrowse.org/genomes/GRCh38/1000g/kidd_lab_cnv/PUR/HG00551.qm2.CN.1k.bw",
"https://jbrowse.org/genomes/GRCh38/1000g/kidd_lab_cnv/PUR/HG00553.qm2.CN.1k.bw"
]
},
"displayDefaults": {
"defaultRendering": "density",
"origin": 2,
"scales": { "y": { "domainMin": 0, "domainMax": 4 } },
"color": {
"field": "score",
"scale": "threshold",
"range": ["#2166ac", "#b2182b"]
}
}
}
The bigWigs shorthand takes a
plain list of absolute URLs and names each subtrack from its filename. Four
display settings turn that into a copy-number heatmap:
defaultRenderingdensitygives each sample one strip of color.origin2puts white at the diploid baseline, andcolorcuts there, painting gains in therange's second colour and losses in its first.scales.y.domainMinandscales.y.domainMaxpin the scale, so two copies are the same color in every window. Keep the bounds symmetric around the origin. The ramp divides both sides by the longer one, so 0 to 4 lets both extremes saturate, and gains past 4 clamp.
Rows are in file order until Clustering → Cluster rows by score... in the track menu brings similar samples together.
The same track is in config_demo, so
the panel opens on a copy-number-polymorphic window of chr3
with these settings already applied.
Read the copy-number heatmap
Six individuals across the range each appear below as a profile:
Two paralogous blocks carry the variation. The right-hand one spans CCL3L1 and CCL4L1, chemokine genes that exist in a variable number of tandem copies. The left-hand one is a TBC1D3 repeat. Between them, an individual in this panel carries anywhere from zero to ten copies.
The same window in the 1000 Genomes SV map
The 1000 Genomes phase 3 integrated SV map covers this window with one CNV
record, at chr17:36,108,706-36,155,499 with three symbolic alleles (<CN2>,
<CN3>, <CN4>). It ends about 35 kb before the block where depth resolves the
widest range, and between 36,155,499 and 36,461,232 the GRCh38 release has no
copy-number record.
A VCF record is one interval with fixed breakpoints and a few symbolic alleles; nested multiallelic copy number does not fit that shape. Depth carries no genotype, allele frequency or phasing either. Where the variation fits the representation, they agree:
Scaling past one population
The track above stops at 104 individuals.
measure_signal_latency.ts
counts what filling this window costs at panel scale, all 2504 BigWigs against a
store holding the same samples, using the readers the browser uses. It takes the
same name/group/url TSV as the converter, which the
build script writes:
curl -fO https://raw.githubusercontent.com/GMOD/jbrowse-components/main/scripts/measure_signal_latency.ts
npm install @gmod/bbi generic-filehandle2
node measure_signal_latency.ts --samples 1000g_cnv_build/samples.tsv \
--region chr17:36,080,000-36,270,000 \
--zarr https://jbrowse.org/demos/1000g/qm2_cn_1kb.zarr
Against the hosted files, at a median range request of 25 ms:
| chr17:36,080,000-36,270,000, 2504 samples | 2504 BigWigs | Zarr store |
|---|---|---|
| requests | 15,048 | 3 |
| bytes | 48.39 MB | 0.22 MB |
| wall clock | 24.5 s | 0.2 s |
Each BigWig takes six reads; the Zarr store takes two metadata reads plus one chunk of 2504 samples by 256 bins. Every BigWig needs a few dependent reads to find where a region's values live, so the cost is a round trip times the number of files. The Zarr store is one array of samples by bins, so a single read covers every sample and the window loads in a couple of requests.
Zarr v3 is that format and needs no tile server:
zarrita.js reads chunks off static
hosting.
jbrowse-plugin-zarr adds a
MultiWiggleZarrAdapter, and the display, clustering and settings above are
unchanged.
The plugin is in beta and not in the plugin store yet, but its built bundle is hosted (see configuring plugins):
{
"plugins": [
{
"name": "Zarr",
"url": "https://jbrowse.org/demos/zarr/jbrowse-plugin-zarr.umd.production.min.js"
}
],
"tracks": [
{
"type": "MultiQuantitativeTrack",
"trackId": "cnv_1000g_zarr",
"name": "1000 Genomes copy number, 2504 individuals",
"assemblyNames": ["hg38"],
"adapter": {
"type": "MultiWiggleZarrAdapter",
"uri": "qm2_cn_1kb.zarr"
},
"displayDefaults": {
"defaultRendering": "density",
"origin": 2,
"scales": { "y": { "domainMin": 0, "domainMax": 4 } },
"color": {
"field": "score",
"scale": "threshold",
"range": ["#2166ac", "#b2182b"]
}
}
}
]
}
The adapter config gives only the store's location; the sample list, bin size
and resolution levels are attributes of the store. A relative uri resolves
against the config that holds it.
Two requests are metadata, once per store; the rest are chunks, each carrying every sample across a range of bins, so a view's cost follows the width of the window.
A nested deletion
The store also covers chr3:162.5-163.2 Mb, where a 22 kb deletion sits inside a 114 kb one. Clustering the panel there sorts individuals by which of the two they carry, and the long-read assembly calls of the Human Genome Structural Variation Consortium (Logsdon et al. 2025) place both:
Build the store
build_signal_zarr.ts
turns a list of BigWigs into one store. It takes a TSV of name and url, with
an optional group column between them (here the population, which labels and
groups the rows). It imports two npm packages and nothing else:
curl -fO https://raw.githubusercontent.com/GMOD/jbrowse-components/main/scripts/build_signal_zarr.ts
npm install @gmod/bbi generic-filehandle2
node build_signal_zarr.ts \
--samples samples.tsv \
--out qm2_cn_1kb.zarr \
--region chr17:35000000-37500000 \
--region chr4:68000000-69000000 \
--region chr1:25150000-25450000 \
--region chr3:162500000-163200000 \
--levels 1000,10000
The command above is the one behind the hosted store, run over all 2504 samples and the windows shown in its figures; the store lands at 2.4 MB.
--levels sets the resolution pyramid. Each entry is one samples-by-bins array,
with coarser ones averaged from the finest. The adapter reads the coarsest level
whose bins are no wider than a screen pixel, so a whole-chromosome view costs
the same couple of requests. Give it your input's bin size first, then steps of
roughly 3x: 10000,30000,100000 rather than 10000,100000, since a 10x gap
leaves a view fetching 10x the bins it can draw.
Every level above the finest stores the minimum and maximum of the bins it
averages alongside the mean.
summaryScoreMode
picks which a view draws, so an amplification narrower than a bin is visible
under max and averaged away under avg.
The finest level is held whole in memory while the rest derive from it. Without
the --region flags this panel is a few GB at 10 kb bins and about 31 GB at the
BigWigs' own 1 kb, so a whole-genome pyramid starts coarse. The converter prints
that level's size before allocating and refuses when it will not fit.
The output is a folder of files. Copy it to any static host with CORS enabled and point a track at it. To write a store from something other than BigWigs, the plugin's store format gives the layout.
Your own samples
Run QuicK-mer2 over your aligned reads.
The lab's
tutorial takes
one 30x 1000 Genomes CRAM through count and est, and for GRCh38 the k-mer
index is
prebuilt.
The tutorial reports 67 GB of reference files, roughly 50 GB of RAM, and about
25 minutes on six threads per sample.
est writes copy number in 1 kb windows, and its four columns are bedGraph once
the decoy and EBV contigs are dropped:
grep -v decoy sample.qm2.CN.1k.bed | grep -v chrEBV >sample.bedgraph
samtools faidx GRCh38_BSM.fa
cut -f1,2 GRCh38_BSM.fa.fai >GRCh38_BSM.chrom.sizes
bedGraphToBigWig sample.bedgraph GRCh38_BSM.chrom.sizes sample.qm2.CN.1k.bw
Host it, then add its URL to bigWigs, or a name and url row to
samples.tsv for the Zarr build. Running an individual the panel already covers
gives the lab's estimate as a check.
Reproduce it end to end
build_1000g_cnv_zarr.sh
derives the full 2504-sample list from the Kidd lab trackDb and runs the
converter over it, fetching the converter and its two packages itself:
curl -fO https://raw.githubusercontent.com/GMOD/jbrowse-components/main/scripts/build_1000g_cnv_zarr.sh
bash build_1000g_cnv_zarr.sh # the tutorial's windows, 1kb base, into ./1000g_cnv_build
bash build_1000g_cnv_zarr.sh --whole-genome # every main contig, 10kb base and five levels
See also
- User guide: Quantitative track
- CNV cohort (TCGA)
- Single-cell RNA pseudobulk
- Structural variants (1000 Genomes)
- Structural variants (Dog10K)
- Clustering rows
References
- Shen & Kidd (2020). Rapid, Paralog-Sensitive CNV Analysis of 2457 Human Genomes Using QuicK-mer2, the citation for the copy-number data used throughout this page
- KiddLab/kmer_1KG, the Kidd lab track hub these files come from, and KiddLab/QuicK-mer2, the caller that produced them
- The QuicK-mer2 tutorial, one sample from CRAM to copy number, with the lab's own output to check against
- 1000 Genomes phase 3 integrated SV map
- Logsdon et al. (2025). Complex genetic variation in nearly complete human genomes, the HGSVC3 structural variant calls in the nested deletion figure
- Zarr v3 specification
Feedback on this tutorial is welcome: contact us.