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.
- checksums.yaml +4 -4
- data/README.md +79 -49
- data/TUTORIAL.md +9 -20
- 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 +1108 -0
- data/ext/htslib_native/native_bcf.c +369 -0
- data/ext/htslib_native/native_faidx.c +155 -0
- data/ext/htslib_native/native_tabix.c +246 -0
- data/lib/hts/bam/auxi.rb +87 -342
- data/lib/hts/bam/base_mod.rb +37 -73
- data/lib/hts/bam/cigar.rb +9 -55
- data/lib/hts/bam/flag.rb +19 -27
- 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 +102 -42
- data/lib/hts/bcf/errors.rb +1 -0
- data/lib/hts/bcf/format.rb +278 -379
- data/lib/hts/bcf/header.rb +38 -123
- data/lib/hts/bcf/header_record.rb +9 -35
- data/lib/hts/bcf/info.rb +72 -320
- data/lib/hts/bcf/record.rb +61 -102
- data/lib/hts/bcf.rb +149 -323
- data/lib/hts/faidx.rb +28 -87
- data/lib/hts/hts.rb +6 -94
- data/lib/hts/native.rb +13 -0
- data/lib/hts/tabix.rb +93 -48
- 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 -109
- 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 -79
- data/lib/hts/libhts/tbx_funcs.rb +0 -32
- data/lib/hts/libhts/thread_pool.rb +0 -139
- data/lib/hts/libhts/vcf.rb +0 -557
- data/lib/hts/libhts/vcf_funcs.rb +0 -353
- data/lib/hts/libhts.rb +0 -47
data/lib/hts/bcf/info.rb
CHANGED
|
@@ -2,385 +2,137 @@
|
|
|
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
|
+
return nil 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
|
-
get(key, :flag)
|
|
26
|
+
code = TYPE_CODES.fetch(requested || actual_type)
|
|
27
|
+
native.info_get(header_native, key.to_s, code)
|
|
93
28
|
end
|
|
94
29
|
|
|
95
|
-
def
|
|
96
|
-
|
|
97
|
-
|
|
30
|
+
def get_int(key) = get(key, :int)
|
|
31
|
+
def get_float(key) = get(key, :float)
|
|
32
|
+
def get_int64(key) = get(key, :int64)
|
|
33
|
+
def get_string(key) = get(key, :string)
|
|
34
|
+
def get_flag(key) = get(key, :flag)
|
|
35
|
+
def [](key) = get(key)
|
|
98
36
|
|
|
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
37
|
def []=(key, value)
|
|
108
38
|
case value
|
|
109
|
-
when nil
|
|
110
|
-
|
|
111
|
-
when true, false
|
|
112
|
-
update_flag(key, value)
|
|
39
|
+
when nil then delete(key)
|
|
40
|
+
when true, false then update_flag(key, value)
|
|
113
41
|
when Integer
|
|
114
|
-
unless
|
|
42
|
+
unless int32?(value)
|
|
115
43
|
raise RangeError,
|
|
116
44
|
"Integer out of int32 range for []=. Current htslib backend does not support int64 INFO update."
|
|
117
45
|
end
|
|
118
46
|
|
|
119
47
|
update_int(key, [value])
|
|
120
|
-
when Float
|
|
121
|
-
|
|
122
|
-
when String
|
|
123
|
-
update_string(key, value)
|
|
48
|
+
when Float then update_float(key, [value])
|
|
49
|
+
when String then update_string(key, value)
|
|
124
50
|
when Array
|
|
125
|
-
if value.empty?
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
unless value.all?
|
|
51
|
+
raise ArgumentError, "Cannot set INFO field to empty array. Use nil to delete." if value.empty?
|
|
52
|
+
|
|
53
|
+
if value.all? { |item| item.is_a?(Integer) }
|
|
54
|
+
unless value.all? do |item|
|
|
55
|
+
int32?(item)
|
|
56
|
+
end
|
|
129
57
|
raise RangeError,
|
|
130
58
|
"Integer array contains out-of-int32 values for []=. Current htslib backend does not support int64 INFO update."
|
|
131
59
|
end
|
|
132
60
|
|
|
133
61
|
update_int(key, value)
|
|
134
|
-
elsif value.all? { |
|
|
62
|
+
elsif value.all? { |item| item.is_a?(Numeric) }
|
|
135
63
|
update_float(key, value)
|
|
136
64
|
else
|
|
137
65
|
raise ArgumentError, "INFO array must contain only integers or floats, got: #{value.map(&:class).uniq}"
|
|
138
66
|
end
|
|
139
|
-
else
|
|
140
|
-
raise ArgumentError, "Unsupported INFO value type: #{value.class}"
|
|
67
|
+
else raise ArgumentError, "Unsupported INFO value type: #{value.class}"
|
|
141
68
|
end
|
|
142
69
|
end
|
|
143
70
|
|
|
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
|
|
71
|
+
def update_int(key, values) = update(key, Native::BCF_HT_INT, Array(values).map { |value| Integer(value) }, "int")
|
|
72
|
+
def update_float(key, values) = update(key, Native::BCF_HT_REAL, Array(values).map(&:to_f), "float")
|
|
73
|
+
def update_string(key, value) = update(key, Native::BCF_HT_STR, value.to_s, "string")
|
|
211
74
|
|
|
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
|
|
75
|
+
def update_int64(_key,
|
|
76
|
+
_values) = raise(UnsupportedInfoOperationError,
|
|
77
|
+
"htslib backend does not implement int64 INFO update (BCF_HT_LONG)")
|
|
238
78
|
|
|
239
|
-
|
|
240
|
-
end
|
|
79
|
+
def update_flag(key, present = true) = update(key, Native::BCF_HT_FLAG, !!present, "flag")
|
|
241
80
|
|
|
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
81
|
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
|
|
82
|
+
schema = header.schema("INFO", key.to_s)
|
|
83
|
+
return false unless schema && key?(key)
|
|
260
84
|
|
|
85
|
+
native.info_update(header_native, key.to_s, TYPE_CODES.fetch(schema.first), false)
|
|
261
86
|
true
|
|
262
87
|
end
|
|
263
88
|
|
|
264
|
-
# Check if an INFO field exists.
|
|
265
|
-
# @param key [String] INFO tag name
|
|
266
|
-
# @return [Boolean] true if the field exists
|
|
267
89
|
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
|
|
90
|
+
schema = header.schema("INFO", key.to_s)
|
|
91
|
+
schema ? native.info_present?(header_native, key.to_s, TYPE_CODES.fetch(schema.first)) : false
|
|
283
92
|
end
|
|
284
|
-
|
|
285
93
|
alias include? key?
|
|
286
94
|
|
|
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
|
|
95
|
+
def fields = native.info_fields(header_native)
|
|
96
|
+
def keys = fields.map { |field| field[:key] }
|
|
97
|
+
def length = fields.length
|
|
98
|
+
alias size length
|
|
99
|
+
def to_h = fields.to_h { |field| [field[:name], get(field[:name])] }
|
|
318
100
|
|
|
319
101
|
private
|
|
320
102
|
|
|
321
|
-
def
|
|
322
|
-
|
|
323
|
-
|
|
103
|
+
def native = @record.__send__(:native_handle)
|
|
104
|
+
def header = @record.header.__send__(:native_handle)
|
|
105
|
+
alias header_native header
|
|
324
106
|
|
|
325
|
-
def
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
end
|
|
329
|
-
end
|
|
107
|
+
def update(key, type, value, label)
|
|
108
|
+
result = native.info_update(header_native, key.to_s, type, value)
|
|
109
|
+
raise InfoUpdateError, "Failed to update INFO #{label} field '#{key}': #{result}" if result.negative?
|
|
330
110
|
|
|
331
|
-
|
|
332
|
-
header_info_type(key)
|
|
111
|
+
result
|
|
333
112
|
end
|
|
334
113
|
|
|
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
|
|
114
|
+
def type_compatible?(actual, requested)
|
|
115
|
+
case requested
|
|
116
|
+
when :int, :int32 then actual == :int
|
|
117
|
+
when :int64, :long then %i[int int64].include?(actual)
|
|
118
|
+
when :float, :real then actual == :float
|
|
119
|
+
when :flag, :bool then actual == :flag
|
|
120
|
+
when :string, :str then actual == :string
|
|
121
|
+
else actual == requested
|
|
371
122
|
end
|
|
372
123
|
end
|
|
373
124
|
|
|
374
125
|
def type_label(type)
|
|
375
|
-
case type
|
|
376
|
-
when :int, :int32 then "integer"
|
|
377
|
-
when :int64, :long then "integer"
|
|
126
|
+
case type
|
|
127
|
+
when :int, :int32, :int64, :long then "integer"
|
|
378
128
|
when :float, :real then "float"
|
|
379
129
|
when :flag, :bool then "flag"
|
|
380
130
|
when :string, :str then "string"
|
|
381
131
|
else type.to_s
|
|
382
132
|
end
|
|
383
133
|
end
|
|
134
|
+
|
|
135
|
+
def int32?(value) = value.between?(-2_147_483_648, 2_147_483_647)
|
|
384
136
|
end
|
|
385
137
|
end
|
|
386
138
|
end
|
data/lib/hts/bcf/record.rb
CHANGED
|
@@ -2,148 +2,107 @@
|
|
|
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
|
-
LibHTS.bcf_hdr_id2name(@header.struct, rid)
|
|
29
|
+
def id=(value)
|
|
30
|
+
@native.set_id(@header.__send__(:native_handle), value)
|
|
34
31
|
end
|
|
35
32
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
def clear_id
|
|
34
|
+
@native.set_id(@header.__send__(:native_handle), ".")
|
|
35
|
+
nil
|
|
39
36
|
end
|
|
40
37
|
|
|
41
|
-
def
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
def alleles = @native.alleles
|
|
39
|
+
def ref = alleles.first
|
|
40
|
+
def alt = alleles.drop(1)
|
|
41
|
+
def allele_count = alleles.length
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
end
|
|
43
|
+
def alleles=(values)
|
|
44
|
+
values = Array(values).map(&:to_s)
|
|
45
|
+
raise ArgumentError, "at least one allele is required" if values.empty?
|
|
49
46
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
LibHTS.bcf_unpack(@bcf1, LibHTS::BCF_UN_INFO)
|
|
53
|
-
@bcf1[:d][:id]
|
|
47
|
+
@native.set_alleles(@header.__send__(:native_handle), values.join(","))
|
|
48
|
+
values
|
|
54
49
|
end
|
|
55
50
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
# Native migration replacement for the former pointer-yielding API.
|
|
52
|
+
def each_allele_raw
|
|
53
|
+
return to_enum(__method__) unless block_given?
|
|
59
54
|
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
alleles.each { |allele| yield allele, allele.bytesize }
|
|
56
|
+
self
|
|
62
57
|
end
|
|
63
58
|
|
|
64
|
-
def
|
|
65
|
-
LibHTS.bcf_unpack(@bcf1, LibHTS::BCF_UN_STR)
|
|
66
|
-
@bcf1[:d][:allele].get_pointer(0).read_string
|
|
67
|
-
end
|
|
59
|
+
def qual = @native.core_get(:qual)
|
|
68
60
|
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
@bcf1[:d][:allele].get_array_of_pointer(
|
|
72
|
-
FFI::TYPE_POINTER.size, @bcf1[:n_allele] - 1
|
|
73
|
-
).map(&:read_string)
|
|
61
|
+
def qual=(value)
|
|
62
|
+
@native.core_set(:qual, value)
|
|
74
63
|
end
|
|
75
64
|
|
|
76
|
-
def
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
65
|
+
def filter
|
|
66
|
+
names = @native.filter_names(@header.__send__(:native_handle))
|
|
67
|
+
case names.length
|
|
68
|
+
when 0 then "PASS"
|
|
69
|
+
when 1 then names.first
|
|
70
|
+
else names
|
|
71
|
+
end
|
|
81
72
|
end
|
|
82
73
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
@bcf1[:qual]
|
|
86
|
-
end
|
|
74
|
+
def each_filter_id(&block)
|
|
75
|
+
return to_enum(__method__) unless block_given?
|
|
87
76
|
|
|
88
|
-
|
|
89
|
-
|
|
77
|
+
@native.filter_ids.each(&block)
|
|
78
|
+
self
|
|
90
79
|
end
|
|
91
80
|
|
|
92
|
-
def
|
|
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
|
|
110
|
-
end
|
|
81
|
+
def filter_ids = @native.filter_ids
|
|
82
|
+
def filter_id?(target_id) = filter_ids.include?(Integer(target_id))
|
|
111
83
|
|
|
112
84
|
def info(key = nil)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if key
|
|
116
|
-
info.get(key)
|
|
117
|
-
else
|
|
118
|
-
info
|
|
119
|
-
end
|
|
85
|
+
accessor = (@info_accessor ||= Info.new(self))
|
|
86
|
+
key ? accessor.get(key) : accessor
|
|
120
87
|
end
|
|
121
88
|
|
|
122
89
|
def format(key = nil)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
Format.new(self).get(key)
|
|
126
|
-
else
|
|
127
|
-
Format.new(self)
|
|
128
|
-
end
|
|
90
|
+
accessor = (@format_accessor ||= Format.new(self))
|
|
91
|
+
key ? accessor.get(key) : accessor
|
|
129
92
|
end
|
|
130
93
|
|
|
131
|
-
def to_s
|
|
132
|
-
ksr = LibHTS::KString.new
|
|
133
|
-
begin
|
|
134
|
-
raise "Failed to format record" if LibHTS.vcf_format(@header.struct, @bcf1, ksr) == -1
|
|
135
|
-
|
|
136
|
-
ksr.read_string_copy
|
|
137
|
-
ensure
|
|
138
|
-
ksr.free_buffer
|
|
139
|
-
end
|
|
140
|
-
end
|
|
94
|
+
def to_s = @native.format_record(@header.__send__(:native_handle))
|
|
141
95
|
|
|
142
96
|
private
|
|
143
97
|
|
|
144
|
-
def
|
|
145
|
-
|
|
146
|
-
|
|
98
|
+
def native_handle = @native
|
|
99
|
+
|
|
100
|
+
def initialize_copy(original)
|
|
101
|
+
super
|
|
102
|
+
@header = original.header
|
|
103
|
+
@native = original.__send__(:native_handle).duplicate
|
|
104
|
+
@info_accessor = nil
|
|
105
|
+
@format_accessor = nil
|
|
147
106
|
end
|
|
148
107
|
end
|
|
149
108
|
end
|