Class: HTS::Bam::BaseMod

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/hts/bam/base_mod.rb

Overview

Note:

BaseMod is a view object that references data in a Record. The state is maintained in hts_base_mod_state structure.

Base modification information from MM/ML tags

This class provides access to DNA/RNA base modifications such as methylation. It wraps the htslib base modification API and provides a Ruby-friendly interface.

Defined Under Namespace

Classes: Modification, NotParsedError, Position

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(record, auto_parse: true) ⇒ BaseMod

Initialize a new BaseMod object

Parameters:

  • record (Record)

    The BAM record to extract modifications from

  • auto_parse (Boolean) (defaults to: true)

    If true, parse MM/ML lazily on first access

Raises:



134
135
136
137
138
139
140
141
# File 'lib/hts/bam/base_mod.rb', line 134

def initialize(record, auto_parse: true)
  @record = record
  @state = LibHTS.hts_base_mod_state_alloc
  @closed = false
  @auto_parse = !!auto_parse
  @parsed = false
  raise Error, "Failed to allocate hts_base_mod_state" if @state.null?
end

Instance Attribute Details

#recordObject (readonly)

Returns the value of attribute record.



17
18
19
# File 'lib/hts/bam/base_mod.rb', line 17

def record
  @record
end

Instance Method Details

#[](position) ⇒ Position?

Array-style access to modifications at a position

Parameters:

  • position (Integer)

    Query position (0-based)

Returns:

  • (Position, nil)

    Position object with modifications, or nil if none



203
204
205
# File 'lib/hts/bam/base_mod.rb', line 203

def [](position)
  at_pos(position)
end

#at_pos(position, max_mods: 10) ⇒ Position?

Get modification information at a specific query position

Parameters:

  • position (Integer)

    Query position (0-based)

  • max_mods (Integer) (defaults to: 10)

    Maximum number of modifications to retrieve

Returns:

  • (Position, nil)

    Position object with modifications, or nil if none



187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/hts/bam/base_mod.rb', line 187

def at_pos(position, max_mods: 10)
  # Reset state to ensure deterministic results even after prior iteration
  parsed? ? parse : ensure_parsed!

  mods_ptr = FFI::MemoryPointer.new(LibHTS::HtsBaseMod, max_mods)

  ret = LibHTS.bam_mods_at_qpos(@record.struct, position, @state,
                                mods_ptr, max_mods)
  return nil if ret <= 0

  build_position(position, mods_ptr, [ret, max_mods].min)
end

#closevoid

This method returns an undefined value.

Explicitly free the state



145
146
147
148
149
150
151
152
# File 'lib/hts/bam/base_mod.rb', line 145

def close
  return if @closed

  # With HtsBaseModState as an AutoPointer, releasing the Ruby object
  # is sufficient. Avoid manual free to prevent double-free.
  @state = nil
  @closed = true
end

#each_position(max_mods: 10) {|Position| ... } ⇒ Enumerator Also known as: each

Iterate over all positions with modifications

Parameters:

  • max_mods (Integer) (defaults to: 10)

    Maximum number of modifications per position

Yields:

  • (Position)

    Position object for each modified position

Returns:

  • (Enumerator)

    If no block given



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/hts/bam/base_mod.rb', line 211

def each_position(max_mods: 10)
  return enum_for(__method__, max_mods: max_mods) unless block_given?

  # Reset state at the start of iteration to allow repeated enumerations
  parsed? ? parse : ensure_parsed!

  pos_ptr = FFI::MemoryPointer.new(:int)
  mods_ptr = FFI::MemoryPointer.new(LibHTS::HtsBaseMod, max_mods)

  loop do
    ret = LibHTS.bam_next_basemod(@record.struct, @state,
                                  mods_ptr, max_mods, pos_ptr)
    break if ret <= 0

    position = pos_ptr.read_int
    yield build_position(position, mods_ptr, [ret, max_mods].min)
  end
end

#ensure_parsed!(flags = 0) ⇒ void

This method returns an undefined value.

Ensure MM/ML have been parsed, performing lazy parse if enabled.

Parameters:

  • flags (Integer) (defaults to: 0)

Raises:



163
164
165
166
167
168
169
# File 'lib/hts/bam/base_mod.rb', line 163

def ensure_parsed!(flags = 0)
  return if @parsed

  raise NotParsedError, "BaseMod is not parsed. Call #parse first (auto_parse is disabled)." unless @auto_parse

  parse(flags)
end

#inspectString

Inspect string

Returns:

  • (String)

    Inspect string



314
315
316
# File 'lib/hts/bam/base_mod.rb', line 314

def inspect
  to_s
end

#modification_typesArray<Integer> Also known as: recorded_types

Get list of modification types present in this record

Returns:

  • (Array<Integer>)

    Array of modification codes (char code or -ChEBI)



234
235
236
237
238
239
240
241
242
243
244
# File 'lib/hts/bam/base_mod.rb', line 234

def modification_types
  ensure_parsed!

  ntype_ptr = FFI::MemoryPointer.new(:int)
  codes_ptr = LibHTS.bam_mods_recorded(@state, ntype_ptr)

  ntype = ntype_ptr.read_int
  return [] if ntype <= 0 || codes_ptr.null?

  codes_ptr.read_array_of_int(ntype)
end

#parse(flags = 0) ⇒ Integer

Parse MM and ML tags from the record

Parameters:

  • flags (Integer) (defaults to: 0)

    Parsing flags (default: 0)

Returns:

  • (Integer)

    Number of modification types found, or -1 on error

Raises:

  • (Error)

    If parsing fails



175
176
177
178
179
180
181
# File 'lib/hts/bam/base_mod.rb', line 175

def parse(flags = 0)
  ret = LibHTS.bam_parse_basemod2(@record.struct, @state, flags)
  raise Error, "Failed to parse base modifications" if ret < 0

  @parsed = true
  ret
end

#parsed?Boolean

Whether this object has parsed MM/ML tags already

Returns:

  • (Boolean)


156
157
158
# File 'lib/hts/bam/base_mod.rb', line 156

def parsed?
  @parsed
end

#query_type(code) ⇒ Hash?

Query information about a specific modification type by code

Parameters:

  • code (Integer, String)

    Modification code (char code or -ChEBI, or single char string)

Returns:

  • (Hash, nil)

    Hash with canonical, strand, implicit info, or nil if not found



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/hts/bam/base_mod.rb', line 251

def query_type(code)
  ensure_parsed!

  code = code.ord if code.is_a?(String)

  strand_ptr = FFI::MemoryPointer.new(:int)
  implicit_ptr = FFI::MemoryPointer.new(:int)
  canonical_ptr = FFI::MemoryPointer.new(:char, 1)

  ret = LibHTS.bam_mods_query_type(@state, code, strand_ptr,
                                   implicit_ptr, canonical_ptr)
  return nil if ret < 0

  {
    canonical: canonical_ptr.read_char.chr,
    strand: strand_ptr.read_int,
    implicit: implicit_ptr.read_int != 0
  }
end

#query_type_at(index) ⇒ Hash?

Query information about i-th modification type

Parameters:

  • index (Integer)

    Modification type index (0-based)

Returns:

  • (Hash, nil)

    Hash with code, canonical, strand, implicit info



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/hts/bam/base_mod.rb', line 274

def query_type_at(index)
  ensure_parsed!

  strand_ptr = FFI::MemoryPointer.new(:int)
  implicit_ptr = FFI::MemoryPointer.new(:int)
  canonical_ptr = FFI::MemoryPointer.new(:char, 1)

  ret = LibHTS.bam_mods_queryi(@state, index, strand_ptr,
                               implicit_ptr, canonical_ptr)
  return nil if ret < 0

  types = modification_types
  {
    code: types[index],
    canonical: canonical_ptr.read_char.chr,
    strand: strand_ptr.read_int,
    implicit: implicit_ptr.read_int != 0
  }
end

#to_aArray<Position>

Get all modifications as an array

Returns:

  • (Array<Position>)

    Array of all positions with modifications



296
297
298
# File 'lib/hts/bam/base_mod.rb', line 296

def to_a
  each_position.to_a
end

#to_sString

String representation for debugging

Returns:

  • (String)

    String representation



302
303
304
305
306
307
308
309
310
# File 'lib/hts/bam/base_mod.rb', line 302

def to_s
  return "#<HTS::Bam::BaseMod (not parsed)>" unless @parsed

  mods = []
  each_position do |pos|
    mods << pos.to_s
  end
  "#<HTS::Bam::BaseMod #{mods.join(' ')}>"
end