Class: HTS::Bcf

Inherits:
Hts
  • Object
show all
Includes:
Enumerable
Defined in:
lib/hts/bcf.rb,
lib/hts/bcf/info.rb,
lib/hts/bcf/errors.rb,
lib/hts/bcf/format.rb,
lib/hts/bcf/header.rb,
lib/hts/bcf/record.rb,
lib/hts/bcf/header_record.rb

Defined Under Namespace

Classes: Error, FieldError, Format, FormatDefinitionError, FormatError, FormatReadError, FormatTypeError, FormatUpdateError, Header, HeaderError, HeaderRecord, IndexError, Info, InfoDefinitionError, InfoError, InfoReadError, InfoTypeError, InfoUpdateError, InvalidBorrowedViewError, MissingIndexError, OpenError, QueryError, Record, RecordError, SubsetError, UnknownSampleError, UnsupportedFormatOperationError, UnsupportedInfoOperationError, WriteError

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file_name, mode = "r", index: nil, threads: nil, build_index: false, subset: nil, samples: nil, unpack: :all) ⇒ Bcf

Returns a new instance of Bcf.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/hts/bcf.rb', line 51

def initialize(file_name, mode = "r", index: nil, threads: nil, build_index: false,
               subset: nil, samples: nil, unpack: :all)
  raise "HTS::Bcf.new() does not take block; Please use HTS::Bcf.open() instead" if block_given?
  raise ArgumentError, "specify either samples: or subset:, not both" if samples && subset

  subset = samples unless samples.nil?
  @unpack = unpack.to_sym
  @max_unpack = resolve_max_unpack(@unpack)
  @file_name = file_name
  @index_name = index
  @mode = mode
  @nthreads = threads
  @index_load_attempted = false
  @native = Native::BcfFileHandle.open(@file_name, mode)
  set_threads(threads) if threads
  if subset && mode.start_with?("w")
    raise SubsetError,
          "Sample subsetting is only available when reading BCF/VCF files"
  end
  if writing?
    @auto_index_on_close = build_index
    @index_name_on_close = index
    return
  end

  @read_header = Header.new(@native.read_header)
  @header = subset ? @read_header.subset(subset) : @read_header
  if build_index
    build_index(index)
    load_index(index)
  elsif index
    load_index(index)
  end
  @start_position = tell
rescue Errno::ENOENT
  raise OpenError, "Failed to open #{@file_name}"
end

Instance Attribute Details

#file_nameObject (readonly)

Returns the value of attribute file_name.



25
26
27
# File 'lib/hts/bcf.rb', line 25

def file_name
  @file_name
end

#headerObject

Returns the value of attribute header.



25
26
27
# File 'lib/hts/bcf.rb', line 25

def header
  @header
end

#index_nameObject (readonly)

Returns the value of attribute index_name.



25
26
27
# File 'lib/hts/bcf.rb', line 25

def index_name
  @index_name
end

#modeObject (readonly)

Returns the value of attribute mode.



25
26
27
# File 'lib/hts/bcf.rb', line 25

def mode
  @mode
end

#nthreadsObject (readonly)

Returns the value of attribute nthreads.



25
26
27
# File 'lib/hts/bcf.rb', line 25

def nthreads
  @nthreads
end

#unpackObject (readonly)

Returns the value of attribute unpack.



25
26
27
# File 'lib/hts/bcf.rb', line 25

def unpack
  @unpack
end

Class Method Details

.build_index(file_name, index_name = nil, min_shift = 14, threads = 0, verbose = true) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
# File 'lib/hts/bcf.rb', line 39

def self.build_index(file_name, index_name = nil, min_shift = 14, threads = 0, verbose = true)
  warn(index_name ? "Create index for #{file_name} to #{index_name}" : "Create index for #{file_name}") if verbose
  case Native::BcfFileHandle.build_index(file_name, index_name, min_shift, threads)
  when 0 then nil
  when -1 then raise IndexError, "Indexing failed for #{file_name}"
  when -2 then raise IndexError, "Opening #{file_name} failed while building the index"
  when -3 then raise IndexError, "#{file_name} is not in an indexable format"
  when -4 then raise IndexError, "Failed to create or save the index for #{file_name}"
  else raise IndexError, "Unknown index build error for #{file_name}"
  end
end

.filter_records(records, rid: nil, beg: nil, end_: nil, min_qual: nil, filter_id: nil) ⇒ Object



16
17
18
19
20
21
22
23
# File 'lib/hts/bcf.rb', line 16

def self.filter_records(records, rid: nil, beg: nil, end_: nil, min_qual: nil, filter_id: nil)
  Array(records).select do |record|
    (rid.nil? || record.rid == Integer(rid)) &&
      (beg.nil? || record.endpos > Integer(beg)) && (end_.nil? || record.pos < Integer(end_)) &&
      (min_qual.nil? || (!record.qual.nan? && record.qual >= Float(min_qual))) &&
      (filter_id.nil? || record.filter_id?(filter_id))
  end
end

.open(*args, **keywords) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
# File 'lib/hts/bcf.rb', line 27

def self.open(*args, **keywords)
  file = new(*args, **keywords)
  return file unless block_given?

  begin
    result = yield file
  ensure
    file.close
  end
  result
end

Instance Method Details

#<<(record) ⇒ Object



176
177
178
179
# File 'lib/hts/bcf.rb', line 176

def <<(record)
  write(record)
  self
end

#build_index(index_name = nil, min_shift: 14, verbose: true) ⇒ Object



89
90
91
92
93
94
95
# File 'lib/hts/bcf.rb', line 89

def build_index(index_name = nil, min_shift: 14, verbose: true)
  check_closed
  self.class.build_index(@file_name, index_name, min_shift, @nthreads || 0, verbose)
  @index_name = index_name
  @index_load_attempted = false
  self
end

#closeObject

Raises:



115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/hts/bcf.rb', line 115

def close
  was_closed = closed?
  result = @native&.close
  raise WriteError, "Failed to close #{@file_name}: buffered output may be incomplete" if writing? && result&.negative?

  if writing? && @auto_index_on_close && !was_closed
    @auto_index_on_close = false
    self.class.build_index(@file_name, @index_name_on_close, 14, @nthreads || 0, false)
  end

  nil
end

#closed?Boolean

Returns:

  • (Boolean)


127
# File 'lib/hts/bcf.rb', line 127

def closed? = @native.nil? || @native.closed?

#collect_recordsObject



218
# File 'lib/hts/bcf.rb', line 218

def collect_records = each(copy: true).to_a

#each(copy: false, &block) ⇒ Object



247
# File 'lib/hts/bcf.rb', line 247

def each(copy: false, &block) = copy ? each_record_copy(&block) : each_record_reuse(&block)

#each_format(key) ⇒ Object



240
241
242
243
244
245
# File 'lib/hts/bcf.rb', line 240

def each_format(key)
  return to_enum(__method__, key) unless block_given?

  each { |record| yield record.format(key) }
  self
end

#each_info(key) ⇒ Object



233
234
235
236
237
238
# File 'lib/hts/bcf.rb', line 233

def each_info(key)
  return to_enum(__method__, key) unless block_given?

  each { |record| yield record.info(key) }
  self
end

#file_formatObject



128
# File 'lib/hts/bcf.rb', line 128

def file_format = @native.file_format

#file_format_versionObject



129
# File 'lib/hts/bcf.rb', line 129

def file_format_version = @native.file_format_version

#format(key = nil) ⇒ Object Also known as: format_array

Raises:

  • (NotImplementedError)


209
210
211
212
213
214
215
# File 'lib/hts/bcf.rb', line 209

def format(key = nil)
  check_closed
  raise NotImplementedError unless key

  position = tell
  map { |record| record.format(key) }.tap { seek(position) if position }
end

#index_loaded?Boolean

Returns:

  • (Boolean)


110
111
112
113
# File 'lib/hts/bcf.rb', line 110

def index_loaded?
  check_closed
  @native.index_loaded?
end

#info(key = nil) ⇒ Object Also known as: info_array

Raises:

  • (NotImplementedError)


200
201
202
203
204
205
206
# File 'lib/hts/bcf.rb', line 200

def info(key = nil)
  check_closed
  raise NotImplementedError unless key

  position = tell
  map { |record| record.info(key) }.tap { seek(position) if position }
end

#load_index(index_name = nil) ⇒ Object

Raises:



97
98
99
100
101
# File 'lib/hts/bcf.rb', line 97

def load_index(index_name = nil)
  return self if try_load_index(index_name)

  raise MissingIndexError, "Failed to load index #{index_name || "for #{@file_name}"}"
end

#nsamplesObject



181
182
183
184
# File 'lib/hts/bcf.rb', line 181

def nsamples
  check_closed
  header.nsamples
end

#query(region, beg = nil, end_ = nil, copy: false, &block) ⇒ Object



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/hts/bcf.rb', line 249

def query(region, beg = nil, end_ = nil, copy: false, &block)
  check_closed
  unless ensure_index_loaded
    raise MissingIndexError, "Index file is required to call the query method for #{@file_name}"
  end

  case region
  when Array
    raise ArgumentError, "beg and end must not be specified when region is an Array" unless beg.nil? && end_.nil?
    return to_enum(__method__, region, copy:) unless block

    region.each { |item| query(item, copy:, &block) }
    self
  else
    if beg && end_
      iterate_query(@native.query_interval(read_header_native, header.name2id(region), beg, end_), copy, region,
                    &block)
    elsif beg.nil? && end_.nil?
      iterate_query(@native.query_region(read_header_native, region), copy, region, &block)
    else
      raise ArgumentError, "beg and end must be specified together"
    end
  end
end

#rewindObject



133
134
135
136
137
138
139
140
# File 'lib/hts/bcf.rb', line 133

def rewind
  raise "Cannot rewind: no start position" unless @start_position

  result = seek(@start_position)
  raise "Failed to rewind: #{result}" if result.negative?

  tell
end

#samplesObject



186
187
188
189
# File 'lib/hts/bcf.rb', line 186

def samples
  check_closed
  header.samples
end

#seek(offset) ⇒ Object



130
# File 'lib/hts/bcf.rb', line 130

def seek(offset) = @native.seek(offset)

#set_threads(count = nil) ⇒ Object

Raises:

  • (TypeError)


142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/hts/bcf.rb', line 142

def set_threads(count = nil)
  if count.nil?
    require "etc"
    count = [Etc.nprocessors - 1, 1].max
  end
  raise TypeError unless count.is_a?(Integer)
  raise ArgumentError, "Number of threads must be positive" if count < 1
  raise "Failed to set number of threads: #{count}" if @native.set_threads(count).negative?

  @nthreads = count
  self
end

#tellObject



131
# File 'lib/hts/bcf.rb', line 131

def tell = @native.tell

#to_aObject

Materialize independent, owning records rather than retaining the reused Record yielded by the allocation-conscious default iterator.



222
# File 'lib/hts/bcf.rb', line 222

def to_a = collect_records

#try_load_index(index_name = nil) ⇒ Object



103
104
105
106
107
108
# File 'lib/hts/bcf.rb', line 103

def try_load_index(index_name = nil)
  check_closed
  @index_name = index_name
  @index_load_attempted = true
  @native.load_index(index_name)
end

#write(record) ⇒ Object



168
169
170
171
172
173
174
# File 'lib/hts/bcf.rb', line 168

def write(record)
  check_closed
  result = @native.write(header.__send__(:native_handle), record.__send__(:native_handle))
  raise "Failed to write record" if result.negative?

  nil
end

#write_header(header) ⇒ Object

Raises:



155
156
157
158
159
160
161
162
# File 'lib/hts/bcf.rb', line 155

def write_header(header)
  check_closed
  @header = header.dup
  result = @native.write_header(header.__send__(:native_handle))
  raise HeaderError, "Failed to write BCF header" if result.negative?

  nil
end