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/format.rb
CHANGED
|
@@ -3,500 +3,406 @@
|
|
|
3
3
|
module HTS
|
|
4
4
|
class Bcf < Hts
|
|
5
5
|
class Format
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
end
|
|
6
|
+
GT_MISSING = 0
|
|
7
|
+
GT_VECTOR_END = Native::BCF_INT32_VECTOR_END
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return decode_genotypes if key == "GT"
|
|
18
|
-
|
|
19
|
-
case header_format_type(key)
|
|
20
|
-
when :int
|
|
21
|
-
decode_integer_values(key)
|
|
22
|
-
when :float
|
|
23
|
-
decode_float_values(key)
|
|
24
|
-
when :flag
|
|
25
|
-
raise_unsupported_format_flag(key)
|
|
26
|
-
when :string
|
|
27
|
-
get_string_values(key)
|
|
28
|
-
end
|
|
9
|
+
class << self
|
|
10
|
+
def gt_unphased(allele) = (Integer(allele) + 1) << 1
|
|
11
|
+
def gt_phased(allele) = ((Integer(allele) + 1) << 1) | 1
|
|
12
|
+
def gt_allele(value) = (Integer(value) >> 1) - 1
|
|
13
|
+
def gt_missing?(value) = (Integer(value) >> 1).zero?
|
|
14
|
+
def gt_phased?(value) = (Integer(value) & 1) == 1
|
|
15
|
+
def gt_vector_end?(value) = Integer(value) == GT_VECTOR_END
|
|
29
16
|
end
|
|
30
17
|
|
|
31
|
-
|
|
32
|
-
# The GT FORMAT field is special in that it is marked as a string in the header,
|
|
33
|
-
# but it is actually encoded as an integer.
|
|
34
|
-
type = if type.nil?
|
|
35
|
-
key == "GT" ? :int : header_format_type(key)
|
|
36
|
-
else
|
|
37
|
-
type.to_sym
|
|
38
|
-
end
|
|
18
|
+
BufferState = Struct.new(:generation)
|
|
39
19
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
when :string, :str
|
|
50
|
-
return decode_genotypes if key == "GT"
|
|
51
|
-
|
|
52
|
-
raise_unsupported_format_flag(key)
|
|
53
|
-
get_string_values(key)
|
|
20
|
+
class NumericVectorView
|
|
21
|
+
include Enumerable
|
|
22
|
+
def initialize(type) = @type = type
|
|
23
|
+
|
|
24
|
+
def reset(values, buffer, generation)
|
|
25
|
+
@values = values
|
|
26
|
+
@buffer = buffer
|
|
27
|
+
@generation = generation
|
|
28
|
+
self
|
|
54
29
|
end
|
|
55
|
-
end
|
|
56
30
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
get_raw(key, :int)
|
|
60
|
-
end
|
|
31
|
+
def each(&block)
|
|
32
|
+
return to_enum(__method__) unless block_given?
|
|
61
33
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
34
|
+
ensure_valid!
|
|
35
|
+
@values.each(&block)
|
|
36
|
+
self
|
|
37
|
+
end
|
|
66
38
|
|
|
67
|
-
|
|
68
|
-
def get_flag(key)
|
|
69
|
-
get_raw(key, :flag)
|
|
70
|
-
end
|
|
39
|
+
def to_a = each.to_a
|
|
71
40
|
|
|
72
|
-
|
|
73
|
-
def get_string(key)
|
|
74
|
-
get_raw(key, :string)
|
|
75
|
-
end
|
|
41
|
+
private
|
|
76
42
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
get_numeric_values("GT", LibHTS::BCF_HT_INT, "genotype") { |dst, len| dst.read_array_of_int32(len) }
|
|
80
|
-
end
|
|
43
|
+
def ensure_valid!
|
|
44
|
+
return if @buffer.generation == @generation
|
|
81
45
|
|
|
82
|
-
|
|
83
|
-
|
|
46
|
+
raise InvalidBorrowedViewError,
|
|
47
|
+
"borrowed FORMAT view is no longer valid"
|
|
48
|
+
end
|
|
84
49
|
end
|
|
85
50
|
|
|
86
|
-
|
|
87
|
-
|
|
51
|
+
class GenotypeView
|
|
52
|
+
include Enumerable
|
|
88
53
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
54
|
+
def reset(values, buffer, generation)
|
|
55
|
+
@values = values
|
|
56
|
+
@buffer = buffer
|
|
57
|
+
@generation = generation
|
|
58
|
+
self
|
|
59
|
+
end
|
|
92
60
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
check_update_rc!(LibHTS.bcf_update_format_int32(@record.header.struct, @record.struct, key, ptr, values.size),
|
|
96
|
-
key)
|
|
97
|
-
end
|
|
61
|
+
def each_allele
|
|
62
|
+
return to_enum(__method__) unless block_given?
|
|
98
63
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
validate_numeric_sample_count!(key, values.size)
|
|
64
|
+
ensure_valid!
|
|
65
|
+
@values.each_with_index do |encoded, index|
|
|
66
|
+
break if encoded == GT_VECTOR_END
|
|
103
67
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
68
|
+
missing = gt_missing?(encoded)
|
|
69
|
+
# The phase bit describes the separator before this allele. It has
|
|
70
|
+
# no semantic meaning for the first allele, and HTSlib versions do
|
|
71
|
+
# not consistently clear it while parsing VCF text.
|
|
72
|
+
phased = index.positive? && gt_phased?(encoded)
|
|
73
|
+
yield(missing ? nil : gt_allele(encoded), phased, missing)
|
|
74
|
+
end
|
|
75
|
+
self
|
|
76
|
+
end
|
|
77
|
+
alias each each_allele
|
|
78
|
+
def to_s
|
|
79
|
+
result = +""
|
|
80
|
+
each_allele.with_index do |(allele, phased, missing), index|
|
|
81
|
+
result << (phased && index.positive? ? "|" : "/") if index.positive?
|
|
82
|
+
result << (missing ? "." : allele.to_s)
|
|
83
|
+
end
|
|
84
|
+
result
|
|
85
|
+
end
|
|
109
86
|
|
|
110
|
-
|
|
111
|
-
raise UnsupportedFormatOperationError, "Use update_genotypes for GT" if key == "GT"
|
|
87
|
+
private
|
|
112
88
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
89
|
+
def ensure_valid!
|
|
90
|
+
return if @buffer.generation == @generation
|
|
91
|
+
|
|
92
|
+
raise InvalidBorrowedViewError,
|
|
93
|
+
"borrowed FORMAT view is no longer valid"
|
|
94
|
+
end
|
|
116
95
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
check_update_rc!(LibHTS.bcf_update_format_string(@record.header.struct, @record.struct, key, ptr, values.size),
|
|
121
|
-
key)
|
|
96
|
+
def gt_missing?(value) = (value >> 1).zero?
|
|
97
|
+
def gt_allele(value) = (value >> 1) - 1
|
|
98
|
+
def gt_phased?(value) = (value & 1) == 1
|
|
122
99
|
end
|
|
123
100
|
|
|
124
|
-
def
|
|
125
|
-
|
|
101
|
+
def initialize(record)
|
|
102
|
+
@record = record
|
|
103
|
+
@buffers = {}
|
|
104
|
+
end
|
|
126
105
|
|
|
127
|
-
|
|
128
|
-
|
|
106
|
+
def get(key, type = nil)
|
|
107
|
+
key = key.to_s
|
|
108
|
+
schema = format_schema(key)
|
|
109
|
+
return nil unless schema
|
|
129
110
|
|
|
130
|
-
|
|
131
|
-
ptr.write_array_of_int32(values)
|
|
132
|
-
check_update_rc!(LibHTS.bcf_update_genotypes(@record.header.struct, @record.struct, ptr, values.size), "GT")
|
|
133
|
-
end
|
|
111
|
+
raise_unsupported_flag(key) if schema.first == :flag
|
|
134
112
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
type = key == "GT" ? LibHTS::BCF_HT_INT : header_format_type_code(key)
|
|
140
|
-
ret = LibHTS.bcf_update_format(@record.header.struct, @record.struct, key, FFI::Pointer::NULL, 0, type)
|
|
141
|
-
raise FormatUpdateError, "Failed to delete FORMAT field '#{key}': #{ret}" if ret < 0
|
|
142
|
-
|
|
143
|
-
true
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def fields
|
|
147
|
-
ids.map do |id|
|
|
148
|
-
name = LibHTS.bcf_hdr_int2id(@record.header.struct, LibHTS::BCF_DT_ID, id)
|
|
149
|
-
num = LibHTS.bcf_hdr_id2number(@record.header.struct, LibHTS::BCF_HL_FMT, id)
|
|
150
|
-
type = LibHTS.bcf_hdr_id2type(@record.header.struct, LibHTS::BCF_HL_FMT, id)
|
|
151
|
-
{
|
|
152
|
-
name:,
|
|
153
|
-
n: num,
|
|
154
|
-
type: ht_type_to_sym(type),
|
|
155
|
-
id:
|
|
156
|
-
}
|
|
113
|
+
requested = type&.to_sym
|
|
114
|
+
if requested && !type_compatible?(schema.first, requested, key)
|
|
115
|
+
raise FormatTypeError, "Tag #{key} is not #{type_label(requested)} FORMAT field"
|
|
157
116
|
end
|
|
158
|
-
|
|
117
|
+
return genotype_strings(key) if key == "GT" && (!requested || %i[string str].include?(requested))
|
|
159
118
|
|
|
160
|
-
|
|
161
|
-
@record.struct[:n_fmt]
|
|
162
|
-
end
|
|
119
|
+
return get_float(key) if %i[float real].include?(requested)
|
|
163
120
|
|
|
164
|
-
|
|
165
|
-
|
|
121
|
+
raw = get_raw(key, requested)
|
|
122
|
+
return raw if requested
|
|
123
|
+
return raw if schema.first == :string
|
|
124
|
+
|
|
125
|
+
shape_values(raw, key, schema)
|
|
166
126
|
end
|
|
167
127
|
|
|
168
|
-
def
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
128
|
+
def get_raw(key, type = nil)
|
|
129
|
+
key = key.to_s
|
|
130
|
+
schema = format_schema(key)
|
|
131
|
+
return nil unless schema
|
|
132
|
+
|
|
133
|
+
requested = type&.to_sym
|
|
134
|
+
if requested && !type_compatible?(schema.first, requested, key)
|
|
135
|
+
raise FormatTypeError, "Tag #{key} is not #{type_label(requested)} FORMAT field"
|
|
173
136
|
end
|
|
174
|
-
ret
|
|
175
|
-
end
|
|
176
137
|
|
|
177
|
-
|
|
138
|
+
code = key == "GT" ? Native::BCF_HT_INT : type_code(requested || schema.first)
|
|
139
|
+
raw_float = code == Native::BCF_HT_REAL
|
|
140
|
+
invalidate_views!(key)
|
|
141
|
+
native.format_get(header_native, key, code, raw_float)
|
|
142
|
+
end
|
|
178
143
|
|
|
179
|
-
|
|
144
|
+
def get_int_raw(key) = get_raw(key, :int)
|
|
180
145
|
|
|
181
|
-
def
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
dst_ptr.write_pointer(FFI::Pointer::NULL)
|
|
186
|
-
|
|
187
|
-
ret = LibHTS.bcf_get_format_values(@record.header.struct, @record.struct, key, dst_ptr, ndst, hts_type)
|
|
188
|
-
ret = normalize_format_rc(ret, key, expected_type)
|
|
189
|
-
return nil unless ret
|
|
190
|
-
|
|
191
|
-
dst = dst_ptr.read_pointer
|
|
192
|
-
begin
|
|
193
|
-
yield(dst, ret)
|
|
194
|
-
ensure
|
|
195
|
-
LibHTS.hts_free(dst) unless dst.null?
|
|
196
|
-
dst_ptr.write_pointer(FFI::Pointer::NULL)
|
|
146
|
+
def get_int(key)
|
|
147
|
+
values = get_int_raw(key)
|
|
148
|
+
values&.each_with_object([]) do |value, decoded|
|
|
149
|
+
decoded << missing_int(value) unless value == Native::BCF_INT32_VECTOR_END
|
|
197
150
|
end
|
|
198
151
|
end
|
|
199
152
|
|
|
200
|
-
def
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
dst_ptr = FFI::MemoryPointer.new(:pointer)
|
|
204
|
-
dst_ptr.write_pointer(FFI::Pointer::NULL)
|
|
205
|
-
|
|
206
|
-
ret = LibHTS.bcf_get_format_string(@record.header.struct, @record.struct, key, dst_ptr, ndst)
|
|
207
|
-
ret = normalize_format_rc(ret, key, "string")
|
|
208
|
-
return nil unless ret
|
|
209
|
-
|
|
210
|
-
dst = dst_ptr.read_pointer
|
|
211
|
-
sample_count = @record.header.nsamples
|
|
212
|
-
begin
|
|
213
|
-
dst.read_array_of_pointer(sample_count).map(&:read_string)
|
|
214
|
-
ensure
|
|
215
|
-
unless dst.null?
|
|
216
|
-
collapsed = sample_count.positive? ? dst.get_pointer(0) : FFI::Pointer::NULL
|
|
217
|
-
LibHTS.hts_free(collapsed) unless collapsed.null?
|
|
218
|
-
LibHTS.hts_free(dst)
|
|
219
|
-
end
|
|
220
|
-
dst_ptr.write_pointer(FFI::Pointer::NULL)
|
|
221
|
-
end
|
|
153
|
+
def get_float(key)
|
|
154
|
+
words = get_raw(key, :float)
|
|
155
|
+
words&.map { |word| decode_float_word(word) }
|
|
222
156
|
end
|
|
223
157
|
|
|
224
|
-
def
|
|
158
|
+
def get_flag(key) = get(key, :flag)
|
|
159
|
+
def get_string(key) = get(key, :string)
|
|
160
|
+
def get_genotypes = get_raw("GT", :int)
|
|
161
|
+
def [](key) = get(key)
|
|
162
|
+
|
|
163
|
+
def each_genotype(key = "GT")
|
|
164
|
+
return enum_for(__method__, key) unless block_given?
|
|
165
|
+
raise ArgumentError, "genotype FORMAT key must be GT" unless key.to_s == "GT"
|
|
166
|
+
|
|
225
167
|
values = get_raw(key, :int)
|
|
226
168
|
return nil unless values
|
|
227
169
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
else
|
|
234
|
-
sample_values.map do |values_per_sample|
|
|
235
|
-
map_integer_missing(trim_integer_vector_end(values_per_sample))
|
|
236
|
-
end
|
|
170
|
+
count, width = sample_layout(values.length)
|
|
171
|
+
buffer, generation = advance_buffer(key, :genotype)
|
|
172
|
+
view = GenotypeView.new
|
|
173
|
+
count.times do |sample|
|
|
174
|
+
yield sample, view.reset(values.slice(sample * width, width), buffer, generation)
|
|
237
175
|
end
|
|
176
|
+
self
|
|
238
177
|
end
|
|
239
178
|
|
|
240
|
-
def
|
|
241
|
-
values =
|
|
179
|
+
def genotype_at(key, sample_index)
|
|
180
|
+
values = get_raw(key, :int)
|
|
242
181
|
return nil unless values
|
|
243
182
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
end
|
|
249
|
-
else
|
|
250
|
-
sample_values.map do |values_per_sample|
|
|
251
|
-
map_float_words(trim_float_vector_end(values_per_sample))
|
|
252
|
-
end
|
|
253
|
-
end
|
|
254
|
-
end
|
|
183
|
+
count, width = sample_layout(values.length)
|
|
184
|
+
sample_index = Integer(sample_index)
|
|
185
|
+
sample_index += count if sample_index.negative?
|
|
186
|
+
raise IndexError, "sample index #{sample_index} outside of FORMAT" unless sample_index.between?(0, count - 1)
|
|
255
187
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
188
|
+
buffer, generation = advance_buffer(key, :genotype)
|
|
189
|
+
GenotypeView.new.reset(values.slice(sample_index * width, width), buffer, generation)
|
|
190
|
+
end
|
|
259
191
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
192
|
+
def genotype_strings(key = "GT")
|
|
193
|
+
strings = []
|
|
194
|
+
found = each_genotype(key) { |_, genotype| strings << genotype.to_s }
|
|
195
|
+
found ? strings : nil
|
|
263
196
|
end
|
|
264
197
|
|
|
265
|
-
def
|
|
266
|
-
|
|
267
|
-
allele = if LibHTS.bcf_gt_is_missing(value) != 0
|
|
268
|
-
"."
|
|
269
|
-
else
|
|
270
|
-
LibHTS.bcf_gt_allele(value).to_s
|
|
271
|
-
end
|
|
198
|
+
def each_i32(key)
|
|
199
|
+
return enum_for(__method__, key) unless block_given?
|
|
272
200
|
|
|
273
|
-
|
|
201
|
+
ensure_scalar!(key, :int)
|
|
202
|
+
values = get_raw(key, :int)
|
|
203
|
+
return self unless values
|
|
274
204
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
end.join
|
|
205
|
+
values.each_with_index { |value, index| yield index, missing_int(value) }
|
|
206
|
+
self
|
|
278
207
|
end
|
|
279
208
|
|
|
280
|
-
def
|
|
281
|
-
|
|
282
|
-
return [] if sample_count <= 0
|
|
209
|
+
def each_i32_vector(key, &block) = each_vector(key, :int, &block)
|
|
210
|
+
def each_f32_vector(key, &block) = each_vector(key, :float, &block)
|
|
283
211
|
|
|
284
|
-
|
|
212
|
+
def update_int(key, values)
|
|
213
|
+
raise UnsupportedFormatOperationError, "Use update_genotypes for GT" if key.to_s == "GT"
|
|
285
214
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
values[start, values_per_sample]
|
|
290
|
-
end
|
|
215
|
+
values = normalize_values(values) { |value| Integer(value) }
|
|
216
|
+
validate_sample_divisibility!(key, values.length)
|
|
217
|
+
update_format(key, Native::BCF_HT_INT, values)
|
|
291
218
|
end
|
|
292
219
|
|
|
293
|
-
def
|
|
294
|
-
|
|
295
|
-
|
|
220
|
+
def update_float(key, values)
|
|
221
|
+
values = normalize_values(values, &:to_f)
|
|
222
|
+
validate_sample_divisibility!(key, values.length)
|
|
223
|
+
update_format(key, Native::BCF_HT_REAL, values)
|
|
296
224
|
end
|
|
297
225
|
|
|
298
|
-
def
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
226
|
+
def update_float_words(key, values)
|
|
227
|
+
values = normalize_values(values) { |value| Integer(value) }
|
|
228
|
+
validate_sample_divisibility!(key, values.length)
|
|
229
|
+
raise FormatDefinitionError, "FORMAT tag #{key} not defined in header" unless format_schema(key)
|
|
302
230
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
values[0, end_index]
|
|
306
|
-
end
|
|
231
|
+
result = native.format_update_float_words(header_native, key.to_s, values)
|
|
232
|
+
raise FormatUpdateError, "Failed to update FORMAT field '#{key}': #{result}" if result.negative?
|
|
307
233
|
|
|
308
|
-
|
|
309
|
-
values.map { |value| map_integer_missing_value(value) }
|
|
234
|
+
result
|
|
310
235
|
end
|
|
311
236
|
|
|
312
|
-
def
|
|
313
|
-
|
|
314
|
-
|
|
237
|
+
def update_string(key, values)
|
|
238
|
+
values = Array(values).map(&:to_s)
|
|
239
|
+
expected = sample_count
|
|
240
|
+
unless values.length == expected
|
|
241
|
+
raise ArgumentError, "FORMAT string values for #{key} must provide one entry per sample (#{expected})"
|
|
242
|
+
end
|
|
315
243
|
|
|
316
|
-
|
|
317
|
-
values.map { |value| decode_float_word(value) }
|
|
244
|
+
update_format(key, Native::BCF_HT_STR, values)
|
|
318
245
|
end
|
|
319
246
|
|
|
320
|
-
def
|
|
321
|
-
|
|
322
|
-
|
|
247
|
+
def update_genotypes(values)
|
|
248
|
+
values = normalize_values(values) { |value| Integer(value) }
|
|
249
|
+
validate_sample_divisibility!("GT", values.length)
|
|
250
|
+
result = native.genotype_update(header_native, values)
|
|
251
|
+
raise FormatUpdateError, "Failed to update FORMAT field 'GT': #{result}" if result.negative?
|
|
323
252
|
|
|
324
|
-
|
|
253
|
+
result
|
|
325
254
|
end
|
|
326
255
|
|
|
327
|
-
def
|
|
328
|
-
|
|
329
|
-
|
|
256
|
+
def delete(key)
|
|
257
|
+
schema = format_schema(key)
|
|
258
|
+
return false unless schema && !get_raw(key).nil?
|
|
330
259
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
when :float, :real
|
|
334
|
-
raise_unsupported_format_flag(key)
|
|
335
|
-
words = get_float_words(key)
|
|
336
|
-
words&.map { |word| decode_float_word(word) }
|
|
337
|
-
else
|
|
338
|
-
get_raw(key, type)
|
|
339
|
-
end
|
|
260
|
+
result = native.format_delete(header_native, key.to_s, type_code(schema.first))
|
|
261
|
+
result >= 0
|
|
340
262
|
end
|
|
341
263
|
|
|
342
|
-
def
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
when -4
|
|
349
|
-
raise FormatReadError, "Failed to read FORMAT/#{key}"
|
|
350
|
-
else
|
|
351
|
-
rc
|
|
352
|
-
end
|
|
353
|
-
end
|
|
264
|
+
def fields = native.format_fields(header_native)
|
|
265
|
+
def ids = fields.map { |field| field[:id] }
|
|
266
|
+
def get_float_words(key) = get_raw(key, :float)
|
|
267
|
+
def length = fields.length
|
|
268
|
+
alias size length
|
|
269
|
+
def to_h = fields.to_h { |field| [field[:name], get(field[:name])] }
|
|
354
270
|
|
|
355
|
-
|
|
356
|
-
return unless header_format_type(key) == :flag
|
|
271
|
+
private
|
|
357
272
|
|
|
358
|
-
|
|
359
|
-
|
|
273
|
+
def native = @record.__send__(:native_handle)
|
|
274
|
+
def header_native = @record.header.__send__(:native_handle)
|
|
275
|
+
def sample_count = @record.header.nsamples
|
|
276
|
+
def format_schema(key) = header_native.schema("FORMAT", key.to_s)
|
|
277
|
+
|
|
278
|
+
def type_code(type)
|
|
279
|
+
{ int: Native::BCF_HT_INT, int32: Native::BCF_HT_INT,
|
|
280
|
+
float: Native::BCF_HT_REAL, real: Native::BCF_HT_REAL,
|
|
281
|
+
string: Native::BCF_HT_STR, str: Native::BCF_HT_STR }.fetch(type)
|
|
360
282
|
end
|
|
361
283
|
|
|
362
|
-
def
|
|
363
|
-
|
|
364
|
-
raise FormatDefinitionError, "FORMAT tag #{key} not defined in header" if actual_type.nil?
|
|
284
|
+
def type_compatible?(actual, requested, key)
|
|
285
|
+
return true if key == "GT" && %i[int int32 string str].include?(requested)
|
|
365
286
|
|
|
366
|
-
|
|
367
|
-
|
|
287
|
+
actual == case requested
|
|
288
|
+
when :int, :int32 then :int
|
|
289
|
+
when :float, :real then :float
|
|
290
|
+
when :string, :str then :string
|
|
291
|
+
when :flag then :flag
|
|
292
|
+
else requested
|
|
293
|
+
end
|
|
368
294
|
end
|
|
369
295
|
|
|
370
|
-
def
|
|
371
|
-
|
|
296
|
+
def type_label(type)
|
|
297
|
+
case type
|
|
298
|
+
when :int, :int32 then "integer"
|
|
299
|
+
when :float, :real then "float"
|
|
300
|
+
when :string, :str then "string"
|
|
301
|
+
else type.to_s
|
|
302
|
+
end
|
|
372
303
|
end
|
|
373
304
|
|
|
374
|
-
def
|
|
375
|
-
|
|
376
|
-
when -1
|
|
377
|
-
raise FormatDefinitionError, "FORMAT tag #{key} not defined in header"
|
|
378
|
-
when 0
|
|
379
|
-
rc
|
|
380
|
-
else
|
|
381
|
-
raise FormatUpdateError, "Failed to update FORMAT field '#{key}': #{rc}" if rc.negative?
|
|
382
|
-
|
|
383
|
-
rc
|
|
384
|
-
end
|
|
305
|
+
def raise_unsupported_flag(key)
|
|
306
|
+
raise UnsupportedFormatOperationError, "FORMAT flag fields are not supported: #{key}"
|
|
385
307
|
end
|
|
386
308
|
|
|
387
|
-
def
|
|
388
|
-
|
|
389
|
-
raise
|
|
390
|
-
return if (value_count % sample_count).zero?
|
|
309
|
+
def sample_layout(value_count)
|
|
310
|
+
count = sample_count
|
|
311
|
+
raise FormatReadError, "invalid FORMAT sample layout" if count <= 0 || (value_count % count) != 0
|
|
391
312
|
|
|
392
|
-
|
|
313
|
+
[count, value_count / count]
|
|
393
314
|
end
|
|
394
315
|
|
|
395
|
-
def
|
|
396
|
-
|
|
397
|
-
raise ArgumentError, "FORMAT fields require at least one sample" if sample_count <= 0
|
|
398
|
-
return if value_count == sample_count
|
|
316
|
+
def shape_values(values, _key, schema)
|
|
317
|
+
return nil unless values
|
|
399
318
|
|
|
400
|
-
|
|
319
|
+
count, width = sample_layout(values.length)
|
|
320
|
+
scalar = schema[1] == 1
|
|
321
|
+
Array.new(count) do |sample|
|
|
322
|
+
row = values.slice(sample * width, width)
|
|
323
|
+
row = trim_vector(row, schema.first)
|
|
324
|
+
scalar ? row.first : row
|
|
325
|
+
end
|
|
401
326
|
end
|
|
402
327
|
|
|
403
|
-
def
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
328
|
+
def trim_vector(values, type)
|
|
329
|
+
if type == :int
|
|
330
|
+
values.take_while { |value| value != Native::BCF_INT32_VECTOR_END }.map { |value| missing_int(value) }
|
|
331
|
+
else
|
|
332
|
+
values.take_while { |word| word != Native::BCF_FLOAT_VECTOR_END }.map { |word| decode_float_word(word) }
|
|
333
|
+
end
|
|
334
|
+
end
|
|
408
335
|
|
|
409
|
-
|
|
336
|
+
def missing_int(value)
|
|
337
|
+
[Native::BCF_INT32_MISSING, Native::BCF_INT32_VECTOR_END].include?(value) ? nil : value
|
|
410
338
|
end
|
|
411
339
|
|
|
412
|
-
def
|
|
413
|
-
|
|
414
|
-
raise ArgumentError, "Cannot update FORMAT field with empty array. Use delete instead." if values.empty?
|
|
415
|
-
raise ArgumentError, "FORMAT float values must all be Numeric" unless values.all?(Numeric)
|
|
340
|
+
def decode_float_word(word)
|
|
341
|
+
return nil if [Native::BCF_FLOAT_MISSING, Native::BCF_FLOAT_VECTOR_END].include?(word)
|
|
416
342
|
|
|
417
|
-
|
|
343
|
+
[word].pack("L<").unpack1("e")
|
|
418
344
|
end
|
|
419
345
|
|
|
420
|
-
def
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
values
|
|
346
|
+
def advance_buffer(key, type)
|
|
347
|
+
buffer = (@buffers[[key.to_s, type]] ||= BufferState.new(0))
|
|
348
|
+
buffer.generation += 1
|
|
349
|
+
[buffer, buffer.generation]
|
|
426
350
|
end
|
|
427
351
|
|
|
428
|
-
def
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
else
|
|
432
|
-
case header_format_type(key)
|
|
433
|
-
when :int then !get_int(key).nil?
|
|
434
|
-
when :float then !get_float(key).nil?
|
|
435
|
-
when :string then !get_string(key).nil?
|
|
436
|
-
else false
|
|
437
|
-
end
|
|
352
|
+
def invalidate_views!(key)
|
|
353
|
+
@buffers.each do |(buffer_key, _type), buffer|
|
|
354
|
+
buffer.generation += 1 if buffer_key == key.to_s
|
|
438
355
|
end
|
|
439
356
|
end
|
|
440
357
|
|
|
441
|
-
def
|
|
442
|
-
|
|
443
|
-
end
|
|
358
|
+
def each_vector(key, type)
|
|
359
|
+
return enum_for(type == :int ? :each_i32_vector : :each_f32_vector, key) unless block_given?
|
|
444
360
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
fmt[:id]
|
|
448
|
-
end
|
|
449
|
-
end
|
|
361
|
+
values = get_raw(key, type)
|
|
362
|
+
return self unless values
|
|
450
363
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
type = LibHTS.bcf_hdr_id2type(@record.header.struct, LibHTS::BCF_HL_FMT, id)
|
|
458
|
-
return type
|
|
459
|
-
end
|
|
364
|
+
count, width = sample_layout(values.length)
|
|
365
|
+
buffer, generation = advance_buffer(key, type)
|
|
366
|
+
view = NumericVectorView.new(type)
|
|
367
|
+
count.times do |sample|
|
|
368
|
+
decoded = trim_vector(values.slice(sample * width, width), type)
|
|
369
|
+
yield sample, view.reset(decoded, buffer, generation)
|
|
460
370
|
end
|
|
461
|
-
|
|
371
|
+
self
|
|
462
372
|
end
|
|
463
373
|
|
|
464
|
-
def
|
|
465
|
-
|
|
374
|
+
def ensure_scalar!(key, expected)
|
|
375
|
+
schema = format_schema(key)
|
|
376
|
+
return unless schema
|
|
377
|
+
raise FormatTypeError, "Tag #{key} is not #{type_label(expected)} FORMAT field" unless schema.first == expected
|
|
378
|
+
raise FormatReadError, "FORMAT field #{key} is not scalar" unless schema[1] == 1
|
|
466
379
|
end
|
|
467
380
|
|
|
468
|
-
def
|
|
469
|
-
|
|
470
|
-
return nil if id.negative?
|
|
471
|
-
return nil unless LibHTS.bcf_hdr_idinfo_exists(@record.header.struct, LibHTS::BCF_HL_FMT, id)
|
|
472
|
-
|
|
473
|
-
LibHTS.bcf_hdr_id2number(@record.header.struct, LibHTS::BCF_HL_FMT, id)
|
|
381
|
+
def normalize_values(values, &block)
|
|
382
|
+
Array(values).map(&block)
|
|
474
383
|
end
|
|
475
384
|
|
|
476
|
-
def
|
|
477
|
-
|
|
478
|
-
return
|
|
479
|
-
return nil unless LibHTS.bcf_hdr_idinfo_exists(@record.header.struct, LibHTS::BCF_HL_FMT, id)
|
|
385
|
+
def validate_sample_divisibility!(key, count)
|
|
386
|
+
samples = sample_count
|
|
387
|
+
return if samples.positive? && (count % samples).zero?
|
|
480
388
|
|
|
481
|
-
|
|
389
|
+
raise ArgumentError, "FORMAT values for #{key} must be divisible by sample count (#{samples})"
|
|
482
390
|
end
|
|
483
391
|
|
|
484
|
-
def
|
|
485
|
-
|
|
486
|
-
|
|
392
|
+
def update_format(key, type, values)
|
|
393
|
+
schema = format_schema(key)
|
|
394
|
+
raise FormatDefinitionError, "FORMAT tag #{key} not defined in header" unless schema
|
|
487
395
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
when LibHTS::BCF_HT_STR then :string
|
|
494
|
-
when LibHTS::BCF_HT_LONG then :int64
|
|
396
|
+
expected = type_code(schema.first)
|
|
397
|
+
unless expected == type
|
|
398
|
+
requested = { Native::BCF_HT_INT => :int, Native::BCF_HT_REAL => :float,
|
|
399
|
+
Native::BCF_HT_STR => :string }.fetch(type)
|
|
400
|
+
raise FormatTypeError, "Tag #{key} is not #{type_label(requested)} FORMAT field"
|
|
495
401
|
end
|
|
496
|
-
|
|
402
|
+
result = native.format_update(header_native, key.to_s, type, values)
|
|
403
|
+
raise FormatUpdateError, "Failed to update FORMAT field '#{key}': #{result}" if result.negative?
|
|
497
404
|
|
|
498
|
-
|
|
499
|
-
value >= -2_147_483_648 && value <= 2_147_483_647
|
|
405
|
+
result
|
|
500
406
|
end
|
|
501
407
|
end
|
|
502
408
|
end
|