xengsort 2.1.0__tar.gz → 2.2.0__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.
Files changed (55) hide show
  1. {xengsort-2.1.0/xengsort.egg-info → xengsort-2.2.0}/PKG-INFO +1 -1
  2. {xengsort-2.1.0 → xengsort-2.2.0}/pyproject.toml +1 -1
  3. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/cptasks_kmers.py +4 -4
  4. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/hash_s3c_fbcbvb.py +1 -1
  5. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/io/fastqio.py +226 -2
  6. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/io/generalio.py +7 -2
  7. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/lowlevel/llvm.py +4 -2
  8. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/srhash.py +1 -1
  9. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/xengsort/config/classify.yaml +1 -1
  10. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/xengsort/xengsort_classify.py +261 -83
  11. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/xengsort/xengsort_index.py +5 -5
  12. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/xengsort/xengsort_main.py +4 -2
  13. {xengsort-2.1.0 → xengsort-2.2.0/xengsort.egg-info}/PKG-INFO +1 -1
  14. {xengsort-2.1.0 → xengsort-2.2.0}/LICENSE +0 -0
  15. {xengsort-2.1.0 → xengsort-2.2.0}/MANIFEST.in +0 -0
  16. {xengsort-2.1.0 → xengsort-2.2.0}/README.md +0 -0
  17. {xengsort-2.1.0 → xengsort-2.2.0}/setup.cfg +0 -0
  18. {xengsort-2.1.0 → xengsort-2.2.0}/tests/test_trimmed_reads.py +0 -0
  19. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/dnaencode.py +0 -0
  20. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/dnaencode_fast.py +0 -0
  21. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/fastcash_info.py +0 -0
  22. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/fastcash_main.py +0 -0
  23. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/fastcash_optimize.py +0 -0
  24. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/hash_minimizer.py +0 -0
  25. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/hash_srh_fbcbvb.py +0 -0
  26. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/io/binaryio.py +0 -0
  27. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/io/fastaio.py +0 -0
  28. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/io/hashio.py +0 -0
  29. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/io/seqio.py +0 -0
  30. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/io/textio.py +0 -0
  31. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/io/xorio.py +0 -0
  32. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/kmers.py +0 -0
  33. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/lowlevel/aligned_arrays.py +0 -0
  34. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/lowlevel/bitarray.py +0 -0
  35. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/lowlevel/conpro.py +0 -0
  36. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/lowlevel/debug.py +0 -0
  37. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/lowlevel/intbitarray.py +0 -0
  38. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/lowlevel/libc.py +0 -0
  39. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/lowlevel/numbautils.py +0 -0
  40. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/lowlevel/packedarray.py +0 -0
  41. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/mask.py +0 -0
  42. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/mathutils.py +0 -0
  43. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/parameters.py +0 -0
  44. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/subtable_hashfunctions.py +0 -0
  45. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/values/xenograft.py +0 -0
  46. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/xengsort/config/index.yaml +0 -0
  47. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/xengsort/xengsort_info.py +0 -0
  48. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/xengsort/xengsort_load.py +0 -0
  49. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/xengsort/xengsort_remove.py +0 -0
  50. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort/xengsort/xengsort_weak.py +0 -0
  51. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort.egg-info/SOURCES.txt +0 -0
  52. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort.egg-info/dependency_links.txt +0 -0
  53. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort.egg-info/entry_points.txt +0 -0
  54. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort.egg-info/requires.txt +0 -0
  55. {xengsort-2.1.0 → xengsort-2.2.0}/xengsort.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xengsort
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: xenograft sorting of FASTQ reads into five categories: host, graft, both, neither, ambiguous.
5
5
  Author-email: Jens Zentgraf <zentgraf@cs.uni-saarland.de>, Sven Rahmann <rahmann@cs.uni-saarland.de>
6
6
  License: MIT License
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "xengsort"
8
- version = "2.1.0"
8
+ version = "2.2.0"
9
9
 
10
10
  license = {file = "LICENSE"}
11
11
 
@@ -33,7 +33,7 @@ def compile_cptask_scatter_kmers_from_linemarked(
33
33
  mask, rcmode, hf0,
34
34
  nsubtables, noutbuffers_per_subtable, outbufsize,
35
35
  new_encoding=False,
36
- bisulfite=False):
36
+ methylation=False):
37
37
  """
38
38
  Compile a cptask function that
39
39
  creates (gapped) k-mers from linemarked buffers.
@@ -47,7 +47,7 @@ def compile_cptask_scatter_kmers_from_linemarked(
47
47
  """
48
48
  debugprint0, debugprint1, debugprint2 = debug.debugprint
49
49
  both = (rcmode[0].lower() == "b")
50
- if bisulfite:
50
+ if methylation:
51
51
  if new_encoding:
52
52
  raise NotImplementedError('WGBS with new encoding not supported')
53
53
 
@@ -202,7 +202,7 @@ def compile_cptask_scatter_kmers_from_linemarked(
202
202
  nactive = -1 # active input buffer
203
203
  outidx = np.full(nsubtables, (-1), dtype=np.int32)
204
204
  outpos = np.full(nsubtables, outbufsize, dtype=np.int32)
205
- if bisulfite:
205
+ if methylation:
206
206
  seq_buffer = np.empty(inbuffers[0].size, dtype=np.uint8)
207
207
  while True:
208
208
  nactive, wait = find_buffer_for_reading(incontrol, nactive)
@@ -217,7 +217,7 @@ def compile_cptask_scatter_kmers_from_linemarked(
217
217
  errorcode = 0
218
218
  linemarks = linemarkbuffers[nactive]
219
219
  for i in range(nseqs):
220
- if bisulfite:
220
+ if methylation:
221
221
  seq_len = linemarks[i, 1] - linemarks[i, 0]
222
222
  seq_buffer[0:seq_len] = active_buffer[linemarks[i, 0]:linemarks[i, 1]]
223
223
  quick_dna_to_2bits_ct(seq_buffer[0:seq_len])
@@ -137,7 +137,7 @@ def build_hash(universe, n, subtables, bucketsize,
137
137
 
138
138
  @njit(nogil=True, locals=dict(
139
139
  bucket=int64, startbit=uint64))
140
- def prefetch_for_key(table, key):
140
+ def prefetch_key(table, key):
141
141
  st, sk = get_subtable_subkey_from_key(key)
142
142
  bucket, _ = get_bs1(sk)
143
143
  startbit = st * subtablebits + bucket * bucketsizebits
@@ -1,7 +1,7 @@
1
1
  from argparse import ArgumentParser
2
2
 
3
3
  import numpy as np
4
- from numba import njit, int32, int64
4
+ from numba import njit, int32, int64, uint64
5
5
 
6
6
  from .seqio import FormatError, _universal_reads
7
7
  from ..lowlevel.libc import read_block
@@ -14,6 +14,7 @@ debugprint0, debugprint1, debugprint2 = debug.debugprint
14
14
 
15
15
  # Python FASTQ/gz handling ######################################
16
16
 
17
+
17
18
  def fastq_reads(files, sequences_only=False, dirty=False):
18
19
  """
19
20
  For the given 'files':
@@ -274,6 +275,229 @@ def cptask_read_pairedfastq_into_linemarked_buffers(fd, inbuffers, incontrol, in
274
275
  return (wait_read, wait_write, errorcode)
275
276
 
276
277
 
278
+ # read fastq without computing linemarks
279
+
280
+ @njit(nogil=True, locals=dict(
281
+ ntodo1=int32, ntodo2=int32, errorcode=int32, offset1=int32, offset2=int32,
282
+ skip=int32, nxt1=int32, nxt2=int32, nseqs=int32, nseqs1=int32, nseqs2=int32, nlines1=int32, nlines2=int32,
283
+ wait=int64, wait_read=int64, wait_write=int64, buffersize=int64))
284
+ def cptask_read_pairedfastq_into_buffers(fd, inbuffers, incontrol, ininfos, outbuffers, outcontrol, outinfos):
285
+ """
286
+ Has to be run as a thread within a consumer producer task.
287
+ Keep reading bytes into one of the outbuffers (cycling),
288
+ and compute linemarks in one of the outinfos (same index, cycling),
289
+ until EOF is reached or an error occurs.
290
+ Return 0 (EOF), or an error code (negative), corresponding to -os.errno.
291
+ """
292
+ fd1, fd2 = fd
293
+ M, N = outinfos.shape
294
+ assert N % 4 == 0
295
+
296
+ offset1 = offset2 = ntodo1 = ntodo2 = wait_read = wait_write = 0
297
+ nseqs1 = nseqs2 = 0
298
+ nactive = -1
299
+ active_buffer = outbuffers[0] # irrelevant
300
+ buffersize = active_buffer.shape[0] // 2
301
+ active_buffer1 = active_buffer[:len(active_buffer) // 2]
302
+ active_buffer2 = active_buffer[len(active_buffer) // 2:]
303
+
304
+ while True:
305
+ old = nactive
306
+ old_buffer1 = active_buffer1
307
+ old_buffer2 = active_buffer2
308
+ nactive, wait = find_buffer_for_writing(outcontrol, old)
309
+ if nactive < 0:
310
+ break
311
+ wait_write += wait
312
+ active_buffer = outbuffers[nactive]
313
+ active_buffer1 = active_buffer[:buffersize]
314
+ active_buffer2 = active_buffer[buffersize:buffersize * 2]
315
+
316
+ if old >= 0:
317
+ assert nactive != old
318
+ if offset1 > 0:
319
+ for i in range(offset1):
320
+ active_buffer1[i] = old_buffer1[ntodo1 - offset1 + i]
321
+ if offset2 > 0:
322
+ for i in range(offset2):
323
+ active_buffer2[i] = old_buffer2[ntodo2 - offset2 + i]
324
+ mark_buffer_for_reading(outcontrol, old)
325
+
326
+ errorcode = outcontrol[nactive, 1]
327
+ if errorcode != 0:
328
+ errorcode = -errorcode
329
+ break
330
+
331
+ # only read if the buffer is filled less than 80%
332
+ if offset1 < active_buffer1.size * 4 // 5 or nseqs1 == 0:
333
+ inner_offset1 = offset1
334
+ while inner_offset1 < active_buffer1.size * 4 // 5 or nseqs1 == 0:
335
+ ntodo1 = read_block(int32(fd1), active_buffer1, inner_offset1)
336
+ if ntodo1 == 0:
337
+ ntodo1 = inner_offset1
338
+ break
339
+ if ntodo1 == -99_999:
340
+ debugprint0("- Failed to read file into buffer (probably due to too long reads, len(header+seq+quality) must be smaller than buffersize, default 65kB).")
341
+ debugprint0("- Please increase buffersize using --buffersize.")
342
+ break
343
+ inner_offset1 = ntodo1
344
+ if inner_offset1 >= active_buffer1.size * 4 // 5:
345
+ break
346
+ else:
347
+ ntodo1 = offset1
348
+
349
+ if offset2 < active_buffer2.size * 4 // 5 or nseqs2 == 0:
350
+ inner_offset2 = offset2
351
+ while inner_offset2 < active_buffer2.size * 4 // 5 or nseqs2 == 0:
352
+ ntodo2 = read_block(int32(fd2), active_buffer2, inner_offset2)
353
+ if ntodo2 == 0:
354
+ ntodo2 = inner_offset2
355
+ break
356
+ if ntodo2 == -99_999:
357
+ debugprint0("- Failed to read file into buffer (probably due to too long reads, len(header+seq+quality) must be smaller than buffersize, default 65kB).")
358
+ debugprint0("- Please increase buffersize using --buffersize.")
359
+ break
360
+ inner_offset2 = ntodo2
361
+ if inner_offset2 >= active_buffer2.size * 4 // 5:
362
+ break
363
+ else:
364
+ ntodo2 = offset2
365
+
366
+ if ntodo1 <= 0 or ntodo2 <= 0:
367
+ # Error reading first file
368
+ if ntodo1 < 0:
369
+ errorcode = ntodo1
370
+ break
371
+ # Error reading second file
372
+ if ntodo2 < 0:
373
+ errorcode = ntodo2
374
+ break
375
+ # ntodo1=ntodo2=0 nothing read and no offset left; we are done
376
+ if ntodo1 == ntodo2 == offset1 == offset2 == 0:
377
+ errorcode = ntodo1
378
+ break
379
+ if ntodo1 == 0:
380
+ ntodo1 = offset1
381
+ elif ntodo2 == 0:
382
+ ntodo2 = offset2
383
+ else:
384
+ assert False
385
+
386
+ nlines1, nlines2 = 0, 0
387
+ for i in range(ntodo1):
388
+ nlines1 += active_buffer1[i] == 10
389
+ for i in range(ntodo2):
390
+ nlines2 += active_buffer2[i] == 10
391
+ nseqs1 = nlines1 // 4
392
+ nseqs2 = nlines2 // 4
393
+ nseqs = min(nseqs1, nseqs2)
394
+
395
+ outcontrol[nactive, 7] = nseqs
396
+ outcontrol[nactive, 6] = 0
397
+
398
+ rm_lines = nlines1 - nseqs * 4
399
+ assert rm_lines >= 0
400
+ if rm_lines == 0 and active_buffer1[ntodo1 - 1] == 10:
401
+ offset1 = 0
402
+ else:
403
+ nlines1 = offset1 = 0
404
+ for i in range(ntodo1):
405
+ nlines1 += active_buffer1[ntodo1 - i - 1] == 10
406
+ if nlines1 == rm_lines + 1:
407
+ break
408
+ offset1 += 1
409
+
410
+ rm_lines = nlines2 - nseqs * 4
411
+ assert rm_lines >= 0
412
+ if rm_lines == 0 and active_buffer2[ntodo2 - 1] == 10:
413
+ offset2 = 0
414
+ else:
415
+ nlines2 = offset2 = 0
416
+ for i in range(ntodo2):
417
+ nlines2 += active_buffer2[ntodo2 - i - 1] == 10
418
+ if nlines2 == rm_lines + 1:
419
+ break
420
+ offset2 += 1
421
+
422
+ mark_buffer_for_writing(outcontrol, nactive, force=True) # nothing left to read; re-use buffer
423
+ return (wait_read, wait_write, errorcode)
424
+
425
+
426
+ @njit(nogil=True, locals=dict(
427
+ ntodo1=int32, ntodo2=int32, errorcode=int32, offset1=int32, offset2=int32,
428
+ skip=int32, nxt1=int32, nxt2=int32, nseqs=int32, nseqs1=int32, nseqs2=int32, nlines1=int32, nlines2=int32,
429
+ wait=int64, wait_read=int64, wait_write=int64, buffersize=int64))
430
+ def cptask_read_fastq_into_buffers(fd, inbuffers, incontrol, ininfos, outbuffers, outcontrol, outinfos):
431
+ """
432
+ Has to be run as a thread within a consumer producer task.
433
+ Keep reading bytes into one of the outbuffers (cycling),
434
+ and compute linemarks in one of the outinfos (same index, cycling),
435
+ until EOF is reached or an error occurs.
436
+ Return 0 (EOF), or an error code (negative), corresponding to -os.errno.
437
+ """
438
+ M, N = outinfos.shape
439
+ assert N % 4 == 0
440
+
441
+ offset = ntodo = wait_read = wait_write = 0
442
+ nseqs = 0
443
+ nactive = -1
444
+ active_buffer = outbuffers[0] # irrelevant
445
+
446
+ while True:
447
+ old = nactive
448
+ old_buffer = active_buffer
449
+ nactive, wait = find_buffer_for_writing(outcontrol, old)
450
+ if nactive < 0:
451
+ break
452
+ wait_write += wait
453
+ active_buffer = outbuffers[nactive]
454
+
455
+ if old >= 0:
456
+ assert nactive != old
457
+ if offset > 0:
458
+ for i in range(offset):
459
+ active_buffer[i] = old_buffer[ntodo - offset + i]
460
+ mark_buffer_for_reading(outcontrol, old)
461
+
462
+ errorcode = outcontrol[nactive, 1]
463
+ if errorcode != 0:
464
+ errorcode = -errorcode
465
+ break
466
+
467
+ ntodo = read_block(int32(fd), active_buffer, offset)
468
+ if ntodo <= 0:
469
+ if ntodo < 0 or offset == 0:
470
+ errorcode = ntodo
471
+ if errorcode == -99_999:
472
+ debugprint0("- Failed to read file into buffer (probably due to too long reads, len(header+seq+quality) must be smaller than buffersize, default 65kB).")
473
+ debugprint0("- Please increase buffersize using --buffersize.")
474
+ break
475
+ ntodo = offset
476
+
477
+ nlines = 0
478
+ for i in range(ntodo):
479
+ nlines += active_buffer[i] == 10
480
+ nseqs = nlines // 4
481
+
482
+ outcontrol[nactive, 7] = nseqs
483
+ outcontrol[nactive, 6] = 0
484
+
485
+ rm_lines = nlines - nseqs * 4
486
+ assert rm_lines >= 0
487
+ if rm_lines == 0 and active_buffer[ntodo - 1] == 10:
488
+ offset = 0
489
+ else:
490
+ nlines = offset = 0
491
+ for i in range(ntodo):
492
+ nlines += active_buffer[ntodo - i - 1] == 10
493
+ if nlines == rm_lines + 1:
494
+ break
495
+ offset += 1
496
+
497
+ mark_buffer_for_writing(outcontrol, nactive, force=True) # nothing left to read; re-use buffer
498
+ return (wait_read, wait_write, errorcode)
499
+
500
+
277
501
  @njit(nogil=True, locals=dict(
278
502
  ntodo=int32, errorcode=int32, offset=int32, wait=int64,
279
503
  wait_write=int64, sequence_id=int64, nseq=int64))
@@ -332,7 +556,7 @@ def cptask_read_fastq_seqid_pos_into_buffers(fd, inbuffers, incontrol, ininfos,
332
556
 
333
557
  # numba FASTQ handling ######################################
334
558
 
335
- @njit(nogil=True)
559
+ @njit(nogil=True, locals=dict(M=int64, i=uint64, m=int64, nxt=int64, line=uint64))
336
560
  def _find_fastq_seqmarks(buf, linemarks, sequence_id=-1):
337
561
  """
338
562
  Find start and end positions of lines in byte buffer 'buf'.
@@ -1,5 +1,6 @@
1
1
  import sys
2
2
  from os.path import splitext
3
+ from pathlib import Path
3
4
  from shlex import split as cmdsplit
4
5
  from subprocess import Popen, PIPE
5
6
  from shutil import which
@@ -79,8 +80,12 @@ class InputFileHandler:
79
80
  file_extension = splitext(fname)[1]
80
81
  file_type = _filetypes.get(file_extension, None)
81
82
  if file_type is None:
82
- debugprint0(f"Error: InputFileHandler: Unknown file type for '{filename}'")
83
- sys.exit(1)
83
+ if Path(filename).is_fifo():
84
+ file_type = 'fifo'
85
+ debugprint0(f'Warning: InputFileHandler: {filename} is a stream. No file type set.')
86
+ else:
87
+ debugprint0(f"Error: InputFileHandler: Unknown file type for '{filename}'")
88
+ sys.exit(1)
84
89
 
85
90
  if is_compressed:
86
91
  if which(tool.split()[0]) is None:
@@ -298,11 +298,13 @@ def compile_prefetch_array_element(nbits=None):
298
298
  sig = numba.void(numba.types.uintp)
299
299
  return sig, codegen
300
300
 
301
+ # ToDo make consistent to either get position or startbit!!!
302
+ # prefetch_array_element_ptr gets starbit and prefetch_array_element position
301
303
  if nbits is not None:
302
304
  @numba.njit(nogil=True)
303
305
  def prefetch_array_element_ptr(array_ptr, i):
304
- """prefetch array element a[i]"""
305
- return prefetch_address(array_ptr + (nbits * i) // 8)
306
+ """prefetch array element"""
307
+ return prefetch_address(array_ptr + i // 8)
306
308
 
307
309
  return prefetch_array_element_ptr
308
310
 
@@ -92,7 +92,7 @@ SRHash_private = namedtuple("SRHash_private", [
92
92
  "get_subkey_from_bucket_signature",
93
93
  "get_subkey_choice_from_bucket_signature",
94
94
  "prefetch_bucket",
95
- "prefetch_for_key",
95
+ "prefetch_key",
96
96
 
97
97
  # Copies of the public functions but with subtable/subkey interface
98
98
  # (only defined for hashes with subtables; otherwise None)
@@ -16,7 +16,7 @@ params_count:
16
16
  Mn_factor: 4
17
17
  Mn_min: 3
18
18
 
19
- params_bisulfite:
19
+ params_methylation:
20
20
  nothing: 0
21
21
  few: 6
22
22
  Ag: 3
@@ -28,6 +28,9 @@ from ..lowlevel import debug
28
28
  from ..mask import create_mask
29
29
 
30
30
 
31
+ MAXUINT64 = np.iinfo(np.uint64).max
32
+
33
+
31
34
  # ####### Classification methods ######
32
35
  def compile_count_based_classification(params):
33
36
  Mh_factor = params.get("Mh", 4)
@@ -327,8 +330,7 @@ def compile_classify_read_from_fastq(
327
330
  mode, mask, rcmode, bits, path, h, threads, pairs,
328
331
  bufsize=2**23, chunkreads=(2**23) // 200, quick=False,
329
332
  filt=False, count=False, prefetchlevel=0, params=dict(),
330
- compression="gz", show_progress=False):
331
-
333
+ compression="gz", show_progress=False, use_start_sites=False):
332
334
 
333
335
  if compression != "none":
334
336
  compression = "." + compression
@@ -339,6 +341,10 @@ def compile_classify_read_from_fastq(
339
341
  ba = None
340
342
  count_kmers = True
341
343
 
344
+ if mode != "methylation" and use_start_sites:
345
+ debugprint0('Using start_sites and without a methylation index is not supported')
346
+ exit(1)
347
+
342
348
  if mode == "count":
343
349
  debugprint0("- Using count based classification mode")
344
350
  classify = compile_count_based_classification(params)
@@ -351,8 +357,8 @@ def compile_classify_read_from_fastq(
351
357
  elif mode == "quick":
352
358
  debugprint0("- using quick classification mode")
353
359
  assert False
354
- elif mode == 'bisulfite':
355
- debugprint0("- Using count based classification mode for bisulfite sequencing data")
360
+ elif mode == 'methylation':
361
+ debugprint0("- Using count based classification mode for methylation sequencing data")
356
362
  classify = compile_count_based_classification(params)
357
363
  ct_table = _TABLE_DNA_TO_2BITS.copy()
358
364
  ct_table[99] = 3
@@ -363,13 +369,39 @@ def compile_classify_read_from_fastq(
363
369
  ga_table[103] = 0
364
370
  ga_table[71] = 0
365
371
  quick_dna_to_2bits_ga = compile_quick_dna_to_2bits(ga_table)
372
+
373
+ # Classification matrix for pairs
374
+ # H: Host G: Graft A: Ambiguous B: Both N: Neither
375
+ # Given the two classifications of the read pair, we return the value in the matrix
376
+ # H G A B N
377
+ # 0 1 2 3 4
378
+ # H 0 0 2 0 0 0
379
+ # G 1 2 1 1 1 1
380
+ # A 2 0 1 2 2 2
381
+ # B 3 0 1 2 3 2
382
+ # N 4 0 1 2 2 4
383
+ matrix = [[0, 2, 0, 0, 0], [2, 1, 1, 1, 1], [0, 1, 2, 2, 2], [0, 1, 2, 3, 2], [0, 1, 2, 2, 4]]
384
+
385
+ methylation_paired = np.array(matrix, dtype=np.uint8)
386
+
387
+ if use_start_sites:
388
+ start_sites = np.frombuffer(b"CGGTGG", dtype=np.uint8)
366
389
  else:
367
390
  raise ValueError(f"Unknown mode '{mode}'")
368
391
 
392
+ @njit(nogil=True, locals=dict(code=uint64))
393
+ def _collect_code(_, code, pos, codes):
394
+ """Store the k-mer code at its sequence position; hash lookup deferred."""
395
+ codes[pos] = code
396
+
397
+ _, collect_kmers = compile_positional_kmer_processor(mask.tuple, _collect_code, rcmode=rcmode, new_encoding=False)
398
+
369
399
  get_kmer_values = compile_get_kmer_values(mask, rcmode, h, prefetchlevel, count_kmers, ba)
370
400
  _, twobit_to_code = compile_twobit_to_codes(mask, rcmode)
371
401
 
372
402
  get_value = h.get_value
403
+ prefetch_key = h.private.prefetch_key
404
+ LOOKAHEAD = 16
373
405
  k, w = mask.k, mask.w
374
406
 
375
407
 
@@ -422,7 +454,7 @@ def compile_classify_read_from_fastq(
422
454
  @njit(nogil=True, locals=dict(
423
455
  third_kmer=uint64, third=uint64,
424
456
  thirdlast_kmer=uint64, thirdlast=uint64))
425
- def get_bisulfite_classification(ht, sq, kcount):
457
+ def get_methylation_classification(ht, sq, kcount):
426
458
  # if quick:
427
459
  # # get 2 k-mers (3rd and 3rd-last)
428
460
  # third_kmer = twobit_to_code(sq, 2)
@@ -490,33 +522,132 @@ def compile_classify_read_from_fastq(
490
522
  sq = buf[linemarks[i, 0]:linemarks[i, 1]]
491
523
  classifications[i] = get_classification(ht, sq, counts)
492
524
  twobits_to_dna_inplace(buf, linemarks[i, 0], linemarks[i, 1])
493
- elif mode == "bisulfite":
525
+ elif mode == "methylation":
494
526
  local_seq = np.empty(250, dtype=np.uint8)
495
- for i in range(n):
496
- seq_len = linemarks[i, 1] - linemarks[i, 0]
497
-
527
+ counts2 = np.zeros(8, dtype=np.uint32)
528
+ max_kmers = buf.size // 2 # Upper limit for number of k-mers in a buffer. Buffer size divided by two for qualities
529
+ codes_ct = np.empty(max_kmers, dtype=np.uint64)
530
+ codes_ga = np.empty(max_kmers, dtype=np.uint64)
531
+ start_codes = 0
532
+ # pass 1.
533
+
534
+ # Does it make sense to process the buffer twice for ct and ga so the codes_ arrays are linear?
535
+ for readnum in range(n):
536
+
537
+ if use_start_sites:
538
+ if not ((buf[linemarks[readnum, 0]:linemarks[readnum, 0] + 3] == start_sites[:3]).all() or \
539
+ (buf[linemarks[readnum, 0]:linemarks[readnum, 0] + 3] == start_sites[3:]).all()):
540
+ continue
541
+ seq_len = linemarks[readnum, 1] - linemarks[readnum, 0]
542
+ nk = seq_len - w + 1
498
543
  if seq_len > local_seq.size:
499
544
  debugprint2('WGBS, local sequence length increased to ', seq_len * 2)
500
545
  local_seq = np.empty(seq_len * 2, dtype=np.uint8)
501
-
502
- counts[:] = 0
503
- # C -> T
504
- local_seq[:seq_len] = buf[linemarks[i, 0]:linemarks[i, 1]]
546
+ local_seq[:seq_len] = buf[linemarks[readnum, 0]:linemarks[readnum, 1]]
547
+ end_codes = start_codes + nk
505
548
  quick_dna_to_2bits_ct(local_seq[:seq_len])
506
- get_bisulfite_classification(ht, local_seq[:seq_len], counts)
549
+ codes_ct[start_codes:end_codes] = MAXUINT64
550
+ collect_kmers(None, local_seq[:seq_len], 0, seq_len, codes_ct[start_codes:end_codes])
507
551
 
508
- # G -> A
509
- local_seq[:seq_len] = buf[linemarks[i, 0]:linemarks[i, 1]]
552
+ local_seq[:seq_len] = buf[linemarks[readnum, 0]:linemarks[readnum, 1]]
510
553
  quick_dna_to_2bits_ga(local_seq[:seq_len])
511
- get_bisulfite_classification(ht, local_seq[:seq_len], counts)
512
- r1_clasification = classify(counts)
513
- classifications[i] = r1_clasification
554
+ codes_ga[start_codes:end_codes] = MAXUINT64
555
+ collect_kmers(None, local_seq[:seq_len], 0, seq_len, codes_ga[start_codes:end_codes])
556
+ start_codes = end_codes
557
+
558
+ total_kmers = start_codes
559
+ # 2. Pass: classify all reads
560
+ start_codes = 0
561
+ found = 0
562
+ not_found = 0
563
+ for readnum, (start_seq, end_seq, start_record, end_record) in enumerate(linemarks):
564
+ if use_start_sites:
565
+ if not ((buf[linemarks[readnum, 0]:linemarks[readnum, 0] + 3] == start_sites[:3]).all() or \
566
+ (buf[linemarks[readnum, 0]:linemarks[readnum, 0] + 3] == start_sites[3:]).all()):
567
+ classifications[readnum] = 5 # no start site
568
+ continue
569
+ counts[:] = 0
570
+ counts2[:] = 0
571
+ nk = end_seq - start_seq - w + 1
572
+
573
+ for pos in range(nk):
574
+ code_pos = pos + start_codes
575
+ if code_pos + LOOKAHEAD < total_kmers:
576
+ ahead_code = codes_ct[code_pos + LOOKAHEAD]
577
+ if ahead_code != MAXUINT64:
578
+ prefetch_key(ht, ahead_code)
579
+ ahead_code = codes_ga[code_pos + LOOKAHEAD]
580
+ if ahead_code != MAXUINT64:
581
+ prefetch_key(ht, ahead_code)
582
+
583
+ code = codes_ct[code_pos]
584
+ if code != MAXUINT64:
585
+ found += 1
586
+ v = get_value(ht, code)
587
+ counts[v] += 1
588
+ else:
589
+ not_found += 1
590
+
591
+ code = codes_ga[code_pos]
592
+ if code != MAXUINT64:
593
+ found += 1
594
+ v = get_value(ht, code)
595
+ counts2[v] += 1
596
+ else:
597
+ not_found += 1
598
+ if counts[0] < counts2[0] // 2:
599
+ classification = classify(counts)
600
+ elif counts2[0] < counts[0] // 2:
601
+ classification = classify(counts2)
602
+ else:
603
+ counts += counts2
604
+ classification = classify(counts)
605
+
606
+ classifications[readnum] = classification
607
+ start_codes += nk
514
608
 
515
609
  else:
516
610
  raise ValueError(f"Classification method {mode} is not supported.")
517
611
 
518
612
  return threadid, (classifications, linemarks)
519
613
 
614
+ @njit(nogil=True)
615
+ def code_ct(buf, seq_len, local_seq, codes, start_codes, end_codes):
616
+ codes[start_codes:end_codes] = MAXUINT64
617
+ local_seq[:seq_len] = buf
618
+ quick_dna_to_2bits_ct(local_seq[:seq_len])
619
+ codes[start_codes:end_codes] = MAXUINT64
620
+ collect_kmers(None, local_seq[:seq_len], 0, seq_len, codes[start_codes:end_codes])
621
+
622
+ @njit(nogil=True)
623
+ def code_ga(buf, seq_len, local_seq, codes, start_codes, end_codes):
624
+ codes[start_codes:end_codes] = MAXUINT64
625
+ local_seq[:seq_len] = buf
626
+ quick_dna_to_2bits_ga(local_seq[:seq_len])
627
+ codes[start_codes:end_codes] = MAXUINT64
628
+ collect_kmers(None, local_seq[:seq_len], 0, seq_len, codes[start_codes:end_codes])
629
+
630
+ @njit(nogil=True)
631
+ def prefetch_index(ht, arr, index):
632
+ ahead_code = arr[index]
633
+ if ahead_code != MAXUINT64:
634
+ prefetch_key(ht, ahead_code)
635
+
636
+ @njit(nogil=True)
637
+ def get_v(ht, code, counts):
638
+ if code != MAXUINT64:
639
+ v = get_value(ht, code)
640
+ counts[v] += 1
641
+
642
+ @njit(nogil=True)
643
+ def meth_classify(counts_ct, counts_ga):
644
+ if counts_ct[0] < counts_ga[0] // 2:
645
+ return classify(counts_ct)
646
+ if counts_ga[0] < counts_ct[0] // 2:
647
+ return classify(counts_ga)
648
+ counts_ct += counts_ga
649
+ return classify(counts_ct)
650
+
520
651
  @njit(nogil=True)
521
652
  def classify_paired_kmers_chunkwise(threadid, buf, linemarks, buf1, linemarks1, ht,
522
653
  ba_neither=None, ba_stronghost=None, ba_stronggraft=None, ba_both=None,
@@ -580,47 +711,86 @@ def compile_classify_read_from_fastq(
580
711
  classifications[i] = get_paired_classification(ht, sq1, sq2, counts)
581
712
  twobits_to_dna_inplace(buf, linemarks[i, 0], linemarks[i, 1])
582
713
  twobits_to_dna_inplace(buf1, linemarks1[i, 0], linemarks1[i, 1])
583
- elif mode == "bisulfite":
584
-
714
+ elif mode == "methylation":
715
+
716
+ counts_ct_r1 = np.zeros(8, dtype=np.uint32)
717
+ counts_ga_r1 = np.zeros(8, dtype=np.uint32)
718
+ counts_ct_r2 = np.zeros(8, dtype=np.uint32)
719
+ counts_ga_r2 = np.zeros(8, dtype=np.uint32)
720
+
721
+ max_kmers = buf.size // 2 # Upper limit for number of k-mers in a buffer. Buffer size divided by two for qualities
722
+ codes_ct_r1 = np.empty(max_kmers, dtype=np.uint64)
723
+ codes_ct_r2 = np.empty(max_kmers, dtype=np.uint64)
724
+ codes_ga_r1 = np.empty(max_kmers, dtype=np.uint64)
725
+ codes_ga_r2 = np.empty(max_kmers, dtype=np.uint64)
585
726
  local_seq = np.empty(250, dtype=np.uint8)
586
- for i in range(n):
587
- seq_len = linemarks[i, 1] - linemarks[i, 0]
588
- seq_len1 = linemarks1[i, 1] - linemarks1[i, 0]
589
727
 
590
- max_seq_len = max(seq_len, seq_len1)
591
- if max_seq_len > local_seq.size:
592
- debugprint2('WGBS, local sequence length increased to ', max_seq_len * 2)
593
- local_seq = np.empty(max_seq_len * 2, dtype=np.uint8)
728
+ start_codes = 0
729
+ # pass 1.
594
730
 
595
- # R1
596
- counts[:] = 0
597
- # C -> T
598
- local_seq[:seq_len] = buf[linemarks[i, 0]:linemarks[i, 1]]
599
- quick_dna_to_2bits_ct(local_seq[:seq_len])
600
- q1 = get_bisulfite_classification(ht, local_seq[:seq_len], counts)
601
- # G -> A
602
- local_seq[:seq_len] = buf[linemarks[i, 0]:linemarks[i, 1]]
603
- quick_dna_to_2bits_ct(local_seq[:seq_len])
604
- q2 = get_bisulfite_classification(ht, local_seq[:seq_len], counts)
605
- r1_clasification = classify(counts)
731
+ for readnum in range(n):
732
+ seq_len_r1 = linemarks[readnum, 1] - linemarks[readnum, 0]
733
+ seq_len_r2 = linemarks1[readnum, 1] - linemarks1[readnum, 0]
734
+ seq_len = max(seq_len_r1, seq_len_r2)
735
+ nk = seq_len - w + 1
736
+
737
+ if use_start_sites:
738
+ if not ((buf[linemarks[readnum, 0]:linemarks[readnum, 0] + 3] == start_sites[:3]).all() or \
739
+ (buf[linemarks[readnum, 0]:linemarks[readnum, 0] + 3] == start_sites[3:]).all()):
740
+ continue
741
+
742
+ if seq_len > local_seq.size:
743
+ debugprint2('WGBS, local sequence length increased to ', seq_len * 2)
744
+ local_seq = np.empty(seq_len * 2, dtype=np.uint8)
745
+
746
+ end_codes = start_codes + nk
747
+
748
+ code_ct(buf[linemarks[readnum, 0]:linemarks[readnum, 1]], seq_len_r1, local_seq, codes_ct_r1, start_codes, end_codes)
749
+ code_ga(buf[linemarks[readnum, 0]:linemarks[readnum, 1]], seq_len_r1, local_seq, codes_ga_r1, start_codes, end_codes)
750
+
751
+ code_ct(buf1[linemarks1[readnum, 0]:linemarks1[readnum, 1]], seq_len_r2, local_seq, codes_ct_r2, start_codes, end_codes)
752
+ code_ga(buf1[linemarks1[readnum, 0]:linemarks1[readnum, 1]], seq_len_r2, local_seq, codes_ga_r2, start_codes, end_codes)
753
+
754
+ start_codes = end_codes
755
+
756
+ total_kmers = start_codes
757
+ # 2. Pass: classify all reads
758
+ start_codes = 0
759
+ for readnum in range(n):
760
+
761
+ if use_start_sites:
762
+ if not ((buf[linemarks[readnum, 0]:linemarks[readnum, 0] + 3] == start_sites[:3]).all() or \
763
+ (buf[linemarks[readnum, 0]:linemarks[readnum, 0] + 3] == start_sites[3:]).all()):
764
+ classifications[readnum] = 5 # no start site
765
+ continue
766
+ seq_len_r1 = linemarks[readnum, 1] - linemarks[readnum, 0]
767
+ seq_len_r2 = linemarks1[readnum, 1] - linemarks1[readnum, 0]
768
+ seq_len = max(seq_len_r1, seq_len_r2)
769
+ nk = seq_len - w + 1
770
+
771
+ counts_ct_r1[:] = 0
772
+ counts_ga_r1[:] = 0
773
+ counts_ct_r2[:] = 0
774
+ counts_ga_r2[:] = 0
775
+
776
+ for pos in range(nk):
777
+ code_pos = pos + start_codes
778
+ if code_pos + LOOKAHEAD < total_kmers:
779
+ prefetch_index(ht, codes_ct_r1, code_pos + LOOKAHEAD)
780
+ prefetch_index(ht, codes_ga_r1, code_pos + LOOKAHEAD)
781
+ prefetch_index(ht, codes_ct_r2, code_pos + LOOKAHEAD)
782
+ prefetch_index(ht, codes_ga_r2, code_pos + LOOKAHEAD)
783
+
784
+ get_v(ht, codes_ct_r1[code_pos], counts_ct_r1)
785
+ get_v(ht, codes_ga_r1[code_pos], counts_ga_r1)
786
+ get_v(ht, codes_ct_r2[code_pos], counts_ct_r2)
787
+ get_v(ht, codes_ga_r2[code_pos], counts_ga_r2)
788
+ start_codes += nk
789
+ r1_classification = meth_classify(counts_ct_r1, counts_ga_r1)
790
+ r2_classification = meth_classify(counts_ct_r2, counts_ga_r2)
791
+
792
+ classifications[readnum] = methylation_paired[r1_classification, r2_classification]
606
793
 
607
- # R2
608
- counts[:] = 0
609
- # C -> T
610
- local_seq[:seq_len1] = buf1[linemarks1[i, 0]:linemarks1[i, 1]]
611
- quick_dna_to_2bits_ga(local_seq[:seq_len1])
612
- get_bisulfite_classification(ht, local_seq[:seq_len1], counts)
613
-
614
- # G -> A
615
- local_seq[:seq_len1] = buf1[linemarks1[i, 0]:linemarks1[i, 1]]
616
- quick_dna_to_2bits_ga(local_seq[:seq_len1])
617
- get_bisulfite_classification(ht, local_seq[:seq_len1], counts)
618
- r2_clasification = classify(counts)
619
-
620
- if r1_clasification == r2_clasification:
621
- classifications[i] = r1_clasification
622
- else:
623
- classifications[i] = 2 # ambiguous
624
794
  else:
625
795
  raise ValueError(f"{mode} is not a supported classification mode")
626
796
 
@@ -667,8 +837,10 @@ def compile_classify_read_from_fastq(
667
837
  yield outfile.file
668
838
 
669
839
  def classify_read_from_fastq_paired(fastqs, pairs, ht):
670
- counts = [0, 0, 0, 0, 0] # host, graft, amb., both, neither
840
+ counts = [0, 0, 0, 0, 0, 0] # host, graft, amb., both, neither
671
841
  nprocessed = 0
842
+
843
+ site_count = count and not use_start_sites
672
844
  with cond_contextmgr(path, f"-host.1.fq{compression}", count, filt) as host1, \
673
845
  cond_contextmgr(path, f"-host.2.fq{compression}", count, filt) as host2, \
674
846
  cond_contextmgr(path, f"-graft.1.fq{compression}", count, filt) as graft1, \
@@ -678,8 +850,10 @@ def compile_classify_read_from_fastq(
678
850
  cond_contextmgr(path, f"-both.1.fq{compression}", count, filt) as both1, \
679
851
  cond_contextmgr(path, f"-both.2.fq{compression}", count, filt) as both2, \
680
852
  cond_contextmgr(path, f"-neither.1.fq{compression}", count, filt) as neither1, \
681
- cond_contextmgr(path, f"-neither.2.fq{compression}", count, filt) as neither2:
682
- streams = ((host1, host2), (graft1, graft2), (ambiguous1, ambiguous2), (both1, both2), (neither1, neither2))
853
+ cond_contextmgr(path, f"-neither.2.fq{compression}", count, filt) as neither2, \
854
+ cond_contextmgr(path, f"-sites.1.fq{compression}", site_count, filt) as sites1, \
855
+ cond_contextmgr(path, f"-sites.2.fq{compression}", site_count, filt) as sites2:
856
+ streams = ((host1, host2), (graft1, graft2), (ambiguous1, ambiguous2), (both1, both2), (neither1, neither2), (sites1, sites2))
683
857
 
684
858
  with ThreadPoolExecutor(max_workers=threads) as executor:
685
859
  for fastq1, fastq2 in zip(fastqs, pairs):
@@ -734,14 +908,16 @@ def compile_classify_read_from_fastq(
734
908
  return counts
735
909
 
736
910
  def classify_read_from_fastq_single(fastqs, ht):
737
- counts = [0, 0, 0, 0, 0]
911
+ counts = [0, 0, 0, 0, 0, 0]
738
912
  nprocessed = 0
913
+ site_count = count and not use_start_sites
739
914
  with cond_contextmgr(path, f"-host.fq{compression}", count, filt) as host, \
740
915
  cond_contextmgr(path, f"-graft.fq{compression}", count, filt) as graft, \
741
916
  cond_contextmgr(path, f"-ambiguous.fq{compression}", count, filt) as ambiguous, \
742
917
  cond_contextmgr(path, f"-both.fq{compression}", count, filt) as both, \
743
- cond_contextmgr(path, f"-neither.fq{compression}", count, filt) as neither:
744
- streams = (host, graft, ambiguous, both, neither)
918
+ cond_contextmgr(path, f"-neither.fq{compression}", count, filt) as neither, \
919
+ cond_contextmgr(path, f"-sites.fq{compression}", site_count, filt) as sites:
920
+ streams = (host, graft, ambiguous, both, neither, sites)
745
921
 
746
922
  running_jobs = []
747
923
  with ThreadPoolExecutor(max_workers=threads) as executor:
@@ -794,24 +970,26 @@ def compile_classify_read_from_fastq(
794
970
  return classify_read_from_fastq
795
971
 
796
972
 
797
- def print_class_stats(prefix, stats):
798
- classes = ["host", "graft", "ambiguous", "both", "neither"]
973
+ def print_class_stats(prefix, stats, sites):
974
+ classes = ["host", "graft", "ambiguous", "both", "neither", 'sites']
799
975
 
976
+ nstats = len(stats) - (not sites)
977
+ stats = stats[:nstats]
800
978
  percentages = [i / sum(stats) * 100 for i in stats]
801
979
  str_counts = "\t".join(str(i) for i in stats)
802
980
  ndigits = max(map(lambda x: len(str(x)), stats))
803
981
 
804
- print("\n## Classification Statistics")
805
- print("\n```")
806
- print("prefix\thost\tgraft\tambiguous\tboth\tneither")
807
- print(f"{prefix}\t{str_counts}")
808
- print("```\n")
809
- print("```")
810
- print(f"| prefix | {prefix} ")
811
- for i in range(len(classes)):
812
- print(f"| {classes[i]:9s} | {stats[i]:{ndigits}d} | {percentages[i]:5.2f}% |")
813
- print("```")
814
- print()
982
+ debugprint0("\n## Classification Statistics")
983
+ debugprint0("\n```")
984
+ debugprint0("prefix\thost\tgraft\tambiguous\tboth\tneither")
985
+ debugprint0(f"{prefix}\t{str_counts}")
986
+ debugprint0("```\n")
987
+ debugprint0("```")
988
+ debugprint0(f"| prefix | {prefix} ")
989
+ for i in range(len(classes) - (not sites)):
990
+ debugprint0(f"| {classes[i]:9s} | {stats[i]:{ndigits}d} | {percentages[i]:5.2f}% |")
991
+ debugprint0("```")
992
+ debugprint0()
815
993
 
816
994
 
817
995
  def main(args):
@@ -850,13 +1028,13 @@ def main(args):
850
1028
  if mode == "quick":
851
1029
  mode = "count"
852
1030
  quick = True
853
- if 'bisulfite' in appinfo:
854
- if appinfo['bisulfite']:
855
- debugprint0('- The provided index is build for WGBS analysis.\n The classification mode will be switched to bisulfite.')
856
- mode = 'bisulfite'
857
- if mode == 'bisulfite':
1031
+ if 'methylation' in appinfo:
1032
+ if appinfo['methylation']:
1033
+ debugprint0('- The provided index is build for WGBS analysis.\n The classification mode will be switched to methylation.')
1034
+ mode = 'methylation'
1035
+ if mode == 'methylation':
858
1036
  params = args["params_" + 'count']
859
- params = args["params_" + 'bisulfite']
1037
+ params = args["params_" + 'methylation']
860
1038
  else:
861
1039
  params = args["params_" + mode]
862
1040
  if not args.fastq:
@@ -893,13 +1071,13 @@ def main(args):
893
1071
  bufsize=chunksize, chunkreads=chunkreads, quick=quick,
894
1072
  filt=args.filter, count=args.count, prefetchlevel=args.prefetchlevel,
895
1073
  params=params, compression=args.compression,
896
- show_progress=args.progress)
1074
+ show_progress=args.progress, use_start_sites=args.use_start_sites)
897
1075
 
898
1076
  if paired:
899
1077
  counts = classify_read_from_fastq(args.fastq, args.pairs, h.hashtable)
900
1078
  else:
901
1079
  counts = classify_read_from_fastq(args.fastq, h.hashtable)
902
- print_class_stats(args.prefix, counts)
1080
+ print_class_stats(args.prefix, counts, args.use_start_sites)
903
1081
 
904
1082
  debugprint0("## Running time statistics\n")
905
1083
  timestamp0(starttime, msg="- Running time")
@@ -104,7 +104,7 @@ def create_new_index(nsubtables, args):
104
104
  return (h, values, valueset, mask, rcmode)
105
105
 
106
106
 
107
- def process_files(constant_value, h, fnames, mask, rcmode, bisulfite, *,
107
+ def process_files(constant_value, h, fnames, mask, rcmode, methylation, *,
108
108
  maxfailures=0, maxwalk=1000,
109
109
  threads_read=1, threads_split=1):
110
110
 
@@ -134,7 +134,7 @@ def process_files(constant_value, h, fnames, mask, rcmode, bisulfite, *,
134
134
  outbufsize = 2**16
135
135
  cptask_split = compile_cptask_scatter_kmers_from_linemarked(
136
136
  mask, rcmode, hashfunc0,
137
- nsubtables, nbuffers_per_subtable, outbufsize, bisulfite=bisulfite)
137
+ nsubtables, nbuffers_per_subtable, outbufsize, methylation=methylation)
138
138
 
139
139
  splitter_jobs = ConsumerProducer(
140
140
  name='kmer_splitter',
@@ -189,7 +189,7 @@ def main(args):
189
189
 
190
190
  # process all host files
191
191
  if args.host:
192
- success_host = process_files(1, h, args.host, mask, rcmode, args.bisulfite,
192
+ success_host = process_files(1, h, args.host, mask, rcmode, args.methylation,
193
193
  maxfailures=0, maxwalk=args.maxwalk,
194
194
  threads_read=threads_read, threads_split=threads_split)
195
195
 
@@ -198,7 +198,7 @@ def main(args):
198
198
  exit(1)
199
199
 
200
200
  # process all graft files
201
- success_graft = process_files(2, h, args.graft, mask, rcmode, args.bisulfite,
201
+ success_graft = process_files(2, h, args.graft, mask, rcmode, args.methylation,
202
202
  maxfailures=0, maxwalk=args.maxwalk,
203
203
  threads_read=threads_read, threads_split=threads_split)
204
204
 
@@ -220,7 +220,7 @@ def main(args):
220
220
  # #################### Save Hash ####################
221
221
 
222
222
  optinfo = dict(walkseed=args.walkseed, maxwalk=args.maxwalk, maxfailures=0)
223
- appinfo = dict(rcmode=rcmode, mask=mask.tuple, k=mask.k, bisulfite=args.bisulfite)
223
+ appinfo = dict(rcmode=rcmode, mask=mask.tuple, k=mask.k, methylation=args.methylation)
224
224
  save_hash(args.index, h, valuetup, optinfo, appinfo)
225
225
 
226
226
  print_statistics(h, level=args.statistics)
@@ -40,7 +40,7 @@ def classify(p):
40
40
  choices=CL_CHOICES, help=f"classification mode {CL_CHOICES}")
41
41
  p.add_argument("--params_count", type=dict, help=SUPPRESS)
42
42
  # help="All paramteres for classification based on counts as a dictionary. Update in config file.")
43
- p.add_argument("--params_bisulfite", type=dict, help=SUPPRESS)
43
+ p.add_argument("--params_methylation", type=dict, help=SUPPRESS)
44
44
  # help="All paramteres for classification based on counts as a dictionary. Update in config file.")
45
45
  p.add_argument("--params_coverage", type=dict, help=SUPPRESS)
46
46
  # help="All paramteres for classification based on coverage as a dictionary. Update in config file.")
@@ -60,6 +60,8 @@ def classify(p):
60
60
  help="maximum number of reads per chunk per thread [SIZE_MB*(2**20) // 200]")
61
61
  p.add_argument("--progress", "-P", action="store_true",
62
62
  help="Show progress")
63
+ p.add_argument("--use-start-sites", action="store_true",
64
+ help="Filter read that do not start with the expected start sites.")
63
65
 
64
66
 
65
67
  def index(p):
@@ -118,7 +120,7 @@ def index(p):
118
120
  help="continue even after this many failures; forever: -1]")
119
121
  p.add_argument("--walkseed", type=int, metavar="INT",
120
122
  help="seed for random walks while inserting elements")
121
- p.add_argument("--bisulfite", action='store_true',
123
+ p.add_argument("--bisulfite", "--methylation", action='store_true', dest="methylation",
122
124
  help="filtering WGBS data with translated index (EXPERIMENTAL)")
123
125
 
124
126
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xengsort
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: xenograft sorting of FASTQ reads into five categories: host, graft, both, neither, ambiguous.
5
5
  Author-email: Jens Zentgraf <zentgraf@cs.uni-saarland.de>, Sven Rahmann <rahmann@cs.uni-saarland.de>
6
6
  License: MIT License
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes