P4 (WIP): GenCall-equivalent calling, BAF/LRR, sex, GTC v5, simurg genotype
- genotype/clusters.py: EGT aligned to manifest order (float32 mean R) - genotype/gencall.py: flair, six-case likelihood, priors/posteriors, fuzzy sub-scores and score map, vectorised with the reference's float/double evaluation order and libm transcendentals - genotype/baf_lrr.py, sex.py (rules v1/v2), sample.py (IDAT -> Gtc) - pipeline/inputs.py shared by ingest and genotype - pipeline/genotype.py + CLI: GTC per sample, sample_summary.tsv/json (OUT-060..063), run manifest - GTCs from IDATs are byte-identical to the oracle for 36 GSA samples x 2 normalization versions (NaN sign canonicalised to the x86 pattern)
This commit is contained in:
1 parent
d87b004cdb
commit
fcc1f6c3e3
12 files changed
+1270
-53
No files matched your search
@@ -36,9 +36,9 @@ from simurg.core.schemas import (
|
||||
write_parquet,
|
||||
)
|
||||
from simurg.formats.bpm import Manifest, read_bpm
|
||||
from simurg.formats.discovery import ResolvedSample, discover_idats, resolve_input_folder, resolve_samples
|
||||
from simurg.formats.discovery import ResolvedSample
|
||||
from simurg.formats.idat import check_pair, parse_idat_filename, read_idat
|
||||
from simurg.formats.samplesheet import SampleSheet, output_stem, parse_samplesheet
|
||||
from simurg.pipeline.inputs import check_paths, resolve_sample_set, sample_dir
|
||||
from simurg.signal.raw_xy import LocusMapCache, assemble_raw_xy, check_coverage
|
||||
|
||||
SUMMARY_COLUMNS = [
|
||||
@@ -93,7 +93,7 @@ def _init_worker(manifest: Manifest, manifest_sha256: str) -> None:
|
||||
|
||||
|
||||
def _sample_paths(out: Path, stem: str, layout: str) -> tuple[Path, Path]:
|
||||
d = out / "samples" / stem if layout == "nested" else out
|
||||
d = sample_dir(out, stem, layout)
|
||||
return d / f"{stem}.intensity.parquet", d / f"{stem}.controls.parquet"
|
||||
|
||||
|
||||
@@ -202,61 +202,18 @@ def run_ingest(opts: IngestOptions, rep: Reporter) -> int:
|
||||
v = getattr(opts, k)
|
||||
rm.set_option(k, str(v) if v is not None else None, "cli")
|
||||
|
||||
for p, what in (
|
||||
(opts.bpm, "BPM"),
|
||||
(opts.sheet, "sample sheet"),
|
||||
(opts.csv, "CSV manifest"),
|
||||
(opts.idats, "IDAT folder"),
|
||||
):
|
||||
if p is not None and not Path(p).exists():
|
||||
raise SimurgError("E_FILE_NOT_FOUND", f"{what} not found: {p}", path=str(p))
|
||||
|
||||
# --- inputs (IN-001 … IN-022) --------------------------------------------------------------------
|
||||
sheet: SampleSheet | None = parse_samplesheet(opts.sheet) if opts.sheet else None
|
||||
if sheet:
|
||||
rm.sheet_header = sheet.header
|
||||
for code, msg in sheet.warnings:
|
||||
rep.warn(code, msg)
|
||||
folder, notes = resolve_input_folder(opts.idats, sheet)
|
||||
for code, msg in notes:
|
||||
rep.warn(code, msg) if code.startswith("W_") else rep.info(msg, code)
|
||||
disc = discover_idats(folder, on_duplicate=opts.on_duplicate_idat)
|
||||
for code, msg in disc.warnings:
|
||||
rep.warn(code, msg)
|
||||
samples, notes = resolve_samples(disc, sheet)
|
||||
for code, msg in notes:
|
||||
rep.info(msg, code)
|
||||
if not samples:
|
||||
raise SimurgError("E_NO_INPUT", f"no IDAT pairs found under {folder}")
|
||||
|
||||
# --- names and output paths (IN-010, IN-012) -----------------------------------------------------
|
||||
use_idat_names = sheet is None or not sheet.has_sample_name
|
||||
stems: dict[str, str] = {}
|
||||
check_paths(
|
||||
[(opts.bpm, "BPM"), (opts.sheet, "sample sheet"), (opts.csv, "CSV manifest"), (opts.idats, "IDAT folder")]
|
||||
)
|
||||
sset = resolve_sample_set(opts.idats, opts.sheet, opts.on_duplicate_idat, rep, rm)
|
||||
samples = sset.samples
|
||||
jobs: list[SampleJob] = []
|
||||
rows: list[dict[str, Any]] = []
|
||||
for s in samples:
|
||||
if use_idat_names and s.pair is not None and s.pair.grn is not None:
|
||||
# sheet Sample_Name > IDAT sample name (field 406) > sample_id
|
||||
try:
|
||||
idat_name = read_idat(s.pair.grn, load_arrays=False).sample_id.strip()
|
||||
except SimurgError:
|
||||
idat_name = ""
|
||||
if idat_name:
|
||||
s.sample_name = idat_name
|
||||
stem = output_stem(s.sample_name)
|
||||
if stem in stems and stems[stem] != s.sample_id:
|
||||
if not use_idat_names: # sheet names that sanitise to the same stem
|
||||
raise SimurgError(
|
||||
"E_SHEET_NAME_COLLISION",
|
||||
f"samples {stems[stem]} and {s.sample_id} both map to output name {stem!r}",
|
||||
)
|
||||
rep.info(f"{s.sample_id}: IDAT sample name {s.sample_name!r} is not unique; using the sample ID")
|
||||
s.sample_name = s.sample_id
|
||||
stem = output_stem(s.sample_id)
|
||||
stems[stem] = s.sample_id
|
||||
if s.status != "READY" or s.pair is None or not s.pair.complete:
|
||||
rows.append(_missing_row(s))
|
||||
continue
|
||||
stem = sset.stems[s.sample_id]
|
||||
ip, cp = _sample_paths(opts.out, stem, opts.layout)
|
||||
jobs.append(
|
||||
SampleJob(s.sample_id, s.sample_name, stem, str(s.pair.grn), str(s.pair.red), str(ip), str(cp), opts.resume)
|
||||
|
||||
Reference in new issue
Block a user