105 lines
3.5 KiB
Python
105 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|||
|
|
"""Create minimal synthetic RNA-seq data for strandness parameter validation."""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import gzip
|
||
|
|
import random
|
||
|
|
import shutil
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
try:
|
||
|
|
import yaml # type: ignore
|
||
|
|
except Exception as exc: # pragma: no cover
|
||
|
|
raise SystemExit(f"PyYAML is required: {exc}")
|
||
|
|
|
||
|
|
|
||
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||
|
|
BASE = PROJECT_ROOT / "项目记录" / "rnaseqUpdate" / "testdata" / "strandness_minimal"
|
||
|
|
|
||
|
|
|
||
|
|
def revcomp(seq: str) -> str:
|
||
|
|
return seq.translate(str.maketrans("ACGTN", "TGCAN"))[::-1]
|
||
|
|
|
||
|
|
|
||
|
|
def write_fastq(path: Path, records: list[tuple[str, str]]) -> None:
|
||
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||
|
|
with gzip.open(path, "wt", encoding="utf-8") as handle:
|
||
|
|
for name, seq in records:
|
||
|
|
handle.write(f"@{name}\n{seq}\n+\n{'I' * len(seq)}\n")
|
||
|
|
|
||
|
|
|
||
|
|
def sample_yaml(strandness: str, mixed: bool = False) -> dict:
|
||
|
|
samples = {
|
||
|
|
"S1": {
|
||
|
|
"run": "SYN001",
|
||
|
|
"r1": "S1_1.clean.fastq.gz",
|
||
|
|
"r2": "S1_2.clean.fastq.gz",
|
||
|
|
"condition": "CK",
|
||
|
|
"replicate": 1,
|
||
|
|
"library_strandness": strandness,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if mixed:
|
||
|
|
samples["S2"] = {
|
||
|
|
"run": "SYN002",
|
||
|
|
"r1": "S2_1.clean.fastq.gz",
|
||
|
|
"r2": "S2_2.clean.fastq.gz",
|
||
|
|
"condition": "TRT",
|
||
|
|
"replicate": 1,
|
||
|
|
"library_strandness": "reverse",
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
"species": {"code": "Test", "speciesName": "Synthetic test", "keggOrganism": ""},
|
||
|
|
"rnaseq": {"output_prefix": "Test"},
|
||
|
|
"samples": samples,
|
||
|
|
"groups": {sample: meta["condition"] for sample, meta in samples.items()},
|
||
|
|
"experiment": {"design": "two_group", "control_group": "CK"},
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def main() -> int:
|
||
|
|
if BASE.exists():
|
||
|
|
shutil.rmtree(BASE)
|
||
|
|
input_dir = BASE / "input"
|
||
|
|
final_dir = BASE / "workspace" / "job_synthetic_strandness" / "final"
|
||
|
|
input_dir.mkdir(parents=True, exist_ok=True)
|
||
|
|
final_dir.mkdir(parents=True, exist_ok=True)
|
||
|
|
|
||
|
|
rng = random.Random(7)
|
||
|
|
genome = "".join(rng.choice("ACGT") for _ in range(1000))
|
||
|
|
(input_dir / "Test.genome.fasta").write_text(f">chr1\n{genome}\n", encoding="utf-8")
|
||
|
|
(input_dir / "Test.gtf").write_text(
|
||
|
|
'chr1\tsynthetic\texon\t80\t320\t.\t+\t.\tgene_id "gene1"; transcript_id "tx1";\n',
|
||
|
|
encoding="utf-8",
|
||
|
|
)
|
||
|
|
|
||
|
|
r1_records = []
|
||
|
|
r2_records = []
|
||
|
|
for idx, start in enumerate((100, 120, 140, 160), start=1):
|
||
|
|
frag_start = start - 1
|
||
|
|
r1 = genome[frag_start : frag_start + 50]
|
||
|
|
r2 = revcomp(genome[frag_start + 100 : frag_start + 150])
|
||
|
|
r1_records.append((f"S1_{idx}/1", r1))
|
||
|
|
r2_records.append((f"S1_{idx}/2", r2))
|
||
|
|
write_fastq(final_dir / "S1_1.clean.fastq.gz", r1_records)
|
||
|
|
write_fastq(final_dir / "S1_2.clean.fastq.gz", r2_records)
|
||
|
|
write_fastq(final_dir / "S2_1.clean.fastq.gz", r1_records)
|
||
|
|
write_fastq(final_dir / "S2_2.clean.fastq.gz", r2_records)
|
||
|
|
|
||
|
|
for strandness in ("non_stranded", "forward", "reverse"):
|
||
|
|
payload = sample_yaml(strandness)
|
||
|
|
(input_dir / f"samples_{strandness}.yaml").write_text(
|
||
|
|
yaml.safe_dump(payload, allow_unicode=True, sort_keys=False),
|
||
|
|
encoding="utf-8",
|
||
|
|
)
|
||
|
|
(input_dir / "samples_mixed.yaml").write_text(
|
||
|
|
yaml.safe_dump(sample_yaml("forward", mixed=True), allow_unicode=True, sort_keys=False),
|
||
|
|
encoding="utf-8",
|
||
|
|
)
|
||
|
|
print(BASE)
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
raise SystemExit(main())
|