Skip to content

Commit 70875fa

Browse files
committed
Enhance pileup and mpileup iterators with RAII-style resource management
1 parent 1d606b8 commit 70875fa

3 files changed

Lines changed: 94 additions & 66 deletions

File tree

lib/hts/bam.rb

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -228,20 +228,24 @@ def query(region, beg = nil, end_ = nil, copy: false, &block)
228228
end
229229

230230
# Pileup iterator over this file. Optional region can be specified.
231-
# When a block is given, yields PileupColumn and returns self.
232-
# Without a block, returns an Enumerator.
231+
# When a block is given, uses RAII-style and ensures the iterator is closed at block end.
232+
# Without a block, returns an Enumerator over a live Pileup instance; caller should close when done.
233233
#
234234
# @param region [String, nil] region string like "chr1:100-200"
235235
# @param beg [Integer, nil]
236236
# @param end_ [Integer, nil]
237237
# @param maxcnt [Integer, nil] cap on depth per position
238238
def pileup(region = nil, beg = nil, end_: nil, maxcnt: nil, &block)
239239
check_closed
240-
piter = Pileup.new(self, region:, beg:, end_: end_, maxcnt: maxcnt)
241-
return piter.to_enum(:each) unless block_given?
242-
243-
piter.each(&block)
244-
self
240+
if block_given?
241+
Pileup.open(self, region:, beg:, end_: end_, maxcnt: maxcnt) do |piter|
242+
piter.each(&block)
243+
end
244+
self
245+
else
246+
piter = Pileup.new(self, region:, beg:, end_: end_, maxcnt: maxcnt)
247+
piter.to_enum(:each)
248+
end
245249
end
246250

247251
private

lib/hts/bam/mpileup.rb

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,22 @@ class Bam < Hts
66
class Mpileup
77
include Enumerable
88

9+
# Usage:
10+
# HTS::Bam::Mpileup.open([bam1, bam2], region: "chr1:1-100") do |mpl|
11+
# mpl.each { |cols| ... }
12+
# end
13+
def self.open(*args, **kw)
14+
m = new(*args, **kw)
15+
return m unless block_given?
16+
17+
begin
18+
yield m
19+
ensure
20+
m.close
21+
end
22+
m
23+
end
24+
925
# Normalize inputs to HTS::Bam instances
1026
# Accepts array of HTS::Bam or filenames (String)
1127
def initialize(inputs, region: nil, beg: nil, end_: nil, maxcnt: nil, overlaps: false)
@@ -107,39 +123,35 @@ def each
107123
plp1_size = HTS::LibHTS::BamPileup1.size
108124
headers = @bams.map(&:header)
109125

110-
begin
111-
while HTS::LibHTS.bam_mplp64_auto(@iter, tid_ptr, pos_ptr, n_ptr, plp_ptr) > 0
112-
tid = tid_ptr.read_int
113-
pos = pos_ptr.read_long_long
114-
115-
counts = n_ptr.read_array_of_int(n)
116-
plp_arr = plp_ptr.read_array_of_pointer(n)
117-
118-
cols = Array.new(n)
119-
i = 0
120-
while i < n
121-
c = counts[i]
122-
if c <= 0 || plp_arr[i].null?
123-
cols[i] = HTS::Bam::Pileup::PileupColumn.new(tid: tid, pos: pos, alignments: [])
124-
else
125-
base_ptr = plp_arr[i]
126-
aligns = Array.new(c)
127-
j = 0
128-
while j < c
129-
e_ptr = base_ptr + (j * plp1_size)
130-
entry = HTS::LibHTS::BamPileup1.new(e_ptr)
131-
aligns[j] = HTS::Bam::Pileup::PileupRecord.new(entry, headers[i])
132-
j += 1
133-
end
134-
cols[i] = HTS::Bam::Pileup::PileupColumn.new(tid: tid, pos: pos, alignments: aligns)
126+
while HTS::LibHTS.bam_mplp64_auto(@iter, tid_ptr, pos_ptr, n_ptr, plp_ptr) > 0
127+
tid = tid_ptr.read_int
128+
pos = pos_ptr.read_long_long
129+
130+
counts = n_ptr.read_array_of_int(n)
131+
plp_arr = plp_ptr.read_array_of_pointer(n)
132+
133+
cols = Array.new(n)
134+
i = 0
135+
while i < n
136+
c = counts[i]
137+
if c <= 0 || plp_arr[i].null?
138+
cols[i] = HTS::Bam::Pileup::PileupColumn.new(tid: tid, pos: pos, alignments: [])
139+
else
140+
base_ptr = plp_arr[i]
141+
aligns = Array.new(c)
142+
j = 0
143+
while j < c
144+
e_ptr = base_ptr + (j * plp1_size)
145+
entry = HTS::LibHTS::BamPileup1.new(e_ptr)
146+
aligns[j] = HTS::Bam::Pileup::PileupRecord.new(entry, headers[i])
147+
j += 1
135148
end
136-
i += 1
149+
cols[i] = HTS::Bam::Pileup::PileupColumn.new(tid: tid, pos: pos, alignments: aligns)
137150
end
138-
139-
yield cols
151+
i += 1
140152
end
141-
ensure
142-
close
153+
154+
yield cols
143155
end
144156

145157
self

lib/hts/bam/pileup.rb

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,22 @@ class Bam < Hts
66
class Pileup
77
include Enumerable
88

9+
# Usage:
10+
# HTS::Bam::Pileup.open(bam, region: "chr1:1-100") do |pl|
11+
# pl.each { |col| ... }
12+
# end
13+
def self.open(*args, **kw)
14+
pu = new(*args, **kw)
15+
return pu unless block_given?
16+
17+
begin
18+
yield pu
19+
ensure
20+
pu.close
21+
end
22+
pu
23+
end
24+
925
# A column at a reference position with pileup alignments
1026
PileupColumn = Struct.new(:tid, :pos, :alignments, keyword_init: true) do
1127
def depth
@@ -135,40 +151,36 @@ def each
135151
plp1_size = HTS::LibHTS::BamPileup1.size
136152
header_local = @header
137153

138-
begin
139-
loop do
140-
base_ptr = HTS::LibHTS.bam_plp64_auto(@plp, tid_ptr, pos_ptr, n_ptr)
154+
loop do
155+
base_ptr = HTS::LibHTS.bam_plp64_auto(@plp, tid_ptr, pos_ptr, n_ptr)
141156

142-
# When base_ptr is NULL, check n to distinguish EOF (n == 0) from error (n < 0)
143-
if base_ptr.null?
144-
n = n_ptr.read_int
145-
raise "HTSlib pileup error (bam_plp64_auto)" if n < 0
157+
# When base_ptr is NULL, check n to distinguish EOF (n == 0) from error (n < 0)
158+
if base_ptr.null?
159+
n = n_ptr.read_int
160+
raise "HTSlib pileup error (bam_plp64_auto)" if n < 0
146161

147-
break
148-
end
162+
break
163+
end
149164

150-
tid = tid_ptr.read_int
151-
pos = pos_ptr.read_long_long
152-
n = n_ptr.read_int
153-
154-
# Construct alignment entries with minimal allocations
155-
if n.zero?
156-
alignments = []
157-
else
158-
alignments = Array.new(n)
159-
i = 0
160-
while i < n
161-
e_ptr = base_ptr + (i * plp1_size)
162-
entry = HTS::LibHTS::BamPileup1.new(e_ptr)
163-
alignments[i] = PileupRecord.new(entry, header_local)
164-
i += 1
165-
end
165+
tid = tid_ptr.read_int
166+
pos = pos_ptr.read_long_long
167+
n = n_ptr.read_int
168+
169+
# Construct alignment entries with minimal allocations
170+
if n.zero?
171+
alignments = []
172+
else
173+
alignments = Array.new(n)
174+
i = 0
175+
while i < n
176+
e_ptr = base_ptr + (i * plp1_size)
177+
entry = HTS::LibHTS::BamPileup1.new(e_ptr)
178+
alignments[i] = PileupRecord.new(entry, header_local)
179+
i += 1
166180
end
167-
168-
yield PileupColumn.new(tid: tid, pos: pos, alignments: alignments)
169181
end
170-
ensure
171-
close
182+
183+
yield PileupColumn.new(tid: tid, pos: pos, alignments: alignments)
172184
end
173185

174186
self

0 commit comments

Comments
 (0)