# Bio Workflows Cnv Pipeline

> Orchestrates the copy-number pipeline from BAM to segmented, integer-called, annotated CNVs, forking on germline-vs-somatic - CNVkit (somatic exome/panel: coverage -> assay-matched reference/PoN -> fix -> segment -> purity/ploidy-aware call), GATK gCNV (germline rare-CNV cohort), and allele-specific callers (ASCAT/FACETS/PURPLE) for purity/ploidy. Use when committing the build + target/access BED + PoN once (assay-matched), building the reference from normals BEFORE segmenting, fitting purity/ploidy BEFORE integer calls in tumors, centering on the true (non-diploid) mode before GISTIC2 recurrence, or routing cfDNA to ichorCNA. Hands mechanism to the copy-number component skills; not a re-teach of any single step.

- Skill: `fridrichmethod/bio-workflows-cnv-pipeline` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add fridrichmethod/bio-workflows-cnv-pipeline`
- Raw SKILL.md: https://api.skillmd.com/api/skills/fridrichmethod/bio-workflows-cnv-pipeline/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: FridrichMethod (https://skillmd.com/u/fridrichmethod)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/fridrichmethod/bio-workflows-cnv-pipeline

---


## Version Compatibility

Reference examples tested with: CNVkit 0.9.10+, GATK 4.5+ (gCNV / ModelSegments), ASCAT/FACETS/PURPLE (allele-specific), GISTIC2 2.0.23 (recurrent), ichorCNA 0.5+ (cfDNA)

Before using code patterns, verify installed versions match. If versions differ:
- CLI: `<tool> --version` then `<tool> --help` to confirm flags

If code throws ImportError, AttributeError, or TypeError, introspect the installed
package and adapt the example to match the actual API rather than retrying.

Note: GATK gCNV runs COHORT mode (model all normals, no prior) vs CASE mode (score a singlet against a prior model) — order is DetermineGermlineContigPloidy -> GermlineCNVCaller -> PostprocessGermlineCNVCalls. Sequenza's `copynumber` dependency was REMOVED from Bioconductor 3.18+ (needs a fork). GATK gCNV/ModelSegments have no single method paper — cite the GATK docs. Confirm in-tool before quoting.

# CNV Pipeline

**"Detect copy number variants from my sequencing data"** -> Fork germline-vs-somatic, commit the build + target/access BED + assay-matched reference, bias-correct against normals, segment, and integer-call off a fitted purity/ploidy.
- CLI: cnvkit target/access/antitarget -> coverage -> reference(normals) -> fix -> segment -> call  (OR GATK gCNV for germline cohorts)

This is a workflow skill: it owns the chaining decisions and hand-offs, not the internals of any one step. Every step below cross-references the component skill that teaches its mechanism.

## The governing principle

A CNV callset is decided at three seams, not inside the caller.

1. **The reference build + target/access BED + reference/PoN is one made-once commitment inherited by everything downstream.** The capture-kit target BED, the `access` mappability BED, and the annotation refFlat must all be the SAME build as the BAMs (a GRCh37 BED against GRCh38 BAMs silently produces zero-coverage bins). And the PoN is the identity of the assay: it MUST be built from the same capture kit, chemistry, and (ideally) batch as the cases. A PoN from a different kit imports the wrong bias profile and fabricates CNVs at capture boundaries.
2. **The reference/PoN is built BEFORE anything is segmented, and it absorbs shared signal.** `fix` needs the reference to bias-correct; segmenting raw log2 without normalizing segments the capture bias, not biology. Beware: tangent normalization / a pooled PoN ABSORBS any CNV shared across the normals — a real common CNV becomes invisible; GC correction alone does NOT remove the replication-timing wave.
3. **The diploid baseline is a commitment, not a given — fit purity/ploidy BEFORE integer calls in tumors.** In WGD/hyper-aneuploid tumors the data mode is not diploid; naive centering inverts every call. `cnvkit.py call` with wrong `--purity`/`--ploidy` (or defaults on an impure/WGD tumor) assigns integer copy numbers off the wrong baseline. Fit purity/ploidy (ASCAT/FACETS/PURPLE) first; below ~40% purity calls degrade and below ~20% no bulk caller works.

## Pipeline map

```
BAM (tumor +/- matched normal, OR germline cohort)
  | fork: germline rare-CNV cohort? --> GATK gCNV  (copy-number/gatk-cnv)
  v  else somatic exome/panel:
  | [1] target/access/antitarget BED (build-matched)   (copy-number/cnvkit-analysis)
  v
  | [2] per-sample coverage
  v
  | [3] build reference/PoN from NORMALS first (assay-matched)
  v     ^-- tangent absorbs CNV shared across the PoN
  | [4] fix (bias-correct) -> segment -> call
  v     ^-- purity/ploidy fitted BEFORE integer call (copy-number/allele-specific-copy-number)
  | [5] visualize + gene-level annotate            (copy-number/cnv-visualization, cnv-annotation)
  v
  | [6] (cohort) center on true mode -> GISTIC2 recurrence  (copy-number/recurrent-cnv)
  v
Segmented, integer-called, annotated CNVs
```

## Made-once commitments

| Commitment | Consequence inherited downstream |
|------------|----------------------------------|
| Build + target/access/refFlat BED | Any build mismatch -> zero-coverage bins / shifted annotations |
| Reference / PoN (assay-matched) | A different-kit PoN imports the wrong bias -> false CNVs at capture boundaries; tangent absorbs CNVs shared across the PoN |
| Purity/ploidy (fitted, not default) | Wrong baseline shifts every integer call; WGD inverts calls |
| Diploid centering (cohort) | Uncentered WGD segments into GISTIC2 invert recurrence |

## The canonical order and why

1. **Prepare target/access/antitarget BEDs** on the committed build.
2. **Per-sample coverage** (target + antitarget/off-target bins).
3. **Build the reference/PoN from normals FIRST** — order-trap: `fix` needs the reference; segmenting raw log2 segments capture bias.
4. **fix -> segment -> call**, with purity/ploidy fitted BEFORE the integer call — order-trap: default purity/ploidy on an impure/WGD tumor mis-assigns every integer CN.
5. **Visualize + gene-level annotate** (positive control: known CNVs recovered if present).
6. **(Cohort) center on the true mode, THEN GISTIC2** — order-trap: uncentered WGD inverts recurrence; and do NOT concatenate per-sample `.cns` and call recurrence naively — feed a diploid-centered `.seg` matrix to GISTIC2.

## Choosing the caller (the germline-vs-somatic fork)

Pipeline-level selection only; mechanism lives in the component skills.

| Situation | Lean toward | Hand off to |
|-----------|-------------|-------------|
| Exome/targeted panel, somatic (tumor) CNV | CNVkit (target + antitarget bins) | copy-number/cnvkit-analysis |
| Germline rare-CNV from a cohort of exomes | GATK gCNV (DetermineGermlineContigPloidy -> GermlineCNVCaller -> PostprocessGermlineCNVCalls) | copy-number/gatk-cnv |
| WGS, need allele-specific CN + purity/ploidy | ASCAT / Sequenza / FACETS / PURPLE | copy-number/allele-specific-copy-number |
| Relative copy-ratio segments (research) | GATK ModelSegments/CallCopyRatioSegments | copy-number/copy-ratio-segmentation |
| Cohort recurrent/driver CNV | GISTIC2 (diploid-centered input) | copy-number/recurrent-cnv |
| cfDNA / low-pass tumor fraction | ichorCNA (NOT CNVkit) | workflows/liquid-biopsy-pipeline |

## Primary path: CNVkit (somatic exome/panel)

```bash
# 1. Targets on the committed build (annotate with refFlat, split for WES)
cnvkit.py target capture_targets.bed --annotate refFlat.txt --split -o targets.bed
cnvkit.py access genome.fa -o access.bed
cnvkit.py antitarget targets.bed --access access.bed -o antitargets.bed

# 2-3. Coverage per sample, then build the reference from NORMALS (assay-matched) BEFORE any fix
cnvkit.py coverage $bam targets.bed -o cov/${s}.targetcoverage.cnn
cnvkit.py coverage $bam antitargets.bed -o cov/${s}.antitargetcoverage.cnn
cnvkit.py reference cov/normal*.{,anti}targetcoverage.cnn --fasta genome.fa -o reference.cnn

# 4. fix (bias-correct) -> segment -> call. Fit purity/ploidy first (ASCAT/FACETS) for tumors:
cnvkit.py fix cov/${s}.targetcoverage.cnn cov/${s}.antitargetcoverage.cnn reference.cnn -o ${s}.cnr
cnvkit.py segment ${s}.cnr -o ${s}.cns
cnvkit.py call ${s}.cns --purity 0.6 --ploidy 2 -o ${s}.call.cns   # purity/ploidy from an allele-specific fit
```

A runnable somatic CNVkit script (manual target -> coverage -> reference -> fix -> segment -> call path) is in this skill's examples/; germline cohorts use GATK gCNV (copy-number/gatk-cnv), not CNVkit.

## QC checkpoints between steps

| After | Gate | Interpretation |
|-------|------|----------------|
| Coverage | Uniform depth across targets; flag low-depth targets | Capture dropout -> phantom deletions |
| fix | `.cnr` log2 spread / MAD within tolerance | High bin noise is the #1 CNV false-positive lever (over-segmentation) |
| segment/call | Sane segment count; integer CN consistent with known events; purity plausible | Over-segmentation = noisy reference / low purity; wrong purity shifts every call |
| annotate | Known CNVs recovered (positive control) | Build/BED mismatch surfaces as missing known events |
| recurrent | GISTIC2 input diploid-centered | Uncentered WGD inverts recurrence |

## Common Errors

| Symptom | Cause | Fix |
|---------|-------|-----|
| Zero-coverage bins / shifted annotations | Target BED build != BAM build | Pin one build across BED, access, refFlat, BAMs |
| False CNVs at capture boundaries | PoN from a different kit/chemistry | Build the PoN from the same kit/chemistry/batch |
| A real common CNV vanishes | Tangent/pooled PoN absorbed the shared signal | Use a PoN that does not carry the event, or germline-CNV logic |
| Every integer call shifted / inverted | Default purity/ploidy on an impure/WGD tumor | Fit purity/ploidy (ASCAT/FACETS/PURPLE) BEFORE `call` |
| Inverted recurrence in the cohort | Uncentered WGD segments into GISTIC2 | Center on the true (non-diploid) mode first |
| Cohort recurrence looks wrong | Concatenated per-sample `.cns` naively | Feed a diploid-centered `.seg` matrix to GISTIC2 (copy-number/recurrent-cnv) |
| Sequenza install fails | `copynumber` removed from Bioconductor 3.18+ | Use a maintained fork (ShixiangWang/igordot) |

## Related Skills

- copy-number/cnvkit-analysis - CNVkit coverage/fix/segment/call details
- copy-number/gatk-cnv - GATK gCNV (germline cohort) and ModelSegments
- copy-number/copy-ratio-segmentation - segmentation algorithm and depth-bias correction
- copy-number/allele-specific-copy-number - purity/ploidy and integer allele-specific CN (ASCAT/FACETS/PURPLE)
- copy-number/cnv-visualization - scatter/diagram/heatmap plotting
- copy-number/cnv-annotation - gene-level CNV annotation
- copy-number/recurrent-cnv - cohort recurrent/driver CNV with GISTIC2
- copy-number/hrd-scoring - HRD scar score for PARP eligibility
- workflows/liquid-biopsy-pipeline - cfDNA tumor-fraction CNV (ichorCNA)
- workflows/somatic-variant-pipeline - consumes purity/ploidy for VAF-to-CCF

## References

- Steele CD, Abbasi A, Islam SMA, et al (2022) Signatures of copy number alterations in human cancer. *Nature* 606:984-991. DOI 10.1038/s41586-022-04738-6. (copy-number signatures need ABSOLUTE CN.)
- Telli ML, Timms KM, Reid J, et al (2016) Homologous Recombination Deficiency (HRD) score predicts response to platinum-containing neoadjuvant chemotherapy. *Clinical Cancer Research* 22:3764-3773. DOI 10.1158/1078-0432.CCR-15-2477. (GIS >= 42 HRD threshold.)
- GATK gCNV / ModelSegments have no single method paper — cite the GATK/Broad documentation.

