Skip to content

Commit ac664df

Browse files
committed
.
1 parent 9f12ec3 commit ac664df

2 files changed

Lines changed: 56 additions & 22 deletions

File tree

recmpox/recmpox.py

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1558,24 +1558,14 @@ def _run_one_phylogeny(
15581558
for i in range(0, len(seq), line_len):
15591559
out.write(seq[i : i + line_len] + "\n")
15601560
for sid, seq in part1_seqs.items():
1561-
# Tract FASTA headers include coverage suffix like
1562-
# "<safe_sample_id>_<ref1_label>_tract_HC_<cov>%". Recover the base
1563-
# sample ID so that phylogeny tract IDs can be mapped back to the
1564-
# per-genome records by stripping the coverage part.
1565-
base_id = sid
1566-
cov_suffix = f"_{ref1_label}_tract_HC_"
1567-
if cov_suffix in base_id:
1568-
base_id = base_id.split(cov_suffix, 1)[0]
1569-
header = _safe_fasta_id(base_id) + f"_{ref1_label}_tracts"
1561+
# Use tract FASTA headers (which already encode sample, clade and coverage)
1562+
# but sanitize them for downstream tools (e.g. remove '%' characters).
1563+
header = _safe_fasta_id(sid)
15701564
out.write(f">{header}\n")
15711565
for i in range(0, len(seq), line_len):
15721566
out.write(seq[i : i + line_len] + "\n")
15731567
for sid, seq in part2_seqs.items():
1574-
base_id = sid
1575-
cov_suffix = f"_{ref2_label}_tract_HC_"
1576-
if cov_suffix in base_id:
1577-
base_id = base_id.split(cov_suffix, 1)[0]
1578-
header = _safe_fasta_id(base_id) + f"_{ref2_label}_tracts"
1568+
header = _safe_fasta_id(sid)
15791569
out.write(f">{header}\n")
15801570
for i in range(0, len(seq), line_len):
15811571
out.write(seq[i : i + line_len] + "\n")
@@ -1620,9 +1610,11 @@ def _outbreak_label_from_header(h: str) -> str:
16201610
return "Ib"
16211611
return "other"
16221612

1623-
ref_ids = [hid for hid in aln_dict.keys() if not hid.endswith(f"_{ref1_label}_tracts") and not hid.endswith(f"_{ref2_label}_tracts")]
1624-
tract1_ids = [hid for hid in aln_dict.keys() if hid.endswith(f"_{ref1_label}_tracts")]
1625-
tract2_ids = [hid for hid in aln_dict.keys() if hid.endswith(f"_{ref2_label}_tracts")]
1613+
# Partition alignment IDs into references vs tract sequences for ref1/ref2.
1614+
tract1_ids = [hid for hid in aln_dict.keys() if f"_{ref1_label}_tract_HC_" in hid]
1615+
tract2_ids = [hid for hid in aln_dict.keys() if f"_{ref2_label}_tract_HC_" in hid]
1616+
tract_ids = set(tract1_ids) | set(tract2_ids)
1617+
ref_ids = [hid for hid in aln_dict.keys() if hid not in tract_ids]
16261618

16271619
ref_outbreak = {rid: _outbreak_label_from_header(rid) for rid in ref_ids}
16281620

@@ -1657,8 +1649,10 @@ def _nearest_outbreak(seq_id: str) -> str:
16571649
args._phylogeny_ancestors = {}
16581650

16591651
def _sample_key_from_tract(hid: str, label: str) -> str:
1660-
suffix = f"_{label}_tracts"
1661-
return hid[: -len(suffix)] if hid.endswith(suffix) else hid
1652+
# Tract IDs contain coverage like "<safe_id>_<label>_tract_HC_<cov>".
1653+
# Recover the base sample ID by stripping the coverage suffix.
1654+
cov_suffix = f"_{label}_tract_HC_"
1655+
return hid.split(cov_suffix, 1)[0] if cov_suffix in hid else hid
16621656

16631657
for hid in tract1_ids:
16641658
key = _sample_key_from_tract(hid, ref1_label)
@@ -1799,8 +1793,8 @@ def _nearest_outbreak_tree(seq_id: str) -> Optional[str]:
17991793
return best_label
18001794

18011795
def _sample_key_from_tract(hid: str, label: str) -> str:
1802-
suffix = f"_{label}_tracts"
1803-
return hid[: -len(suffix)] if hid.endswith(suffix) else hid
1796+
cov_suffix = f"_{label}_tract_HC_"
1797+
return hid.split(cov_suffix, 1)[0] if cov_suffix in hid else hid
18041798

18051799
for hid in tract1_ids:
18061800
key = _sample_key_from_tract(hid, ref1_label)

recmpox/references/root_tree_figure.R

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,47 @@ rooted_path <- file.path(out_dir, "rooted.tree")
3333
write.tree(tr, rooted_path)
3434

3535
labels <- tr$tip.label
36-
type <- ifelse(grepl("_tracts", labels, fixed = TRUE), "recombinant", "reference")
36+
37+
# Shorten recombinant tract labels for plotting: keep a 20-char sample ID prefix,
38+
# the clade label (Ia/Ib/IIa/IIb), and the tract coverage (HC_xx.x) when present.
39+
shorten_label <- function(x) {
40+
if (!grepl("_tract_HC_", x, fixed = TRUE)) {
41+
return(x)
42+
}
43+
clade <- NA_character_
44+
split_pat <- NULL
45+
if (grepl("_Ia_tract_HC_", x, fixed = TRUE)) {
46+
split_pat <- "_Ia_tract_HC_"
47+
clade <- "Ia"
48+
} else if (grepl("_Ib_tract_HC_", x, fixed = TRUE)) {
49+
split_pat <- "_Ib_tract_HC_"
50+
clade <- "Ib"
51+
} else if (grepl("_IIa_tract_HC_", x, fixed = TRUE)) {
52+
split_pat <- "_IIa_tract_HC_"
53+
clade <- "IIa"
54+
} else if (grepl("_IIb_tract_HC_", x, fixed = TRUE)) {
55+
split_pat <- "_IIb_tract_HC_"
56+
clade <- "IIb"
57+
} else {
58+
return(x)
59+
}
60+
parts <- strsplit(x, split_pat, fixed = TRUE)[[1]]
61+
base <- parts[1]
62+
cov <- if (length(parts) >= 2) parts[2] else ""
63+
base_short <- substr(base, 1, 20)
64+
cov_clean <- gsub("[^0-9.]", "", cov)
65+
if (nzchar(cov_clean)) {
66+
cov_id <- gsub("\\.", "_", cov_clean)
67+
return(paste0(base_short, "_", clade, "_tract_HC_", cov_id))
68+
} else {
69+
return(paste0(base_short, "_", clade, "_tract"))
70+
}
71+
}
72+
labels <- vapply(labels, shorten_label, character(1))
73+
# Use shortened labels in the tree so PDF/SVG and any downstream plot show them
74+
tr$tip.label <- labels
75+
76+
type <- ifelse(grepl("_tract_HC_", labels, fixed = TRUE), "recombinant", "reference")
3777
clade <- rep("Ia", length(labels))
3878
clade[type == "recombinant"] <- "Recombinant"
3979
clade[grepl("sh2024Ib", labels, fixed = TRUE)] <- "sh2024Ib"

0 commit comments

Comments
 (0)