v2 methylation control QC (WIP): simurg methyl qc|convert-manifest (MET-001..018)
Methylation CSV manifest reader (MET-002/003), raw M/U assembly with bead masking (MET-005), pOOBAH detection p-values, noob background correction, linear/nonlinear dye bias, beta and M-values (MET-010..012), the 21 methylation control metrics (MET-006, bs-conv red-bg set option), per-sample ctrl/cgs tables, summary tsv/xlsx/html with the detection histogram and a beta PCA, pcs.tsv.gz, controls_raw/controls_qc.tsv; methylation datasets load in qc report (MET-014); qc controls --array-type methylation; minfi-style Sentrix_ID/Sentrix_Position sheet keys. Vendor EPIC manifests carry a 5th field on some control lines: >= 4 fields accepted. Runs on the public minfiDataEPIC set (3 x NA12878).
This commit is contained in:
1 parent
d235dc5419
commit
3eb51639cc
13 files changed
+1292
-21
No files matched your search
@@ -36,6 +36,7 @@ from simurg.qc.controls import (
|
||||
ControlBead,
|
||||
ControlQC,
|
||||
beads_from_definitions,
|
||||
canonical_id,
|
||||
compare_definitions,
|
||||
compute_control_metrics,
|
||||
control_intensities,
|
||||
@@ -123,9 +124,10 @@ def control_sample(
|
||||
beads: Sequence[ControlBead],
|
||||
bg_offset: float,
|
||||
sheet_values: Mapping[str, str] | None = None,
|
||||
array_type: str = "genotyping",
|
||||
) -> ControlSample:
|
||||
g, r = control_intensities(grn, red, beads)
|
||||
qc = compute_control_metrics(beads, g, r, "genotyping", bg_offset)
|
||||
qc = compute_control_metrics(beads, g, r, array_type, bg_offset)
|
||||
return ControlSample(sample_id, idat_metadata(grn, red, sheet_values), g, r, qc)
|
||||
|
||||
|
||||
@@ -250,8 +252,8 @@ def run_qc_controls(opts: QcControlsOptions, rep: Reporter) -> int:
|
||||
|
||||
rm = RunManifest(command="qc controls", argv=opts.argv)
|
||||
rep.run_id = rm.run_id
|
||||
if opts.array_type == "methylation":
|
||||
raise SimurgError("E_QC_METHYLATION", "methylation control metrics are planned for v2 (QC-006)")
|
||||
if opts.array_type == "methylation" and opts.csv is None:
|
||||
raise SimurgError("E_NO_INPUT", "methylation control QC needs the methylation CSV manifest (--csv)")
|
||||
if opts.bpm is None and opts.csv is None:
|
||||
raise SimurgError("E_NO_INPUT", "give --bpm or --csv for the control definitions (QC-001)")
|
||||
if opts.bg_offset < 0:
|
||||
@@ -261,8 +263,15 @@ def run_qc_controls(opts: QcControlsOptions, rep: Reporter) -> int:
|
||||
names = (COMPAT_RAW, COMPAT_QC) if opts.compat else (CONTROLS_RAW, CONTROLS_QC)
|
||||
targets = [opts.out / n for n in names]
|
||||
check_outputs_absent(targets, force=opts.force) # QC-014: other files in the folder are fine
|
||||
manifest = read_bpm(opts.bpm) if opts.bpm else None
|
||||
beads = load_control_beads(manifest, opts.csv, rep)
|
||||
if opts.array_type == "methylation":
|
||||
from simurg.formats.methyl_manifest import read_methyl_manifest
|
||||
|
||||
assert opts.csv is not None
|
||||
mman = read_methyl_manifest(opts.csv)
|
||||
beads = [ControlBead(c.type, c.color_channel, c.name, c.address, canonical_id(c.name)) for c in mman.controls]
|
||||
else:
|
||||
manifest = read_bpm(opts.bpm) if opts.bpm else None
|
||||
beads = load_control_beads(manifest, opts.csv, rep)
|
||||
if not beads:
|
||||
raise SimurgError("E_BPM_FORMAT", "the manifest has no control definitions")
|
||||
samples: list[ControlSample] = []
|
||||
@@ -273,7 +282,9 @@ def run_qc_controls(opts: QcControlsOptions, rep: Reporter) -> int:
|
||||
assert s.pair.grn is not None and s.pair.red is not None
|
||||
grn, red = read_idat(s.pair.grn), read_idat(s.pair.red)
|
||||
check_pair(grn, red)
|
||||
samples.append(control_sample(s.sample_id, grn, red, beads, opts.bg_offset, sheet_values(s, sset.sheet)))
|
||||
samples.append(
|
||||
control_sample(s.sample_id, grn, red, beads, opts.bg_offset, sheet_values(s, sset.sheet), opts.array_type)
|
||||
)
|
||||
# QC-014: sample IDs of an existing summary must match
|
||||
summary = opts.out / "sample_summary.tsv"
|
||||
if summary.exists():
|
||||
@@ -282,7 +293,9 @@ def run_qc_controls(opts: QcControlsOptions, rep: Reporter) -> int:
|
||||
if ids ^ mine:
|
||||
rep.warn("W_QC_UNMATCHED_SAMPLES", f"{len(ids ^ mine)} sample ID(s) differ from {summary}")
|
||||
opts.out.mkdir(parents=True, exist_ok=True)
|
||||
written = write_control_tables(opts.out, samples, beads, opts.bg_offset, rep, compat=opts.compat)
|
||||
written = write_control_tables(
|
||||
opts.out, samples, beads, opts.bg_offset, rep, array_type=opts.array_type, compat=opts.compat
|
||||
)
|
||||
for p in written:
|
||||
rm.add_output(p, "simurg.controls/1")
|
||||
rm.summary = {"samples": len(samples)}
|
||||
|
||||
Reference in new issue
Block a user