htslib 0.4.2 → 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 -122
- 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 -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,399 @@
|
|
|
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
|
-
|
|
166
|
-
|
|
121
|
+
raw = get_raw(key, requested)
|
|
122
|
+
return raw if requested
|
|
123
|
+
return raw if schema.first == :string
|
|
167
124
|
|
|
168
|
-
|
|
169
|
-
ret = {}
|
|
170
|
-
ids.each do |id|
|
|
171
|
-
name = LibHTS.bcf_hdr_int2id(@record.header.struct, LibHTS::BCF_DT_ID, id)
|
|
172
|
-
ret[name] = get(name)
|
|
173
|
-
end
|
|
174
|
-
ret
|
|
125
|
+
shape_values(raw, key, schema)
|
|
175
126
|
end
|
|
176
127
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
128
|
+
def get_raw(key, type = nil)
|
|
129
|
+
key = key.to_s
|
|
130
|
+
schema = format_schema(key)
|
|
131
|
+
return nil unless schema
|
|
180
132
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
dst_ptr = FFI::MemoryPointer.new(:pointer)
|
|
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)
|
|
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"
|
|
197
136
|
end
|
|
137
|
+
|
|
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)
|
|
198
142
|
end
|
|
199
143
|
|
|
200
|
-
def
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
|
144
|
+
def get_int(key) = get(key, :int)
|
|
145
|
+
|
|
146
|
+
def get_float(key)
|
|
147
|
+
words = get_raw(key, :float)
|
|
148
|
+
words&.map { |word| decode_float_word(word) }
|
|
222
149
|
end
|
|
223
150
|
|
|
224
|
-
def
|
|
151
|
+
def get_flag(key) = get(key, :flag)
|
|
152
|
+
def get_string(key) = get(key, :string)
|
|
153
|
+
def get_genotypes = get_raw("GT", :int)
|
|
154
|
+
def [](key) = get(key)
|
|
155
|
+
|
|
156
|
+
def each_genotype(key = "GT")
|
|
157
|
+
return enum_for(__method__, key) unless block_given?
|
|
158
|
+
raise ArgumentError, "genotype FORMAT key must be GT" unless key.to_s == "GT"
|
|
159
|
+
|
|
225
160
|
values = get_raw(key, :int)
|
|
226
161
|
return nil unless values
|
|
227
162
|
|
|
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
|
|
163
|
+
count, width = sample_layout(values.length)
|
|
164
|
+
buffer, generation = advance_buffer(key, :genotype)
|
|
165
|
+
view = GenotypeView.new
|
|
166
|
+
count.times do |sample|
|
|
167
|
+
yield sample, view.reset(values.slice(sample * width, width), buffer, generation)
|
|
237
168
|
end
|
|
169
|
+
self
|
|
238
170
|
end
|
|
239
171
|
|
|
240
|
-
def
|
|
241
|
-
values =
|
|
172
|
+
def genotype_at(key, sample_index)
|
|
173
|
+
values = get_raw(key, :int)
|
|
242
174
|
return nil unless values
|
|
243
175
|
|
|
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
|
|
176
|
+
count, width = sample_layout(values.length)
|
|
177
|
+
sample_index = Integer(sample_index)
|
|
178
|
+
sample_index += count if sample_index.negative?
|
|
179
|
+
raise IndexError, "sample index #{sample_index} outside of FORMAT" unless sample_index.between?(0, count - 1)
|
|
255
180
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
181
|
+
buffer, generation = advance_buffer(key, :genotype)
|
|
182
|
+
GenotypeView.new.reset(values.slice(sample_index * width, width), buffer, generation)
|
|
183
|
+
end
|
|
259
184
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
185
|
+
def genotype_strings(key = "GT")
|
|
186
|
+
strings = []
|
|
187
|
+
found = each_genotype(key) { |_, genotype| strings << genotype.to_s }
|
|
188
|
+
found ? strings : nil
|
|
263
189
|
end
|
|
264
190
|
|
|
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
|
|
191
|
+
def each_i32(key)
|
|
192
|
+
return enum_for(__method__, key) unless block_given?
|
|
272
193
|
|
|
273
|
-
|
|
194
|
+
ensure_scalar!(key, :int)
|
|
195
|
+
values = get_raw(key, :int)
|
|
196
|
+
return self unless values
|
|
274
197
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
end.join
|
|
198
|
+
values.each_with_index { |value, index| yield index, missing_int(value) }
|
|
199
|
+
self
|
|
278
200
|
end
|
|
279
201
|
|
|
280
|
-
def
|
|
281
|
-
|
|
282
|
-
return [] if sample_count <= 0
|
|
202
|
+
def each_i32_vector(key, &block) = each_vector(key, :int, &block)
|
|
203
|
+
def each_f32_vector(key, &block) = each_vector(key, :float, &block)
|
|
283
204
|
|
|
284
|
-
|
|
205
|
+
def update_int(key, values)
|
|
206
|
+
raise UnsupportedFormatOperationError, "Use update_genotypes for GT" if key.to_s == "GT"
|
|
285
207
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
values[start, values_per_sample]
|
|
290
|
-
end
|
|
208
|
+
values = normalize_values(values) { |value| Integer(value) }
|
|
209
|
+
validate_sample_divisibility!(key, values.length)
|
|
210
|
+
update_format(key, Native::BCF_HT_INT, values)
|
|
291
211
|
end
|
|
292
212
|
|
|
293
|
-
def
|
|
294
|
-
|
|
295
|
-
|
|
213
|
+
def update_float(key, values)
|
|
214
|
+
values = normalize_values(values, &:to_f)
|
|
215
|
+
validate_sample_divisibility!(key, values.length)
|
|
216
|
+
update_format(key, Native::BCF_HT_REAL, values)
|
|
296
217
|
end
|
|
297
218
|
|
|
298
|
-
def
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
219
|
+
def update_float_words(key, values)
|
|
220
|
+
values = normalize_values(values) { |value| Integer(value) }
|
|
221
|
+
validate_sample_divisibility!(key, values.length)
|
|
222
|
+
raise FormatDefinitionError, "FORMAT tag #{key} not defined in header" unless format_schema(key)
|
|
302
223
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
values[0, end_index]
|
|
306
|
-
end
|
|
224
|
+
result = native.format_update_float_words(header_native, key.to_s, values)
|
|
225
|
+
raise FormatUpdateError, "Failed to update FORMAT field '#{key}': #{result}" if result.negative?
|
|
307
226
|
|
|
308
|
-
|
|
309
|
-
values.map { |value| map_integer_missing_value(value) }
|
|
227
|
+
result
|
|
310
228
|
end
|
|
311
229
|
|
|
312
|
-
def
|
|
313
|
-
|
|
314
|
-
|
|
230
|
+
def update_string(key, values)
|
|
231
|
+
values = Array(values).map(&:to_s)
|
|
232
|
+
expected = sample_count
|
|
233
|
+
unless values.length == expected
|
|
234
|
+
raise ArgumentError, "FORMAT string values for #{key} must provide one entry per sample (#{expected})"
|
|
235
|
+
end
|
|
315
236
|
|
|
316
|
-
|
|
317
|
-
values.map { |value| decode_float_word(value) }
|
|
237
|
+
update_format(key, Native::BCF_HT_STR, values)
|
|
318
238
|
end
|
|
319
239
|
|
|
320
|
-
def
|
|
321
|
-
|
|
322
|
-
|
|
240
|
+
def update_genotypes(values)
|
|
241
|
+
values = normalize_values(values) { |value| Integer(value) }
|
|
242
|
+
validate_sample_divisibility!("GT", values.length)
|
|
243
|
+
result = native.genotype_update(header_native, values)
|
|
244
|
+
raise FormatUpdateError, "Failed to update FORMAT field 'GT': #{result}" if result.negative?
|
|
323
245
|
|
|
324
|
-
|
|
246
|
+
result
|
|
325
247
|
end
|
|
326
248
|
|
|
327
|
-
def
|
|
328
|
-
|
|
329
|
-
|
|
249
|
+
def delete(key)
|
|
250
|
+
schema = format_schema(key)
|
|
251
|
+
return false unless schema && !get_raw(key).nil?
|
|
330
252
|
|
|
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
|
|
253
|
+
result = native.format_delete(header_native, key.to_s, type_code(schema.first))
|
|
254
|
+
result >= 0
|
|
340
255
|
end
|
|
341
256
|
|
|
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
|
|
257
|
+
def fields = native.format_fields(header_native)
|
|
258
|
+
def ids = fields.map { |field| field[:id] }
|
|
259
|
+
def get_float_words(key) = get_raw(key, :float)
|
|
260
|
+
def length = fields.length
|
|
261
|
+
alias size length
|
|
262
|
+
def to_h = fields.to_h { |field| [field[:name], get(field[:name])] }
|
|
354
263
|
|
|
355
|
-
|
|
356
|
-
return unless header_format_type(key) == :flag
|
|
264
|
+
private
|
|
357
265
|
|
|
358
|
-
|
|
359
|
-
|
|
266
|
+
def native = @record.__send__(:native_handle)
|
|
267
|
+
def header_native = @record.header.__send__(:native_handle)
|
|
268
|
+
def sample_count = @record.header.nsamples
|
|
269
|
+
def format_schema(key) = header_native.schema("FORMAT", key.to_s)
|
|
270
|
+
|
|
271
|
+
def type_code(type)
|
|
272
|
+
{ int: Native::BCF_HT_INT, int32: Native::BCF_HT_INT,
|
|
273
|
+
float: Native::BCF_HT_REAL, real: Native::BCF_HT_REAL,
|
|
274
|
+
string: Native::BCF_HT_STR, str: Native::BCF_HT_STR }.fetch(type)
|
|
360
275
|
end
|
|
361
276
|
|
|
362
|
-
def
|
|
363
|
-
|
|
364
|
-
raise FormatDefinitionError, "FORMAT tag #{key} not defined in header" if actual_type.nil?
|
|
277
|
+
def type_compatible?(actual, requested, key)
|
|
278
|
+
return true if key == "GT" && %i[int int32 string str].include?(requested)
|
|
365
279
|
|
|
366
|
-
|
|
367
|
-
|
|
280
|
+
actual == case requested
|
|
281
|
+
when :int, :int32 then :int
|
|
282
|
+
when :float, :real then :float
|
|
283
|
+
when :string, :str then :string
|
|
284
|
+
when :flag then :flag
|
|
285
|
+
else requested
|
|
286
|
+
end
|
|
368
287
|
end
|
|
369
288
|
|
|
370
|
-
def
|
|
371
|
-
|
|
289
|
+
def type_label(type)
|
|
290
|
+
case type
|
|
291
|
+
when :int, :int32 then "integer"
|
|
292
|
+
when :float, :real then "float"
|
|
293
|
+
when :string, :str then "string"
|
|
294
|
+
else type.to_s
|
|
295
|
+
end
|
|
372
296
|
end
|
|
373
297
|
|
|
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
|
|
298
|
+
def raise_unsupported_flag(key)
|
|
299
|
+
raise UnsupportedFormatOperationError, "FORMAT flag fields are not supported: #{key}"
|
|
385
300
|
end
|
|
386
301
|
|
|
387
|
-
def
|
|
388
|
-
|
|
389
|
-
raise
|
|
390
|
-
return if (value_count % sample_count).zero?
|
|
302
|
+
def sample_layout(value_count)
|
|
303
|
+
count = sample_count
|
|
304
|
+
raise FormatReadError, "invalid FORMAT sample layout" if count <= 0 || (value_count % count) != 0
|
|
391
305
|
|
|
392
|
-
|
|
306
|
+
[count, value_count / count]
|
|
393
307
|
end
|
|
394
308
|
|
|
395
|
-
def
|
|
396
|
-
|
|
397
|
-
raise ArgumentError, "FORMAT fields require at least one sample" if sample_count <= 0
|
|
398
|
-
return if value_count == sample_count
|
|
309
|
+
def shape_values(values, _key, schema)
|
|
310
|
+
return nil unless values
|
|
399
311
|
|
|
400
|
-
|
|
312
|
+
count, width = sample_layout(values.length)
|
|
313
|
+
scalar = schema[1] == 1
|
|
314
|
+
Array.new(count) do |sample|
|
|
315
|
+
row = values.slice(sample * width, width)
|
|
316
|
+
row = trim_vector(row, schema.first)
|
|
317
|
+
scalar ? row.first : row
|
|
318
|
+
end
|
|
401
319
|
end
|
|
402
320
|
|
|
403
|
-
def
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
321
|
+
def trim_vector(values, type)
|
|
322
|
+
if type == :int
|
|
323
|
+
values.take_while { |value| value != Native::BCF_INT32_VECTOR_END }.map { |value| missing_int(value) }
|
|
324
|
+
else
|
|
325
|
+
values.take_while { |word| word != Native::BCF_FLOAT_VECTOR_END }.map { |word| decode_float_word(word) }
|
|
326
|
+
end
|
|
327
|
+
end
|
|
408
328
|
|
|
409
|
-
|
|
329
|
+
def missing_int(value)
|
|
330
|
+
[Native::BCF_INT32_MISSING, Native::BCF_INT32_VECTOR_END].include?(value) ? nil : value
|
|
410
331
|
end
|
|
411
332
|
|
|
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)
|
|
333
|
+
def decode_float_word(word)
|
|
334
|
+
return nil if [Native::BCF_FLOAT_MISSING, Native::BCF_FLOAT_VECTOR_END].include?(word)
|
|
416
335
|
|
|
417
|
-
|
|
336
|
+
[word].pack("L<").unpack1("e")
|
|
418
337
|
end
|
|
419
338
|
|
|
420
|
-
def
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
values
|
|
339
|
+
def advance_buffer(key, type)
|
|
340
|
+
buffer = (@buffers[[key.to_s, type]] ||= BufferState.new(0))
|
|
341
|
+
buffer.generation += 1
|
|
342
|
+
[buffer, buffer.generation]
|
|
426
343
|
end
|
|
427
344
|
|
|
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
|
|
345
|
+
def invalidate_views!(key)
|
|
346
|
+
@buffers.each do |(buffer_key, _type), buffer|
|
|
347
|
+
buffer.generation += 1 if buffer_key == key.to_s
|
|
438
348
|
end
|
|
439
349
|
end
|
|
440
350
|
|
|
441
|
-
def
|
|
442
|
-
|
|
443
|
-
end
|
|
351
|
+
def each_vector(key, type)
|
|
352
|
+
return enum_for(type == :int ? :each_i32_vector : :each_f32_vector, key) unless block_given?
|
|
444
353
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
fmt[:id]
|
|
448
|
-
end
|
|
449
|
-
end
|
|
354
|
+
values = get_raw(key, type)
|
|
355
|
+
return self unless values
|
|
450
356
|
|
|
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
|
|
357
|
+
count, width = sample_layout(values.length)
|
|
358
|
+
buffer, generation = advance_buffer(key, type)
|
|
359
|
+
view = NumericVectorView.new(type)
|
|
360
|
+
count.times do |sample|
|
|
361
|
+
decoded = trim_vector(values.slice(sample * width, width), type)
|
|
362
|
+
yield sample, view.reset(decoded, buffer, generation)
|
|
460
363
|
end
|
|
461
|
-
|
|
364
|
+
self
|
|
462
365
|
end
|
|
463
366
|
|
|
464
|
-
def
|
|
465
|
-
|
|
367
|
+
def ensure_scalar!(key, expected)
|
|
368
|
+
schema = format_schema(key)
|
|
369
|
+
return unless schema
|
|
370
|
+
raise FormatTypeError, "Tag #{key} is not #{type_label(expected)} FORMAT field" unless schema.first == expected
|
|
371
|
+
raise FormatReadError, "FORMAT field #{key} is not scalar" unless schema[1] == 1
|
|
466
372
|
end
|
|
467
373
|
|
|
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)
|
|
374
|
+
def normalize_values(values, &block)
|
|
375
|
+
Array(values).map(&block)
|
|
474
376
|
end
|
|
475
377
|
|
|
476
|
-
def
|
|
477
|
-
|
|
478
|
-
return
|
|
479
|
-
return nil unless LibHTS.bcf_hdr_idinfo_exists(@record.header.struct, LibHTS::BCF_HL_FMT, id)
|
|
378
|
+
def validate_sample_divisibility!(key, count)
|
|
379
|
+
samples = sample_count
|
|
380
|
+
return if samples.positive? && (count % samples).zero?
|
|
480
381
|
|
|
481
|
-
|
|
382
|
+
raise ArgumentError, "FORMAT values for #{key} must be divisible by sample count (#{samples})"
|
|
482
383
|
end
|
|
483
384
|
|
|
484
|
-
def
|
|
485
|
-
|
|
486
|
-
|
|
385
|
+
def update_format(key, type, values)
|
|
386
|
+
schema = format_schema(key)
|
|
387
|
+
raise FormatDefinitionError, "FORMAT tag #{key} not defined in header" unless schema
|
|
487
388
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
when LibHTS::BCF_HT_STR then :string
|
|
494
|
-
when LibHTS::BCF_HT_LONG then :int64
|
|
389
|
+
expected = type_code(schema.first)
|
|
390
|
+
unless expected == type
|
|
391
|
+
requested = { Native::BCF_HT_INT => :int, Native::BCF_HT_REAL => :float,
|
|
392
|
+
Native::BCF_HT_STR => :string }.fetch(type)
|
|
393
|
+
raise FormatTypeError, "Tag #{key} is not #{type_label(requested)} FORMAT field"
|
|
495
394
|
end
|
|
496
|
-
|
|
395
|
+
result = native.format_update(header_native, key.to_s, type, values)
|
|
396
|
+
raise FormatUpdateError, "Failed to update FORMAT field '#{key}': #{result}" if result.negative?
|
|
497
397
|
|
|
498
|
-
|
|
499
|
-
value >= -2_147_483_648 && value <= 2_147_483_647
|
|
398
|
+
result
|
|
500
399
|
end
|
|
501
400
|
end
|
|
502
401
|
end
|