htslib 0.2.5 → 0.2.8

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.
data/lib/hts/bam/auxi.rb CHANGED
@@ -3,22 +3,96 @@
3
3
  # Q. Why is the file name auxi.rb and not aux.rb?
4
4
  #
5
5
  # A. This is for compatibility with Windows.
6
+ #
6
7
  # In Windows, aux is a reserved word
7
8
  # You cannot create a file named aux.
9
+ #
10
+ # What?! That's crazy!
8
11
 
9
12
  module HTS
10
13
  class Bam < Hts
11
14
  # Auxiliary record data
15
+ #
16
+ # @noge Aux is a View object.
17
+ # The result of the alignment is assigned to the bam1 structure.
18
+ # Ruby's Aux class references a part of it. There is no one-to-one
19
+ # correspondence between C structures and Ruby's Aux class.
12
20
  class Aux
21
+ include Enumerable
22
+ attr_reader :record
23
+
13
24
  def initialize(record)
14
25
  @record = record
15
26
  end
16
27
 
28
+ # @note Why is this method named "get" instead of "fetch"?
29
+ # This is for compatibility with the Crystal language
30
+ # which provides methods like `get_int`, `get_float`, etc.
31
+ # I think they are better than `fetch_int`` and `fetch_float`.
17
32
  def get(key, type = nil)
18
33
  aux = LibHTS.bam_aux_get(@record.struct, key)
19
34
  return nil if aux.null?
20
35
 
21
- type ||= aux.read_string(1)
36
+ get_ruby_aux(aux, type)
37
+ end
38
+
39
+ # For compatibility with HTS.cr.
40
+ def get_int(key)
41
+ get(key, "i")
42
+ end
43
+
44
+ # For compatibility with HTS.cr.
45
+ def get_float(key)
46
+ get(key, "f")
47
+ end
48
+
49
+ # For compatibility with HTS.cr.
50
+ def get_string(key)
51
+ get(key, "Z")
52
+ end
53
+
54
+ def [](key)
55
+ get(key)
56
+ end
57
+
58
+ def first
59
+ aux = LibHTS.bam_aux_first(@record.struct)
60
+ return nil if aux.null?
61
+
62
+ get_ruby_aux(aux)
63
+ end
64
+
65
+ def each
66
+ return enum_for(__method__) unless block_given?
67
+
68
+ aux = LibHTS.bam_aux_first(@record.struct)
69
+ return nil if aux.null?
70
+
71
+ loop do
72
+ yield get_ruby_aux(aux)
73
+ aux = LibHTS.bam_aux_next(@record.struct, aux)
74
+ break if aux.null?
75
+ end
76
+ end
77
+
78
+ def to_h
79
+ h = {}
80
+ aux = LibHTS.bam_aux_first(@record.struct)
81
+ return h if aux.null?
82
+
83
+ loop do
84
+ key = FFI::Pointer.new(aux.address - 2).read_string(2)
85
+ h[key] = get_ruby_aux(aux)
86
+ aux = LibHTS.bam_aux_next(@record.struct, aux)
87
+ break if aux.null?
88
+ end
89
+ h
90
+ end
91
+
92
+ private
93
+
94
+ def get_ruby_aux(aux, type = nil)
95
+ type = type ? type.to_s : aux.read_string(1)
22
96
 
23
97
  # A (character), B (general array),
24
98
  # f (real number), H (hexadecimal array),
@@ -33,14 +107,23 @@ module HTS
33
107
  LibHTS.bam_aux2Z(aux)
34
108
  when "A" # char
35
109
  LibHTS.bam_aux2A(aux).chr
110
+ when "B" # array
111
+ t2 = aux.read_string(2)[1] # just a little less efficient
112
+ l = LibHTS.bam_auxB_len(aux)
113
+ case t2
114
+ when "c", "C", "s", "S", "i", "I"
115
+ # FIXME : Not efficient.
116
+ Array.new(l) { |i| LibHTS.bam_auxB2i(aux, i) }
117
+ when "f", "d"
118
+ # FIXME : Not efficient.
119
+ Array.new(l) { |i| LibHTS.bam_auxB2f(aux, i) }
120
+ else
121
+ raise NotImplementedError, "type: #{type} #{t2}"
122
+ end
36
123
  else
37
- raise NotImplementedError, "type: #{t}"
124
+ raise NotImplementedError, "type: #{type}"
38
125
  end
39
126
  end
40
-
41
- def [](key)
42
- get(key)
43
- end
44
127
  end
45
128
  end
46
129
  end
data/lib/hts/bam/cigar.rb CHANGED
@@ -6,11 +6,31 @@ module HTS
6
6
  class Cigar
7
7
  include Enumerable
8
8
 
9
- def initialize(pointer, n_cigar)
10
- @n_cigar = n_cigar
11
- # Read the pointer before the memory is changed.
12
- # Especially when called from a block of `each` iterator.
13
- @c = pointer.read_array_of_uint32(n_cigar)
9
+ # a uint32_t array (with 32 bits for every CIGAR op: length<<4|operation)
10
+ attr_accessor :array
11
+
12
+ # Create a new Cigar object from a string.
13
+ # @param [String] cigar_str
14
+ # The CIGAR string is converted to a uint32_t array in htslib.
15
+ def self.parse(str)
16
+ c = FFI::MemoryPointer.new(:pointer)
17
+ m = FFI::MemoryPointer.new(:size_t)
18
+ LibHTS.sam_parse_cigar(str, FFI::Pointer::NULL, c, m)
19
+ cigar_array = c.read_pointer.read_array_of_uint32(m.read(:size_t))
20
+ obj = new
21
+ obj.array = cigar_array
22
+ obj
23
+ end
24
+
25
+ def initialize(record = nil)
26
+ if record
27
+ # The record is used at initialization and is not retained after that.
28
+ bam1 = record.struct
29
+ n_cigar = bam1[:core][:n_cigar]
30
+ @array = LibHTS.bam_get_cigar(bam1).read_array_of_uint32(n_cigar)
31
+ else
32
+ @array = []
33
+ end
14
34
  end
15
35
 
16
36
  def to_s
@@ -20,12 +40,32 @@ module HTS
20
40
  def each
21
41
  return to_enum(__method__) unless block_given?
22
42
 
23
- @c.each do |c|
43
+ @array.each do |c|
24
44
  op = LibHTS.bam_cigar_opchr(c)
25
45
  len = LibHTS.bam_cigar_oplen(c)
26
46
  yield [op, len]
27
47
  end
28
48
  end
49
+
50
+ def qlen
51
+ a = FFI::MemoryPointer.new(:uint32, @array.size)
52
+ a.write_array_of_uint32(@array)
53
+ LibHTS.bam_cigar2qlen(@array.size, a)
54
+ end
55
+
56
+ def rlen
57
+ a = FFI::MemoryPointer.new(:uint32, @array.size)
58
+ a.write_array_of_uint32(@array)
59
+ LibHTS.bam_cigar2rlen(@array.size, a)
60
+ end
61
+
62
+ def ==(other)
63
+ other.is_a?(Cigar) && (@array == other.array)
64
+ end
65
+
66
+ def eql?(other)
67
+ other.is_a?(Cigar) && @array.eql?(other.array)
68
+ end
29
69
  end
30
70
  end
31
71
  end
data/lib/hts/bam/flag.rb CHANGED
@@ -28,8 +28,6 @@ module HTS
28
28
  # BAM_FDUP = 1024
29
29
  # BAM_FSUPPLEMENTARY = 2048
30
30
 
31
- # TODO: Enabling bitwise operations?
32
-
33
31
  TABLE = { paired?: LibHTS::BAM_FPAIRED,
34
32
  proper_pair?: LibHTS::BAM_FPROPER_PAIR,
35
33
  unmapped?: LibHTS::BAM_FUNMAP,
@@ -43,16 +41,57 @@ module HTS
43
41
  duplicate?: LibHTS::BAM_FDUP,
44
42
  supplementary?: LibHTS::BAM_FSUPPLEMENTARY }.freeze
45
43
 
46
- TABLE.each do |name, v|
44
+ # @!macro [attach] generate_flag_methods
45
+ # @!method $1
46
+ # @return [Boolean]
47
+ def self.generate(name)
47
48
  define_method(name) do
48
- has_flag?(v)
49
+ (@value & TABLE[name]) != 0
49
50
  end
50
51
  end
52
+ private_class_method :generate
53
+
54
+ generate :paired?
55
+ generate :proper_pair?
56
+ generate :unmapped?
57
+ generate :mate_unmapped?
58
+ generate :reverse?
59
+ generate :mate_reverse?
60
+ generate :read1?
61
+ generate :read2?
62
+ generate :secondary?
63
+ generate :qcfail?
64
+ generate :duplicate?
65
+ generate :supplementary?
51
66
 
52
67
  def has_flag?(f)
53
68
  (@value & f) != 0
54
69
  end
55
70
 
71
+ def &(other)
72
+ Flag.new(@value & other.to_i)
73
+ end
74
+
75
+ def |(other)
76
+ Flag.new(@value | other.to_i)
77
+ end
78
+
79
+ def ^(other)
80
+ Flag.new(@value ^ other.to_i)
81
+ end
82
+
83
+ def ~
84
+ Flag.new(~@value)
85
+ end
86
+
87
+ def <<(f)
88
+ Flag.new(@value << f.to_i)
89
+ end
90
+
91
+ def >>(other)
92
+ Flag.new(@value >> other.to_i)
93
+ end
94
+
56
95
  def to_i
57
96
  @value
58
97
  end
@@ -87,6 +87,14 @@ module HTS
87
87
  LibHTS.sam_hdr_str(@sam_hdr)
88
88
  end
89
89
 
90
+ def name2tid(name)
91
+ LibHTS.sam_hdr_name2tid(@sam_hdr, name)
92
+ end
93
+
94
+ def tid2name(tid)
95
+ LibHTS.sam_hdr_tid2name(@sam_hdr, tid)
96
+ end
97
+
90
98
  private
91
99
 
92
100
  def initialize_copy(orig)
@@ -12,8 +12,8 @@ module HTS
12
12
 
13
13
  attr_reader :header
14
14
 
15
- def initialize(bam1_t, header)
16
- @bam1 = bam1_t
15
+ def initialize(header, bam1_t = nil)
16
+ @bam1 = bam1_t || LibHTS.bam_init1
17
17
  @header = header
18
18
  end
19
19
 
@@ -32,6 +32,10 @@ module HTS
32
32
  LibHTS.bam_get_qname(@bam1).read_string
33
33
  end
34
34
 
35
+ def qname=(name)
36
+ LibHTS.bam_set_qname(@bam1, name)
37
+ end
38
+
35
39
  # Get the chromosome ID of the alignment. -1 if not mapped.
36
40
  # @return [Integer] chromosome ID
37
41
  def tid
@@ -151,12 +155,13 @@ module HTS
151
155
  # Get the Bam::Cigar object.
152
156
  # @return [Bam::Cigar] cigar
153
157
  def cigar
154
- Cigar.new(LibHTS.bam_get_cigar(@bam1), @bam1[:core][:n_cigar])
158
+ Cigar.new(self)
155
159
  end
156
160
 
157
161
  # Calculate query length from CIGAR.
158
162
  # @return [Integer] query length
159
163
  def qlen
164
+ # cigar.qlen will be slower because it converts to a Ruby array.
160
165
  LibHTS.bam_cigar2qlen(
161
166
  @bam1[:core][:n_cigar],
162
167
  LibHTS.bam_get_cigar(@bam1)
@@ -177,7 +182,7 @@ module HTS
177
182
  def seq
178
183
  r = LibHTS.bam_get_seq(@bam1)
179
184
  seq = String.new
180
- (@bam1[:core][:l_qseq]).times do |i|
185
+ len.times do |i|
181
186
  seq << SEQ_NT16_STR[LibHTS.bam_seqi(r, i)]
182
187
  end
183
188
  seq
@@ -194,8 +199,8 @@ module HTS
194
199
  # @param [Integer] i index
195
200
  # @return [String] base
196
201
  def base(n)
197
- n += @bam1[:core][:l_qseq] if n < 0
198
- return "." if (n >= @bam1[:core][:l_qseq]) || (n < 0) # eg. base(-1000)
202
+ n += len if n < 0
203
+ return "." if (n >= len) || (n < 0) # eg. base(-1000)
199
204
 
200
205
  r = LibHTS.bam_get_seq(@bam1)
201
206
  SEQ_NT16_STR[LibHTS.bam_seqi(r, n)]
@@ -205,7 +210,7 @@ module HTS
205
210
  # @return [Array] base qualities
206
211
  def qual
207
212
  q_ptr = LibHTS.bam_get_qual(@bam1)
208
- q_ptr.read_array_of_uint8(@bam1[:core][:l_qseq])
213
+ q_ptr.read_array_of_uint8(len)
209
214
  end
210
215
 
211
216
  # Get the base qualities as a string. (a.k.a QUAL)
@@ -219,8 +224,8 @@ module HTS
219
224
  # @param [Integer] i index
220
225
  # @return [Integer] base quality
221
226
  def base_qual(n)
222
- n += @bam1[:core][:l_qseq] if n < 0
223
- return 0 if (n >= @bam1[:core][:l_qseq]) || (n < 0) # eg. base_qual(-1000)
227
+ n += len if n < 0
228
+ return 0 if (n >= len) || (n < 0) # eg. base_qual(-1000)
224
229
 
225
230
  q_ptr = LibHTS.bam_get_qual(@bam1)
226
231
  q_ptr.get_uint8(n)
@@ -255,11 +260,11 @@ module HTS
255
260
  end
256
261
  end
257
262
 
258
- # TODO: add a method to get the auxillary fields as a hash.
263
+ # TODO: add a method to get the auxiliary fields as a hash.
259
264
 
260
- # TODO: add a method to set the auxillary fields.
265
+ # TODO: add a method to set the auxiliary fields.
261
266
 
262
- # TODO: add a method to remove the auxillary fields.
267
+ # TODO: add a method to remove the auxiliary fields.
263
268
 
264
269
  # TODO: add a method to set variable length data (qname, cigar, seq, qual).
265
270
 
data/lib/hts/bam.rb CHANGED
@@ -59,7 +59,7 @@ module HTS
59
59
  @start_position = tell
60
60
  end
61
61
 
62
- def build_index(index_name = nil, min_shift: 0)
62
+ def build_index(index_name = nil, min_shift: 0, threads: 2)
63
63
  check_closed
64
64
 
65
65
  if index_name
@@ -67,10 +67,15 @@ module HTS
67
67
  else
68
68
  warn "Create index for #{@file_name}"
69
69
  end
70
- r = LibHTS.sam_index_build3(@file_name, index_name, min_shift, @nthreads)
71
- raise "Failed to build index for #{@file_name}" if r < 0
72
-
73
- self
70
+ case LibHTS.sam_index_build3(@file_name, index_name, min_shift, (@nthreads || threads))
71
+ when 0 # successful
72
+ when -1 then raise "indexing failed"
73
+ when -2 then raise "opening #{@file_name} failed"
74
+ when -3 then raise "format not indexable"
75
+ when -4 then raise "failed to create and/or save the index"
76
+ else raise "unknown error"
77
+ end
78
+ self # for method chaining
74
79
  end
75
80
 
76
81
  def load_index(index_name = nil)
@@ -95,11 +100,6 @@ module HTS
95
100
  super
96
101
  end
97
102
 
98
- def fai=(fai)
99
- check_closed
100
- LibHTS.hts_set_fai_filename(@hts_file, fai) > 0 || raise
101
- end
102
-
103
103
  def write_header(header)
104
104
  check_closed
105
105
 
@@ -107,28 +107,19 @@ module HTS
107
107
  LibHTS.sam_hdr_write(@hts_file, header)
108
108
  end
109
109
 
110
- def write(aln)
110
+ def header=(header)
111
+ write_header(header)
112
+ end
113
+
114
+ def write(record)
111
115
  check_closed
112
116
 
113
- aln_dup = aln.dup
114
- r = LibHTS.sam_write1(@hts_file, header, aln_dup)
117
+ r = LibHTS.sam_write1(@hts_file, header, record)
115
118
  raise "Failed to write record" if r < 0
116
119
  end
117
120
 
118
- def each(copy: false, &block)
119
- if copy
120
- each_record_copy(&block)
121
- else
122
- each_record_reuse(&block)
123
- end
124
- end
125
-
126
- def query(region, copy: false, &block)
127
- if copy
128
- query_copy(region, &block)
129
- else
130
- query_reuse(region, &block)
131
- end
121
+ def <<(record)
122
+ write(record)
132
123
  end
133
124
 
134
125
  # @!macro [attach] define_getter
@@ -150,9 +141,10 @@ module HTS
150
141
  alias isize insert_size
151
142
  alias mpos mate_pos
152
143
 
144
+ # FXIME: experimental
153
145
  def aux(tag)
154
- warn "experimental"
155
146
  check_closed
147
+
156
148
  position = tell
157
149
  ary = map { |r| r.aux(tag) }
158
150
  seek(position)
@@ -177,8 +169,8 @@ module HTS
177
169
  alias each_isize each_insert_size
178
170
  alias each_mpos each_mate_pos
179
171
 
172
+ # FIXME: experimental
180
173
  def each_aux(tag)
181
- warn "experimental"
182
174
  check_closed
183
175
  return to_enum(__method__, tag) unless block_given?
184
176
 
@@ -189,47 +181,44 @@ module HTS
189
181
  self
190
182
  end
191
183
 
192
- private
184
+ def each(copy: false, &block)
185
+ if copy
186
+ each_record_copy(&block)
187
+ else
188
+ each_record_reuse(&block)
189
+ end
190
+ end
193
191
 
194
- def query_reuse(region)
192
+ def query(region, beg = nil, end_ = nil, copy: false, &block)
195
193
  check_closed
196
194
  raise "Index file is required to call the query method." unless index_loaded?
197
- return to_enum(__method__, region) unless block_given?
198
-
199
- qiter = LibHTS.sam_itr_querys(@idx, header, region)
200
- raise "Failed to query region: #{region}" if qiter.null?
201
195
 
202
- bam1 = LibHTS.bam_init1
203
- record = Record.new(bam1, header)
204
- begin
205
- yield record while LibHTS.sam_itr_next(@hts_file, qiter, bam1) > 0
206
- ensure
207
- LibHTS.hts_itr_destroy(qiter)
196
+ if beg && end_
197
+ tid = header.name2tid(region)
198
+ queryi(tid, beg, end_, copy:, &block)
199
+ elsif beg.nil? && end_.nil?
200
+ querys(region, copy:, &block)
201
+ else
202
+ raise ArgumentError, "beg and end_ must be specified together"
208
203
  end
209
- self
210
204
  end
211
205
 
212
- def query_copy(region)
213
- check_closed
214
- raise "Index file is required to call the query method." unless index_loaded?
215
- return to_enum(__method__, region) unless block_given?
206
+ private
216
207
 
217
- qiter = LibHTS.sam_itr_querys(@idx, header, region)
218
- raise "Failed to query region: #{region}" if qiter.null?
208
+ def queryi(tid, beg, end_, copy: false, &block)
209
+ if copy
210
+ queryi_copy(tid, beg, end_, &block)
211
+ else
212
+ queryi_reuse(tid, beg, end_, &block)
213
+ end
214
+ end
219
215
 
220
- begin
221
- loop do
222
- bam1 = LibHTS.bam_init1
223
- slen = LibHTS.sam_itr_next(@hts_file, qiter, bam1)
224
- break if slen == -1
225
- raise if slen < -1
226
-
227
- yield Record.new(bam1, header)
228
- end
229
- ensure
230
- LibHTS.hts_itr_destroy(qiter)
216
+ def querys(region, copy: false, &block)
217
+ if copy
218
+ querys_copy(region, &block)
219
+ else
220
+ querys_reuse(region, &block)
231
221
  end
232
- self
233
222
  end
234
223
 
235
224
  def each_record_reuse
@@ -239,7 +228,7 @@ module HTS
239
228
  return to_enum(__method__) unless block_given?
240
229
 
241
230
  bam1 = LibHTS.bam_init1
242
- record = Record.new(bam1, header)
231
+ record = Record.new(header, bam1)
243
232
  yield record while LibHTS.sam_read1(@hts_file, header, bam1) != -1
244
233
  self
245
234
  end
@@ -249,10 +238,73 @@ module HTS
249
238
  return to_enum(__method__) unless block_given?
250
239
 
251
240
  while LibHTS.sam_read1(@hts_file, header, bam1 = LibHTS.bam_init1) != -1
252
- record = Record.new(bam1, header)
241
+ record = Record.new(header, bam1)
253
242
  yield record
254
243
  end
255
244
  self
256
245
  end
246
+
247
+ def queryi_reuse(tid, beg, end_, &block)
248
+ return to_enum(__method__, region, beg, end_) unless block_given?
249
+
250
+ qiter = LibHTS.sam_itr_queryi(@idx, tid, beg, end_)
251
+ raise "Failed to query region: #{tid} #{beg} #{end_}" if qiter.null?
252
+
253
+ query_reuse_yield(qiter, &block)
254
+ self
255
+ end
256
+
257
+ def queryi_copy(tid, beg, end_, &block)
258
+ return to_enum(__method__, tid, beg, end_) unless block_given?
259
+
260
+ qiter = LibHTS.sam_itr_queryi(@idx, tid, beg, end_)
261
+ raise "Failed to query region: #{tid} #{beg} #{end_}" if qiter.null?
262
+
263
+ query_copy(qiter, &block)
264
+ self
265
+ end
266
+
267
+ def querys_reuse(region, &block)
268
+ return to_enum(__method__, region) unless block_given?
269
+
270
+ qiter = LibHTS.sam_itr_querys(@idx, header, region)
271
+ raise "Failed to query region: #{region}" if qiter.null?
272
+
273
+ query_reuse_yield(qiter, &block)
274
+ self
275
+ end
276
+
277
+ def querys_copy(region, &block)
278
+ return to_enum(__method__, region) unless block_given?
279
+
280
+ qiter = LibHTS.sam_itr_querys(@idx, header, region)
281
+ raise "Failed to query region: #{region}" if qiter.null?
282
+
283
+ query_copy(qiter, &block)
284
+ self
285
+ end
286
+
287
+ def query_reuse_yield(qiter)
288
+ bam1 = LibHTS.bam_init1
289
+ record = Record.new(header, bam1)
290
+ begin
291
+ yield record while LibHTS.sam_itr_next(@hts_file, qiter, bam1) > 0
292
+ ensure
293
+ LibHTS.hts_itr_destroy(qiter)
294
+ end
295
+ end
296
+
297
+ def query_copy(qiter)
298
+ loop do
299
+ bam1 = LibHTS.bam_init1
300
+ slen = LibHTS.sam_itr_next(@hts_file, qiter, bam1)
301
+ break if slen == -1
302
+ raise if slen < -1
303
+
304
+ yield Record.new(header, bam1)
305
+ end
306
+ ensure
307
+ LibHTS.hts_itr_destroy(qiter)
308
+ end
257
309
  end
258
310
  end
@@ -8,30 +8,10 @@ module HTS
8
8
  @p1 = FFI::MemoryPointer.new(:pointer) # FIXME: naming
9
9
  end
10
10
 
11
- # For compatibility with HTS.cr.
12
- def get_int(key)
13
- get(key, :int)
14
- end
15
-
16
- # For compatibility with HTS.cr.
17
- def get_float(key)
18
- get(key, :float)
19
- end
20
-
21
- # For compatibility with HTS.cr.
22
- def get_flag(key)
23
- get(key, :flag)
24
- end
25
-
26
- # For compatibility with HTS.cr.
27
- def get_string(key)
28
- get(key, :string)
29
- end
30
-
31
- def [](key)
32
- get(key)
33
- end
34
-
11
+ # @note: Why is this method named "get" instead of "fetch"?
12
+ # This is for compatibility with the Crystal language
13
+ # which provides methods like `get_int`, `get_float`, etc.
14
+ # I think they are better than `fetch_int`` and `fetch_float`.
35
15
  def get(key, type = nil)
36
16
  n = FFI::MemoryPointer.new(:int)
37
17
  p1 = @p1
@@ -73,6 +53,30 @@ module HTS
73
53
  end
74
54
  end
75
55
 
56
+ # For compatibility with HTS.cr.
57
+ def get_int(key)
58
+ get(key, :int)
59
+ end
60
+
61
+ # For compatibility with HTS.cr.
62
+ def get_float(key)
63
+ get(key, :float)
64
+ end
65
+
66
+ # For compatibility with HTS.cr.
67
+ def get_flag(key)
68
+ get(key, :flag)
69
+ end
70
+
71
+ # For compatibility with HTS.cr.
72
+ def get_string(key)
73
+ get(key, :string)
74
+ end
75
+
76
+ def [](key)
77
+ get(key)
78
+ end
79
+
76
80
  def fields
77
81
  ids.map do |id|
78
82
  name = LibHTS.bcf_hdr_int2id(@record.header.struct, LibHTS::BCF_DT_ID, id)