htslib 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +79 -49
- data/TUTORIAL.md +9 -20
- data/ext/htslib_native/extconf.rb +41 -0
- data/ext/htslib_native/htslib_native.h +11 -0
- data/ext/htslib_native/htslib_native_ext.c +48 -0
- data/ext/htslib_native/native_bam.c +1108 -0
- data/ext/htslib_native/native_bcf.c +369 -0
- data/ext/htslib_native/native_faidx.c +155 -0
- data/ext/htslib_native/native_tabix.c +246 -0
- data/lib/hts/bam/auxi.rb +87 -342
- data/lib/hts/bam/base_mod.rb +37 -73
- data/lib/hts/bam/cigar.rb +9 -55
- data/lib/hts/bam/flag.rb +19 -27
- data/lib/hts/bam/header.rb +29 -56
- data/lib/hts/bam/mpileup.rb +158 -132
- data/lib/hts/bam/pileup.rb +120 -145
- data/lib/hts/bam/record.rb +233 -270
- data/lib/hts/bam.rb +102 -42
- data/lib/hts/bcf/errors.rb +1 -0
- data/lib/hts/bcf/format.rb +278 -379
- data/lib/hts/bcf/header.rb +38 -122
- data/lib/hts/bcf/header_record.rb +9 -35
- data/lib/hts/bcf/info.rb +72 -320
- data/lib/hts/bcf/record.rb +61 -102
- data/lib/hts/bcf.rb +149 -323
- data/lib/hts/faidx.rb +28 -87
- data/lib/hts/hts.rb +6 -94
- data/lib/hts/native.rb +13 -0
- data/lib/hts/tabix.rb +93 -48
- data/lib/hts/version.rb +1 -1
- data/lib/htslib.rb +6 -52
- metadata +13 -64
- data/lib/hts/ffi_ext/README.md +0 -8
- data/lib/hts/ffi_ext/pointer.rb +0 -18
- data/lib/hts/ffi_ext/struct.rb +0 -45
- data/lib/hts/libhts/bgzf.rb +0 -199
- data/lib/hts/libhts/constants.rb +0 -658
- data/lib/hts/libhts/cram.rb +0 -471
- data/lib/hts/libhts/fai.rb +0 -146
- data/lib/hts/libhts/hfile.rb +0 -121
- data/lib/hts/libhts/hts.rb +0 -477
- data/lib/hts/libhts/kfunc.rb +0 -38
- data/lib/hts/libhts/sam.rb +0 -760
- data/lib/hts/libhts/sam_funcs.rb +0 -155
- data/lib/hts/libhts/tbx.rb +0 -94
- data/lib/hts/libhts/tbx_funcs.rb +0 -36
- data/lib/hts/libhts/thread_pool.rb +0 -139
- data/lib/hts/libhts/vcf.rb +0 -567
- data/lib/hts/libhts/vcf_funcs.rb +0 -366
- data/lib/hts/libhts.rb +0 -47
data/lib/hts/bam/record.rb
CHANGED
|
@@ -6,374 +6,337 @@ require_relative "auxi"
|
|
|
6
6
|
|
|
7
7
|
module HTS
|
|
8
8
|
class Bam < Hts
|
|
9
|
-
# A class for working with alignment records.
|
|
10
9
|
class Record
|
|
11
10
|
SEQ_NT16_STR = "=ACMGRSVTWYHKDBN"
|
|
12
|
-
|
|
11
|
+
UNSET = Object.new.freeze
|
|
12
|
+
private_constant :UNSET
|
|
13
13
|
attr_reader :header
|
|
14
14
|
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
pos: nil,
|
|
24
|
-
mapq: nil,
|
|
25
|
-
cigar: nil,
|
|
26
|
-
mtid: nil,
|
|
27
|
-
mpos: nil,
|
|
28
|
-
isize: nil,
|
|
29
|
-
seq: nil,
|
|
30
|
-
qual: nil,
|
|
31
|
-
l_aux: nil
|
|
32
|
-
)
|
|
33
|
-
@bam1 = bam1 || LibHTS.bam_init1
|
|
15
|
+
# Build a BAM record from Ruby values. Coordinates are zero-based, matching
|
|
16
|
+
# the rest of this API. +qualities+ is an Array of numeric Phred scores;
|
|
17
|
+
# use +quality_string+ for a FASTQ/SAM-style Phred+33 String.
|
|
18
|
+
def initialize(header, native_record = nil, qname: "*", flag: 0, tid: nil, chrom: nil,
|
|
19
|
+
pos: -1, mapq: 0, cigar: nil, mtid: nil, mate_chrom: nil,
|
|
20
|
+
mate_pos: -1, insert_size: 0, seq: UNSET, sequence: UNSET,
|
|
21
|
+
qual: UNSET, qualities: UNSET, quality_string: UNSET, aux: nil)
|
|
22
|
+
@native = native_record || Native::BamRecordHandle.create
|
|
34
23
|
@header = header
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
qual = qual.pack("C*")
|
|
50
|
-
elsif qual.is_a?(String)
|
|
51
|
-
raise "Which implementation is better? +33 or not? Please tell me."
|
|
52
|
-
end
|
|
53
|
-
r = LibHTS.bam_set1(
|
|
54
|
-
@bam1,
|
|
55
|
-
qname.length,
|
|
56
|
-
qname,
|
|
57
|
-
flag,
|
|
58
|
-
tid,
|
|
59
|
-
pos,
|
|
60
|
-
mapq,
|
|
61
|
-
cigar_array.length,
|
|
62
|
-
cigar_pointer,
|
|
63
|
-
mtid,
|
|
64
|
-
mpos,
|
|
65
|
-
isize,
|
|
66
|
-
seq.length,
|
|
67
|
-
seq,
|
|
68
|
-
qual,
|
|
69
|
-
l_aux
|
|
70
|
-
)
|
|
71
|
-
raise "bam_set1 failed: #{r}" if r < 0
|
|
72
|
-
else
|
|
73
|
-
warn "Ignore bam_set1 because some arguments are missing."
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# Return the FFI::Struct object.
|
|
78
|
-
def struct
|
|
79
|
-
@bam1
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def to_ptr
|
|
83
|
-
@bam1.to_ptr
|
|
24
|
+
return if native_record
|
|
25
|
+
|
|
26
|
+
sequence = normalize_sequence(seq, sequence)
|
|
27
|
+
qualities = normalize_qualities(qual, qualities, quality_string, sequence.bytesize)
|
|
28
|
+
cigar_values = normalize_cigar(cigar)
|
|
29
|
+
validate_cigar_length!(cigar_values, sequence)
|
|
30
|
+
|
|
31
|
+
@native.replace(
|
|
32
|
+
normalize_qname(qname), normalize_flag(flag), resolve_tid(tid, chrom, "chrom"),
|
|
33
|
+
normalize_position(pos, "pos"), normalize_mapq(mapq), cigar_values,
|
|
34
|
+
resolve_tid(mtid, mate_chrom, "mate_chrom"), normalize_position(mate_pos, "mate_pos"),
|
|
35
|
+
Integer(insert_size), sequence, qualities
|
|
36
|
+
)
|
|
37
|
+
assign_aux(aux) if aux
|
|
84
38
|
end
|
|
85
39
|
|
|
86
|
-
|
|
87
|
-
# @return [String] query template name
|
|
88
|
-
def qname
|
|
89
|
-
LibHTS.bam_get_qname(@bam1).read_string
|
|
90
|
-
end
|
|
40
|
+
def qname = @native.qname
|
|
91
41
|
|
|
92
42
|
def qname=(name)
|
|
93
|
-
|
|
43
|
+
@native.qname = name
|
|
94
44
|
end
|
|
95
45
|
|
|
96
|
-
|
|
97
|
-
# @return [Integer] chromosome ID
|
|
98
|
-
def tid
|
|
99
|
-
@bam1[:core][:tid]
|
|
100
|
-
end
|
|
46
|
+
def tid = core_get(:tid)
|
|
101
47
|
|
|
102
|
-
def tid=(
|
|
103
|
-
|
|
48
|
+
def tid=(value)
|
|
49
|
+
core_set(:tid, value)
|
|
104
50
|
end
|
|
105
51
|
|
|
106
|
-
|
|
107
|
-
# @return [Integer] chromosome ID
|
|
108
|
-
def mtid
|
|
109
|
-
@bam1[:core][:mtid]
|
|
110
|
-
end
|
|
52
|
+
def mtid = core_get(:mtid)
|
|
111
53
|
|
|
112
|
-
def mtid=(
|
|
113
|
-
|
|
54
|
+
def mtid=(value)
|
|
55
|
+
core_set(:mtid, value)
|
|
114
56
|
end
|
|
115
57
|
|
|
116
|
-
|
|
117
|
-
# @return [Integer] 0-based leftmost coordinate
|
|
118
|
-
def pos
|
|
119
|
-
@bam1[:core][:pos]
|
|
120
|
-
end
|
|
58
|
+
def pos = core_get(:pos)
|
|
121
59
|
|
|
122
|
-
def pos=(
|
|
123
|
-
|
|
60
|
+
def pos=(value)
|
|
61
|
+
core_set(:pos, value)
|
|
124
62
|
end
|
|
125
63
|
|
|
126
|
-
|
|
127
|
-
# @return [Integer] 0-based leftmost coordinate
|
|
128
|
-
def mate_pos
|
|
129
|
-
@bam1[:core][:mpos]
|
|
130
|
-
end
|
|
64
|
+
def mate_pos = core_get(:mpos)
|
|
131
65
|
|
|
132
|
-
def mate_pos=(
|
|
133
|
-
|
|
66
|
+
def mate_pos=(value)
|
|
67
|
+
core_set(:mpos, value)
|
|
134
68
|
end
|
|
135
|
-
|
|
136
69
|
alias mpos mate_pos
|
|
137
70
|
alias mpos= mate_pos=
|
|
71
|
+
def bin = core_get(:bin)
|
|
138
72
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def bin
|
|
142
|
-
@bam1[:core][:bin]
|
|
73
|
+
def bin=(value)
|
|
74
|
+
core_set(:bin, value)
|
|
143
75
|
end
|
|
144
76
|
|
|
145
|
-
def
|
|
146
|
-
@bam1[:core][:bin] = bin
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
# Get the rightmost base position of the alignment on the reference genome.
|
|
150
|
-
# @return [Integer] 0-based rightmost coordinate
|
|
151
|
-
def endpos
|
|
152
|
-
LibHTS.bam_endpos @bam1
|
|
153
|
-
end
|
|
77
|
+
def endpos = @native.endpos
|
|
154
78
|
|
|
155
|
-
# Get the reference sequence name of the alignment. (a.k.a RNAME)
|
|
156
|
-
# '' if not mapped.
|
|
157
|
-
# @return [String] reference sequence name
|
|
158
79
|
def chrom
|
|
159
80
|
return "" if tid == -1
|
|
160
81
|
|
|
161
|
-
|
|
82
|
+
@header.__send__(:native_handle).target_name(tid)
|
|
162
83
|
end
|
|
163
|
-
|
|
164
84
|
alias contig chrom
|
|
85
|
+
def chrom=(name)
|
|
86
|
+
self.tid = resolve_tid(nil, name, "chrom")
|
|
87
|
+
end
|
|
88
|
+
alias contig= chrom=
|
|
165
89
|
|
|
166
|
-
# Get the reference sequence name of the mate.
|
|
167
|
-
# '' if not mapped.
|
|
168
|
-
# @return [String] reference sequence name
|
|
169
90
|
def mate_chrom
|
|
170
91
|
return "" if mtid == -1
|
|
171
92
|
|
|
172
|
-
|
|
93
|
+
@header.__send__(:native_handle).target_name(mtid)
|
|
173
94
|
end
|
|
174
|
-
|
|
175
95
|
alias mate_contig mate_chrom
|
|
96
|
+
def mate_chrom=(name)
|
|
97
|
+
self.mtid = resolve_tid(nil, name, "mate_chrom")
|
|
98
|
+
end
|
|
99
|
+
alias mate_contig= mate_chrom=
|
|
176
100
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
def
|
|
180
|
-
|
|
101
|
+
def strand = reverse? ? "-" : "+"
|
|
102
|
+
def mate_strand = mate_reverse? ? "-" : "+"
|
|
103
|
+
def insert_size = core_get(:isize)
|
|
104
|
+
|
|
105
|
+
def insert_size=(value)
|
|
106
|
+
core_set(:isize, value)
|
|
181
107
|
end
|
|
108
|
+
alias isize insert_size
|
|
109
|
+
alias isize= insert_size=
|
|
110
|
+
def mapq = core_get(:mapq)
|
|
182
111
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def mate_strand
|
|
186
|
-
LibHTS.bam_is_mrev(@bam1) ? "-" : "+"
|
|
112
|
+
def mapq=(value)
|
|
113
|
+
core_set(:mapq, value)
|
|
187
114
|
end
|
|
188
115
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def
|
|
192
|
-
|
|
116
|
+
def cigar = Cigar.new(self)
|
|
117
|
+
|
|
118
|
+
def cigar=(value)
|
|
119
|
+
raise ArgumentError, "cigar must be a String or Bam::Cigar" unless value.is_a?(String) || value.is_a?(Cigar)
|
|
120
|
+
|
|
121
|
+
@native.cigar = value.to_s
|
|
193
122
|
end
|
|
194
123
|
|
|
195
|
-
def
|
|
196
|
-
|
|
124
|
+
def qlen = @native.qlen
|
|
125
|
+
def rlen = @native.rlen
|
|
126
|
+
def seq = @native.sequence
|
|
127
|
+
alias sequence seq
|
|
128
|
+
|
|
129
|
+
def each_base(&block)
|
|
130
|
+
return to_enum(__method__) unless block_given?
|
|
131
|
+
|
|
132
|
+
seq.each_char(&block)
|
|
133
|
+
self
|
|
197
134
|
end
|
|
198
135
|
|
|
199
|
-
|
|
200
|
-
|
|
136
|
+
def each_base_code(&block)
|
|
137
|
+
return to_enum(__method__) unless block_given?
|
|
201
138
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
def mapq
|
|
205
|
-
@bam1[:core][:qual]
|
|
139
|
+
@native.sequence_codes.each(&block)
|
|
140
|
+
self
|
|
206
141
|
end
|
|
207
142
|
|
|
208
|
-
def
|
|
209
|
-
|
|
143
|
+
def len = core_get(:length)
|
|
144
|
+
|
|
145
|
+
def base(index)
|
|
146
|
+
index += len if index.negative?
|
|
147
|
+
code = @native.base_code(index)
|
|
148
|
+
code ? SEQ_NT16_STR[code] : "."
|
|
210
149
|
end
|
|
150
|
+
alias base_at base
|
|
211
151
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
152
|
+
def qual = @native.qualities
|
|
153
|
+
def qual_string = @native.quality_string
|
|
154
|
+
|
|
155
|
+
def each_qual(&block)
|
|
156
|
+
return to_enum(__method__) unless block_given?
|
|
157
|
+
|
|
158
|
+
@native.qualities.each(&block)
|
|
159
|
+
self
|
|
216
160
|
end
|
|
217
161
|
|
|
218
|
-
def
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
r = LibHTS.bam_parse_cigar(str.to_s, FFI::Pointer::NULL, @bam1)
|
|
222
|
-
raise "bam_parse_cigar failed: #{r}" if r.negative?
|
|
223
|
-
else
|
|
224
|
-
raise ArgumentError, "cigar must be a String or Bam::Cigar"
|
|
225
|
-
end
|
|
162
|
+
def base_qual(index)
|
|
163
|
+
index += len if index.negative?
|
|
164
|
+
@native.quality_at(index) || 0
|
|
226
165
|
end
|
|
166
|
+
alias qual_at base_qual
|
|
227
167
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
168
|
+
def flag = Flag.new(flag_value)
|
|
169
|
+
def flag_value = core_get(:flag)
|
|
170
|
+
|
|
171
|
+
def flag=(value)
|
|
172
|
+
case value
|
|
173
|
+
when Integer then core_set(:flag, value)
|
|
174
|
+
when Flag then core_set(:flag, value.value)
|
|
175
|
+
else raise "Invalid flag type: #{value.class}"
|
|
176
|
+
end
|
|
236
177
|
end
|
|
237
178
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
LibHTS.bam_cigar2rlen(
|
|
242
|
-
@bam1[:core][:n_cigar],
|
|
243
|
-
LibHTS.bam_get_cigar(@bam1)
|
|
244
|
-
)
|
|
179
|
+
def aux(key = nil)
|
|
180
|
+
accessor = (@aux_accessor ||= Aux.new(self))
|
|
181
|
+
key ? accessor.get(key) : accessor
|
|
245
182
|
end
|
|
246
183
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
def
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
184
|
+
def base_mod(auto_parse: true) = BaseMod.new(self, auto_parse:)
|
|
185
|
+
|
|
186
|
+
def each_base_mod_raw(max_mods: 10, &block)
|
|
187
|
+
return enum_for(__method__, max_mods:) unless block
|
|
188
|
+
|
|
189
|
+
mods = BaseMod.new(self)
|
|
190
|
+
begin
|
|
191
|
+
mods.each_raw(max_mods:, &block)
|
|
192
|
+
ensure
|
|
193
|
+
mods.close
|
|
254
194
|
end
|
|
255
|
-
|
|
195
|
+
self
|
|
256
196
|
end
|
|
257
|
-
alias sequence seq
|
|
258
197
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
def len
|
|
262
|
-
@bam1[:core][:l_qseq]
|
|
198
|
+
Flag::TABLE.each do |method_name, mask|
|
|
199
|
+
define_method(method_name) { (flag_value & mask) != 0 }
|
|
263
200
|
end
|
|
264
201
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
# @return [String] base
|
|
268
|
-
def base(n)
|
|
269
|
-
n += len if n < 0
|
|
270
|
-
return "." if (n >= len) || (n < 0) # eg. base(-1000)
|
|
202
|
+
def each_cigar_raw
|
|
203
|
+
return to_enum(__method__) unless block_given?
|
|
271
204
|
|
|
272
|
-
|
|
273
|
-
|
|
205
|
+
@native.cigar_values.each { |encoded| yield encoded & 15, encoded >> 4 }
|
|
206
|
+
self
|
|
274
207
|
end
|
|
275
208
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
def
|
|
282
|
-
|
|
283
|
-
|
|
209
|
+
def to_s = @native.format(@header.__send__(:native_handle))
|
|
210
|
+
|
|
211
|
+
private
|
|
212
|
+
|
|
213
|
+
def native_handle = @native
|
|
214
|
+
def core_get(field) = @native.core_get(field)
|
|
215
|
+
def core_set(field, value) = @native.core_set(field, value)
|
|
216
|
+
|
|
217
|
+
def normalize_qname(value)
|
|
218
|
+
value = value.to_s
|
|
219
|
+
raise ArgumentError, "qname must not contain NUL bytes" if value.include?("\0")
|
|
220
|
+
|
|
221
|
+
value
|
|
284
222
|
end
|
|
285
223
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
224
|
+
def normalize_flag(value)
|
|
225
|
+
integer = value.is_a?(Flag) ? value.value : Integer(value)
|
|
226
|
+
raise RangeError, "flag must be between 0 and 65535" unless integer.between?(0, 65_535)
|
|
227
|
+
|
|
228
|
+
integer
|
|
291
229
|
end
|
|
292
230
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
def base_qual(n)
|
|
297
|
-
n += len if n < 0
|
|
298
|
-
return 0 if (n >= len) || (n < 0) # eg. base_qual(-1000)
|
|
231
|
+
def normalize_mapq(value)
|
|
232
|
+
integer = Integer(value)
|
|
233
|
+
raise RangeError, "mapq must be between 0 and 255" unless integer.between?(0, 255)
|
|
299
234
|
|
|
300
|
-
|
|
301
|
-
q_ptr.get_uint8(n)
|
|
235
|
+
integer
|
|
302
236
|
end
|
|
303
237
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
238
|
+
def normalize_position(value, name)
|
|
239
|
+
integer = Integer(value)
|
|
240
|
+
raise RangeError, "#{name} must be -1 or greater" if integer < -1
|
|
241
|
+
|
|
242
|
+
integer
|
|
308
243
|
end
|
|
309
244
|
|
|
310
|
-
def
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
245
|
+
def resolve_tid(numeric, name, label)
|
|
246
|
+
raise ArgumentError, "specify either #{label} or its numeric id, not both" if !numeric.nil? && !name.nil?
|
|
247
|
+
|
|
248
|
+
id = if name.nil?
|
|
249
|
+
numeric.nil? ? -1 : Integer(numeric)
|
|
250
|
+
elsif name.to_s == "*"
|
|
251
|
+
-1
|
|
252
|
+
else
|
|
253
|
+
@header.get_tid(name.to_s)
|
|
254
|
+
end
|
|
255
|
+
raise ArgumentError, "unknown reference #{name.inspect}" if !name.nil? && id.negative? && name.to_s != "*"
|
|
256
|
+
raise RangeError, "reference id must be -1 or greater" if id < -1
|
|
257
|
+
raise RangeError, "reference id #{id} is not present in the header" if id >= @header.target_count
|
|
258
|
+
|
|
259
|
+
id
|
|
319
260
|
end
|
|
320
261
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
aux
|
|
262
|
+
def normalize_sequence(short, long)
|
|
263
|
+
raise ArgumentError, "specify either seq or sequence, not both" unless short.equal?(UNSET) || long.equal?(UNSET)
|
|
264
|
+
|
|
265
|
+
value = long.equal?(UNSET) ? short : long
|
|
266
|
+
value = "" if value.equal?(UNSET) || value.nil? || value == "*"
|
|
267
|
+
value = value.to_s
|
|
268
|
+
unless /\A[=ACMGRSVTWYHKDBNacmgrsvtwyhkdbn]*\z/.match?(value)
|
|
269
|
+
raise ArgumentError, "sequence contains an invalid BAM base"
|
|
330
270
|
end
|
|
271
|
+
|
|
272
|
+
value
|
|
331
273
|
end
|
|
332
274
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
275
|
+
def normalize_qualities(short, long, string, sequence_length)
|
|
276
|
+
supplied = [short, long, string].count { |value| !value.equal?(UNSET) }
|
|
277
|
+
raise ArgumentError, "specify only one of qual, qualities, or quality_string" if supplied > 1
|
|
278
|
+
|
|
279
|
+
value = if !string.equal?(UNSET)
|
|
280
|
+
quality_string_to_bytes(string)
|
|
281
|
+
elsif !long.equal?(UNSET)
|
|
282
|
+
quality_values_to_bytes(long)
|
|
283
|
+
elsif !short.equal?(UNSET)
|
|
284
|
+
short.is_a?(String) ? quality_string_to_bytes(short) : quality_values_to_bytes(short)
|
|
285
|
+
end
|
|
286
|
+
raise ArgumentError, "qualities length must match sequence length" if value && value.bytesize != sequence_length
|
|
287
|
+
|
|
288
|
+
value
|
|
338
289
|
end
|
|
339
290
|
|
|
340
|
-
|
|
291
|
+
def quality_values_to_bytes(values)
|
|
292
|
+
return nil if values.nil?
|
|
341
293
|
|
|
342
|
-
|
|
294
|
+
array = Array(values).map { |value| Integer(value) }
|
|
295
|
+
invalid = array.find { |value| !value.between?(0, 93) }
|
|
296
|
+
raise RangeError, "quality scores must be between 0 and 93" if invalid
|
|
343
297
|
|
|
344
|
-
|
|
298
|
+
array.pack("C*")
|
|
299
|
+
end
|
|
345
300
|
|
|
346
|
-
|
|
301
|
+
def quality_string_to_bytes(value)
|
|
302
|
+
return nil if value.nil? || value == "*"
|
|
347
303
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
flag.send(m)
|
|
304
|
+
string = value.to_s
|
|
305
|
+
unless string.ascii_only? && string.bytes.all? { |byte| byte.between?(33, 126) }
|
|
306
|
+
raise ArgumentError, "quality_string must contain only Phred+33 characters from ! to ~"
|
|
352
307
|
end
|
|
308
|
+
|
|
309
|
+
string.bytes.map { |byte| byte - 33 }.pack("C*")
|
|
353
310
|
end
|
|
354
311
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
kstr = LibHTS::KString.new
|
|
358
|
-
begin
|
|
359
|
-
raise "Failed to format bam record" if LibHTS.sam_format1(@header.struct, @bam1, kstr) == -1
|
|
312
|
+
def normalize_cigar(value)
|
|
313
|
+
return [] if value.nil? || value.to_s == "*"
|
|
360
314
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
315
|
+
case value
|
|
316
|
+
when String then Cigar.parse(value).array
|
|
317
|
+
when Cigar then value.array.dup
|
|
318
|
+
else raise ArgumentError, "cigar must be a String or Bam::Cigar"
|
|
364
319
|
end
|
|
365
320
|
end
|
|
366
321
|
|
|
367
|
-
|
|
322
|
+
def validate_cigar_length!(values, sequence)
|
|
323
|
+
query_length = Native.cigar_qlen(values)
|
|
324
|
+
return if values.empty? || query_length == sequence.bytesize
|
|
368
325
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
@header = orig.header
|
|
372
|
-
# Deep-copy underlying bam1_t to detach from original buffer
|
|
373
|
-
dup_bam1 = LibHTS.bam_dup1(orig.struct)
|
|
374
|
-
raise "bam_dup1 failed" if dup_bam1.null?
|
|
326
|
+
raise ArgumentError, "CIGAR query length #{query_length} does not match sequence length #{sequence.bytesize}"
|
|
327
|
+
end
|
|
375
328
|
|
|
376
|
-
|
|
329
|
+
def assign_aux(values)
|
|
330
|
+
raise ArgumentError, "aux must be a Hash-like object" unless values.respond_to?(:each_pair)
|
|
331
|
+
|
|
332
|
+
values.each_pair { |key, value| aux[key.to_s] = value }
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def initialize_copy(original)
|
|
336
|
+
super
|
|
337
|
+
@header = original.header
|
|
338
|
+
@native = original.__send__(:native_handle).duplicate
|
|
339
|
+
@aux_accessor = nil
|
|
377
340
|
end
|
|
378
341
|
end
|
|
379
342
|
end
|