Class: HTS::Bcf::Info

Inherits:
Object
  • Object
show all
Defined in:
lib/hts/bcf/info.rb

Overview

Info field

Instance Method Summary collapse

Constructor Details

#initialize(record) ⇒ Info

Returns a new instance of Info.



7
8
9
# File 'lib/hts/bcf/info.rb', line 7

def initialize(record)
  @record = record
end

Instance Method Details

#[](key) ⇒ Object



95
96
97
# File 'lib/hts/bcf/info.rb', line 95

def [](key)
  get(key)
end

#[]=(key, value) ⇒ Object

Set INFO field value with automatic type detection.

Parameters:

  • key (String)

    INFO tag name

  • value (Integer, Float, String, Array, true, false, nil)

    value to set

    • Integer or Array<Integer> -> update_int

    • Float or Array<Float,Integer> -> update_float

    • String -> update_string

    • true/false -> update_flag

    • nil -> delete the INFO field



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/hts/bcf/info.rb', line 107

def []=(key, value)
  case value
  when nil
    delete(key)
  when true, false
    update_flag(key, value)
  when Integer
    unless int32_range?(value)
      raise RangeError,
            "Integer out of int32 range for []=. Current htslib backend does not support int64 INFO update."
    end

    update_int(key, [value])
  when Float
    update_float(key, [value])
  when String
    update_string(key, value)
  when Array
    if value.empty?
      raise ArgumentError, "Cannot set INFO field to empty array. Use nil to delete."
    elsif value.all? { |v| v.is_a?(Integer) }
      unless value.all? { |v| int32_range?(v) }
        raise RangeError,
              "Integer array contains out-of-int32 values for []=. Current htslib backend does not support int64 INFO update."
      end

      update_int(key, value)
    elsif value.all? { |v| v.is_a?(Numeric) }
      update_float(key, value)
    else
      raise ArgumentError, "INFO array must contain only integers or floats, got: #{value.map(&:class).uniq}"
    end
  else
    raise ArgumentError, "Unsupported INFO value type: #{value.class}"
  end
end

#delete(key) ⇒ Boolean

Delete an INFO field.

Parameters:

  • key (String)

    INFO tag name

Returns:

  • (Boolean)

    true if field was deleted, false if it didn’t exist



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/hts/bcf/info.rb', line 245

def delete(key)
  type = get_info_type(key)
  return false if type.nil?
  return false unless key?(key)

  # Delete by setting n=0
  ret = LibHTS.bcf_update_info(
    @record.header.struct,
    @record.struct,
    key,
    FFI::Pointer::NULL,
    0,
    type
  )
  return false if ret < 0

  true
end

#fieldsObject

FIXME: naming? room for improvement.



288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/hts/bcf/info.rb', line 288

def fields
  keys.map do |key|
    name = LibHTS.bcf_hdr_int2id(@record.header.struct, LibHTS::BCF_DT_ID, key)
    num  = LibHTS.bcf_hdr_id2number(@record.header.struct, LibHTS::BCF_HL_INFO, key)
    type = LibHTS.bcf_hdr_id2type(@record.header.struct, LibHTS::BCF_HL_INFO, key)
    {
      name:,
      n: num,
      type: ht_type_to_sym(type),
      key:
    }
  end
end

#get(key, type = nil) ⇒ Object

Note:

Specify the type. If you don’t specify a type, it will still work, but it will be slower.

@note: Why is this method named “get” instead of “fetch”? This is for compatibility with the Crystal language which provides methods like ‘get_int`, `get_float`, etc. I think they are better than `fetch_int“ and `fetch_float`.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/hts/bcf/info.rb', line 16

def get(key, type = nil)
  n = FFI::MemoryPointer.new(:int)
  p1 = FFI::MemoryPointer.new(:pointer)
  p1.write_pointer(FFI::Pointer::NULL)
  h = @record.header.struct
  r = @record.struct

  info_values = proc do |typ, reader|
    ret = LibHTS.bcf_get_info_values(h, r, key, p1, n, typ)
    return nil if ret < 0 # return from method.

    dst = p1.read_pointer
    begin
      reader.call(dst, n.read_int)
    ensure
      LibHTS.hts_free(dst) unless dst.null?
      p1.write_pointer(FFI::Pointer::NULL)
    end
  end

  actual_type = ht_type_to_sym(get_info_type(key))
  if type && actual_type && !info_type_compatible?(actual_type, type.to_sym)
    raise InfoTypeError, "Tag #{key} is not #{type_label(type)} INFO field"
  end

  type ||= actual_type
  return nil if actual_type && !key?(key)

  case type&.to_sym
  when :int, :int32
    info_values.call(LibHTS::BCF_HT_INT, ->(dst, len) { dst.read_array_of_int32(len) })
  when :int64, :long
    info_values.call(LibHTS::BCF_HT_LONG, ->(dst, len) { dst.read_array_of_int64(len) })
  when :float, :real
    info_values.call(LibHTS::BCF_HT_REAL, ->(dst, len) { dst.read_array_of_float(len) })
  when :flag, :bool
    begin
      case ret = LibHTS.bcf_get_info_flag(h, r, key, p1, n)
      when 1 then true
      when 0 then false
      when -1 then nil
      else
        raise InfoReadError, "Unknown return value from bcf_get_info_flag: #{ret}"
      end
    ensure
      dst = p1.read_pointer
      LibHTS.hts_free(dst) unless dst.null?
      p1.write_pointer(FFI::Pointer::NULL)
    end
  when :string, :str
    info_values.call(LibHTS::BCF_HT_STR, ->(dst, _len) { dst.read_string })
  end
end

#get_flag(key) ⇒ Object

For compatibility with HTS.cr.



91
92
93
# File 'lib/hts/bcf/info.rb', line 91

def get_flag(key)
  get(key, :flag)
end

#get_float(key) ⇒ Object

For compatibility with HTS.cr.



76
77
78
# File 'lib/hts/bcf/info.rb', line 76

def get_float(key)
  get(key, :float)
end

#get_int(key) ⇒ Object

For compatibility with HTS.cr.



71
72
73
# File 'lib/hts/bcf/info.rb', line 71

def get_int(key)
  get(key, :int)
end

#get_int64(key) ⇒ Object

For compatibility with HTS.cr.



81
82
83
# File 'lib/hts/bcf/info.rb', line 81

def get_int64(key)
  get(key, :int64)
end

#get_string(key) ⇒ Object

For compatibility with HTS.cr.



86
87
88
# File 'lib/hts/bcf/info.rb', line 86

def get_string(key)
  get(key, :string)
end

#key?(key) ⇒ Boolean Also known as: include?

Check if an INFO field exists.

Parameters:

  • key (String)

    INFO tag name

Returns:

  • (Boolean)

    true if the field exists



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/hts/bcf/info.rb', line 267

def key?(key)
  type = header_info_type(key)
  return false if type.nil?

  ndst = FFI::MemoryPointer.new(:int)
  ndst.write_int(0)
  dst_ptr = FFI::MemoryPointer.new(:pointer)
  dst_ptr.write_pointer(FFI::Pointer::NULL)

  ret = LibHTS.bcf_get_info_values(@record.header.struct, @record.struct, key, dst_ptr, ndst, type)
  type == LibHTS::BCF_HT_FLAG ? ret == 1 : ret >= 0
ensure
  if dst_ptr
    dst = dst_ptr.read_pointer
    LibHTS.hts_free(dst) unless dst.null?
  end
end

#lengthObject



302
303
304
# File 'lib/hts/bcf/info.rb', line 302

def length
  @record.struct[:n_info]
end

#sizeObject



306
307
308
# File 'lib/hts/bcf/info.rb', line 306

def size
  length
end

#to_hObject



310
311
312
313
314
315
316
317
# File 'lib/hts/bcf/info.rb', line 310

def to_h
  ret = {}
  keys.each do |key|
    name = LibHTS.bcf_hdr_int2id(@record.header.struct, LibHTS::BCF_DT_ID, key)
    ret[name] = get(name)
  end
  ret
end

#update_flag(key, present = true) ⇒ Object

Update INFO flag field. For compatibility with HTS.cr.

Parameters:

  • key (String)

    INFO tag name

  • present (Boolean) (defaults to: true)

    true to set flag, false to remove it

Raises:



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/hts/bcf/info.rb', line 216

def update_flag(key, present = true)
  ret = if present
          LibHTS.bcf_update_info(
            @record.header.struct,
            @record.struct,
            key,
            FFI::Pointer::NULL,
            1,
            LibHTS::BCF_HT_FLAG
          )
        else
          # Remove flag by setting n=0
          LibHTS.bcf_update_info(
            @record.header.struct,
            @record.struct,
            key,
            FFI::Pointer::NULL,
            0,
            LibHTS::BCF_HT_FLAG
          )
        end
  raise InfoUpdateError, "Failed to update INFO flag field '#{key}': #{ret}" if ret < 0

  ret
end

#update_float(key, values) ⇒ Object

Update INFO field with float value(s). For compatibility with HTS.cr.

Parameters:

  • key (String)

    INFO tag name

  • values (Array<Float>)

    float values (use single-element array for scalar)

Raises:



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/hts/bcf/info.rb', line 177

def update_float(key, values)
  values = Array(values).map(&:to_f)
  ptr = FFI::MemoryPointer.new(:float, values.size)
  ptr.write_array_of_float(values)
  ret = LibHTS.bcf_update_info(
    @record.header.struct,
    @record.struct,
    key,
    ptr,
    values.size,
    LibHTS::BCF_HT_REAL
  )
  raise InfoUpdateError, "Failed to update INFO float field '#{key}': #{ret}" if ret < 0

  ret
end

#update_int(key, values) ⇒ Object

Update INFO field with integer value(s). For compatibility with HTS.cr.

Parameters:

  • key (String)

    INFO tag name

  • values (Array<Integer>)

    integer values (use single-element array for scalar)

Raises:



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/hts/bcf/info.rb', line 148

def update_int(key, values)
  values = Array(values)
  ptr = FFI::MemoryPointer.new(:int32, values.size)
  ptr.write_array_of_int32(values)
  ret = LibHTS.bcf_update_info(
    @record.header.struct,
    @record.struct,
    key,
    ptr,
    values.size,
    LibHTS::BCF_HT_INT
  )
  raise InfoUpdateError, "Failed to update INFO int field '#{key}': #{ret}" if ret < 0

  ret
end

#update_int64(_key, _values) ⇒ Object

Note:

int64 INFO values are primarily relevant for VCF output.

Update INFO field with int64 value(s).

Parameters:

  • key (String)

    INFO tag name

  • values (Array<Integer>)

    integer values (use single-element array for scalar)

Raises:



169
170
171
# File 'lib/hts/bcf/info.rb', line 169

def update_int64(_key, _values)
  raise UnsupportedInfoOperationError, "htslib backend does not implement int64 INFO update (BCF_HT_LONG)"
end

#update_string(key, value) ⇒ Object

Update INFO field with string value. For compatibility with HTS.cr.

Parameters:

  • key (String)

    INFO tag name

  • value (String)

    string value

Raises:



198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/hts/bcf/info.rb', line 198

def update_string(key, value)
  ret = LibHTS.bcf_update_info(
    @record.header.struct,
    @record.struct,
    key,
    value.to_s,
    1,
    LibHTS::BCF_HT_STR
  )
  raise InfoUpdateError, "Failed to update INFO string field '#{key}': #{ret}" if ret < 0

  ret
end