yamcot 1.0.0__cp310-cp310-win_amd64.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.
- yamcot/__init__.py +46 -0
- yamcot/_core/__init__.py +17 -0
- yamcot/_core/_core.cp310-win_amd64.pyd +0 -0
- yamcot/_core/bindings.cpp +28 -0
- yamcot/_core/core_functions.h +29 -0
- yamcot/_core/fasta_to_plain.h +182 -0
- yamcot/_core/mco_prc.cpp +1476 -0
- yamcot/_core/pfm_to_pwm.h +130 -0
- yamcot/cli.py +621 -0
- yamcot/comparison.py +1066 -0
- yamcot/execute.py +97 -0
- yamcot/functions.py +787 -0
- yamcot/io.py +522 -0
- yamcot/models.py +1161 -0
- yamcot/pipeline.py +402 -0
- yamcot/ragged.py +126 -0
- yamcot-1.0.0.dist-info/METADATA +433 -0
- yamcot-1.0.0.dist-info/RECORD +21 -0
- yamcot-1.0.0.dist-info/WHEEL +5 -0
- yamcot-1.0.0.dist-info/entry_points.txt +3 -0
- yamcot-1.0.0.dist-info/licenses/LICENSE +21 -0
yamcot/cli.py
ADDED
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Any, Dict
|
|
7
|
+
|
|
8
|
+
from yamcot.pipeline import run_pipeline
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def setup_logging(verbose: bool):
|
|
12
|
+
"""Setup logging configuration."""
|
|
13
|
+
level = logging.DEBUG if verbose else logging.INFO
|
|
14
|
+
logging.basicConfig(level=level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
|
15
|
+
if verbose:
|
|
16
|
+
logging.getLogger("numba").setLevel(logging.WARNING)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def create_arg_parser() -> argparse.ArgumentParser:
|
|
20
|
+
"""Create and configure argument parser with subcommands."""
|
|
21
|
+
parser = argparse.ArgumentParser(
|
|
22
|
+
description=(
|
|
23
|
+
"UniMotifComparator: Compare motifs using three distinct approaches - score, sequence, and tomtom-like"
|
|
24
|
+
),
|
|
25
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
26
|
+
epilog="""
|
|
27
|
+
Examples:
|
|
28
|
+
# Profile-based comparison
|
|
29
|
+
unimotifcomparator profile scores_1.fasta scores_2.fasta \\
|
|
30
|
+
--metric corr --perm 1000 --distortion 0.5
|
|
31
|
+
|
|
32
|
+
# Motif-based comparison with PWM models
|
|
33
|
+
unimotifcomparator motif model1.meme model2.pfm --model1-type pwm --model2-type pwm \\
|
|
34
|
+
--fasta sequences.fa --metric co --perm 1000 \\
|
|
35
|
+
--distortion 0.3
|
|
36
|
+
|
|
37
|
+
# Motif-based comparison with BAMM models
|
|
38
|
+
unimotifcomparator motif model1.hbcp model2.ihbcp --model1-type bamm --model2-type bamm \\
|
|
39
|
+
--fasta sequences.fa --promoters promoters.fa --search-range 15 \\
|
|
40
|
+
--min-kernel-size 5 --max-kernel-size 15 --jobs 4 --seed 42
|
|
41
|
+
|
|
42
|
+
# Motali comparison with SiteGA models
|
|
43
|
+
unimotifcomparator motali model1.mat model2.meme --model1-type sitega --model2-type pwm \\
|
|
44
|
+
--fasta sequences.fa --promoters promoters.fa \\
|
|
45
|
+
--tmp-dir . --num-sequences 5000 --seq-length 150
|
|
46
|
+
|
|
47
|
+
# TomTom-like comparison with PWM models
|
|
48
|
+
unimotifcomparator tomtom-like model1.meme model2.pfm --model1-type pwm --model2-type pwm \\
|
|
49
|
+
--metric pcc --permutations 1000 --permute-rows \\
|
|
50
|
+
--jobs 8 --seed 123
|
|
51
|
+
|
|
52
|
+
# TomTom-like comparison with BAMM models using PFM mode
|
|
53
|
+
unimotifcomparator tomtom-like model1.hbcp model2.ihbcp --model1-type bamm --model2-type bamm \\
|
|
54
|
+
--pfm-mode --num-sequences 10000 --seq-length 120 --metric ed \\
|
|
55
|
+
--permutations 500 --permute-rows
|
|
56
|
+
""",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Create subparsers for the three main modes
|
|
60
|
+
subparsers = parser.add_subparsers(dest="mode", help="Operation mode", required=True)
|
|
61
|
+
|
|
62
|
+
# Score-based comparison subcommand
|
|
63
|
+
profile_parser = subparsers.add_parser(
|
|
64
|
+
"profile", help="Compare motifs based on pre-calculated score profiles (uses DataComparator engine)."
|
|
65
|
+
)
|
|
66
|
+
profile_parser.add_argument("profile1", help="Path to the first profile file containing pre-calculated scores.")
|
|
67
|
+
profile_parser.add_argument("profile2", help="Path to the second profile file containing pre-calculated scores.")
|
|
68
|
+
|
|
69
|
+
profile_group = profile_parser.add_argument_group("Profile Comparator Options")
|
|
70
|
+
profile_group.add_argument(
|
|
71
|
+
"--metric",
|
|
72
|
+
choices=["cj", "co", "corr"],
|
|
73
|
+
default="cj",
|
|
74
|
+
help=(
|
|
75
|
+
"Similarity metric for comparing frequency profiles. "
|
|
76
|
+
"Choices: cj (Continuous Jaccard), co (Continuous Overlap), "
|
|
77
|
+
"corr (Pearson Correlation). (default: %(default)s)"
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
profile_group.add_argument(
|
|
81
|
+
"--perm",
|
|
82
|
+
type=int,
|
|
83
|
+
default=0,
|
|
84
|
+
help="Number of permutations to perform for p-value calculation. (default: %(default)s)",
|
|
85
|
+
)
|
|
86
|
+
profile_group.add_argument(
|
|
87
|
+
"--distortion",
|
|
88
|
+
type=float,
|
|
89
|
+
default=0.4,
|
|
90
|
+
help=(
|
|
91
|
+
"Distortion level (0.0-1.0) applied to kernels during surrogate data generation. "
|
|
92
|
+
"Higher values increase variance in the null model. Used for cj and co options. "
|
|
93
|
+
"(default: %(default)s)"
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
profile_group.add_argument(
|
|
97
|
+
"--search-range",
|
|
98
|
+
type=int,
|
|
99
|
+
default=10,
|
|
100
|
+
help="Maximum offset (shift) range to explore when aligning profiles. (default: %(default)s)",
|
|
101
|
+
)
|
|
102
|
+
profile_group.add_argument(
|
|
103
|
+
"--min-kernel-size",
|
|
104
|
+
type=int,
|
|
105
|
+
default=3,
|
|
106
|
+
help=(
|
|
107
|
+
"Minimum kernel size for convolution during surrogate generation. "
|
|
108
|
+
"Used for cj and co options. (default: %(default)s)"
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
profile_group.add_argument(
|
|
112
|
+
"--max-kernel-size",
|
|
113
|
+
type=int,
|
|
114
|
+
default=11,
|
|
115
|
+
help=(
|
|
116
|
+
"Maximum kernel size for convolution during surrogate generation. "
|
|
117
|
+
"Used for cj and co options. (default: %(default)s)"
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
profile_technical_group = profile_parser.add_argument_group("Technical Options")
|
|
122
|
+
profile_technical_group.add_argument(
|
|
123
|
+
"-v",
|
|
124
|
+
"--verbose",
|
|
125
|
+
action="store_true",
|
|
126
|
+
help="Enable verbose logging to standard output for detailed execution tracking.",
|
|
127
|
+
)
|
|
128
|
+
profile_technical_group.add_argument(
|
|
129
|
+
"--seed",
|
|
130
|
+
type=int,
|
|
131
|
+
help=(
|
|
132
|
+
"Set a global random seed for reproducible results in stochastic operations "
|
|
133
|
+
"(e.g., permutations, surrogate generation)."
|
|
134
|
+
),
|
|
135
|
+
)
|
|
136
|
+
profile_technical_group.add_argument(
|
|
137
|
+
"--jobs",
|
|
138
|
+
type=int,
|
|
139
|
+
default=-1,
|
|
140
|
+
help="Number of parallel jobs to run. Set to -1 to use all available CPU cores. (default: %(default)s)",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Motif scan-based comparison subcommand
|
|
144
|
+
motif_parser = subparsers.add_parser(
|
|
145
|
+
"motif", help="Compare motifs by calculating scores derived from scanning sequences with models."
|
|
146
|
+
)
|
|
147
|
+
motif_parser.add_argument("model1", help="Path to the first motif model file.")
|
|
148
|
+
motif_parser.add_argument("model2", help="Path to the second motif model file.")
|
|
149
|
+
|
|
150
|
+
# Input/Output Options for sequence parser
|
|
151
|
+
motif_io_group = motif_parser.add_argument_group("Input/Output Options")
|
|
152
|
+
motif_io_group.add_argument(
|
|
153
|
+
"--model1-type",
|
|
154
|
+
choices=["pwm", "bamm", "sitega"],
|
|
155
|
+
required=True,
|
|
156
|
+
help="Format of the first model. Choices: pwm, bamm, sitega.",
|
|
157
|
+
)
|
|
158
|
+
motif_io_group.add_argument(
|
|
159
|
+
"--model2-type",
|
|
160
|
+
choices=["pwm", "bamm", "sitega"],
|
|
161
|
+
required=True,
|
|
162
|
+
help="Format of the second model. Choices: pwm, bamm, sitega.",
|
|
163
|
+
)
|
|
164
|
+
motif_io_group.add_argument(
|
|
165
|
+
"--fasta",
|
|
166
|
+
help=(
|
|
167
|
+
"Path to a FASTA file containing target sequences for comparison. "
|
|
168
|
+
"If omitted, random sequences are generated."
|
|
169
|
+
),
|
|
170
|
+
)
|
|
171
|
+
motif_io_group.add_argument(
|
|
172
|
+
"--promoters",
|
|
173
|
+
help=(
|
|
174
|
+
"Path to a FASTA file containing promoter sequences, required for "
|
|
175
|
+
"calculating threshold tables in Motali comparisons."
|
|
176
|
+
),
|
|
177
|
+
)
|
|
178
|
+
motif_io_group.add_argument(
|
|
179
|
+
"--num-sequences",
|
|
180
|
+
type=int,
|
|
181
|
+
default=1000,
|
|
182
|
+
help="Number of random sequences to generate if --fasta is not provided. (default: %(default)s)",
|
|
183
|
+
)
|
|
184
|
+
motif_io_group.add_argument(
|
|
185
|
+
"--seq-length",
|
|
186
|
+
type=int,
|
|
187
|
+
default=200,
|
|
188
|
+
help="Length of each random sequence to generate if --fasta is not provided. (default: %(default)s)",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Motif / Data Comparator options
|
|
192
|
+
motif_group = motif_parser.add_argument_group("Motif Comparator Options")
|
|
193
|
+
motif_group.add_argument(
|
|
194
|
+
"--metric",
|
|
195
|
+
choices=["cj", "co", "corr"],
|
|
196
|
+
default="cj",
|
|
197
|
+
help=(
|
|
198
|
+
"Similarity metric for comparing frequency profiles. "
|
|
199
|
+
"Choices: cj (Continuous Jaccard), co (Continuous Overlap), "
|
|
200
|
+
"corr (Pearson Correlation). (default: %(default)s)"
|
|
201
|
+
),
|
|
202
|
+
)
|
|
203
|
+
motif_group.add_argument(
|
|
204
|
+
"--perm",
|
|
205
|
+
type=int,
|
|
206
|
+
default=0,
|
|
207
|
+
help="Number of permutations to perform for p-value calculation. (default: %(default)s)",
|
|
208
|
+
)
|
|
209
|
+
motif_group.add_argument(
|
|
210
|
+
"--distortion",
|
|
211
|
+
type=float,
|
|
212
|
+
default=0.4,
|
|
213
|
+
help=(
|
|
214
|
+
"Distortion level (0.0-1.0) applied to kernels during surrogate data generation. "
|
|
215
|
+
"Higher values increase variance in the null model. Used for cj and co options. "
|
|
216
|
+
"(default: %(default)s)"
|
|
217
|
+
),
|
|
218
|
+
)
|
|
219
|
+
motif_group.add_argument(
|
|
220
|
+
"--search-range",
|
|
221
|
+
type=int,
|
|
222
|
+
default=10,
|
|
223
|
+
help="Maximum offset (shift) range to explore when aligning profiles. (default: %(default)s)",
|
|
224
|
+
)
|
|
225
|
+
motif_group.add_argument(
|
|
226
|
+
"--min-kernel-size",
|
|
227
|
+
type=int,
|
|
228
|
+
default=3,
|
|
229
|
+
help=(
|
|
230
|
+
"Minimum kernel size for convolution during surrogate generation. "
|
|
231
|
+
"Used for cj and co options. (default: %(default)s)"
|
|
232
|
+
),
|
|
233
|
+
)
|
|
234
|
+
motif_group.add_argument(
|
|
235
|
+
"--max-kernel-size",
|
|
236
|
+
type=int,
|
|
237
|
+
default=11,
|
|
238
|
+
help=(
|
|
239
|
+
"Maximum kernel size for convolution during surrogate generation. "
|
|
240
|
+
"Used for cj and co options. (default: %(default)s)"
|
|
241
|
+
),
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
motif_technical_group = motif_parser.add_argument_group("Technical Options")
|
|
245
|
+
motif_technical_group.add_argument(
|
|
246
|
+
"-v",
|
|
247
|
+
"--verbose",
|
|
248
|
+
action="store_true",
|
|
249
|
+
help="Enable verbose logging to standard output for detailed execution tracking.",
|
|
250
|
+
)
|
|
251
|
+
motif_technical_group.add_argument(
|
|
252
|
+
"--seed",
|
|
253
|
+
type=int,
|
|
254
|
+
help=(
|
|
255
|
+
"Set a global random seed for reproducible results in stochastic operations "
|
|
256
|
+
"(e.g., permutations, surrogate generation)."
|
|
257
|
+
),
|
|
258
|
+
)
|
|
259
|
+
motif_technical_group.add_argument(
|
|
260
|
+
"--jobs",
|
|
261
|
+
type=int,
|
|
262
|
+
default=-1,
|
|
263
|
+
help="Number of parallel jobs to run. Set to -1 to use all available CPU cores. (default: %(default)s)",
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Motali
|
|
267
|
+
motali_parser = subparsers.add_parser(
|
|
268
|
+
"motali", help="Compare motifs by calculating PRC AUC derived from scanning sequences with models."
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Motali-specific options
|
|
272
|
+
motali_group = motali_parser.add_argument_group("Motali Options")
|
|
273
|
+
|
|
274
|
+
motali_group.add_argument("model1", help="Path to the first motif model file.")
|
|
275
|
+
motali_group.add_argument("model2", help="Path to the second motif model file.")
|
|
276
|
+
|
|
277
|
+
# Input/Output Options for sequence parser
|
|
278
|
+
motali_io_group = motali_parser.add_argument_group("Input/Output Options")
|
|
279
|
+
motali_io_group.add_argument(
|
|
280
|
+
"--model1-type",
|
|
281
|
+
choices=["pwm", "sitega"],
|
|
282
|
+
required=True,
|
|
283
|
+
help="Format of the first model. Choices: pwm, bamm, sitega.",
|
|
284
|
+
)
|
|
285
|
+
motali_io_group.add_argument(
|
|
286
|
+
"--model2-type",
|
|
287
|
+
choices=["pwm", "sitega"],
|
|
288
|
+
required=True,
|
|
289
|
+
help="Format of the second model. Choices: pwm, bamm, sitega.",
|
|
290
|
+
)
|
|
291
|
+
motali_io_group.add_argument(
|
|
292
|
+
"--fasta",
|
|
293
|
+
help=(
|
|
294
|
+
"Path to a FASTA file containing target sequences for comparison. "
|
|
295
|
+
"If omitted, random sequences are generated."
|
|
296
|
+
),
|
|
297
|
+
)
|
|
298
|
+
motali_io_group.add_argument(
|
|
299
|
+
"--promoters",
|
|
300
|
+
help=(
|
|
301
|
+
"Path to a FASTA file containing promoter sequences, required for "
|
|
302
|
+
"calculating threshold tables in Motali comparisons."
|
|
303
|
+
),
|
|
304
|
+
)
|
|
305
|
+
motali_io_group.add_argument(
|
|
306
|
+
"--num-sequences",
|
|
307
|
+
type=int,
|
|
308
|
+
default=10000,
|
|
309
|
+
help="Number of random sequences to generate if --fasta is not provided. (default: %(default)s)",
|
|
310
|
+
)
|
|
311
|
+
motali_io_group.add_argument(
|
|
312
|
+
"--seq-length",
|
|
313
|
+
type=int,
|
|
314
|
+
default=200,
|
|
315
|
+
help="Length of each random sequence to generate if --fasta is not provided. (default: %(default)s)",
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
motali_io_group.add_argument(
|
|
319
|
+
"--tmp-dir",
|
|
320
|
+
default=".",
|
|
321
|
+
help=(
|
|
322
|
+
"Directory path for storing temporary intermediate files generated "
|
|
323
|
+
"during Motali execution. (default: %(default)s)"
|
|
324
|
+
),
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
motali_technical_group = motali_parser.add_argument_group("Technical Options")
|
|
328
|
+
motali_technical_group.add_argument(
|
|
329
|
+
"-v",
|
|
330
|
+
"--verbose",
|
|
331
|
+
action="store_true",
|
|
332
|
+
help="Enable verbose logging to standard output for detailed execution tracking.",
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
# TomTom-like comparison subcommand
|
|
336
|
+
tomtom_parser = subparsers.add_parser(
|
|
337
|
+
"tomtom-like", help="Compare motifs by direct matrix comparison (uses TomTomComparator engine)."
|
|
338
|
+
)
|
|
339
|
+
tomtom_parser.add_argument("model1", help="Path to the first motif model file.")
|
|
340
|
+
tomtom_parser.add_argument("model2", help="Path to the second motif model file.")
|
|
341
|
+
|
|
342
|
+
# Input/Output Options for tomtom parser
|
|
343
|
+
tomtom_io_group = tomtom_parser.add_argument_group("Input/Output Options")
|
|
344
|
+
tomtom_io_group.add_argument(
|
|
345
|
+
"--model1-type",
|
|
346
|
+
choices=["pwm", "bamm", "sitega"],
|
|
347
|
+
required=True,
|
|
348
|
+
help="Format of the first model. Choices: pwm, bamm, sitega.",
|
|
349
|
+
)
|
|
350
|
+
tomtom_io_group.add_argument(
|
|
351
|
+
"--model2-type",
|
|
352
|
+
choices=["pwm", "bamm", "sitega"],
|
|
353
|
+
required=True,
|
|
354
|
+
help="Format of the second model. Choices: pwm, bamm, sitega.",
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
# TomTom-specific options
|
|
358
|
+
tomtom_options_group = tomtom_parser.add_argument_group("TomTom Options")
|
|
359
|
+
tomtom_options_group.add_argument(
|
|
360
|
+
"--metric",
|
|
361
|
+
choices=["pcc", "ed", "cosine"],
|
|
362
|
+
default="pcc",
|
|
363
|
+
help=(
|
|
364
|
+
"Metric for column-wise motif comparison. "
|
|
365
|
+
"Choices: pcc (Pearson Correlation Coefficient), ed (Euclidean Distance), "
|
|
366
|
+
"cosine (Cosine Similarity). (default: %(default)s)"
|
|
367
|
+
),
|
|
368
|
+
)
|
|
369
|
+
tomtom_options_group.add_argument(
|
|
370
|
+
"--permutations",
|
|
371
|
+
type=int,
|
|
372
|
+
default=0,
|
|
373
|
+
help="Number of Monte Carlo permutations for p-value estimation. (default: %(default)s)",
|
|
374
|
+
)
|
|
375
|
+
tomtom_options_group.add_argument(
|
|
376
|
+
"--permute-rows",
|
|
377
|
+
action="store_true",
|
|
378
|
+
help=(
|
|
379
|
+
"If set, shuffles values within columns during permutation, destroying "
|
|
380
|
+
"nucleotide dependencies. Default behavior shuffles only columns (positions)."
|
|
381
|
+
),
|
|
382
|
+
)
|
|
383
|
+
tomtom_options_group.add_argument(
|
|
384
|
+
"--pfm-mode",
|
|
385
|
+
action="store_true",
|
|
386
|
+
help=(
|
|
387
|
+
"If set, a Position Frequency Matrix (PFM) is derived for the model motifs "
|
|
388
|
+
"by scanning sequences and constructing the PFM based on the top 5% of "
|
|
389
|
+
"predicted binding sites"
|
|
390
|
+
),
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
tomtom_options_group.add_argument(
|
|
394
|
+
"--num-sequences",
|
|
395
|
+
type=int,
|
|
396
|
+
default=20000,
|
|
397
|
+
help="Number of random sequences to generate if --pfm-mode is used. (default: %(default)s)",
|
|
398
|
+
)
|
|
399
|
+
tomtom_options_group.add_argument(
|
|
400
|
+
"--seq-length",
|
|
401
|
+
type=int,
|
|
402
|
+
default=100,
|
|
403
|
+
help="Length of each random sequence to generate if --pfm-mode is used. (default: %(default)s)",
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
tomtom_technical_group = tomtom_parser.add_argument_group("Technical Options")
|
|
407
|
+
tomtom_technical_group.add_argument(
|
|
408
|
+
"-v",
|
|
409
|
+
"--verbose",
|
|
410
|
+
action="store_true",
|
|
411
|
+
help="Enable verbose logging to standard output for detailed execution tracking.",
|
|
412
|
+
)
|
|
413
|
+
tomtom_technical_group.add_argument(
|
|
414
|
+
"--seed",
|
|
415
|
+
type=int,
|
|
416
|
+
help=(
|
|
417
|
+
"Set a global random seed for reproducible results in stochastic operations "
|
|
418
|
+
"(e.g., permutations, surrogate generation)."
|
|
419
|
+
),
|
|
420
|
+
)
|
|
421
|
+
tomtom_technical_group.add_argument(
|
|
422
|
+
"--jobs",
|
|
423
|
+
type=int,
|
|
424
|
+
default=-1,
|
|
425
|
+
help="Number of parallel jobs to run. Set to -1 to use all available CPU cores. (default: %(default)s)",
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
return parser
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def validate_inputs(args) -> None:
|
|
432
|
+
"""Validate input files and parameters."""
|
|
433
|
+
logger = logging.getLogger(__name__)
|
|
434
|
+
# Validate mode-specific inputs
|
|
435
|
+
if args.mode == "profile":
|
|
436
|
+
if not os.path.exists(args.profile1):
|
|
437
|
+
logger.error(f"Profile file not found: {args.profile1}")
|
|
438
|
+
sys.exit(1)
|
|
439
|
+
if not os.path.exists(args.profile2):
|
|
440
|
+
logger.error(f"Profile file not found: {args.profile2}")
|
|
441
|
+
sys.exit(1)
|
|
442
|
+
|
|
443
|
+
elif args.mode in ["motif", "motali"]:
|
|
444
|
+
suffix_1 = ""
|
|
445
|
+
suffix_2 = ""
|
|
446
|
+
if args.model1_type == "bamm":
|
|
447
|
+
suffix_1 = ".ihbcp"
|
|
448
|
+
if args.model2_type == "bamm":
|
|
449
|
+
suffix_2 = ".ihbcp"
|
|
450
|
+
|
|
451
|
+
if not os.path.exists(args.model1 + suffix_1):
|
|
452
|
+
logger.error(f"Model file not found: {args.model1}")
|
|
453
|
+
sys.exit(1)
|
|
454
|
+
if not os.path.exists(args.model2 + suffix_2):
|
|
455
|
+
logger.error(f"Model file not found: {args.model2}")
|
|
456
|
+
sys.exit(1)
|
|
457
|
+
if args.fasta and not os.path.exists(args.fasta):
|
|
458
|
+
logger.error(f"FASTA file not found: {args.fasta}")
|
|
459
|
+
sys.exit(1)
|
|
460
|
+
if args.promoters and not os.path.exists(args.promoters):
|
|
461
|
+
logger.error(f"Promoter threshold file not found: {args.promoters}")
|
|
462
|
+
sys.exit(1)
|
|
463
|
+
|
|
464
|
+
elif args.mode == "tomtom-like":
|
|
465
|
+
suffix_1 = ""
|
|
466
|
+
suffix_2 = ""
|
|
467
|
+
if args.model1_type == "bamm":
|
|
468
|
+
suffix_1 = ".ihbcp"
|
|
469
|
+
if args.model2_type == "bamm":
|
|
470
|
+
suffix_2 = ".ihbcp"
|
|
471
|
+
|
|
472
|
+
if not os.path.exists(args.model1 + suffix_1):
|
|
473
|
+
logger.error(f"Model file not found: {args.model1}")
|
|
474
|
+
sys.exit(1)
|
|
475
|
+
if not os.path.exists(args.model2 + suffix_2):
|
|
476
|
+
logger.error(f"Model file not found: {args.model2}")
|
|
477
|
+
sys.exit(1)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def map_args_to_pipeline_kwargs(args) -> Dict[str, Any]:
|
|
481
|
+
"""Map CLI arguments to pipeline keyword arguments."""
|
|
482
|
+
kwargs = {}
|
|
483
|
+
|
|
484
|
+
if args.mode == "tomtom-like":
|
|
485
|
+
kwargs.update(
|
|
486
|
+
{
|
|
487
|
+
"metric": getattr(args, "metric", "pcc"),
|
|
488
|
+
"n_permutations": getattr(args, "permutations", 1000),
|
|
489
|
+
"permute_rows": getattr(args, "permute_rows", False),
|
|
490
|
+
"n_jobs": getattr(args, "jobs", -1),
|
|
491
|
+
"seed": getattr(args, "seed", None),
|
|
492
|
+
"pfm_mode": getattr(args, "pfm_mode", False),
|
|
493
|
+
"comparator": "tomtom",
|
|
494
|
+
}
|
|
495
|
+
)
|
|
496
|
+
elif args.mode == "motali":
|
|
497
|
+
kwargs.update({"fasta_path": getattr(args, "fasta", None), "tmp_directory": getattr(args, "tmp_dir", ".")})
|
|
498
|
+
elif args.mode == "motif":
|
|
499
|
+
kwargs.update(
|
|
500
|
+
{
|
|
501
|
+
"metric": getattr(args, "metric", "cj"),
|
|
502
|
+
"n_permutations": getattr(args, "perm", 1000),
|
|
503
|
+
"distortion_level": getattr(args, "distortion", 0.4),
|
|
504
|
+
"n_jobs": getattr(args, "jobs", -1),
|
|
505
|
+
"permute_rows": getattr(args, "permute_rows", False),
|
|
506
|
+
"pfm_mode": getattr(args, "pfm_mode", False),
|
|
507
|
+
"seed": getattr(args, "seed", None),
|
|
508
|
+
"search_range": getattr(args, "search_range", 10),
|
|
509
|
+
"min_kernel_size": getattr(args, "min_kernel_size", 3),
|
|
510
|
+
"max_kernel_size": getattr(args, "max_kernel_size", 11),
|
|
511
|
+
}
|
|
512
|
+
)
|
|
513
|
+
elif args.mode == "profile":
|
|
514
|
+
kwargs.update(
|
|
515
|
+
{
|
|
516
|
+
"metric": getattr(args, "metric", "cj"),
|
|
517
|
+
"n_permutations": getattr(args, "perm", 1000),
|
|
518
|
+
"distortion_level": getattr(args, "distortion", 0.4),
|
|
519
|
+
"n_jobs": getattr(args, "jobs", -1),
|
|
520
|
+
"seed": getattr(args, "seed", None),
|
|
521
|
+
"search_range": getattr(args, "search_range", 10),
|
|
522
|
+
"min_kernel_size": getattr(args, "min_kernel_size", 3),
|
|
523
|
+
"max_kernel_size": getattr(args, "max_kernel_size", 11),
|
|
524
|
+
}
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
return kwargs
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def main_cli():
|
|
531
|
+
"""Main CLI entry point."""
|
|
532
|
+
# Parse arguments
|
|
533
|
+
parser = create_arg_parser()
|
|
534
|
+
|
|
535
|
+
if len(sys.argv) == 1:
|
|
536
|
+
parser.print_help(sys.stderr)
|
|
537
|
+
sys.exit(1)
|
|
538
|
+
|
|
539
|
+
args = parser.parse_args()
|
|
540
|
+
|
|
541
|
+
# Setup logging
|
|
542
|
+
setup_logging(args.verbose)
|
|
543
|
+
|
|
544
|
+
# Validate inputs
|
|
545
|
+
validate_inputs(args)
|
|
546
|
+
|
|
547
|
+
# Prepare pipeline arguments based on mode
|
|
548
|
+
if args.mode == "profile":
|
|
549
|
+
# Profile-based comparison
|
|
550
|
+
model1_path = args.profile1
|
|
551
|
+
model2_path = args.profile2
|
|
552
|
+
seq_source1 = None
|
|
553
|
+
seq_source2 = None
|
|
554
|
+
|
|
555
|
+
elif args.mode in ["motif", "motali"]:
|
|
556
|
+
# Sequence-based comparison
|
|
557
|
+
model1_path = args.model1
|
|
558
|
+
model2_path = args.model2
|
|
559
|
+
seq_source1 = args.fasta
|
|
560
|
+
seq_source2 = args.promoters
|
|
561
|
+
|
|
562
|
+
elif args.mode == "tomtom-like":
|
|
563
|
+
model1_path = args.model1
|
|
564
|
+
model2_path = args.model2
|
|
565
|
+
seq_source1 = None
|
|
566
|
+
seq_source2 = None
|
|
567
|
+
|
|
568
|
+
else:
|
|
569
|
+
logger = logging.getLogger(__name__)
|
|
570
|
+
logger.error(f"Unknown mode: {args.mode}")
|
|
571
|
+
sys.exit(1)
|
|
572
|
+
|
|
573
|
+
# Map CLI arguments to pipeline kwargs
|
|
574
|
+
pipeline_kwargs = map_args_to_pipeline_kwargs(args)
|
|
575
|
+
|
|
576
|
+
if args.verbose:
|
|
577
|
+
logger = logging.getLogger(__name__)
|
|
578
|
+
logger.info("=" * 60)
|
|
579
|
+
logger.info(f"UniMotifComparator Pipeline - {args.mode.capitalize()} Mode")
|
|
580
|
+
logger.info("=" * 60)
|
|
581
|
+
logger.info(f"Comparison method: {args.mode}")
|
|
582
|
+
logger.info(f"Model 1: {model1_path}")
|
|
583
|
+
logger.info(f"Model 2: {model2_path}")
|
|
584
|
+
if args.mode in ["motif", "motali", "tomtom-like"]:
|
|
585
|
+
logger.info(f"Model 1 type: {getattr(args, 'model1_type', 'N/A')}")
|
|
586
|
+
logger.info(f"Model 2 type: {getattr(args, 'model2_type', 'N/A')}")
|
|
587
|
+
if args.mode in ["motif", "motali"]:
|
|
588
|
+
logger.info(f"Sequences: {args.fasta or 'Generated internally'}")
|
|
589
|
+
logger.info("=" * 60)
|
|
590
|
+
|
|
591
|
+
try:
|
|
592
|
+
# Run the pipeline
|
|
593
|
+
comparison_type = args.mode
|
|
594
|
+
result = run_pipeline(
|
|
595
|
+
model1_path=model1_path,
|
|
596
|
+
model2_path=model2_path,
|
|
597
|
+
model1_type=getattr(args, "model1_type", ""),
|
|
598
|
+
model2_type=getattr(args, "model2_type", ""),
|
|
599
|
+
comparison_type=comparison_type,
|
|
600
|
+
seq_source1=seq_source1,
|
|
601
|
+
seq_source2=seq_source2,
|
|
602
|
+
num_sequences=getattr(args, "num_sequences", 1000),
|
|
603
|
+
seq_length=getattr(args, "seq_length", 200),
|
|
604
|
+
**pipeline_kwargs,
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
logger = logging.getLogger(__name__)
|
|
608
|
+
json_string = json.dumps(result)
|
|
609
|
+
print(json_string)
|
|
610
|
+
|
|
611
|
+
except Exception as e:
|
|
612
|
+
print(f"ERROR: Pipeline execution failed: {e}")
|
|
613
|
+
if args.verbose:
|
|
614
|
+
import traceback
|
|
615
|
+
|
|
616
|
+
traceback.print_exc()
|
|
617
|
+
sys.exit(1)
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
if __name__ == "__main__":
|
|
621
|
+
main_cli()
|