SequenceDot 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.
- seqdot/__init__.py +1 -0
- seqdot/alphabet.py +5 -0
- seqdot/batch.py +199 -0
- seqdot/cli.py +287 -0
- seqdot/compare.py +144 -0
- seqdot/fasta.py +131 -0
- seqdot/kmer.py +44 -0
- seqdot/plot.py +125 -0
- seqdot/report.py +43 -0
- seqdot/utils.py +147 -0
- sequencedot-0.2.0.dist-info/METADATA +320 -0
- sequencedot-0.2.0.dist-info/RECORD +16 -0
- sequencedot-0.2.0.dist-info/WHEEL +5 -0
- sequencedot-0.2.0.dist-info/entry_points.txt +2 -0
- sequencedot-0.2.0.dist-info/licenses/LICENSE +676 -0
- sequencedot-0.2.0.dist-info/top_level.txt +1 -0
seqdot/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__="0.2.0"
|
seqdot/alphabet.py
ADDED
seqdot/batch.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import itertools
|
|
3
|
+
|
|
4
|
+
from itertools import combinations
|
|
5
|
+
from concurrent.futures import ProcessPoolExecutor, as_completed
|
|
6
|
+
|
|
7
|
+
from tqdm import tqdm
|
|
8
|
+
|
|
9
|
+
from seqdot.compare import compare_sequences, ensure_index
|
|
10
|
+
from seqdot.utils import make_output_filename
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def generate_pairs(sequences, include_self=False):
|
|
14
|
+
"""
|
|
15
|
+
Generate sequence comparison pairs.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
sequences : list
|
|
20
|
+
List of sequence dictionaries.
|
|
21
|
+
|
|
22
|
+
include_self : bool
|
|
23
|
+
Include comparisons of sequences against themselves.
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
list
|
|
28
|
+
List of sequence pairs.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
if include_self:
|
|
32
|
+
|
|
33
|
+
pairs = []
|
|
34
|
+
|
|
35
|
+
for i, seq1 in enumerate(sequences):
|
|
36
|
+
for seq2 in sequences[i:]:
|
|
37
|
+
pairs.append((seq1, seq2))
|
|
38
|
+
|
|
39
|
+
return pairs
|
|
40
|
+
|
|
41
|
+
else:
|
|
42
|
+
|
|
43
|
+
return list(
|
|
44
|
+
combinations(sequences, 2)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def format_pair_name(seq1, seq2, max_length=20):
|
|
49
|
+
"""
|
|
50
|
+
Format sequence names for the progress bar.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def shorten(name):
|
|
54
|
+
if len(name) <= max_length:
|
|
55
|
+
return name
|
|
56
|
+
|
|
57
|
+
return name[: max_length - 3] + "..."
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
f"{shorten(seq1['name'])} vs "
|
|
61
|
+
f"{shorten(seq2['name'])}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def compare_pair(
|
|
65
|
+
seq1,
|
|
66
|
+
seq2,
|
|
67
|
+
kmer,
|
|
68
|
+
strand,
|
|
69
|
+
output_dir,
|
|
70
|
+
point_size,
|
|
71
|
+
):
|
|
72
|
+
"""
|
|
73
|
+
Compare a single pair of sequences.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
output_file = make_output_filename(
|
|
77
|
+
seq1["name"],
|
|
78
|
+
seq2["name"]
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
output_file = output_dir / output_file
|
|
82
|
+
|
|
83
|
+
matches = compare_sequences(
|
|
84
|
+
seq1,
|
|
85
|
+
seq2,
|
|
86
|
+
kmer,
|
|
87
|
+
strand,
|
|
88
|
+
str(output_file),
|
|
89
|
+
point_size
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
"seq1": seq1["name"],
|
|
94
|
+
"seq2": seq2["name"],
|
|
95
|
+
"matches": matches
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def run_all_vs_all(
|
|
100
|
+
sequences,
|
|
101
|
+
kmer,
|
|
102
|
+
strand,
|
|
103
|
+
output_dir,
|
|
104
|
+
point_size=1,
|
|
105
|
+
include_self=False,
|
|
106
|
+
silent=False,
|
|
107
|
+
threads=1,
|
|
108
|
+
thread_mode="auto"
|
|
109
|
+
):
|
|
110
|
+
"""
|
|
111
|
+
Run all-vs-all sequence comparisons.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
pairs = generate_pairs(
|
|
115
|
+
sequences,
|
|
116
|
+
include_self
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
for seq in sequences:
|
|
120
|
+
|
|
121
|
+
ensure_index(
|
|
122
|
+
seq,
|
|
123
|
+
kmer
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
if not silent:
|
|
127
|
+
|
|
128
|
+
typer.echo("SequenceDot batch comparison")
|
|
129
|
+
typer.echo("-" * 50)
|
|
130
|
+
|
|
131
|
+
typer.echo(f"Found sequences : {len(sequences)}")
|
|
132
|
+
typer.echo(f"Comparison mode : all-vs-all")
|
|
133
|
+
typer.echo(f"Include self : {'yes' if include_self else 'no'}")
|
|
134
|
+
typer.echo(f"Total comparisons : {len(pairs)}")
|
|
135
|
+
typer.echo(f"CPU threads : {threads} ({thread_mode})")
|
|
136
|
+
|
|
137
|
+
typer.echo("-" * 50)
|
|
138
|
+
typer.echo()
|
|
139
|
+
|
|
140
|
+
results = []
|
|
141
|
+
|
|
142
|
+
with ProcessPoolExecutor(
|
|
143
|
+
max_workers=threads
|
|
144
|
+
) as executor:
|
|
145
|
+
|
|
146
|
+
future_to_pair = {}
|
|
147
|
+
|
|
148
|
+
for seq1, seq2 in pairs:
|
|
149
|
+
|
|
150
|
+
future = executor.submit(
|
|
151
|
+
compare_pair,
|
|
152
|
+
seq1,
|
|
153
|
+
seq2,
|
|
154
|
+
kmer,
|
|
155
|
+
strand,
|
|
156
|
+
output_dir,
|
|
157
|
+
point_size
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
future_to_pair[future] = (seq1, seq2)
|
|
161
|
+
|
|
162
|
+
if not silent:
|
|
163
|
+
|
|
164
|
+
with tqdm(
|
|
165
|
+
total=len(future_to_pair),
|
|
166
|
+
desc="Comparisons",
|
|
167
|
+
unit="comparison"
|
|
168
|
+
) as pbar:
|
|
169
|
+
|
|
170
|
+
for future in as_completed(future_to_pair):
|
|
171
|
+
|
|
172
|
+
seq1, seq2 = future_to_pair[future]
|
|
173
|
+
|
|
174
|
+
pbar.set_postfix_str(
|
|
175
|
+
format_pair_name(seq1, seq2)
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
results.append(
|
|
179
|
+
future.result()
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
pbar.update(1)
|
|
183
|
+
|
|
184
|
+
else:
|
|
185
|
+
|
|
186
|
+
for future in as_completed(future_to_pair):
|
|
187
|
+
|
|
188
|
+
results.append(
|
|
189
|
+
future.result()
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if not silent:
|
|
193
|
+
|
|
194
|
+
typer.echo()
|
|
195
|
+
typer.echo("✔ Batch comparison completed")
|
|
196
|
+
typer.echo(f"Number of dotplots created: {len(results)}")
|
|
197
|
+
typer.echo(f"Plots written to: {output_dir}")
|
|
198
|
+
|
|
199
|
+
return results
|
seqdot/cli.py
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from seqdot import __version__
|
|
5
|
+
from seqdot.compare import compare_sequences
|
|
6
|
+
from seqdot.fasta import read_fasta, read_multi_fasta
|
|
7
|
+
from seqdot.batch import run_all_vs_all
|
|
8
|
+
from seqdot.utils import make_output_filename, check_for_gaps, resolve_threads
|
|
9
|
+
from seqdot.report import write_summary
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from enum import Enum
|
|
13
|
+
|
|
14
|
+
class Strand(str, Enum):
|
|
15
|
+
forward = "forward"
|
|
16
|
+
reverse = "reverse"
|
|
17
|
+
both = "both"
|
|
18
|
+
|
|
19
|
+
app = typer.Typer(
|
|
20
|
+
name="SequenceDot",
|
|
21
|
+
context_settings={
|
|
22
|
+
"help_option_names": ["-h", "--help"]
|
|
23
|
+
},
|
|
24
|
+
help="""
|
|
25
|
+
Create k-mer-based dotplots from unaligned sequences
|
|
26
|
+
|
|
27
|
+
Supports:
|
|
28
|
+
- comparison of two FASTA files
|
|
29
|
+
- all-vs-all comparison from a multi-sequence FASTA file
|
|
30
|
+
"""
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def version_callback(value: bool):
|
|
35
|
+
|
|
36
|
+
if value:
|
|
37
|
+
|
|
38
|
+
typer.echo(f"SequenceDot {__version__}")
|
|
39
|
+
raise typer.Exit()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@app.command()
|
|
43
|
+
def main(
|
|
44
|
+
sequence1: str | None = typer.Argument(
|
|
45
|
+
None,
|
|
46
|
+
help="First fasta file (.fasta, .fa, .fna, optionally .gz)"
|
|
47
|
+
),
|
|
48
|
+
sequence2: str | None = typer.Argument(
|
|
49
|
+
None,
|
|
50
|
+
help="Second fasta file (.fasta, .fa, .fna, optionally .gz)"
|
|
51
|
+
),
|
|
52
|
+
input_file: str | None = typer.Option(
|
|
53
|
+
None,
|
|
54
|
+
"--file",
|
|
55
|
+
help="Multiple sequence fasta file for batch comparison (.fasta, .fa, .fna, optionally .gz)"
|
|
56
|
+
),
|
|
57
|
+
all_vs_all: bool = typer.Option(
|
|
58
|
+
False,
|
|
59
|
+
"--all-vs-all",
|
|
60
|
+
help="Compare every sequence against every other sequence in --file, default does NOT include self (add --include-self)"
|
|
61
|
+
),
|
|
62
|
+
include_self: bool = typer.Option(
|
|
63
|
+
False,
|
|
64
|
+
"--include-self",
|
|
65
|
+
help="Include self-comparisons in all-vs-all mode"
|
|
66
|
+
),
|
|
67
|
+
kmer: int = typer.Option(
|
|
68
|
+
11,
|
|
69
|
+
"--kmer",
|
|
70
|
+
"-k",
|
|
71
|
+
min=1,
|
|
72
|
+
max=100,
|
|
73
|
+
help="Length of k-mer used for matching (1-100)"
|
|
74
|
+
),
|
|
75
|
+
output: str | None = typer.Option(
|
|
76
|
+
None,
|
|
77
|
+
"--output",
|
|
78
|
+
"-o",
|
|
79
|
+
help="Output file for single comparisons (.png, .pdf, .svg)"
|
|
80
|
+
),
|
|
81
|
+
output_dir: str | None = typer.Option(
|
|
82
|
+
None,
|
|
83
|
+
"--output-dir",
|
|
84
|
+
"-d",
|
|
85
|
+
help="Directory where output files are written, specifically useful for batch mode"
|
|
86
|
+
),
|
|
87
|
+
alphabet: str = typer.Option(
|
|
88
|
+
"DNA",
|
|
89
|
+
"--alphabet",
|
|
90
|
+
"-a",
|
|
91
|
+
help="Sequence alphabet: DNA, RNA, or AA"
|
|
92
|
+
),
|
|
93
|
+
strand: Strand = typer.Option(
|
|
94
|
+
Strand.forward,
|
|
95
|
+
"--strand",
|
|
96
|
+
"-s",
|
|
97
|
+
help="Compare forward strand, reverse complement, or both"
|
|
98
|
+
),
|
|
99
|
+
point_size: float = typer.Option(
|
|
100
|
+
1.0,
|
|
101
|
+
"--point-size",
|
|
102
|
+
"-p",
|
|
103
|
+
min=0.1,
|
|
104
|
+
max=100,
|
|
105
|
+
help="Size of dots in the plot (0.1-100)"
|
|
106
|
+
),
|
|
107
|
+
silent: bool = typer.Option(
|
|
108
|
+
False,
|
|
109
|
+
"--silent",
|
|
110
|
+
"-s",
|
|
111
|
+
help="Suppress progress bar during batch processing"
|
|
112
|
+
),
|
|
113
|
+
version: bool = typer.Option(
|
|
114
|
+
False,
|
|
115
|
+
"--version",
|
|
116
|
+
"-v",
|
|
117
|
+
callback=version_callback,
|
|
118
|
+
is_eager=True,
|
|
119
|
+
help="Show SequenceDot version and exit"
|
|
120
|
+
),
|
|
121
|
+
threads: str = typer.Option(
|
|
122
|
+
"auto",
|
|
123
|
+
"--threads",
|
|
124
|
+
"-t",
|
|
125
|
+
help="Number of CPU workers (default: auto)"
|
|
126
|
+
)
|
|
127
|
+
):
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
"""
|
|
131
|
+
Generate a dotplot from two sequence files.
|
|
132
|
+
"""
|
|
133
|
+
if all_vs_all:
|
|
134
|
+
|
|
135
|
+
if input_file is None:
|
|
136
|
+
raise typer.BadParameter(
|
|
137
|
+
"--all-vs-all requires --file"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
sequences = read_multi_fasta(
|
|
141
|
+
input_file,
|
|
142
|
+
alphabet
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
shortest = min(
|
|
146
|
+
seq["length"]
|
|
147
|
+
for seq in sequences
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if kmer > shortest:
|
|
151
|
+
raise typer.BadParameter(
|
|
152
|
+
f"k-mer size ({kmer}) is larger than the "
|
|
153
|
+
f"shortest sequence ({shortest} bp)."
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if output_dir is None:
|
|
157
|
+
output_dir = Path("seqdot_results")
|
|
158
|
+
|
|
159
|
+
output_dir = Path(output_dir)
|
|
160
|
+
|
|
161
|
+
output_dir.mkdir(
|
|
162
|
+
parents=True,
|
|
163
|
+
exist_ok=True
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
cpu_count = os.cpu_count() or 1
|
|
168
|
+
resolved_threads, thread_mode = resolve_threads(
|
|
169
|
+
threads,
|
|
170
|
+
len(sequences)
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
except ValueError as e:
|
|
174
|
+
raise typer.BadParameter(str(e))
|
|
175
|
+
|
|
176
|
+
typer.echo("\nSequenceDot batch comparison")
|
|
177
|
+
typer.echo("-" * 50)
|
|
178
|
+
|
|
179
|
+
typer.echo(f"Found sequences : {len(sequences)}")
|
|
180
|
+
typer.echo("Comparison mode : all-vs-all")
|
|
181
|
+
typer.echo(f"Include self : {'yes' if include_self else 'no'}")
|
|
182
|
+
|
|
183
|
+
n = len(sequences)
|
|
184
|
+
comparisons = (n * (n - 1) // 2)
|
|
185
|
+
if include_self:
|
|
186
|
+
comparisons += n
|
|
187
|
+
|
|
188
|
+
typer.echo(f"Total comparisons : {comparisons}")
|
|
189
|
+
|
|
190
|
+
if thread_mode == "auto":
|
|
191
|
+
typer.echo(
|
|
192
|
+
f"CPU workers : "
|
|
193
|
+
f"{resolved_threads} "
|
|
194
|
+
f"(auto, {cpu_count} available)"
|
|
195
|
+
)
|
|
196
|
+
else:
|
|
197
|
+
typer.echo(
|
|
198
|
+
f"CPU workers : "
|
|
199
|
+
f"{resolved_threads} "
|
|
200
|
+
"(user)"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
typer.echo("-" * 50)
|
|
204
|
+
|
|
205
|
+
results = run_all_vs_all(
|
|
206
|
+
sequences=sequences,
|
|
207
|
+
kmer=kmer,
|
|
208
|
+
strand=strand.value,
|
|
209
|
+
output_dir=output_dir,
|
|
210
|
+
point_size=point_size,
|
|
211
|
+
include_self=include_self,
|
|
212
|
+
silent=silent,
|
|
213
|
+
threads=resolved_threads,
|
|
214
|
+
thread_mode=thread_mode
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
write_summary(
|
|
218
|
+
results,
|
|
219
|
+
output_dir
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
typer.echo(
|
|
223
|
+
f"Summary written to {output_dir / 'summary.tsv'}"
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
raise typer.Exit()
|
|
227
|
+
|
|
228
|
+
seq1 = read_fasta(sequence1, alphabet)
|
|
229
|
+
seq2 = read_fasta(sequence2, alphabet)
|
|
230
|
+
|
|
231
|
+
shortest = min(
|
|
232
|
+
seq1["length"],
|
|
233
|
+
seq2["length"]
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
for seq in [seq1, seq2]:
|
|
237
|
+
|
|
238
|
+
if check_for_gaps(seq["sequence"]):
|
|
239
|
+
typer.echo(
|
|
240
|
+
f"Warning: {seq['name']} contains gap characters (-). "
|
|
241
|
+
"SequenceDot is designed for unaligned sequences."
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
if kmer > shortest:
|
|
245
|
+
raise typer.BadParameter(
|
|
246
|
+
f"k-mer size ({kmer}) is larger than the "
|
|
247
|
+
f"shortest sequence ({shortest} bp)"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
if output is None:
|
|
251
|
+
output = make_output_filename(
|
|
252
|
+
seq1["name"],
|
|
253
|
+
seq2["name"]
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
if output_dir is not None:
|
|
257
|
+
output_dir = Path(output_dir)
|
|
258
|
+
|
|
259
|
+
output_dir.mkdir(
|
|
260
|
+
parents=True,
|
|
261
|
+
exist_ok=True
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
output = str(output_dir / output)
|
|
265
|
+
|
|
266
|
+
typer.echo(
|
|
267
|
+
f"Building k-mer index (k={kmer})..."
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
matches = compare_sequences(
|
|
271
|
+
seq1,
|
|
272
|
+
seq2,
|
|
273
|
+
kmer,
|
|
274
|
+
strand.value,
|
|
275
|
+
output,
|
|
276
|
+
point_size
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
typer.echo(
|
|
280
|
+
f"Found {matches} matching k-mers"
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
typer.echo("Dotplot saved")
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
if __name__ == "__main__":
|
|
287
|
+
app()
|
seqdot/compare.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from seqdot.kmer import build_kmer_index, find_kmer_matches, build_kmer_index
|
|
2
|
+
from seqdot.plot import create_dotplot
|
|
3
|
+
from seqdot.utils import reverse_complement
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def add_strand(matches, strand):
|
|
7
|
+
"""
|
|
8
|
+
Add strand information to matching coordinates.
|
|
9
|
+
|
|
10
|
+
Converts:
|
|
11
|
+
[(x, y), (x, y)]
|
|
12
|
+
|
|
13
|
+
into:
|
|
14
|
+
[(x, y, strand), (x, y, strand)]
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
return [
|
|
18
|
+
(x, y, strand)
|
|
19
|
+
for x, y in matches
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def ensure_index(sequence, kmer):
|
|
24
|
+
"""
|
|
25
|
+
Build a k-mer index once and reuse it.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
if (
|
|
29
|
+
"kmer_index" not in sequence
|
|
30
|
+
or sequence["kmer_index"] is None
|
|
31
|
+
or sequence.get("kmer_index_k") != kmer
|
|
32
|
+
):
|
|
33
|
+
|
|
34
|
+
sequence["kmer_index"] = build_kmer_index(
|
|
35
|
+
sequence["sequence"],
|
|
36
|
+
kmer
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
sequence["kmer_index_k"] = kmer
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def compare_sequences(
|
|
43
|
+
seq1,
|
|
44
|
+
seq2,
|
|
45
|
+
kmer,
|
|
46
|
+
strand,
|
|
47
|
+
output_file,
|
|
48
|
+
point_size=1,
|
|
49
|
+
):
|
|
50
|
+
"""
|
|
51
|
+
Compare two sequences and create a dotplot.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
seq1 : dict
|
|
56
|
+
First sequence dictionary.
|
|
57
|
+
|
|
58
|
+
seq2 : dict
|
|
59
|
+
Second sequence dictionary.
|
|
60
|
+
|
|
61
|
+
kmer : int
|
|
62
|
+
K-mer size.
|
|
63
|
+
|
|
64
|
+
strand : str
|
|
65
|
+
"forward", "reverse", or "both".
|
|
66
|
+
|
|
67
|
+
output_file : str
|
|
68
|
+
Output filename.
|
|
69
|
+
|
|
70
|
+
point_size : float
|
|
71
|
+
Size of dots in plot.
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
int
|
|
76
|
+
Number of matching k-mers.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
matches = []
|
|
80
|
+
|
|
81
|
+
# Forward comparison
|
|
82
|
+
if strand in ["forward", "both"]:
|
|
83
|
+
|
|
84
|
+
ensure_index(
|
|
85
|
+
seq1,
|
|
86
|
+
kmer
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
index = seq1["kmer_index"]
|
|
90
|
+
|
|
91
|
+
forward_matches = find_kmer_matches(
|
|
92
|
+
seq2["sequence"],
|
|
93
|
+
index,
|
|
94
|
+
kmer
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
matches.extend(
|
|
98
|
+
add_strand(
|
|
99
|
+
forward_matches,
|
|
100
|
+
"forward"
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# Reverse-complement comparison
|
|
106
|
+
if strand in ["reverse", "both"]:
|
|
107
|
+
|
|
108
|
+
reverse_seq2 = reverse_complement(
|
|
109
|
+
seq2["sequence"]
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
ensure_index(
|
|
113
|
+
seq1,
|
|
114
|
+
kmer
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
index = seq1["kmer_index"]
|
|
118
|
+
|
|
119
|
+
reverse_matches = find_kmer_matches(
|
|
120
|
+
reverse_seq2,
|
|
121
|
+
index,
|
|
122
|
+
kmer
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
matches.extend(
|
|
126
|
+
add_strand(
|
|
127
|
+
reverse_matches,
|
|
128
|
+
"reverse"
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
create_dotplot(
|
|
133
|
+
matches,
|
|
134
|
+
seq1["length"],
|
|
135
|
+
seq2["length"],
|
|
136
|
+
kmer,
|
|
137
|
+
name1=seq1["name"],
|
|
138
|
+
name2=seq2["name"],
|
|
139
|
+
output_file=output_file,
|
|
140
|
+
point_size=point_size
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
return len(matches)
|