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



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

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



106
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
# File 'lib/hts/bcf/info.rb', line 106

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



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/hts/bcf/info.rb', line 240

def delete(key)
  # Try to get current type to check existence
  type = get_info_type(key)
  return false if type.nil?

  # 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.



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

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
# 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

  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.



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

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

#get_float(key) ⇒ Object

For compatibility with HTS.cr.



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

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

#get_int(key) ⇒ Object

For compatibility with HTS.cr.



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

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

#get_int64(key) ⇒ Object

For compatibility with HTS.cr.



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

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

#get_string(key) ⇒ Object

For compatibility with HTS.cr.



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

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



262
263
264
265
266
# File 'lib/hts/bcf/info.rb', line 262

def key?(key)
  # Use get() to check if value is actually present
  # (get_info_type only checks header, not actual value)
  !get(key).nil?
end

#lengthObject



285
286
287
# File 'lib/hts/bcf/info.rb', line 285

def length
  @record.struct[:n_info]
end

#sizeObject



289
290
291
# File 'lib/hts/bcf/info.rb', line 289

def size
  length
end

#to_hObject



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

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:



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/hts/bcf/info.rb', line 211

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:



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/hts/bcf/info.rb', line 172

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:



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/hts/bcf/info.rb', line 143

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:



164
165
166
# File 'lib/hts/bcf/info.rb', line 164

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:



193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/hts/bcf/info.rb', line 193

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