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
26c8a0cf40
commit
8758178df6
13 files changed
+1292
-21
No files matched your search
@@ -666,6 +666,72 @@ def qc_controls_cmd(
|
||||
run_guarded(rep, lambda: run_qc_controls(opts, rep))
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------------------------
|
||||
methyl_app = typer.Typer(help="Methylation arrays: control QC, detection, beta values (MET-001).")
|
||||
app.add_typer(methyl_app, name="methyl")
|
||||
|
||||
|
||||
@methyl_app.command("qc")
|
||||
def methyl_qc_cmd(
|
||||
manifest: Annotated[Path, typer.Option("--manifest", help="Methylation CSV manifest (required).")],
|
||||
out: Annotated[Path, typer.Option("--out", help="Output folder (required).")],
|
||||
idats: Annotated[Path | None, typer.Option("--idats", help="Folder searched recursively for IDAT pairs.")] = None,
|
||||
sheet: Annotated[Path | None, typer.Option("--sheet", help="Sample sheet (CSV).")] = None,
|
||||
config: Annotated[Path | None, typer.Option("--config", help="QC threshold YAML.")] = None,
|
||||
profile: Annotated[str, typer.Option("--profile", help="QC profile name, 'auto' or 'none'.")] = "auto",
|
||||
threshold: Annotated[list[str] | None, typer.Option("--threshold", help="KEY=VALUE (repeatable).")] = None,
|
||||
bg_offset: Annotated[float | None, typer.Option("--bg-offset", help="Background offset x (3000).")] = None,
|
||||
bs_set: Annotated[str, typer.Option("--bs-conv-red-bg-set", help="U3-U5 (default) or U4-U6.")] = "U3-U5",
|
||||
pval: Annotated[float, typer.Option("--pval-threshold", help="Detection p threshold.")] = 0.05,
|
||||
beta_offset: Annotated[float, typer.Option("--beta-offset", help="alpha in M/(M+U+alpha).")] = 0.0,
|
||||
dye_bias: Annotated[str, typer.Option("--dye-bias", help="linear|nonlinear")] = "linear",
|
||||
min_prop: Annotated[
|
||||
float | None, typer.Option("--min-prop-passing", help="Fail samples below this detection rate.")
|
||||
] = None,
|
||||
pca_full: Annotated[int, typer.Option("--pca-max-samples-full", min=2)] = 100,
|
||||
pca_probes: Annotated[int, typer.Option("--pca-probes", min=2)] = 10_000,
|
||||
threads: Annotated[int, typer.Option("--threads", min=0, max=1024, help="Worker processes (0 = auto).")] = 0,
|
||||
force: Annotated[bool, typer.Option("--force", help="Replace existing outputs.")] = False,
|
||||
log_json: LogJson = False,
|
||||
debug: Debug = False,
|
||||
quiet: Quiet = False,
|
||||
) -> None:
|
||||
"""Methylation control QC: 21 control metrics, detection p-values, noob/dye-bias corrected β (MET-001)."""
|
||||
from simurg.pipeline.methyl import MethylQcOptions, run_methyl_qc
|
||||
|
||||
if dye_bias not in ("linear", "nonlinear"):
|
||||
raise typer.BadParameter("--dye-bias must be linear or nonlinear")
|
||||
if bs_set not in ("U3-U5", "U4-U6"):
|
||||
raise typer.BadParameter("--bs-conv-red-bg-set must be U3-U5 or U4-U6")
|
||||
if not 0 < pval <= 1:
|
||||
raise typer.BadParameter("--pval-threshold must be in (0, 1]")
|
||||
rep = _reporter("methyl qc", out, log_json, debug, quiet)
|
||||
opts = MethylQcOptions(
|
||||
out=out, manifest=manifest, idats=idats, sheet=sheet, config=config,
|
||||
profile=None if profile == "none" else profile, thresholds=threshold or [], bg_offset=bg_offset,
|
||||
bs_conv_red_bg_set=bs_set, pval_threshold=pval, beta_offset=beta_offset, dye_bias=dye_bias,
|
||||
min_prop_passing=min_prop, pca_max_samples_full=pca_full, pca_probes=pca_probes, threads=threads,
|
||||
force=force, argv=sys.argv[1:],
|
||||
) # fmt: skip
|
||||
run_guarded(rep, lambda: run_methyl_qc(opts, rep))
|
||||
|
||||
|
||||
@methyl_app.command("convert-manifest")
|
||||
def methyl_convert_cmd(
|
||||
manifest: Annotated[Path, typer.Option("--manifest", help="Methylation CSV manifest (required).")],
|
||||
out: Annotated[Path, typer.Option("--out", help="Output folder (required).")],
|
||||
force: Annotated[bool, typer.Option("--force", help="Replace existing outputs.")] = False,
|
||||
log_json: LogJson = False,
|
||||
debug: Debug = False,
|
||||
quiet: Quiet = False,
|
||||
) -> None:
|
||||
"""Methylation manifest -> probes.tsv and controls.tsv (MET-003)."""
|
||||
from simurg.pipeline.methyl import run_convert_manifest
|
||||
|
||||
rep = _reporter("methyl convert-manifest", out, log_json, debug, quiet)
|
||||
run_guarded(rep, lambda: run_convert_manifest(manifest, out, force, rep))
|
||||
|
||||
|
||||
def _qc_cohort(
|
||||
command: str,
|
||||
out: Path,
|
||||
|
||||
Reference in new issue
Block a user