-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathbgen_to_vcf.cpp
More file actions
288 lines (252 loc) · 9.18 KB
/
bgen_to_vcf.cpp
File metadata and controls
288 lines (252 loc) · 9.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
// Copyright Gavin Band 2008 - 2012.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#include <iostream>
#include <fstream>
#include <cassert>
#include <stdexcept>
#include <memory>
#include "genfile/bgen/bgen.hpp"
// ProbSetter is a callback object appropriate for passing to bgen::read_genotype_data_block() or
// the synonymous method of genfile::bgen::View. See the comment in bgen.hpp above
// bgen::read_genotype_data_block(), or the bgen wiki for a description of the API.
// The purpose of this object is to store genotype probability values in the desired
// data structure (which here is a vector of vectors of doubles).
struct ProbSetter {
typedef std::vector< std::vector< double > > Data ;
ProbSetter( Data* result ):
m_result( result ),
m_sample_i(0)
{}
// Called once allowing us to set storage.
void initialise( std::size_t number_of_samples, std::size_t number_of_alleles ) {
m_result->clear() ;
m_result->resize( number_of_samples ) ;
}
// If present with this signature, called once after initialise()
// to set the minimum and maximum ploidy and numbers of probabilities among samples in the data.
// This enables us to set up storage for the data ahead of time.
void set_min_max_ploidy( uint32_t min_ploidy, uint32_t max_ploidy, uint32_t min_entries, uint32_t max_entries ) {
for( std::size_t i = 0; i < m_result->size(); ++i ) {
m_result->at( i ).reserve( max_entries ) ;
}
}
// Called once per sample to determine whether we want data for this sample
bool set_sample( std::size_t i ) {
m_sample_i = i ;
// Yes, here we want info for all samples.
return true ;
}
// Called once per sample to set the number of probabilities that are present.
void set_number_of_entries(
std::size_t ploidy,
std::size_t number_of_entries,
genfile::OrderType order_type,
genfile::ValueType value_type
) {
assert( value_type == genfile::eProbability ) ;
m_result->at( m_sample_i ).resize( number_of_entries ) ;
m_entry_i = 0 ;
}
// Called once for each genotype (or haplotype) probability per sample.
void set_value( uint32_t, double value ) {
m_result->at( m_sample_i ).at( m_entry_i++ ) = value ;
}
// Ditto, but called if data is missing for this sample.
void set_value( uint32_t, genfile::MissingValue value ) {
// Here we encode missing probabilities with -1
m_result->at( m_sample_i ).at( m_entry_i++ ) = -1 ;
}
// If present with this signature, called once after all data has been set.
void finalise() {
// nothing to do in this implementation.
}
private:
Data* m_result ;
std::size_t m_sample_i ;
std::size_t m_entry_i ;
} ;
// BgenParser is a thin wrapper around the core functions in genfile/bgen/bgen.hpp.
// This class tracks file state and handles passing the right callbacks.
struct BgenParser {
BgenParser( std::string const& filename ):
m_filename( filename ),
m_state( e_NotOpen ),
m_have_sample_ids( false )
{
// Open the stream
m_stream.reset(
new std::ifstream( filename, std::ifstream::binary )
) ;
if( !*m_stream ) {
throw std::invalid_argument( filename ) ;
}
m_state = e_Open ;
// Read the offset, header, and sample IDs if present.
genfile::bgen::read_offset( *m_stream, &m_offset ) ;
genfile::bgen::read_header_block( *m_stream, &m_context ) ;
if( m_context.flags & genfile::bgen::e_SampleIdentifiers ) {
genfile::bgen::read_sample_identifier_block(
*m_stream, m_context,
[this]( std::string id ) { m_sample_ids.push_back( id ) ; }
) ;
m_have_sample_ids = true ;
}
// Jump to the first variant data block.
m_stream->seekg( m_offset + 4 ) ;
// We keep track of state (though it's not really needed for this implementation.)
m_state = e_ReadyForVariant ;
}
std::ostream& summarise( std::ostream& o ) const {
o << "BgenParser: bgen file ("
<< ( m_context.flags & genfile::bgen::e_Layout2 ? "v1.2 layout" : "v1.1 layout" )
<< ", "
<< ( m_context.flags & genfile::bgen::e_CompressedSNPBlocks ? "compressed" : "uncompressed" ) << ")"
<< " with "
<< m_context.number_of_samples << " " << ( m_have_sample_ids ? "named" : "anonymous" ) << " samples and "
<< m_context.number_of_variants << " variants.\n" ;
return o ;
}
std::size_t number_of_samples() const {
return m_context.number_of_samples ;
}
// Report the sample IDs in the file using the given setter object
// (If there are no sample IDs in the file, we report a dummy identifier).
template< typename Setter >
void get_sample_ids( Setter setter ) {
if( m_have_sample_ids ) {
for( std::size_t i = 0; i < m_context.number_of_samples; ++i ) {
setter( m_sample_ids[i] ) ;
}
} else {
for( std::size_t i = 0; i < m_context.number_of_samples; ++i ) {
setter( "(unknown_sample_" + std::to_string( i+1 ) + ")" ) ;
}
}
}
// Attempt to read identifying information about a variant from the bgen file, returning
// it in the given fields.
// If this method returns true, data was successfully read, and it should be safe to call read_probs()
// or ignore_probs().
// If this method returns false, data was not successfully read indicating the end of the file.
bool read_variant(
std::string* chromosome,
uint32_t* position,
std::string* rsid,
std::vector< std::string >* alleles
) {
assert( m_state == e_ReadyForVariant ) ;
std::string SNPID ; // read but ignored in this toy implementation
if(
genfile::bgen::read_snp_identifying_data(
*m_stream, m_context,
&SNPID, rsid, chromosome, position,
[&alleles]( std::size_t n ) { alleles->resize( n ) ; },
[&alleles]( std::size_t i, std::string const& allele ) { alleles->at(i) = allele ; }
)
) {
m_state = e_ReadyForProbs ;
return true ;
} else {
return false ;
}
}
// Read genotype probability data for the SNP just read using read_variant()
// After calling this method it should be safe to call read_variant() to fetch
// the next variant from the file.
void read_probs( std::vector< std::vector< double > >* probs ) {
assert( m_state == e_ReadyForProbs ) ;
ProbSetter setter( probs ) ;
genfile::bgen::read_and_parse_genotype_data_block< ProbSetter >(
*m_stream,
m_context,
setter,
&m_buffer1,
&m_buffer2
) ;
m_state = e_ReadyForVariant ;
}
// Ignore genotype probability data for the SNP just read using read_variant()
// After calling this method it should be safe to call read_variant()
// to fetch the next variant from the file.
void ignore_probs() {
genfile::bgen::ignore_genotype_data_block( *m_stream, m_context ) ;
m_state = e_ReadyForVariant ;
}
private:
std::string const m_filename ;
std::unique_ptr< std::istream > m_stream ;
// bgen::Context object holds information from the header block,
// including bgen flags
genfile::bgen::Context m_context ;
// offset byte from top of bgen file.
uint32_t m_offset ;
// We keep track of our state in the file.
// Not strictly necessary for this implentation but makes it clear that
// calls must be read_variant() followed by read_probs() (or ignore_probs())
// repeatedly.
enum State { e_NotOpen = 0, e_Open = 1, e_ReadyForVariant = 2, e_ReadyForProbs = 3, eComplete = 4 } ;
State m_state ;
// If the BGEN file contains samples ids, they will be read here.
bool m_have_sample_ids ;
std::vector< std::string > m_sample_ids ;
// Buffers, these are used as working space by bgen implementation.
std::vector< genfile::byte_t > m_buffer1, m_buffer2 ;
} ;
// This example program reads data from a bgen file specified as the first argument
// and outputs it as a VCF file.
int main( int argc, char** argv ) {
if( argc != 2 ) {
std::cerr << "You must supply an argument, the name of the bgen file to process.\n" ;
exit(-1) ;
}
std::string const filename = argv[1] ;
try {
BgenParser bgenParser( filename ) ;
bgenParser.summarise( std::cerr ) ;
// Output header
std::cout << "##fileformat=VCFv4.2\n"
<< "FORMAT=<ID=GP,Type=Float,Number=G,Description=\"Genotype call probabilities\">\n"
<< "#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO\tFORMAT" ;
bgenParser.get_sample_ids(
[]( std::string const& id ) { std::cout << "\t" << id ; }
) ;
std::cout << "\n" ;
// Output variants
std::string chromosome ;
uint32_t position ;
std::string rsid ;
std::vector< std::string > alleles ;
std::vector< std::vector< double > > probs ;
while( bgenParser.read_variant( &chromosome, &position, &rsid, &alleles )) {
std::cout << chromosome << '\t'
<< position << '\t'
<< rsid << '\t' ;
assert( alleles.size() > 0 ) ;
std::cout << alleles[0] << '\t' ;
for( std::size_t i = 1; i < alleles.size(); ++i ) {
std::cout << ( i > 1 ? "," : "" ) << alleles[i] ;
}
std::cout << "\t.\t.\t.\tGP" ;
bgenParser.read_probs( &probs ) ;
for( std::size_t i = 0; i < probs.size(); ++i ) {
std::cout << '\t' ;
for( std::size_t j = 0; j < probs[i].size(); ++j ) {
std::cout << ( j > 0 ? "," : "" ) ;
if( probs[i][j] == -1 ) {
std::cout << "." ;
} else {
std::cout << probs[i][j] ;
}
}
}
std::cout << "\n" ;
}
return 0 ;
}
catch( genfile::bgen::BGenError const& e ) {
std::cerr << "!! Uh-oh, error parsing bgen file.\n" ;
return -1 ;
}
}