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/bcf/info.rb
CHANGED
|
@@ -2,385 +2,138 @@
|
|
|
2
2
|
|
|
3
3
|
module HTS
|
|
4
4
|
class Bcf < Hts
|
|
5
|
-
# Info field
|
|
6
5
|
class Info
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
# which provides methods like `get_int`, `get_float`, etc.
|
|
15
|
-
# I think they are better than `fetch_int`` and `fetch_float`.
|
|
16
|
-
def get(key, type = nil)
|
|
17
|
-
n = FFI::MemoryPointer.new(:int)
|
|
18
|
-
p1 = FFI::MemoryPointer.new(:pointer)
|
|
19
|
-
p1.write_pointer(FFI::Pointer::NULL)
|
|
20
|
-
h = @record.header.struct
|
|
21
|
-
r = @record.struct
|
|
22
|
-
|
|
23
|
-
info_values = proc do |typ, reader|
|
|
24
|
-
ret = LibHTS.bcf_get_info_values(h, r, key, p1, n, typ)
|
|
25
|
-
return nil if ret < 0 # return from method.
|
|
6
|
+
TYPE_CODES = {
|
|
7
|
+
flag: Native::BCF_HT_FLAG, bool: Native::BCF_HT_FLAG,
|
|
8
|
+
int: Native::BCF_HT_INT, int32: Native::BCF_HT_INT,
|
|
9
|
+
int64: Native::BCF_HT_LONG, long: Native::BCF_HT_LONG,
|
|
10
|
+
float: Native::BCF_HT_REAL, real: Native::BCF_HT_REAL,
|
|
11
|
+
string: Native::BCF_HT_STR, str: Native::BCF_HT_STR
|
|
12
|
+
}.freeze
|
|
26
13
|
|
|
27
|
-
|
|
28
|
-
begin
|
|
29
|
-
reader.call(dst, n.read_int)
|
|
30
|
-
ensure
|
|
31
|
-
LibHTS.hts_free(dst) unless dst.null?
|
|
32
|
-
p1.write_pointer(FFI::Pointer::NULL)
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
actual_type = ht_type_to_sym(get_info_type(key))
|
|
37
|
-
if type && actual_type && !info_type_compatible?(actual_type, type.to_sym)
|
|
38
|
-
raise InfoTypeError, "Tag #{key} is not #{type_label(type)} INFO field"
|
|
39
|
-
end
|
|
14
|
+
def initialize(record) = @record = record
|
|
40
15
|
|
|
41
|
-
|
|
42
|
-
|
|
16
|
+
def get(key, type = nil)
|
|
17
|
+
schema = header.schema("INFO", key.to_s)
|
|
18
|
+
raise InfoDefinitionError, "INFO tag #{key} not defined in header" unless schema
|
|
43
19
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
info_values.call(LibHTS::BCF_HT_LONG, ->(dst, len) { dst.read_array_of_int64(len) })
|
|
49
|
-
when :float, :real
|
|
50
|
-
info_values.call(LibHTS::BCF_HT_REAL, ->(dst, len) { dst.read_array_of_float(len) })
|
|
51
|
-
when :flag, :bool
|
|
52
|
-
begin
|
|
53
|
-
case ret = LibHTS.bcf_get_info_flag(h, r, key, p1, n)
|
|
54
|
-
when 1 then true
|
|
55
|
-
when 0 then false
|
|
56
|
-
when -1 then nil
|
|
57
|
-
else
|
|
58
|
-
raise InfoReadError, "Unknown return value from bcf_get_info_flag: #{ret}"
|
|
59
|
-
end
|
|
60
|
-
ensure
|
|
61
|
-
dst = p1.read_pointer
|
|
62
|
-
LibHTS.hts_free(dst) unless dst.null?
|
|
63
|
-
p1.write_pointer(FFI::Pointer::NULL)
|
|
64
|
-
end
|
|
65
|
-
when :string, :str
|
|
66
|
-
info_values.call(LibHTS::BCF_HT_STR, ->(dst, _len) { dst.read_string })
|
|
20
|
+
actual_type = schema.first
|
|
21
|
+
requested = type&.to_sym
|
|
22
|
+
if requested && !type_compatible?(actual_type, requested)
|
|
23
|
+
raise InfoTypeError, "Tag #{key} is not #{type_label(requested)} INFO field"
|
|
67
24
|
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# For compatibility with HTS.cr.
|
|
71
|
-
def get_int(key)
|
|
72
|
-
get(key, :int)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# For compatibility with HTS.cr.
|
|
76
|
-
def get_float(key)
|
|
77
|
-
get(key, :float)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# For compatibility with HTS.cr.
|
|
81
|
-
def get_int64(key)
|
|
82
|
-
get(key, :int64)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
# For compatibility with HTS.cr.
|
|
86
|
-
def get_string(key)
|
|
87
|
-
get(key, :string)
|
|
88
|
-
end
|
|
89
25
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
26
|
+
code = TYPE_CODES.fetch(requested || actual_type)
|
|
27
|
+
value = native.info_get(header_native, key.to_s, code)
|
|
28
|
+
code == Native::BCF_HT_FLAG && value.nil? ? false : value
|
|
93
29
|
end
|
|
94
30
|
|
|
95
|
-
def
|
|
96
|
-
|
|
97
|
-
|
|
31
|
+
def get_int(key) = get(key, :int)
|
|
32
|
+
def get_float(key) = get(key, :float)
|
|
33
|
+
def get_int64(key) = get(key, :int64)
|
|
34
|
+
def get_string(key) = get(key, :string)
|
|
35
|
+
def get_flag(key) = get(key, :flag)
|
|
36
|
+
def [](key) = get(key)
|
|
98
37
|
|
|
99
|
-
# Set INFO field value with automatic type detection.
|
|
100
|
-
# @param key [String] INFO tag name
|
|
101
|
-
# @param value [Integer, Float, String, Array, true, false, nil] value to set
|
|
102
|
-
# - Integer or Array<Integer> -> update_int
|
|
103
|
-
# - Float or Array<Float,Integer> -> update_float
|
|
104
|
-
# - String -> update_string
|
|
105
|
-
# - true/false -> update_flag
|
|
106
|
-
# - nil -> delete the INFO field
|
|
107
38
|
def []=(key, value)
|
|
108
39
|
case value
|
|
109
|
-
when nil
|
|
110
|
-
|
|
111
|
-
when true, false
|
|
112
|
-
update_flag(key, value)
|
|
40
|
+
when nil then delete(key)
|
|
41
|
+
when true, false then update_flag(key, value)
|
|
113
42
|
when Integer
|
|
114
|
-
unless
|
|
43
|
+
unless int32?(value)
|
|
115
44
|
raise RangeError,
|
|
116
45
|
"Integer out of int32 range for []=. Current htslib backend does not support int64 INFO update."
|
|
117
46
|
end
|
|
118
47
|
|
|
119
48
|
update_int(key, [value])
|
|
120
|
-
when Float
|
|
121
|
-
|
|
122
|
-
when String
|
|
123
|
-
update_string(key, value)
|
|
49
|
+
when Float then update_float(key, [value])
|
|
50
|
+
when String then update_string(key, value)
|
|
124
51
|
when Array
|
|
125
|
-
if value.empty?
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
unless value.all?
|
|
52
|
+
raise ArgumentError, "Cannot set INFO field to empty array. Use nil to delete." if value.empty?
|
|
53
|
+
|
|
54
|
+
if value.all? { |item| item.is_a?(Integer) }
|
|
55
|
+
unless value.all? do |item|
|
|
56
|
+
int32?(item)
|
|
57
|
+
end
|
|
129
58
|
raise RangeError,
|
|
130
59
|
"Integer array contains out-of-int32 values for []=. Current htslib backend does not support int64 INFO update."
|
|
131
60
|
end
|
|
132
61
|
|
|
133
62
|
update_int(key, value)
|
|
134
|
-
elsif value.all? { |
|
|
63
|
+
elsif value.all? { |item| item.is_a?(Numeric) }
|
|
135
64
|
update_float(key, value)
|
|
136
65
|
else
|
|
137
66
|
raise ArgumentError, "INFO array must contain only integers or floats, got: #{value.map(&:class).uniq}"
|
|
138
67
|
end
|
|
139
|
-
else
|
|
140
|
-
raise ArgumentError, "Unsupported INFO value type: #{value.class}"
|
|
68
|
+
else raise ArgumentError, "Unsupported INFO value type: #{value.class}"
|
|
141
69
|
end
|
|
142
70
|
end
|
|
143
71
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
# @param values [Array<Integer>] integer values (use single-element array for scalar)
|
|
148
|
-
def update_int(key, values)
|
|
149
|
-
values = Array(values)
|
|
150
|
-
ptr = FFI::MemoryPointer.new(:int32, values.size)
|
|
151
|
-
ptr.write_array_of_int32(values)
|
|
152
|
-
ret = LibHTS.bcf_update_info(
|
|
153
|
-
@record.header.struct,
|
|
154
|
-
@record.struct,
|
|
155
|
-
key,
|
|
156
|
-
ptr,
|
|
157
|
-
values.size,
|
|
158
|
-
LibHTS::BCF_HT_INT
|
|
159
|
-
)
|
|
160
|
-
raise InfoUpdateError, "Failed to update INFO int field '#{key}': #{ret}" if ret < 0
|
|
161
|
-
|
|
162
|
-
ret
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# Update INFO field with int64 value(s).
|
|
166
|
-
# @note int64 INFO values are primarily relevant for VCF output.
|
|
167
|
-
# @param key [String] INFO tag name
|
|
168
|
-
# @param values [Array<Integer>] integer values (use single-element array for scalar)
|
|
169
|
-
def update_int64(_key, _values)
|
|
170
|
-
raise UnsupportedInfoOperationError, "htslib backend does not implement int64 INFO update (BCF_HT_LONG)"
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
# Update INFO field with float value(s).
|
|
174
|
-
# For compatibility with HTS.cr.
|
|
175
|
-
# @param key [String] INFO tag name
|
|
176
|
-
# @param values [Array<Float>] float values (use single-element array for scalar)
|
|
177
|
-
def update_float(key, values)
|
|
178
|
-
values = Array(values).map(&:to_f)
|
|
179
|
-
ptr = FFI::MemoryPointer.new(:float, values.size)
|
|
180
|
-
ptr.write_array_of_float(values)
|
|
181
|
-
ret = LibHTS.bcf_update_info(
|
|
182
|
-
@record.header.struct,
|
|
183
|
-
@record.struct,
|
|
184
|
-
key,
|
|
185
|
-
ptr,
|
|
186
|
-
values.size,
|
|
187
|
-
LibHTS::BCF_HT_REAL
|
|
188
|
-
)
|
|
189
|
-
raise InfoUpdateError, "Failed to update INFO float field '#{key}': #{ret}" if ret < 0
|
|
190
|
-
|
|
191
|
-
ret
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
# Update INFO field with string value.
|
|
195
|
-
# For compatibility with HTS.cr.
|
|
196
|
-
# @param key [String] INFO tag name
|
|
197
|
-
# @param value [String] string value
|
|
198
|
-
def update_string(key, value)
|
|
199
|
-
ret = LibHTS.bcf_update_info(
|
|
200
|
-
@record.header.struct,
|
|
201
|
-
@record.struct,
|
|
202
|
-
key,
|
|
203
|
-
value.to_s,
|
|
204
|
-
1,
|
|
205
|
-
LibHTS::BCF_HT_STR
|
|
206
|
-
)
|
|
207
|
-
raise InfoUpdateError, "Failed to update INFO string field '#{key}': #{ret}" if ret < 0
|
|
208
|
-
|
|
209
|
-
ret
|
|
210
|
-
end
|
|
72
|
+
def update_int(key, values) = update(key, Native::BCF_HT_INT, Array(values).map { |value| Integer(value) }, "int")
|
|
73
|
+
def update_float(key, values) = update(key, Native::BCF_HT_REAL, Array(values).map(&:to_f), "float")
|
|
74
|
+
def update_string(key, value) = update(key, Native::BCF_HT_STR, value.to_s, "string")
|
|
211
75
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
# @param present [Boolean] true to set flag, false to remove it
|
|
216
|
-
def update_flag(key, present = true)
|
|
217
|
-
ret = if present
|
|
218
|
-
LibHTS.bcf_update_info(
|
|
219
|
-
@record.header.struct,
|
|
220
|
-
@record.struct,
|
|
221
|
-
key,
|
|
222
|
-
FFI::Pointer::NULL,
|
|
223
|
-
1,
|
|
224
|
-
LibHTS::BCF_HT_FLAG
|
|
225
|
-
)
|
|
226
|
-
else
|
|
227
|
-
# Remove flag by setting n=0
|
|
228
|
-
LibHTS.bcf_update_info(
|
|
229
|
-
@record.header.struct,
|
|
230
|
-
@record.struct,
|
|
231
|
-
key,
|
|
232
|
-
FFI::Pointer::NULL,
|
|
233
|
-
0,
|
|
234
|
-
LibHTS::BCF_HT_FLAG
|
|
235
|
-
)
|
|
236
|
-
end
|
|
237
|
-
raise InfoUpdateError, "Failed to update INFO flag field '#{key}': #{ret}" if ret < 0
|
|
76
|
+
def update_int64(_key,
|
|
77
|
+
_values) = raise(UnsupportedInfoOperationError,
|
|
78
|
+
"htslib backend does not implement int64 INFO update (BCF_HT_LONG)")
|
|
238
79
|
|
|
239
|
-
|
|
240
|
-
end
|
|
80
|
+
def update_flag(key, present = true) = update(key, Native::BCF_HT_FLAG, !!present, "flag")
|
|
241
81
|
|
|
242
|
-
# Delete an INFO field.
|
|
243
|
-
# @param key [String] INFO tag name
|
|
244
|
-
# @return [Boolean] true if field was deleted, false if it didn't exist
|
|
245
82
|
def delete(key)
|
|
246
|
-
|
|
247
|
-
return false
|
|
248
|
-
return false unless key?(key)
|
|
249
|
-
|
|
250
|
-
# Delete by setting n=0
|
|
251
|
-
ret = LibHTS.bcf_update_info(
|
|
252
|
-
@record.header.struct,
|
|
253
|
-
@record.struct,
|
|
254
|
-
key,
|
|
255
|
-
FFI::Pointer::NULL,
|
|
256
|
-
0,
|
|
257
|
-
type
|
|
258
|
-
)
|
|
259
|
-
return false if ret < 0
|
|
83
|
+
schema = header.schema("INFO", key.to_s)
|
|
84
|
+
return false unless schema && key?(key)
|
|
260
85
|
|
|
86
|
+
native.info_update(header_native, key.to_s, TYPE_CODES.fetch(schema.first), false)
|
|
261
87
|
true
|
|
262
88
|
end
|
|
263
89
|
|
|
264
|
-
# Check if an INFO field exists.
|
|
265
|
-
# @param key [String] INFO tag name
|
|
266
|
-
# @return [Boolean] true if the field exists
|
|
267
90
|
def key?(key)
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
ndst = FFI::MemoryPointer.new(:int)
|
|
272
|
-
ndst.write_int(0)
|
|
273
|
-
dst_ptr = FFI::MemoryPointer.new(:pointer)
|
|
274
|
-
dst_ptr.write_pointer(FFI::Pointer::NULL)
|
|
275
|
-
|
|
276
|
-
ret = LibHTS.bcf_get_info_values(@record.header.struct, @record.struct, key, dst_ptr, ndst, type)
|
|
277
|
-
type == LibHTS::BCF_HT_FLAG ? ret == 1 : ret >= 0
|
|
278
|
-
ensure
|
|
279
|
-
if dst_ptr
|
|
280
|
-
dst = dst_ptr.read_pointer
|
|
281
|
-
LibHTS.hts_free(dst) unless dst.null?
|
|
282
|
-
end
|
|
91
|
+
schema = header.schema("INFO", key.to_s)
|
|
92
|
+
schema ? native.info_present?(header_native, key.to_s, TYPE_CODES.fetch(schema.first)) : false
|
|
283
93
|
end
|
|
284
|
-
|
|
285
94
|
alias include? key?
|
|
286
95
|
|
|
287
|
-
|
|
288
|
-
def fields
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
type = LibHTS.bcf_hdr_id2type(@record.header.struct, LibHTS::BCF_HL_INFO, key)
|
|
293
|
-
{
|
|
294
|
-
name:,
|
|
295
|
-
n: num,
|
|
296
|
-
type: ht_type_to_sym(type),
|
|
297
|
-
key:
|
|
298
|
-
}
|
|
299
|
-
end
|
|
300
|
-
end
|
|
301
|
-
|
|
302
|
-
def length
|
|
303
|
-
@record.struct[:n_info]
|
|
304
|
-
end
|
|
305
|
-
|
|
306
|
-
def size
|
|
307
|
-
length
|
|
308
|
-
end
|
|
309
|
-
|
|
310
|
-
def to_h
|
|
311
|
-
ret = {}
|
|
312
|
-
keys.each do |key|
|
|
313
|
-
name = LibHTS.bcf_hdr_int2id(@record.header.struct, LibHTS::BCF_DT_ID, key)
|
|
314
|
-
ret[name] = get(name)
|
|
315
|
-
end
|
|
316
|
-
ret
|
|
317
|
-
end
|
|
96
|
+
def fields = native.info_fields(header_native)
|
|
97
|
+
def keys = fields.map { |field| field[:key] }
|
|
98
|
+
def length = fields.length
|
|
99
|
+
alias size length
|
|
100
|
+
def to_h = fields.to_h { |field| [field[:name], get(field[:name])] }
|
|
318
101
|
|
|
319
102
|
private
|
|
320
103
|
|
|
321
|
-
def
|
|
322
|
-
|
|
323
|
-
|
|
104
|
+
def native = @record.__send__(:native_handle)
|
|
105
|
+
def header = @record.header.__send__(:native_handle)
|
|
106
|
+
alias header_native header
|
|
324
107
|
|
|
325
|
-
def
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
end
|
|
329
|
-
end
|
|
108
|
+
def update(key, type, value, label)
|
|
109
|
+
result = native.info_update(header_native, key.to_s, type, value)
|
|
110
|
+
raise InfoUpdateError, "Failed to update INFO #{label} field '#{key}': #{result}" if result.negative?
|
|
330
111
|
|
|
331
|
-
|
|
332
|
-
header_info_type(key)
|
|
112
|
+
result
|
|
333
113
|
end
|
|
334
114
|
|
|
335
|
-
def
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
def ht_type_to_sym(t)
|
|
344
|
-
case t
|
|
345
|
-
when LibHTS::BCF_HT_FLAG then :flag
|
|
346
|
-
when LibHTS::BCF_HT_INT then :int
|
|
347
|
-
when LibHTS::BCF_HT_REAL then :float
|
|
348
|
-
when LibHTS::BCF_HT_STR then :string
|
|
349
|
-
when LibHTS::BCF_HT_LONG then :int64
|
|
350
|
-
end
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
def int32_range?(value)
|
|
354
|
-
value >= -2_147_483_648 && value <= 2_147_483_647
|
|
355
|
-
end
|
|
356
|
-
|
|
357
|
-
def info_type_compatible?(actual_type, requested_type)
|
|
358
|
-
case requested_type
|
|
359
|
-
when :int, :int32
|
|
360
|
-
actual_type == :int
|
|
361
|
-
when :int64, :long
|
|
362
|
-
%i[int int64].include?(actual_type)
|
|
363
|
-
when :float, :real
|
|
364
|
-
actual_type == :float
|
|
365
|
-
when :flag, :bool
|
|
366
|
-
actual_type == :flag
|
|
367
|
-
when :string, :str
|
|
368
|
-
actual_type == :string
|
|
369
|
-
else
|
|
370
|
-
actual_type == requested_type
|
|
115
|
+
def type_compatible?(actual, requested)
|
|
116
|
+
case requested
|
|
117
|
+
when :int, :int32 then actual == :int
|
|
118
|
+
when :int64, :long then %i[int int64].include?(actual)
|
|
119
|
+
when :float, :real then actual == :float
|
|
120
|
+
when :flag, :bool then actual == :flag
|
|
121
|
+
when :string, :str then actual == :string
|
|
122
|
+
else actual == requested
|
|
371
123
|
end
|
|
372
124
|
end
|
|
373
125
|
|
|
374
126
|
def type_label(type)
|
|
375
|
-
case type
|
|
376
|
-
when :int, :int32 then "integer"
|
|
377
|
-
when :int64, :long then "integer"
|
|
127
|
+
case type
|
|
128
|
+
when :int, :int32, :int64, :long then "integer"
|
|
378
129
|
when :float, :real then "float"
|
|
379
130
|
when :flag, :bool then "flag"
|
|
380
131
|
when :string, :str then "string"
|
|
381
132
|
else type.to_s
|
|
382
133
|
end
|
|
383
134
|
end
|
|
135
|
+
|
|
136
|
+
def int32?(value) = value.between?(-2_147_483_648, 2_147_483_647)
|
|
384
137
|
end
|
|
385
138
|
end
|
|
386
139
|
end
|
data/lib/hts/bcf/record.rb
CHANGED
|
@@ -2,148 +2,120 @@
|
|
|
2
2
|
|
|
3
3
|
module HTS
|
|
4
4
|
class Bcf < Hts
|
|
5
|
-
# A class for working with VCF records.
|
|
6
5
|
class Record
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
attr_reader :header
|
|
7
|
+
|
|
8
|
+
def initialize(header, native_record = nil)
|
|
9
|
+
@native = native_record || Native::BcfRecordHandle.create
|
|
9
10
|
@header = header
|
|
10
11
|
end
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
def rid = @native.core_get(:rid)
|
|
13
14
|
|
|
14
|
-
def
|
|
15
|
-
@
|
|
15
|
+
def rid=(value)
|
|
16
|
+
@native.core_set(:rid, value)
|
|
16
17
|
end
|
|
17
18
|
|
|
18
|
-
def
|
|
19
|
-
|
|
20
|
-
end
|
|
19
|
+
def chrom = @header.id2name(rid)
|
|
20
|
+
def pos = @native.core_get(:pos)
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@bcf1[:rid]
|
|
22
|
+
def pos=(value)
|
|
23
|
+
@native.core_set(:pos, value)
|
|
25
24
|
end
|
|
26
25
|
|
|
27
|
-
def
|
|
28
|
-
|
|
29
|
-
end
|
|
26
|
+
def endpos = pos + @native.core_get(:rlen)
|
|
27
|
+
def id = @native.id
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
def id=(value)
|
|
30
|
+
check_update_rc!(@native.set_id(@header.__send__(:native_handle), value), "ID", value)
|
|
31
|
+
value
|
|
34
32
|
end
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
def clear_id
|
|
35
|
+
check_update_rc!(@native.set_id(@header.__send__(:native_handle), "."), "ID", ".")
|
|
36
|
+
nil
|
|
39
37
|
end
|
|
40
38
|
|
|
41
|
-
def
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
def alleles = @native.alleles
|
|
40
|
+
def ref = alleles.first
|
|
41
|
+
def alt = alleles.drop(1)
|
|
42
|
+
def allele_count = alleles.length
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
end
|
|
44
|
+
def alleles=(values)
|
|
45
|
+
values = Array(values).map(&:to_s)
|
|
46
|
+
raise ArgumentError, "at least one allele is required" if values.empty?
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@bcf1[:d][:id]
|
|
48
|
+
encoded = values.join(",")
|
|
49
|
+
check_update_rc!(@native.set_alleles(@header.__send__(:native_handle), encoded), "alleles", encoded)
|
|
50
|
+
values
|
|
54
51
|
end
|
|
55
52
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
# Native migration replacement for the former pointer-yielding API.
|
|
54
|
+
def each_allele_raw
|
|
55
|
+
return to_enum(__method__) unless block_given?
|
|
59
56
|
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
alleles.each { |allele| yield allele, allele.bytesize }
|
|
58
|
+
self
|
|
62
59
|
end
|
|
63
60
|
|
|
64
|
-
def
|
|
65
|
-
LibHTS.bcf_unpack(@bcf1, LibHTS::BCF_UN_STR)
|
|
66
|
-
@bcf1[:d][:allele].get_pointer(0).read_string
|
|
67
|
-
end
|
|
61
|
+
def qual = @native.core_get(:qual)
|
|
68
62
|
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
@bcf1[:d][:allele].get_array_of_pointer(
|
|
72
|
-
FFI::TYPE_POINTER.size, @bcf1[:n_allele] - 1
|
|
73
|
-
).map(&:read_string)
|
|
63
|
+
def qual=(value)
|
|
64
|
+
@native.core_set(:qual, value)
|
|
74
65
|
end
|
|
75
66
|
|
|
76
|
-
def
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
0, @bcf1[:n_allele]
|
|
80
|
-
).map(&:read_string)
|
|
67
|
+
def filters
|
|
68
|
+
names = @native.filter_names(@header.__send__(:native_handle))
|
|
69
|
+
names
|
|
81
70
|
end
|
|
82
71
|
|
|
83
|
-
# Get variant quality.
|
|
84
|
-
def qual
|
|
85
|
-
@bcf1[:qual]
|
|
86
|
-
end
|
|
87
72
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
73
|
+
# VCF FILTER is a list even when it contains a single value. Keeping a
|
|
74
|
+
# stable return type avoids substring matching for single-filter records.
|
|
75
|
+
alias filter filters
|
|
76
|
+
|
|
77
|
+
def passed? = filters == ["PASS"]
|
|
78
|
+
def filter_missing? = filters.empty?
|
|
79
|
+
def filtered? = !passed? && !filter_missing?
|
|
80
|
+
|
|
81
|
+
def each_filter_id(&block)
|
|
82
|
+
return to_enum(__method__) unless block_given?
|
|
91
83
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
d = @bcf1[:d]
|
|
95
|
-
n_flt = d[:n_flt]
|
|
96
|
-
|
|
97
|
-
case n_flt
|
|
98
|
-
when 0
|
|
99
|
-
"PASS"
|
|
100
|
-
when 1
|
|
101
|
-
id = d[:flt].read_int
|
|
102
|
-
LibHTS.bcf_hdr_int2id(@header.struct, LibHTS::BCF_DT_ID, id)
|
|
103
|
-
when 2..
|
|
104
|
-
d[:flt].get_array_of_int(0, n_flt).map do |i|
|
|
105
|
-
LibHTS.bcf_hdr_int2id(@header.struct, LibHTS::BCF_DT_ID, i)
|
|
106
|
-
end
|
|
107
|
-
else
|
|
108
|
-
raise "Unexpected number of filters. n_flt: #{n_flt}"
|
|
109
|
-
end
|
|
84
|
+
@native.filter_ids.each(&block)
|
|
85
|
+
self
|
|
110
86
|
end
|
|
111
87
|
|
|
88
|
+
def filter_ids = @native.filter_ids
|
|
89
|
+
def filter_id?(target_id) = filter_ids.include?(Integer(target_id))
|
|
90
|
+
|
|
112
91
|
def info(key = nil)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if key
|
|
116
|
-
info.get(key)
|
|
117
|
-
else
|
|
118
|
-
info
|
|
119
|
-
end
|
|
92
|
+
accessor = (@info_accessor ||= Info.new(self))
|
|
93
|
+
key ? accessor.get(key) : accessor
|
|
120
94
|
end
|
|
121
95
|
|
|
122
96
|
def format(key = nil)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
Format.new(self).get(key)
|
|
126
|
-
else
|
|
127
|
-
Format.new(self)
|
|
128
|
-
end
|
|
97
|
+
accessor = (@format_accessor ||= Format.new(self))
|
|
98
|
+
key ? accessor.get(key) : accessor
|
|
129
99
|
end
|
|
130
100
|
|
|
131
|
-
def to_s
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
raise "Failed to format record" if LibHTS.vcf_format(@header.struct, @bcf1, ksr) == -1
|
|
101
|
+
def to_s = @native.format_record(@header.__send__(:native_handle))
|
|
102
|
+
|
|
103
|
+
private
|
|
135
104
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
105
|
+
def check_update_rc!(result, field, value)
|
|
106
|
+
return result unless result.negative?
|
|
107
|
+
|
|
108
|
+
raise RecordError, "Failed to update #{field} to #{value.inspect} (HTSlib error #{result})"
|
|
140
109
|
end
|
|
141
110
|
|
|
142
|
-
|
|
111
|
+
def native_handle = @native
|
|
143
112
|
|
|
144
|
-
def initialize_copy(
|
|
145
|
-
|
|
146
|
-
@
|
|
113
|
+
def initialize_copy(original)
|
|
114
|
+
super
|
|
115
|
+
@header = original.header
|
|
116
|
+
@native = original.__send__(:native_handle).duplicate
|
|
117
|
+
@info_accessor = nil
|
|
118
|
+
@format_accessor = nil
|
|
147
119
|
end
|
|
148
120
|
end
|
|
149
121
|
end
|