alnkit 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- alnkit/__init__.py +11 -0
- alnkit/__main__.py +10 -0
- alnkit/_docs.py +53 -0
- alnkit/cli/__init__.py +53 -0
- alnkit/cli/__main__.py +10 -0
- alnkit/cli/_common.py +25 -0
- alnkit/cli/align.py +52 -0
- alnkit/cli/doctor.py +81 -0
- alnkit/cli/long.py +346 -0
- alnkit/cli/plot.py +293 -0
- alnkit/cli/ref.py +50 -0
- alnkit/cli/seg.py +131 -0
- alnkit/cli/selftest.py +308 -0
- alnkit/cli/short.py +245 -0
- alnkit/core/__init__.py +9 -0
- alnkit/core/bam_segments.py +183 -0
- alnkit/core/coords.py +129 -0
- alnkit/core/errors.py +25 -0
- alnkit/core/external.py +63 -0
- alnkit/core/fasta.py +83 -0
- alnkit/core/logging.py +25 -0
- alnkit/core/paf.py +52 -0
- alnkit/core/segments.py +253 -0
- alnkit/core/tables.py +340 -0
- alnkit/longread/__init__.py +5 -0
- alnkit/longread/assemble.py +453 -0
- alnkit/longread/breakpoints.py +910 -0
- alnkit/longread/classify.py +958 -0
- alnkit/longread/graph.py +174 -0
- alnkit/longread/locate.py +904 -0
- alnkit/plot/__init__.py +5 -0
- alnkit/plot/ribbon.py +1306 -0
- alnkit/plot/tracks.py +1035 -0
- alnkit/prepare.py +256 -0
- alnkit/py.typed +0 -0
- alnkit/shortread/__init__.py +20 -0
- alnkit/shortread/bam.py +527 -0
- alnkit/shortread/evidence.py +214 -0
- alnkit/shortread/interference.py +44 -0
- alnkit/shortread/merge.py +126 -0
- alnkit/shortread/model.py +301 -0
- alnkit/shortread/origin.py +507 -0
- alnkit/shortread/report.py +316 -0
- alnkit/shortread/scan.py +788 -0
- alnkit-0.2.0.dist-info/METADATA +163 -0
- alnkit-0.2.0.dist-info/RECORD +49 -0
- alnkit-0.2.0.dist-info/WHEEL +4 -0
- alnkit-0.2.0.dist-info/entry_points.txt +2 -0
- alnkit-0.2.0.dist-info/licenses/LICENSE +674 -0
alnkit/longread/graph.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# ---------------------------------------------------------------------------
|
|
3
|
+
# A4:read-vs-read overlap 边与连通分量
|
|
4
|
+
#
|
|
5
|
+
# 来源:upstream/scripts/overlap_graph.py(124 行)的移植。逻辑未改;
|
|
6
|
+
# 参数解析搬到 alnkit.cli.long、库层收 GraphSettings、main() 改为 run()。
|
|
7
|
+
# 三张产物与旧脚本逐字节一致。
|
|
8
|
+
#
|
|
9
|
+
# 输入:03_classify_reads/related_reads.fastq 的 all-vs-all PAF
|
|
10
|
+
# 03_classify_reads/read_classification.tsv
|
|
11
|
+
# 输出:04_overlap_graph/overlap_edges.tsv
|
|
12
|
+
# 04_overlap_graph/overlap_components.tsv
|
|
13
|
+
# 04_overlap_graph/overlap_summary.txt
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import collections
|
|
18
|
+
import os
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
|
|
21
|
+
from ..core.paf import parse_paf
|
|
22
|
+
from ..core.tables import read_legacy_tsv as read_tsv
|
|
23
|
+
from ..core.tables import write_legacy_tsv as write_tsv
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def load_read_categories(classification_path):
|
|
27
|
+
"""读 read 类别,返回 {read_name: read_category}。"""
|
|
28
|
+
header, rows = read_tsv(classification_path)
|
|
29
|
+
index = {name: i for i, name in enumerate(header)}
|
|
30
|
+
return {row[index["read_name"]]: row[index["read_category"]] for row in rows}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def build_edges(blocks, min_overlap_bp, min_identity):
|
|
34
|
+
"""每对 read 只保留 block_bp 最大的一条 overlap;返回边与邻接表。"""
|
|
35
|
+
best_by_pair = {}
|
|
36
|
+
for block in blocks:
|
|
37
|
+
if block["query"] == block["target"]:
|
|
38
|
+
continue
|
|
39
|
+
if block["block_bp"] < min_overlap_bp or block["identity"] < min_identity:
|
|
40
|
+
continue
|
|
41
|
+
pair = tuple(sorted((block["query"], block["target"])))
|
|
42
|
+
previous = best_by_pair.get(pair)
|
|
43
|
+
if previous is None or (block["block_bp"], block["identity"]) > (
|
|
44
|
+
previous["block_bp"],
|
|
45
|
+
previous["identity"],
|
|
46
|
+
):
|
|
47
|
+
best_by_pair[pair] = block
|
|
48
|
+
|
|
49
|
+
edges = []
|
|
50
|
+
adjacency = collections.defaultdict(set)
|
|
51
|
+
for pair, block in sorted(best_by_pair.items()):
|
|
52
|
+
edges.append(block)
|
|
53
|
+
adjacency[block["query"]].add(block["target"])
|
|
54
|
+
adjacency[block["target"]].add(block["query"])
|
|
55
|
+
return edges, adjacency
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def connected_components(read_names, adjacency):
|
|
59
|
+
"""并查集求连通分量,按大小降序。"""
|
|
60
|
+
parent = {name: name for name in read_names}
|
|
61
|
+
|
|
62
|
+
def find(name):
|
|
63
|
+
while parent[name] != name:
|
|
64
|
+
parent[name] = parent[parent[name]]
|
|
65
|
+
name = parent[name]
|
|
66
|
+
return name
|
|
67
|
+
|
|
68
|
+
def union(left, right):
|
|
69
|
+
left_root, right_root = find(left), find(right)
|
|
70
|
+
if left_root != right_root:
|
|
71
|
+
parent[left_root] = right_root
|
|
72
|
+
|
|
73
|
+
for read_name, neighbors in adjacency.items():
|
|
74
|
+
for neighbor in neighbors:
|
|
75
|
+
union(read_name, neighbor)
|
|
76
|
+
|
|
77
|
+
groups = collections.defaultdict(set)
|
|
78
|
+
for read_name in read_names:
|
|
79
|
+
groups[find(read_name)].add(read_name)
|
|
80
|
+
return sorted(groups.values(), key=lambda members: (-len(members), sorted(members)[0]))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(slots=True)
|
|
84
|
+
class GraphSettings:
|
|
85
|
+
"""``alnkit long graph`` 的全部入参(与旧脚本参数一一对应)。"""
|
|
86
|
+
|
|
87
|
+
paf: str = ""
|
|
88
|
+
classification: str = ""
|
|
89
|
+
outdir: str = "."
|
|
90
|
+
min_overlap: int = 300
|
|
91
|
+
min_identity: float = 0.85
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def run(settings: GraphSettings) -> dict[str, object]:
|
|
95
|
+
os.makedirs(settings.outdir, exist_ok=True)
|
|
96
|
+
|
|
97
|
+
categories = load_read_categories(settings.classification)
|
|
98
|
+
blocks = parse_paf(settings.paf)
|
|
99
|
+
edges, adjacency = build_edges(blocks, settings.min_overlap, settings.min_identity)
|
|
100
|
+
components = connected_components(set(categories), adjacency)
|
|
101
|
+
|
|
102
|
+
write_tsv(
|
|
103
|
+
os.path.join(settings.outdir, "overlap_edges.tsv"),
|
|
104
|
+
[
|
|
105
|
+
"read_a",
|
|
106
|
+
"read_b",
|
|
107
|
+
"block_bp",
|
|
108
|
+
"identity",
|
|
109
|
+
"strand",
|
|
110
|
+
"read_a_start",
|
|
111
|
+
"read_a_end",
|
|
112
|
+
"read_b_start",
|
|
113
|
+
"read_b_end",
|
|
114
|
+
],
|
|
115
|
+
[
|
|
116
|
+
[
|
|
117
|
+
edge["query"],
|
|
118
|
+
edge["target"],
|
|
119
|
+
edge["block_bp"],
|
|
120
|
+
"{0:.4f}".format(edge["identity"]),
|
|
121
|
+
edge["strand"],
|
|
122
|
+
edge["query_start"],
|
|
123
|
+
edge["query_end"],
|
|
124
|
+
edge["target_start"],
|
|
125
|
+
edge["target_end"],
|
|
126
|
+
]
|
|
127
|
+
for edge in edges
|
|
128
|
+
],
|
|
129
|
+
[
|
|
130
|
+
"每对 read 只保留 block_bp 最大的一条 overlap;坐标 0-based 半开。",
|
|
131
|
+
"阈值:block_bp >= --min-overlap,identity >= --min-identity。",
|
|
132
|
+
],
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
component_rows = []
|
|
136
|
+
for index, members in enumerate(components, 1):
|
|
137
|
+
component_rows.append(
|
|
138
|
+
[
|
|
139
|
+
"C{0:02d}".format(index),
|
|
140
|
+
len(members),
|
|
141
|
+
",".join(sorted({categories.get(name, "-") for name in members})),
|
|
142
|
+
sum(1 for name in members if categories.get(name) == "vector_region"),
|
|
143
|
+
";".join(sorted(members)),
|
|
144
|
+
]
|
|
145
|
+
)
|
|
146
|
+
write_tsv(
|
|
147
|
+
os.path.join(settings.outdir, "overlap_components.tsv"),
|
|
148
|
+
[
|
|
149
|
+
"component_id",
|
|
150
|
+
"n_reads",
|
|
151
|
+
"categories",
|
|
152
|
+
"n_vector_reads",
|
|
153
|
+
"read_names",
|
|
154
|
+
],
|
|
155
|
+
component_rows,
|
|
156
|
+
[
|
|
157
|
+
"全局连通分量,只用于观察 overlap 结构;方案 A 的类别由断点共享 reads 决定。",
|
|
158
|
+
],
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
summary = [
|
|
162
|
+
"overlap 边: {0}".format(len(edges)),
|
|
163
|
+
"连通分量: {0}".format(len(components)),
|
|
164
|
+
"最大分量 reads: {0}".format(len(components[0]) if components else 0),
|
|
165
|
+
]
|
|
166
|
+
with open(os.path.join(settings.outdir, "overlap_summary.txt"), "w") as output:
|
|
167
|
+
output.write("\n".join(summary) + "\n")
|
|
168
|
+
print("\n".join(summary))
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
"outdir": settings.outdir,
|
|
172
|
+
"edges": len(edges),
|
|
173
|
+
"components": len(components),
|
|
174
|
+
}
|