cutseq 0.0.4__tar.gz → 0.0.6__tar.gz
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.
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
# Created: 2024-04-19 18:57
|
|
8
8
|
|
|
9
9
|
import argparse
|
|
10
|
+
import importlib.metadata
|
|
10
11
|
import logging
|
|
11
12
|
import re
|
|
12
13
|
import subprocess
|
|
@@ -18,6 +19,7 @@ from cutadapt.adapters import (
|
|
|
18
19
|
NonInternalFrontAdapter,
|
|
19
20
|
PrefixAdapter,
|
|
20
21
|
RightmostFrontAdapter,
|
|
22
|
+
SuffixAdapter,
|
|
21
23
|
)
|
|
22
24
|
from cutadapt.files import InputPaths, OutputFiles
|
|
23
25
|
from cutadapt.modifiers import (
|
|
@@ -29,18 +31,33 @@ from cutadapt.modifiers import (
|
|
|
29
31
|
UnconditionalCutter,
|
|
30
32
|
)
|
|
31
33
|
from cutadapt.pipeline import PairedEndPipeline, SingleEndPipeline
|
|
32
|
-
from cutadapt.predicates import
|
|
34
|
+
from cutadapt.predicates import Predicate, TooShort
|
|
35
|
+
from cutadapt.report import Statistics
|
|
33
36
|
from cutadapt.runners import make_runner
|
|
34
37
|
from cutadapt.steps import (
|
|
35
|
-
InfoFileWriter,
|
|
36
38
|
PairedEndFilter,
|
|
37
39
|
PairedEndSink,
|
|
38
|
-
PairedSingleEndStep,
|
|
39
40
|
SingleEndFilter,
|
|
40
41
|
SingleEndSink,
|
|
41
42
|
)
|
|
42
43
|
from cutadapt.utils import Progress
|
|
43
44
|
|
|
45
|
+
# monkey patching ....
|
|
46
|
+
original_method = Statistics._collect_modifier
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def patched_problematic_method(self, *args, **kwargs):
|
|
50
|
+
try:
|
|
51
|
+
return original_method(self, *args, **kwargs)
|
|
52
|
+
except AssertionError:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
Statistics._collect_modifier = patched_problematic_method
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
__version__ = importlib.metadata.version(__package__ or __name__)
|
|
60
|
+
|
|
44
61
|
logging.basicConfig(
|
|
45
62
|
level=logging.INFO,
|
|
46
63
|
format="%(asctime)s - %(levelname)s - %(message)s",
|
|
@@ -118,7 +135,7 @@ class BarcodeConfig:
|
|
|
118
135
|
class CutadaptConfig:
|
|
119
136
|
def __init__(self):
|
|
120
137
|
self.rname_suffix = False
|
|
121
|
-
self.
|
|
138
|
+
self.ensure_inline_barcode = False
|
|
122
139
|
self.trim_polyA = False
|
|
123
140
|
self.min_length = 20
|
|
124
141
|
self.min_quality = 20
|
|
@@ -126,6 +143,25 @@ class CutadaptConfig:
|
|
|
126
143
|
self.threads = 1
|
|
127
144
|
|
|
128
145
|
|
|
146
|
+
class IsUntrimmedAny(Predicate):
|
|
147
|
+
"""
|
|
148
|
+
Select reads for which no adapter match was found
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
def __init__(self, ref_adapters):
|
|
152
|
+
self.ref_adapters = ref_adapters
|
|
153
|
+
|
|
154
|
+
def __repr__(self):
|
|
155
|
+
return "IsUntrimmedAny()"
|
|
156
|
+
|
|
157
|
+
def test(self, read, info):
|
|
158
|
+
# check not all adapters with ref_adapters are exist in the matches
|
|
159
|
+
match_adapters = [match.adapter for match in info.matches]
|
|
160
|
+
if any([adapter not in match_adapters for adapter in self.ref_adapters]):
|
|
161
|
+
return True
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
|
|
129
165
|
def run_steps(steps, dry_run=False):
|
|
130
166
|
if dry_run:
|
|
131
167
|
print(
|
|
@@ -138,7 +174,7 @@ def run_steps(steps, dry_run=False):
|
|
|
138
174
|
return process.stdout.decode(), process.stderr.decode()
|
|
139
175
|
|
|
140
176
|
|
|
141
|
-
def pipeline_single(input1, output1, short1,
|
|
177
|
+
def pipeline_single(input1, output1, short1, untrimmed1, barcode, settings):
|
|
142
178
|
modifiers = []
|
|
143
179
|
# step 1: remove suffix in the read name
|
|
144
180
|
modifiers.extend([SuffixRemover(".1"), SuffixRemover("/1")])
|
|
@@ -162,14 +198,11 @@ def pipeline_single(input1, output1, short1, untrimed1, barcode, settings):
|
|
|
162
198
|
)
|
|
163
199
|
# step 4: trim inline barcode
|
|
164
200
|
if barcode.inline5.len > 0:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
[PrefixAdapter(sequence=barcode.inline5.fw, max_errors=0.2)],
|
|
168
|
-
times=1,
|
|
169
|
-
)
|
|
170
|
-
)
|
|
201
|
+
adapter_inline5 = PrefixAdapter(sequence=barcode.inline5.fw, max_errors=0.2)
|
|
202
|
+
modifiers.append(AdapterCutter([adapter_inline5], times=1))
|
|
171
203
|
if barcode.inline3.len > 0:
|
|
172
|
-
|
|
204
|
+
adapter_inline3 = SuffixAdapter(sequence=barcode.inline3.fw, max_errors=0.2)
|
|
205
|
+
modifiers.append(AdapterCutter([adapter_inline3], times=1))
|
|
173
206
|
|
|
174
207
|
# step 5: extract UMI
|
|
175
208
|
if barcode.umi5.len > 0:
|
|
@@ -215,19 +248,35 @@ def pipeline_single(input1, output1, short1, untrimed1, barcode, settings):
|
|
|
215
248
|
qualities=runner.input_file_format().has_qualities(),
|
|
216
249
|
interleaved=False,
|
|
217
250
|
)
|
|
218
|
-
steps = [
|
|
251
|
+
steps = []
|
|
252
|
+
steps.append(
|
|
219
253
|
# --info-file=info.txt
|
|
220
254
|
# PairedSingleEndStep(InfoFileWriter(outfiles.open_text("info.txt"))),
|
|
221
255
|
# -m 10
|
|
222
256
|
SingleEndFilter(
|
|
223
257
|
TooShort(settings.min_length), outfiles.open_record_writer(short1)
|
|
224
258
|
),
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
259
|
+
)
|
|
260
|
+
if (
|
|
261
|
+
settings.ensure_inline_barcode
|
|
262
|
+
and barcode.inline5.len + barcode.inline3.len > 0
|
|
263
|
+
):
|
|
264
|
+
ref_adapters = []
|
|
265
|
+
if barcode.inline5.len > 0:
|
|
266
|
+
ref_adapters.append(adapter_inline5)
|
|
267
|
+
if barcode.inline3.len > 0:
|
|
268
|
+
ref_adapters.append(adapter_inline3)
|
|
269
|
+
steps.append(
|
|
270
|
+
# TODO: --max-n=0 support
|
|
271
|
+
SingleEndFilter(
|
|
272
|
+
IsUntrimmedAny(ref_adapters),
|
|
273
|
+
outfiles.open_record_writer(untrimmed1, interleaved=False),
|
|
274
|
+
),
|
|
275
|
+
)
|
|
276
|
+
steps.append(
|
|
228
277
|
# -o
|
|
229
278
|
SingleEndSink(outfiles.open_record_writer(output1, interleaved=False)),
|
|
230
|
-
|
|
279
|
+
)
|
|
231
280
|
pipeline = SingleEndPipeline(modifiers, steps)
|
|
232
281
|
_stats = runner.run(pipeline, Progress(), outfiles)
|
|
233
282
|
# _ = stats.as_json()
|
|
@@ -241,7 +290,7 @@ def pipeline_paired(
|
|
|
241
290
|
output2,
|
|
242
291
|
short1,
|
|
243
292
|
short2,
|
|
244
|
-
|
|
293
|
+
untrimmed1,
|
|
245
294
|
untrimmed2,
|
|
246
295
|
barcode,
|
|
247
296
|
settings,
|
|
@@ -290,23 +339,19 @@ def pipeline_paired(
|
|
|
290
339
|
)
|
|
291
340
|
# step 4: trim inline barcode
|
|
292
341
|
if barcode.inline5.len > 0:
|
|
342
|
+
adapter_inline5 = PrefixAdapter(sequence=barcode.inline5.fw, max_errors=0.2)
|
|
293
343
|
modifiers.append(
|
|
294
344
|
(
|
|
295
|
-
AdapterCutter(
|
|
296
|
-
[PrefixAdapter(sequence=barcode.inline5.fw, max_errors=0.2)],
|
|
297
|
-
times=1,
|
|
298
|
-
),
|
|
345
|
+
AdapterCutter([adapter_inline5], times=1),
|
|
299
346
|
UnconditionalCutter(-barcode.inline5.len),
|
|
300
347
|
)
|
|
301
348
|
)
|
|
302
349
|
if barcode.inline3.len > 0:
|
|
350
|
+
adapter_inline3 = PrefixAdapter(sequence=barcode.inline3.rc, max_errors=0.2)
|
|
303
351
|
modifiers.append(
|
|
304
352
|
(
|
|
305
353
|
UnconditionalCutter(-barcode.inline3.len),
|
|
306
|
-
AdapterCutter(
|
|
307
|
-
[BackAdapter(sequence=barcode.inline3.rc, max_errors=0.2)],
|
|
308
|
-
times=1,
|
|
309
|
-
),
|
|
354
|
+
AdapterCutter([adapter_inline3], times=1),
|
|
310
355
|
)
|
|
311
356
|
)
|
|
312
357
|
|
|
@@ -387,21 +432,40 @@ def pipeline_paired(
|
|
|
387
432
|
qualities=runner.input_file_format().has_qualities(),
|
|
388
433
|
interleaved=False,
|
|
389
434
|
)
|
|
390
|
-
steps = [
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
435
|
+
steps = []
|
|
436
|
+
# --info-file=info.txt
|
|
437
|
+
# PairedSingleEndStep(InfoFileWriter(outfiles.open_text("info.txt"))),
|
|
438
|
+
# -m 10:10
|
|
439
|
+
steps.append(
|
|
394
440
|
PairedEndFilter(
|
|
395
441
|
TooShort(settings.min_length),
|
|
396
442
|
TooShort(settings.min_length),
|
|
397
443
|
outfiles.open_record_writer(short1, short2, interleaved=False),
|
|
398
|
-
)
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
444
|
+
)
|
|
445
|
+
)
|
|
446
|
+
if (
|
|
447
|
+
settings.ensure_inline_barcode
|
|
448
|
+
and barcode.inline5.len + barcode.inline3.len > 0
|
|
449
|
+
):
|
|
450
|
+
steps.append(
|
|
451
|
+
# TODO: --max-n=0 support
|
|
452
|
+
PairedEndFilter(
|
|
453
|
+
IsUntrimmedAny([adapter_inline5])
|
|
454
|
+
if barcode.inline5.len > 0
|
|
455
|
+
else None,
|
|
456
|
+
IsUntrimmedAny([adapter_inline3])
|
|
457
|
+
if barcode.inline3.len > 0
|
|
458
|
+
else None,
|
|
459
|
+
outfiles.open_record_writer(
|
|
460
|
+
untrimmed1, untrimmed2, interleaved=False
|
|
461
|
+
),
|
|
462
|
+
pair_filter_mode="any",
|
|
463
|
+
)
|
|
464
|
+
)
|
|
465
|
+
steps.append(
|
|
402
466
|
# -o ... -p ...
|
|
403
|
-
PairedEndSink(outfiles.open_record_writer(output1, output2))
|
|
404
|
-
|
|
467
|
+
PairedEndSink(outfiles.open_record_writer(output1, output2))
|
|
468
|
+
)
|
|
405
469
|
pipeline = PairedEndPipeline(modifiers, steps)
|
|
406
470
|
_stats = runner.run(pipeline, Progress(), outfiles)
|
|
407
471
|
# _ = stats.as_json()
|
|
@@ -411,12 +475,9 @@ def pipeline_paired(
|
|
|
411
475
|
def run_cutseq(args):
|
|
412
476
|
barcode_config = BarcodeConfig(args.adapter_scheme.upper())
|
|
413
477
|
settings = CutadaptConfig()
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
settings.discarded_untrimmed = True
|
|
418
|
-
if args.trim_polyA:
|
|
419
|
-
settings.trim_polyA = True
|
|
478
|
+
settings.rname_suffix = args.with_rname_suffix
|
|
479
|
+
settings.ensure_inline_barcode = args.ensure_inline_barcode
|
|
480
|
+
settings.trim_polyA = args.trim_polyA
|
|
420
481
|
settings.threads = args.threads
|
|
421
482
|
settings.min_length = args.min_length
|
|
422
483
|
settings.dry_run = args.dry_run
|
|
@@ -506,6 +567,12 @@ def main():
|
|
|
506
567
|
action="store_true",
|
|
507
568
|
help="R1 and R2 suffix cotains suffix. MGI platform.",
|
|
508
569
|
)
|
|
570
|
+
|
|
571
|
+
parser.add_argument(
|
|
572
|
+
"--ensure-inline-barcode",
|
|
573
|
+
action="store_true",
|
|
574
|
+
help="Output discarded reads without inline barcode.",
|
|
575
|
+
)
|
|
509
576
|
parser.add_argument(
|
|
510
577
|
"-U",
|
|
511
578
|
"--untrimmed-file",
|
|
@@ -529,6 +596,11 @@ def main():
|
|
|
529
596
|
action="store_true",
|
|
530
597
|
help="Print command instead of running it.",
|
|
531
598
|
)
|
|
599
|
+
|
|
600
|
+
parser.add_argument(
|
|
601
|
+
"-V", "--version", action="version", version=f"%(prog)s {__version__}"
|
|
602
|
+
)
|
|
603
|
+
|
|
532
604
|
args = parser.parse_args()
|
|
533
605
|
|
|
534
606
|
if args.adapter_name is not None:
|
|
@@ -584,12 +656,8 @@ def main():
|
|
|
584
656
|
args.short_file = validate_output_file(
|
|
585
657
|
args.short_file, args.input_file, args.output_prefix, "short"
|
|
586
658
|
)
|
|
587
|
-
args.untrimmed_file = (
|
|
588
|
-
|
|
589
|
-
args.untrimmed_file, args.input_file, args.output_prefix, "untrimmed"
|
|
590
|
-
)
|
|
591
|
-
if args.untrimmed_file is not None
|
|
592
|
-
else [None] * len(args.input_file)
|
|
659
|
+
args.untrimmed_file = validate_output_file(
|
|
660
|
+
args.untrimmed_file, args.input_file, args.output_prefix, "untrimmed"
|
|
593
661
|
)
|
|
594
662
|
|
|
595
663
|
run_cutseq(args)
|
|
File without changes
|
|
File without changes
|