v2 CN: cn annotate (JSON report, ISCN 2020, cytobands, genes) and the annotation bundle builder
cn call now records per-chromosome and sample statistics (percent het, LRR stats, BAF deviations, robust LogR dev, call rate) in the sidecar; cn annotate writes <sample>.cyto.json (CYTO-033) with ISCN microarray/traditional strings (CYTO-034), bands and RefSeq gene lists (CYTO-035), LOH summaries, weighted median CN (CYTO-026); annotation bundle from UCSC cytoBand + ncbiRefSeq (CYTO-036, IN-054). Tests for ISCN strings, gene overlap, sidecar-missing error and an end-to-end call+annotate.
This commit is contained in:
1 parent
1255f37a16
commit
d3097dd027
6 files changed
+428
-2
No files matched your search
@@ -171,3 +171,51 @@ def test_cn_build_and_call(tmp_path: Path) -> None: # CYTO-028 .. CYTO-032, CYT
|
||||
summ = (out / "cn_sample_summary.tsv").read_text().splitlines()
|
||||
assert summ[0].startswith("sample_id\tsample_name\tstatus") and len(summ) == 3
|
||||
assert (out / "200000000001_R01C01.cn_segments.parquet").exists()
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------------------------ annotate
|
||||
def test_iscn_strings_and_genes() -> None: # CYTO-034 / CYTO-035
|
||||
from simurg.cn.annotate import Bands, Genes, iscn_sample
|
||||
|
||||
bands = Bands.from_ucsc("chr1\t0\t2300000\tp36.33\tgneg\nchr1\t2300000\t5300000\tp36.32\tgpos25\n"
|
||||
"chr9\t0\t2200000\tp24.3\tgneg\n") # fmt: skip
|
||||
dele = {"chrom": "1", "start": 1_567_924, "end": 1_632_825, "call": "DEL", "call_type": "DEL", "copy_number": 1,
|
||||
"mosaic": False} # fmt: skip
|
||||
dup = {"chrom": "1", "start": 2_000_000, "end": 3_000_000, "call": "DUP", "call_type": "DUP", "copy_number": 3,
|
||||
"mosaic": False} # fmt: skip
|
||||
loh = {"chrom": "9", "start": 100, "end": 2_000_000, "call": "LOH", "call_type": "LOH", "copy_number": 2,
|
||||
"mosaic": False} # fmt: skip
|
||||
mos = {"chrom": "9", "start": 100, "end": 1_000, "call": "MDEL", "call_type": "DEL", "copy_number": 1,
|
||||
"mosaic": True, "base_cn": 2} # fmt: skip
|
||||
trad, micro = iscn_sample([loh, dup, dele], bands, "GRCh38", "F")
|
||||
assert micro == ("arr[GRCh38] 1p36.33(1567924_1632825)x1,1p36.33p36.32(2000000_3000000)x3,"
|
||||
"9p24.3(100_2000000)x2 hmz") # fmt: skip
|
||||
assert trad == "del(1)(p36.33p36.33),dup(1)(p36.33p36.32)"
|
||||
assert iscn_sample([mos], bands, "GRCh38", "F")[1] == "arr[GRCh38] 9p24.3(100_1000)x2~1"
|
||||
assert iscn_sample([], bands, "GRCh38", "M")[1] == "arr[GRCh38](1-22)x2,(XY)x1"
|
||||
assert iscn_sample([], bands, "GRCh37", "F")[1] == "arr[GRCh37](1-22,X)x2"
|
||||
assert iscn_sample([], bands, "GRCh38", "U")[1] == "arr[GRCh38](1-22)x2"
|
||||
genes = Genes.from_rows([("1", 100, 5_000, "A"), ("1", 4_000, 4_500, "B"), ("1", 200, 300, "A"),
|
||||
("1", 9_000, 9_100, "C")]) # fmt: skip
|
||||
assert genes.overlapping("1", 4_400, 8_999) == ["A", "B"]
|
||||
|
||||
|
||||
def test_cn_annotate_end_to_end(tmp_path: Path) -> None: # CYTO-032 / CYTO-033
|
||||
bpm, fa, gtcs = _dataset(tmp_path)
|
||||
model, out = tmp_path / "m.simurg-cyto", tmp_path / "cn"
|
||||
run = CliRunner().invoke
|
||||
assert run(app, ["cn", "annotate", "--vcfs", str(tmp_path)]).exit_code == 2 # E_CN_SIDECAR_MISSING
|
||||
assert run(app, ["cn", "build-model", "--bpm", str(bpm), "--fasta", str(fa), "--out", str(model),
|
||||
"--quiet"]).exit_code == 0 # fmt: skip
|
||||
r = run(app, ["cn", "call", "--bpm", str(bpm), "--model", str(model), "--gtcs", str(gtcs), "--fasta", str(fa),
|
||||
"--out", str(out), "--threads", "1", "--unknown-sex-policy", "female", "--quiet"]) # fmt: skip
|
||||
assert r.exit_code == 0, r.output
|
||||
r = run(app, ["cn", "annotate", "--vcfs", str(out), "--min-qual", "0", "--quiet"])
|
||||
assert r.exit_code == 0, r.output
|
||||
doc = json.loads((out / "200000000001_R01C01.cyto.json").read_text())
|
||||
assert doc["schema"] == "simurg.cyto/1" and doc["annotation_type"] == "constitutional"
|
||||
assert doc["iscn_microarray"].startswith("arr[")
|
||||
assert any(e["call_type"] == "DEL" and e["chrom"] == "1" for e in doc["events"])
|
||||
assert {c["id"] for c in doc["chromosomes"]} == {"1", "2"}
|
||||
normal = json.loads((out / "200000000001_R02C01.cyto.json").read_text())
|
||||
assert normal["iscn_microarray"] in ("arr[GRCh38](1-22,X)x2", "arr[GRCh37](1-22,X)x2") or normal["events"]
|
||||
Reference in new issue
Block a user