htslib 0.4.1 → 0.5.0

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +79 -49
  3. data/TUTORIAL.md +9 -20
  4. data/ext/htslib_native/extconf.rb +41 -0
  5. data/ext/htslib_native/htslib_native.h +11 -0
  6. data/ext/htslib_native/htslib_native_ext.c +48 -0
  7. data/ext/htslib_native/native_bam.c +1108 -0
  8. data/ext/htslib_native/native_bcf.c +369 -0
  9. data/ext/htslib_native/native_faidx.c +155 -0
  10. data/ext/htslib_native/native_tabix.c +246 -0
  11. data/lib/hts/bam/auxi.rb +87 -342
  12. data/lib/hts/bam/base_mod.rb +37 -73
  13. data/lib/hts/bam/cigar.rb +9 -55
  14. data/lib/hts/bam/flag.rb +19 -27
  15. data/lib/hts/bam/header.rb +29 -56
  16. data/lib/hts/bam/mpileup.rb +158 -132
  17. data/lib/hts/bam/pileup.rb +120 -145
  18. data/lib/hts/bam/record.rb +233 -270
  19. data/lib/hts/bam.rb +102 -42
  20. data/lib/hts/bcf/errors.rb +1 -0
  21. data/lib/hts/bcf/format.rb +278 -379
  22. data/lib/hts/bcf/header.rb +38 -123
  23. data/lib/hts/bcf/header_record.rb +9 -35
  24. data/lib/hts/bcf/info.rb +72 -320
  25. data/lib/hts/bcf/record.rb +61 -102
  26. data/lib/hts/bcf.rb +149 -323
  27. data/lib/hts/faidx.rb +28 -87
  28. data/lib/hts/hts.rb +6 -94
  29. data/lib/hts/native.rb +13 -0
  30. data/lib/hts/tabix.rb +93 -48
  31. data/lib/hts/version.rb +1 -1
  32. data/lib/htslib.rb +6 -52
  33. metadata +13 -64
  34. data/lib/hts/ffi_ext/README.md +0 -8
  35. data/lib/hts/ffi_ext/pointer.rb +0 -18
  36. data/lib/hts/ffi_ext/struct.rb +0 -45
  37. data/lib/hts/libhts/bgzf.rb +0 -199
  38. data/lib/hts/libhts/constants.rb +0 -658
  39. data/lib/hts/libhts/cram.rb +0 -471
  40. data/lib/hts/libhts/fai.rb +0 -146
  41. data/lib/hts/libhts/hfile.rb +0 -109
  42. data/lib/hts/libhts/hts.rb +0 -477
  43. data/lib/hts/libhts/kfunc.rb +0 -38
  44. data/lib/hts/libhts/sam.rb +0 -760
  45. data/lib/hts/libhts/sam_funcs.rb +0 -155
  46. data/lib/hts/libhts/tbx.rb +0 -79
  47. data/lib/hts/libhts/tbx_funcs.rb +0 -32
  48. data/lib/hts/libhts/thread_pool.rb +0 -139
  49. data/lib/hts/libhts/vcf.rb +0 -557
  50. data/lib/hts/libhts/vcf_funcs.rb +0 -353
  51. data/lib/hts/libhts.rb +0 -47
data/lib/hts/bam.rb CHANGED
@@ -17,6 +17,20 @@ module HTS
17
17
  class Bam
18
18
  include Enumerable
19
19
 
20
+ # Filter an owning batch of records in one native pass when available.
21
+ def self.filter_records(records, required_flags: 0, excluded_flags: 0,
22
+ min_mapq: 0, tid: nil, beg: nil, end_: nil)
23
+ required_flags = Integer(required_flags)
24
+ excluded_flags = Integer(excluded_flags)
25
+ min_mapq = Integer(min_mapq)
26
+ Array(records).select do |record|
27
+ flags = record.flag_value
28
+ (flags & required_flags) == required_flags && (flags & excluded_flags).zero? &&
29
+ record.mapq >= min_mapq && (tid.nil? || record.tid == Integer(tid)) &&
30
+ (beg.nil? || record.endpos > Integer(beg)) && (end_.nil? || record.pos < Integer(end_))
31
+ end
32
+ end
33
+
20
34
  attr_reader :file_name, :index_name, :mode, :header, :nthreads
21
35
 
22
36
  def self.open(*args, **kw)
@@ -40,7 +54,7 @@ module HTS
40
54
  end
41
55
  end
42
56
 
43
- case LibHTS.sam_index_build3(file_name, index_name, min_shift, threads)
57
+ case Native::BamFileHandle.build_index(file_name, index_name, min_shift, threads)
44
58
  when 0 # successful
45
59
  when -1 then raise "indexing failed"
46
60
  when -2 then raise "opening #{file_name} failed"
@@ -63,9 +77,8 @@ module HTS
63
77
  @index_name = index
64
78
  @mode = mode
65
79
  @nthreads = threads
66
- @hts_file = LibHTS.hts_open(@file_name, mode)
67
-
68
- raise Errno::ENOENT, "Failed to open #{@file_name}" if @hts_file.null?
80
+ @index_load_attempted = false
81
+ @native = Native::BamFileHandle.open(@file_name, mode)
69
82
 
70
83
  # Auto-detect and set reference for CRAM files
71
84
  if fai.nil? && @file_name.end_with?(".cram")
@@ -79,7 +92,7 @@ module HTS
79
92
  end
80
93
 
81
94
  if fai
82
- r = LibHTS.hts_set_fai_filename(@hts_file, fai)
95
+ r = @native.set_fai(fai)
83
96
  raise "Failed to load fasta index: #{fai}" if r < 0
84
97
  end
85
98
 
@@ -87,9 +100,13 @@ module HTS
87
100
 
88
101
  return if @mode[0] == "w"
89
102
 
90
- @header = Bam::Header.new(@hts_file)
91
- build_index(index) if build_index
92
- @idx = load_index(index)
103
+ @header = Bam::Header.new(@native.read_header)
104
+ if build_index
105
+ build_index(index)
106
+ load_index(index)
107
+ elsif index
108
+ load_index(index)
109
+ end
93
110
  @start_position = tell
94
111
  end
95
112
 
@@ -97,36 +114,69 @@ module HTS
97
114
  check_closed
98
115
 
99
116
  self.class.build_index(@file_name, index_name, min_shift, @nthreads || 0, verbose)
117
+ @index_name = index_name
118
+ @index_load_attempted = false
100
119
  self # for method chaining
101
120
  end
102
121
 
103
122
  def load_index(index_name = nil)
104
123
  check_closed
105
124
 
106
- if index_name
107
- LibHTS.sam_index_load2(@hts_file, @file_name, index_name)
108
- else
109
- LibHTS.sam_index_load3(@hts_file, @file_name, nil, 2) # should be 3 ? (copy remote file to local?)
110
- end
125
+ @index_name = index_name
126
+ @index_load_attempted = true
127
+ @native.load_index(index_name)
111
128
  end
112
129
 
113
130
  def index_loaded?
114
131
  check_closed
115
132
 
116
- !@idx.null?
133
+ @native.index_loaded?
117
134
  end
118
135
 
119
136
  def close
120
- LibHTS.hts_idx_destroy(@idx) if @idx && !@idx.null?
121
- @idx = nil
122
- super
137
+ @native&.close
123
138
  end
124
139
 
140
+ def closed? = @native.nil? || @native.closed?
141
+ def file_format = @native.file_format
142
+ def file_format_version = @native.file_format_version
143
+
144
+ def set_threads(n = nil)
145
+ if n.nil?
146
+ require "etc"
147
+ n = [Etc.nprocessors - 1, 1].max
148
+ end
149
+ raise TypeError unless n.is_a?(Integer)
150
+ raise ArgumentError, "Number of threads must be positive" if n < 1
151
+ raise "Failed to set number of threads: #{n}" if @native.set_threads(n).negative?
152
+
153
+ @nthreads = n
154
+ self
155
+ end
156
+
157
+ def seek(offset) = @native.seek(offset)
158
+ def tell = @native.tell
159
+
160
+ def rewind
161
+ raise "Cannot rewind: no start position" unless @start_position
162
+
163
+ result = seek(@start_position)
164
+ raise "Failed to rewind: #{result}" if result.negative?
165
+
166
+ tell
167
+ end
168
+
169
+ private
170
+
171
+ def native_handle = @native
172
+
173
+ public
174
+
125
175
  def write_header(header)
126
176
  check_closed
127
177
 
128
178
  @header = header.dup
129
- LibHTS.sam_hdr_write(@hts_file, header)
179
+ @native.write_header(header.__send__(:native_handle))
130
180
  end
131
181
 
132
182
  def header=(header)
@@ -136,7 +186,7 @@ module HTS
136
186
  def write(record)
137
187
  check_closed
138
188
 
139
- r = LibHTS.sam_write1(@hts_file, header, record)
189
+ r = @native.write(header.__send__(:native_handle), record.__send__(:native_handle))
140
190
  raise "Failed to write record" if r < 0
141
191
  end
142
192
 
@@ -172,6 +222,13 @@ module HTS
172
222
  seek(position) if position
173
223
  ary
174
224
  end
225
+ alias aux_array aux
226
+
227
+ # Materialize independent records from the current stream position.
228
+ # Unlike each.to_a, every element owns its bam1_t storage.
229
+ def collect_records
230
+ each(copy: true).to_a
231
+ end
175
232
 
176
233
  # @!macro [attach] define_iterator
177
234
  # @method each_$1
@@ -236,7 +293,7 @@ module HTS
236
293
  # bam.query(["chr1:100-200", "chr2:500-600"]) { |r| puts r.qname }
237
294
  def query(region, beg = nil, end_ = nil, copy: false, &block)
238
295
  check_closed
239
- raise "Index file is required to call the query method." unless index_loaded?
296
+ raise "Index file is required to call the query method." unless ensure_index_loaded
240
297
 
241
298
  case region
242
299
  when Array
@@ -280,6 +337,13 @@ module HTS
280
337
 
281
338
  private
282
339
 
340
+ def ensure_index_loaded
341
+ return true if index_loaded?
342
+ return false if @index_load_attempted
343
+
344
+ load_index(@index_name)
345
+ end
346
+
283
347
  def queryi(tid, beg, end_, copy: false, &block)
284
348
  if copy
285
349
  queryi_copy(tid, beg, end_, &block)
@@ -313,9 +377,8 @@ module HTS
313
377
  # This is the common behavior of IO objects in Ruby.
314
378
  return to_enum(__method__) unless block_given?
315
379
 
316
- bam1 = LibHTS.bam_init1
317
- record = Record.new(header, bam1)
318
- yield record while LibHTS.sam_read1(@hts_file, header, bam1) != -1
380
+ record = Record.new(header)
381
+ yield record while @native.read(header.__send__(:native_handle), record.__send__(:native_handle)) != -1
319
382
  self
320
383
  end
321
384
 
@@ -324,17 +387,16 @@ module HTS
324
387
  check_closed
325
388
  return to_enum(__method__) unless block_given?
326
389
 
327
- bam1 = LibHTS.bam_init1
328
- record = Record.new(header, bam1)
329
- yield record.dup while LibHTS.sam_read1(@hts_file, header, bam1) != -1
390
+ record = Record.new(header)
391
+ yield record.dup while @native.read(header.__send__(:native_handle), record.__send__(:native_handle)) != -1
330
392
  self
331
393
  end
332
394
 
333
395
  def queryi_reuse(tid, beg, end_, &block)
334
396
  return to_enum(__method__, tid, beg, end_) unless block_given?
335
397
 
336
- qiter = LibHTS.sam_itr_queryi(@idx, tid, beg, end_)
337
- raise "Failed to query region: #{tid} #{beg} #{end_}" if qiter.null?
398
+ qiter = @native.query_interval(tid, beg, end_)
399
+ raise "Failed to query region: #{tid} #{beg} #{end_}" unless qiter
338
400
 
339
401
  query_reuse_yield(qiter, &block)
340
402
  self
@@ -343,8 +405,8 @@ module HTS
343
405
  def queryi_copy(tid, beg, end_, &block)
344
406
  return to_enum(__method__, tid, beg, end_) unless block_given?
345
407
 
346
- qiter = LibHTS.sam_itr_queryi(@idx, tid, beg, end_)
347
- raise "Failed to query region: #{tid} #{beg} #{end_}" if qiter.null?
408
+ qiter = @native.query_interval(tid, beg, end_)
409
+ raise "Failed to query region: #{tid} #{beg} #{end_}" unless qiter
348
410
 
349
411
  query_copy(qiter, &block)
350
412
  self
@@ -353,8 +415,8 @@ module HTS
353
415
  def querys_reuse(region, &block)
354
416
  return to_enum(__method__, region) unless block_given?
355
417
 
356
- qiter = LibHTS.sam_itr_querys(@idx, header, region)
357
- raise "Failed to query region: #{region}" if qiter.null?
418
+ qiter = @native.query_region(header.__send__(:native_handle), region)
419
+ raise "Failed to query region: #{region}" unless qiter
358
420
 
359
421
  query_reuse_yield(qiter, &block)
360
422
  self
@@ -363,8 +425,8 @@ module HTS
363
425
  def querys_copy(region, &block)
364
426
  return to_enum(__method__, region) unless block_given?
365
427
 
366
- qiter = LibHTS.sam_itr_querys(@idx, header, region)
367
- raise "Failed to query region: #{region}" if qiter.null?
428
+ qiter = @native.query_region(header.__send__(:native_handle), region)
429
+ raise "Failed to query region: #{region}" unless qiter
368
430
 
369
431
  query_copy(qiter, &block)
370
432
  self
@@ -372,30 +434,28 @@ module HTS
372
434
 
373
435
  # Internal: reused-Record iterator over a query iterator.
374
436
  def query_reuse_yield(qiter)
375
- bam1 = LibHTS.bam_init1
376
- record = Record.new(header, bam1)
437
+ record = Record.new(header)
377
438
  begin
378
- while (slen = LibHTS.sam_itr_next(@hts_file, qiter, bam1)) >= 0
439
+ while (slen = qiter.next(record.__send__(:native_handle))) >= 0
379
440
  yield record
380
441
  end
381
442
  raise if slen < -1
382
443
  ensure
383
- LibHTS.hts_itr_destroy(qiter)
444
+ qiter.close
384
445
  end
385
446
  end
386
447
 
387
448
  def query_copy(qiter)
388
- bam1 = LibHTS.bam_init1
389
- record = Record.new(header, bam1)
449
+ record = Record.new(header)
390
450
  loop do
391
- slen = LibHTS.sam_itr_next(@hts_file, qiter, bam1)
451
+ slen = qiter.next(record.__send__(:native_handle))
392
452
  break if slen == -1
393
453
  raise if slen < -1
394
454
 
395
455
  yield record.dup
396
456
  end
397
457
  ensure
398
- LibHTS.hts_itr_destroy(qiter)
458
+ qiter.close
399
459
  end
400
460
 
401
461
  # Multi-region query using sequential single-region queries
@@ -21,6 +21,7 @@ module HTS
21
21
  class FormatDefinitionError < FormatError; end
22
22
  class FormatTypeError < FormatError; end
23
23
  class FormatReadError < FormatError; end
24
+ class InvalidBorrowedViewError < FormatError; end
24
25
  class FormatUpdateError < FormatError; end
25
26
  class UnsupportedFormatOperationError < FormatError; end
26
27
  end