htslib 0.4.2 → 0.6.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.
- checksums.yaml +4 -4
- data/README.md +83 -51
- data/TUTORIAL.md +11 -22
- data/ext/htslib_native/extconf.rb +41 -0
- data/ext/htslib_native/htslib_native.h +11 -0
- data/ext/htslib_native/htslib_native_ext.c +48 -0
- data/ext/htslib_native/native_bam.c +1167 -0
- data/ext/htslib_native/native_bcf.c +447 -0
- data/ext/htslib_native/native_faidx.c +164 -0
- data/ext/htslib_native/native_tabix.c +255 -0
- data/lib/hts/bam/auxi.rb +87 -342
- data/lib/hts/bam/base_mod.rb +38 -73
- data/lib/hts/bam/cigar.rb +9 -55
- data/lib/hts/bam/flag.rb +24 -30
- data/lib/hts/bam/header.rb +29 -56
- data/lib/hts/bam/mpileup.rb +158 -132
- data/lib/hts/bam/pileup.rb +120 -145
- data/lib/hts/bam/record.rb +233 -270
- data/lib/hts/bam.rb +154 -45
- data/lib/hts/bcf/errors.rb +4 -0
- data/lib/hts/bcf/format.rb +283 -377
- data/lib/hts/bcf/header.rb +40 -122
- data/lib/hts/bcf/header_record.rb +9 -35
- data/lib/hts/bcf/info.rb +73 -320
- data/lib/hts/bcf/record.rb +73 -101
- data/lib/hts/bcf.rb +178 -321
- data/lib/hts/faidx.rb +30 -87
- data/lib/hts/hts.rb +6 -94
- data/lib/hts/native.rb +13 -0
- data/lib/hts/tabix.rb +109 -51
- data/lib/hts/version.rb +1 -1
- data/lib/htslib.rb +6 -52
- metadata +13 -64
- data/lib/hts/ffi_ext/README.md +0 -8
- data/lib/hts/ffi_ext/pointer.rb +0 -18
- data/lib/hts/ffi_ext/struct.rb +0 -45
- data/lib/hts/libhts/bgzf.rb +0 -199
- data/lib/hts/libhts/constants.rb +0 -658
- data/lib/hts/libhts/cram.rb +0 -471
- data/lib/hts/libhts/fai.rb +0 -146
- data/lib/hts/libhts/hfile.rb +0 -121
- data/lib/hts/libhts/hts.rb +0 -477
- data/lib/hts/libhts/kfunc.rb +0 -38
- data/lib/hts/libhts/sam.rb +0 -760
- data/lib/hts/libhts/sam_funcs.rb +0 -155
- data/lib/hts/libhts/tbx.rb +0 -94
- data/lib/hts/libhts/tbx_funcs.rb +0 -36
- data/lib/hts/libhts/thread_pool.rb +0 -139
- data/lib/hts/libhts/vcf.rb +0 -567
- data/lib/hts/libhts/vcf_funcs.rb +0 -366
- data/lib/hts/libhts.rb +0 -47
data/lib/hts/bam.rb
CHANGED
|
@@ -17,6 +17,25 @@ module HTS
|
|
|
17
17
|
class Bam
|
|
18
18
|
include Enumerable
|
|
19
19
|
|
|
20
|
+
class ReadError < HTS::Error; end
|
|
21
|
+
class WriteError < HTS::Error; end
|
|
22
|
+
class OpenError < HTS::Error; end
|
|
23
|
+
class MissingIndexError < HTS::Error; end
|
|
24
|
+
|
|
25
|
+
# Filter an owning batch of records in one native pass when available.
|
|
26
|
+
def self.filter_records(records, required_flags: 0, excluded_flags: 0,
|
|
27
|
+
min_mapq: 0, tid: nil, beg: nil, end_: nil)
|
|
28
|
+
required_flags = Integer(required_flags)
|
|
29
|
+
excluded_flags = Integer(excluded_flags)
|
|
30
|
+
min_mapq = Integer(min_mapq)
|
|
31
|
+
Array(records).select do |record|
|
|
32
|
+
flags = record.flag_value
|
|
33
|
+
(flags & required_flags) == required_flags && (flags & excluded_flags).zero? &&
|
|
34
|
+
record.mapq >= min_mapq && (tid.nil? || record.tid == Integer(tid)) &&
|
|
35
|
+
(beg.nil? || record.endpos > Integer(beg)) && (end_.nil? || record.pos < Integer(end_))
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
20
39
|
attr_reader :file_name, :index_name, :mode, :header, :nthreads
|
|
21
40
|
|
|
22
41
|
def self.open(*args, **kw)
|
|
@@ -24,11 +43,11 @@ module HTS
|
|
|
24
43
|
return file unless block_given?
|
|
25
44
|
|
|
26
45
|
begin
|
|
27
|
-
yield file
|
|
46
|
+
result = yield file
|
|
28
47
|
ensure
|
|
29
48
|
file.close
|
|
30
49
|
end
|
|
31
|
-
|
|
50
|
+
result
|
|
32
51
|
end
|
|
33
52
|
|
|
34
53
|
def self.build_index(file_name, index_name = nil, min_shift = 0, threads = 0, verbose = true)
|
|
@@ -40,7 +59,7 @@ module HTS
|
|
|
40
59
|
end
|
|
41
60
|
end
|
|
42
61
|
|
|
43
|
-
case
|
|
62
|
+
case Native::BamFileHandle.build_index(file_name, index_name, min_shift, threads)
|
|
44
63
|
when 0 # successful
|
|
45
64
|
when -1 then raise "indexing failed"
|
|
46
65
|
when -2 then raise "opening #{file_name} failed"
|
|
@@ -63,9 +82,8 @@ module HTS
|
|
|
63
82
|
@index_name = index
|
|
64
83
|
@mode = mode
|
|
65
84
|
@nthreads = threads
|
|
66
|
-
@
|
|
67
|
-
|
|
68
|
-
raise Errno::ENOENT, "Failed to open #{@file_name}" if @hts_file.null?
|
|
85
|
+
@index_load_attempted = false
|
|
86
|
+
@native = Native::BamFileHandle.open(@file_name, mode)
|
|
69
87
|
|
|
70
88
|
# Auto-detect and set reference for CRAM files
|
|
71
89
|
if fai.nil? && @file_name.end_with?(".cram")
|
|
@@ -79,17 +97,25 @@ module HTS
|
|
|
79
97
|
end
|
|
80
98
|
|
|
81
99
|
if fai
|
|
82
|
-
r =
|
|
100
|
+
r = @native.set_fai(fai)
|
|
83
101
|
raise "Failed to load fasta index: #{fai}" if r < 0
|
|
84
102
|
end
|
|
85
103
|
|
|
86
104
|
set_threads(threads) if threads
|
|
87
105
|
|
|
88
|
-
|
|
106
|
+
if writing?
|
|
107
|
+
@auto_index_on_close = build_index
|
|
108
|
+
@index_name_on_close = index
|
|
109
|
+
return
|
|
110
|
+
end
|
|
89
111
|
|
|
90
|
-
@header = Bam::Header.new(@
|
|
91
|
-
|
|
92
|
-
|
|
112
|
+
@header = Bam::Header.new(@native.read_header)
|
|
113
|
+
if build_index
|
|
114
|
+
build_index(index)
|
|
115
|
+
load_index(index)
|
|
116
|
+
elsif index
|
|
117
|
+
load_index(index)
|
|
118
|
+
end
|
|
93
119
|
@start_position = tell
|
|
94
120
|
end
|
|
95
121
|
|
|
@@ -97,36 +123,87 @@ module HTS
|
|
|
97
123
|
check_closed
|
|
98
124
|
|
|
99
125
|
self.class.build_index(@file_name, index_name, min_shift, @nthreads || 0, verbose)
|
|
126
|
+
@index_name = index_name
|
|
127
|
+
@index_load_attempted = false
|
|
100
128
|
self # for method chaining
|
|
101
129
|
end
|
|
102
130
|
|
|
103
131
|
def load_index(index_name = nil)
|
|
132
|
+
return self if try_load_index(index_name)
|
|
133
|
+
|
|
134
|
+
raise MissingIndexError, "Failed to load index #{index_name || "for #{@file_name}"}"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def try_load_index(index_name = nil)
|
|
104
138
|
check_closed
|
|
105
139
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
LibHTS.sam_index_load3(@hts_file, @file_name, nil, 2) # should be 3 ? (copy remote file to local?)
|
|
110
|
-
end
|
|
140
|
+
@index_name = index_name
|
|
141
|
+
@index_load_attempted = true
|
|
142
|
+
@native.load_index(index_name)
|
|
111
143
|
end
|
|
112
144
|
|
|
113
145
|
def index_loaded?
|
|
114
146
|
check_closed
|
|
115
147
|
|
|
116
|
-
|
|
148
|
+
@native.index_loaded?
|
|
117
149
|
end
|
|
118
150
|
|
|
119
151
|
def close
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
152
|
+
was_closed = closed?
|
|
153
|
+
result = @native&.close
|
|
154
|
+
raise WriteError, "Failed to close #{@file_name}: buffered output may be incomplete" if writing? && result&.negative?
|
|
155
|
+
|
|
156
|
+
if writing? && @auto_index_on_close && !was_closed
|
|
157
|
+
@auto_index_on_close = false
|
|
158
|
+
self.class.build_index(@file_name, @index_name_on_close, 0, @nthreads || 0, false)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
nil
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def closed? = @native.nil? || @native.closed?
|
|
165
|
+
def file_format = @native.file_format
|
|
166
|
+
def file_format_version = @native.file_format_version
|
|
167
|
+
|
|
168
|
+
def set_threads(n = nil)
|
|
169
|
+
if n.nil?
|
|
170
|
+
require "etc"
|
|
171
|
+
n = [Etc.nprocessors - 1, 1].max
|
|
172
|
+
end
|
|
173
|
+
raise TypeError unless n.is_a?(Integer)
|
|
174
|
+
raise ArgumentError, "Number of threads must be positive" if n < 1
|
|
175
|
+
raise "Failed to set number of threads: #{n}" if @native.set_threads(n).negative?
|
|
176
|
+
|
|
177
|
+
@nthreads = n
|
|
178
|
+
self
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def seek(offset) = @native.seek(offset)
|
|
182
|
+
def tell = @native.tell
|
|
183
|
+
|
|
184
|
+
def rewind
|
|
185
|
+
raise "Cannot rewind: no start position" unless @start_position
|
|
186
|
+
|
|
187
|
+
result = seek(@start_position)
|
|
188
|
+
raise "Failed to rewind: #{result}" if result.negative?
|
|
189
|
+
|
|
190
|
+
tell
|
|
123
191
|
end
|
|
124
192
|
|
|
193
|
+
private
|
|
194
|
+
|
|
195
|
+
def writing? = @mode&.start_with?("w", "a")
|
|
196
|
+
|
|
197
|
+
def native_handle = @native
|
|
198
|
+
|
|
199
|
+
public
|
|
200
|
+
|
|
125
201
|
def write_header(header)
|
|
126
202
|
check_closed
|
|
127
203
|
|
|
128
204
|
@header = header.dup
|
|
129
|
-
|
|
205
|
+
@native.write_header(header.__send__(:native_handle))
|
|
206
|
+
nil
|
|
130
207
|
end
|
|
131
208
|
|
|
132
209
|
def header=(header)
|
|
@@ -136,12 +213,15 @@ module HTS
|
|
|
136
213
|
def write(record)
|
|
137
214
|
check_closed
|
|
138
215
|
|
|
139
|
-
r =
|
|
216
|
+
r = @native.write(header.__send__(:native_handle), record.__send__(:native_handle))
|
|
140
217
|
raise "Failed to write record" if r < 0
|
|
218
|
+
|
|
219
|
+
nil
|
|
141
220
|
end
|
|
142
221
|
|
|
143
222
|
def <<(record)
|
|
144
223
|
write(record)
|
|
224
|
+
self
|
|
145
225
|
end
|
|
146
226
|
|
|
147
227
|
# @!macro [attach] define_getter
|
|
@@ -172,6 +252,20 @@ module HTS
|
|
|
172
252
|
seek(position) if position
|
|
173
253
|
ary
|
|
174
254
|
end
|
|
255
|
+
alias aux_array aux
|
|
256
|
+
|
|
257
|
+
# Materialize independent records from the current stream position.
|
|
258
|
+
# Unlike each.to_a, every element owns its bam1_t storage.
|
|
259
|
+
def collect_records
|
|
260
|
+
each(copy: true).to_a
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Materialize independent, owning records. Enumerable#to_a is unsafe for
|
|
264
|
+
# the default reused-record iterator because every array element would
|
|
265
|
+
# otherwise refer to the same native bam1_t buffer.
|
|
266
|
+
def to_a
|
|
267
|
+
collect_records
|
|
268
|
+
end
|
|
175
269
|
|
|
176
270
|
# @!macro [attach] define_iterator
|
|
177
271
|
# @method each_$1
|
|
@@ -236,7 +330,7 @@ module HTS
|
|
|
236
330
|
# bam.query(["chr1:100-200", "chr2:500-600"]) { |r| puts r.qname }
|
|
237
331
|
def query(region, beg = nil, end_ = nil, copy: false, &block)
|
|
238
332
|
check_closed
|
|
239
|
-
raise "Index file is required to call the query method." unless
|
|
333
|
+
raise "Index file is required to call the query method." unless ensure_index_loaded
|
|
240
334
|
|
|
241
335
|
case region
|
|
242
336
|
when Array
|
|
@@ -280,6 +374,13 @@ module HTS
|
|
|
280
374
|
|
|
281
375
|
private
|
|
282
376
|
|
|
377
|
+
def ensure_index_loaded
|
|
378
|
+
return true if index_loaded?
|
|
379
|
+
return false if @index_load_attempted
|
|
380
|
+
|
|
381
|
+
load_index(@index_name)
|
|
382
|
+
end
|
|
383
|
+
|
|
283
384
|
def queryi(tid, beg, end_, copy: false, &block)
|
|
284
385
|
if copy
|
|
285
386
|
queryi_copy(tid, beg, end_, &block)
|
|
@@ -313,9 +414,14 @@ module HTS
|
|
|
313
414
|
# This is the common behavior of IO objects in Ruby.
|
|
314
415
|
return to_enum(__method__) unless block_given?
|
|
315
416
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
417
|
+
record = Record.new(header)
|
|
418
|
+
loop do
|
|
419
|
+
result = @native.read(header.__send__(:native_handle), record.__send__(:native_handle))
|
|
420
|
+
break if result == -1
|
|
421
|
+
raise ReadError, "Failed to read BAM/SAM record (HTSlib error #{result})" if result < -1
|
|
422
|
+
|
|
423
|
+
yield record
|
|
424
|
+
end
|
|
319
425
|
self
|
|
320
426
|
end
|
|
321
427
|
|
|
@@ -324,17 +430,22 @@ module HTS
|
|
|
324
430
|
check_closed
|
|
325
431
|
return to_enum(__method__) unless block_given?
|
|
326
432
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
433
|
+
record = Record.new(header)
|
|
434
|
+
loop do
|
|
435
|
+
result = @native.read(header.__send__(:native_handle), record.__send__(:native_handle))
|
|
436
|
+
break if result == -1
|
|
437
|
+
raise ReadError, "Failed to read BAM/SAM record (HTSlib error #{result})" if result < -1
|
|
438
|
+
|
|
439
|
+
yield record.dup
|
|
440
|
+
end
|
|
330
441
|
self
|
|
331
442
|
end
|
|
332
443
|
|
|
333
444
|
def queryi_reuse(tid, beg, end_, &block)
|
|
334
445
|
return to_enum(__method__, tid, beg, end_) unless block_given?
|
|
335
446
|
|
|
336
|
-
qiter =
|
|
337
|
-
raise "Failed to query region: #{tid} #{beg} #{end_}"
|
|
447
|
+
qiter = @native.query_interval(tid, beg, end_)
|
|
448
|
+
raise "Failed to query region: #{tid} #{beg} #{end_}" unless qiter
|
|
338
449
|
|
|
339
450
|
query_reuse_yield(qiter, &block)
|
|
340
451
|
self
|
|
@@ -343,8 +454,8 @@ module HTS
|
|
|
343
454
|
def queryi_copy(tid, beg, end_, &block)
|
|
344
455
|
return to_enum(__method__, tid, beg, end_) unless block_given?
|
|
345
456
|
|
|
346
|
-
qiter =
|
|
347
|
-
raise "Failed to query region: #{tid} #{beg} #{end_}"
|
|
457
|
+
qiter = @native.query_interval(tid, beg, end_)
|
|
458
|
+
raise "Failed to query region: #{tid} #{beg} #{end_}" unless qiter
|
|
348
459
|
|
|
349
460
|
query_copy(qiter, &block)
|
|
350
461
|
self
|
|
@@ -353,8 +464,8 @@ module HTS
|
|
|
353
464
|
def querys_reuse(region, &block)
|
|
354
465
|
return to_enum(__method__, region) unless block_given?
|
|
355
466
|
|
|
356
|
-
qiter =
|
|
357
|
-
raise "Failed to query region: #{region}"
|
|
467
|
+
qiter = @native.query_region(header.__send__(:native_handle), region)
|
|
468
|
+
raise "Failed to query region: #{region}" unless qiter
|
|
358
469
|
|
|
359
470
|
query_reuse_yield(qiter, &block)
|
|
360
471
|
self
|
|
@@ -363,8 +474,8 @@ module HTS
|
|
|
363
474
|
def querys_copy(region, &block)
|
|
364
475
|
return to_enum(__method__, region) unless block_given?
|
|
365
476
|
|
|
366
|
-
qiter =
|
|
367
|
-
raise "Failed to query region: #{region}"
|
|
477
|
+
qiter = @native.query_region(header.__send__(:native_handle), region)
|
|
478
|
+
raise "Failed to query region: #{region}" unless qiter
|
|
368
479
|
|
|
369
480
|
query_copy(qiter, &block)
|
|
370
481
|
self
|
|
@@ -372,30 +483,28 @@ module HTS
|
|
|
372
483
|
|
|
373
484
|
# Internal: reused-Record iterator over a query iterator.
|
|
374
485
|
def query_reuse_yield(qiter)
|
|
375
|
-
|
|
376
|
-
record = Record.new(header, bam1)
|
|
486
|
+
record = Record.new(header)
|
|
377
487
|
begin
|
|
378
|
-
while (slen =
|
|
488
|
+
while (slen = qiter.next(record.__send__(:native_handle))) >= 0
|
|
379
489
|
yield record
|
|
380
490
|
end
|
|
381
491
|
raise if slen < -1
|
|
382
492
|
ensure
|
|
383
|
-
|
|
493
|
+
qiter.close
|
|
384
494
|
end
|
|
385
495
|
end
|
|
386
496
|
|
|
387
497
|
def query_copy(qiter)
|
|
388
|
-
|
|
389
|
-
record = Record.new(header, bam1)
|
|
498
|
+
record = Record.new(header)
|
|
390
499
|
loop do
|
|
391
|
-
slen =
|
|
500
|
+
slen = qiter.next(record.__send__(:native_handle))
|
|
392
501
|
break if slen == -1
|
|
393
502
|
raise if slen < -1
|
|
394
503
|
|
|
395
504
|
yield record.dup
|
|
396
505
|
end
|
|
397
506
|
ensure
|
|
398
|
-
|
|
507
|
+
qiter.close
|
|
399
508
|
end
|
|
400
509
|
|
|
401
510
|
# Multi-region query using sequential single-region queries
|
data/lib/hts/bcf/errors.rb
CHANGED
|
@@ -5,14 +5,17 @@ module HTS
|
|
|
5
5
|
class Error < HTS::Error; end
|
|
6
6
|
|
|
7
7
|
class OpenError < Error; end
|
|
8
|
+
class WriteError < Error; end
|
|
8
9
|
class IndexError < Error; end
|
|
9
10
|
class MissingIndexError < IndexError; end
|
|
10
11
|
class QueryError < Error; end
|
|
12
|
+
class RecordError < Error; end
|
|
11
13
|
class HeaderError < Error; end
|
|
12
14
|
class SubsetError < HeaderError; end
|
|
13
15
|
class UnknownSampleError < SubsetError; end
|
|
14
16
|
class FieldError < Error; end
|
|
15
17
|
class InfoError < FieldError; end
|
|
18
|
+
class InfoDefinitionError < InfoError; end
|
|
16
19
|
class InfoTypeError < InfoError; end
|
|
17
20
|
class InfoReadError < InfoError; end
|
|
18
21
|
class InfoUpdateError < InfoError; end
|
|
@@ -21,6 +24,7 @@ module HTS
|
|
|
21
24
|
class FormatDefinitionError < FormatError; end
|
|
22
25
|
class FormatTypeError < FormatError; end
|
|
23
26
|
class FormatReadError < FormatError; end
|
|
27
|
+
class InvalidBorrowedViewError < FormatError; end
|
|
24
28
|
class FormatUpdateError < FormatError; end
|
|
25
29
|
class UnsupportedFormatOperationError < FormatError; end
|
|
26
30
|
end
|