GenomicDataStream
A scalable interface between data and analysis
Loading...
Searching...
No Matches
vcfstream.h
Go to the documentation of this file.
1/***********************************************************************
2 * @file vcfstream.h
3 * @author Gabriel Hoffman
4 * @email gabriel.hoffman@mssm.edu
5 * @brief vcfstream reads a VCF/VCFGZ/BCF into a matrix in chunks, storing variants in columns
6 * Copyright (C) 2024 Gabriel Hoffman
7 ***********************************************************************/
8
9
10#ifndef VCF_STREAM_H_
11#define VCF_STREAM_H_
12
13#ifndef DISABLE_EIGEN
14// #include <Eigen/Sparse>
15#include <RcppEigen.h>
16#endif
17
18#include <filesystem>
19#include <string>
20
21#include <vcfpp.h>
22
23#include "VariantInfo.h"
25#include "utils.h"
26#include "GenomicRanges.h"
27
28using namespace std;
29using namespace vcfpp;
30
31namespace gds {
32
36class vcfstream :
37 public GenomicDataStream {
38 public:
39
41
45
46 // check that file exists
47 if( ! filesystem::exists( param.file ) ){
48 throw runtime_error("File does not exist: " + param.file);
49 }
50
51 // check that field was specified
52 if( param.field.compare("") == 0 ){
53 throw runtime_error("Field for VCF/BCF not specified");
54 }
55
56 // initialize
57 reader = new BcfReader( param.file );
58
59 // Set genomic regions regions
61
62 reader->setSamples( param.samples );
63
64 // Initialize record with info in header
65 record = new BcfRecord( reader->header );
66
67 // 1: int; 2: float; 3: string; 0: error;
68 fieldType = reader->header.getFormatType( param.field );
69
70 if( fieldType == 3 && param.field.compare("GT"))
71 throw std::runtime_error("field GT is the only supported string type");
72 if( fieldType == 0)
73 throw std::runtime_error("field not found: " + param.field);
74 // initialize varInfo with sample names
75 vInfo = new VariantInfo( reader->SamplesName );
76
77 // Initialize vector with capacity to store variants
78 // Note, this allocates memory but does not change .size()
79 // After j variants have been inserted, only entries up to j*nsamples are populated
80 // the rest of the vector is allocated doesn't have valid data
81 matDosage.reserve( n_samples() * param.chunkSize );
82 }
83
86 ~vcfstream() override {
87 if( reader != nullptr) delete reader;
88 if( record != nullptr) delete record;
89 if( vInfo != nullptr) delete vInfo;
90 }
91
94 void setRegions(const vector<string> &regions) override {
95
96 validRegions.reserve(regions.size());
97 validRegions.clear();
98 copy(regions.begin(), regions.end(), back_inserter(validRegions));
99
100 // for(auto &region: regions){
101 // checkStatus( region );
102 // }
103
104 // initialize to false
105 continueIterating = false;
106
107 // if valid set is not empty
108 if( validRegions.size() > 0 ){
109 // initialize iterator
110 itReg = validRegions.begin();
111
112 reader->setRegion( *itReg );
113
114 continueIterating = true;
115 }
116 }
117
120 int n_samples() override {
121 return reader->nsamples;
122 }
123
126 vector<string> getSampleNames() override {
127 return reader->header.getSamples();
128 }
129
132 string getStreamType() override {
133 return toString( param.fileType);
134 }
135
137 return GenomicRanges();
138 }
139
140 bool getNextChunk( DataChunk<arma::mat> & chunk, const bool &useFilter = true) override {
141
142 // Update matDosage and vInfo for the chunk
143 bool ret = getNextChunk_helper();
144
145 if( useFilter ){
146 // modifies matDosage and vInfo directly
147 applyVariantFilter(matDosage, vInfo, reader->nsamples, getMAF(), getMinVariance() );
148 }
149
150 // mat(ptr_aux_mem, n_rows, n_cols, copy_aux_mem = true, strict = false)
151 bool copy_aux_mem = false; // create read-only matrix without re-allocating memory
152
153 arma::mat M(matDosage.data(), reader->nsamples, vInfo->size(), copy_aux_mem, true);
154
155 chunk = DataChunk<arma::mat>( M, vInfo );
156
157 return ret;
158 }
159
160 bool getNextChunk( DataChunk<arma::sp_mat> & chunk, const bool &useFilter = true) override {
161
162 // Update matDosage and vInfo for the chunk
163 bool ret = getNextChunk_helper();
164
165 if( useFilter ){
166 // modifies matDosage and vInfo directly
167 applyVariantFilter(matDosage, vInfo, reader->nsamples, getMAF(), getMinVariance() );
168 }
169
170 // otherwise, set chunk and return ret
171 arma::mat M(matDosage.data(), reader->nsamples, vInfo->size(), false, true);
172
173 // create sparse matrix from dense matrix
174 chunk = DataChunk<arma::sp_mat>( arma::sp_mat(M), vInfo );
175
176 return ret;
177 }
178
179 #ifndef DISABLE_EIGEN
180 bool getNextChunk( DataChunk<Eigen::MatrixXd> & chunk, const bool &useFilter = true) override {
181
182 // Update matDosage and vInfo for the chunk
183 bool ret = getNextChunk_helper();
184
185 if( useFilter ){
186 // modifies matDosage and vInfo directly
187 applyVariantFilter(matDosage, vInfo, reader->nsamples, getMAF(), getMinVariance() );
188 }
189
190 Eigen::MatrixXd M = Eigen::Map<Eigen::MatrixXd>(matDosage.data(), reader->nsamples, vInfo->size());
191
192 chunk = DataChunk<Eigen::MatrixXd>( M, vInfo );
193
194 return ret;
195 }
196
197 bool getNextChunk( DataChunk<Eigen::SparseMatrix<double> > & chunk, const bool &useFilter = true) override {
198
199 // Update matDosage and vInfo for the chunk
200 bool ret = getNextChunk_helper();
201
202 if( useFilter ){
203 // modifies matDosage and vInfo directly
204 applyVariantFilter(matDosage, vInfo, reader->nsamples, getMAF(), getMinVariance() );
205 }
206
207 Eigen::MatrixXd M = Eigen::Map<Eigen::MatrixXd>(matDosage.data(), reader->nsamples, vInfo->size());
208
209 chunk = DataChunk<Eigen::SparseMatrix<double> >( M.sparseView(), vInfo );
210
211 return ret;
212 }
213 #endif
214
215 #ifndef DISABLE_RCPP
216 bool getNextChunk( DataChunk<Rcpp::NumericMatrix> & chunk, const bool &useFilter = true) override {
217
218 // Update matDosage and vInfo for the chunk
219 bool ret = getNextChunk_helper();
220
221 if( useFilter ){
222 // modifies matDosage and vInfo directly
223 applyVariantFilter(matDosage, vInfo, reader->nsamples, getMAF(), getMinVariance() );
224 }
225
226 Rcpp::NumericMatrix M(reader->nsamples, vInfo->size(), matDosage.data());
227 colnames(M) = Rcpp::wrap( vInfo->getFeatureNames() );
228 rownames(M) = Rcpp::wrap( vInfo->sampleNames );
229
230 chunk = DataChunk<Rcpp::NumericMatrix>( M, vInfo );
231
232 return ret;
233 }
234 #endif
235
236 bool getNextChunk( DataChunk<vector<double>> & chunk, const bool &useFilter = true) override {
237
238 // Update matDosage and vInfo for the chunk
239 bool ret = getNextChunk_helper();
240
241 if( useFilter ){
242 // modifies matDosage and vInfo directly
243 applyVariantFilter(matDosage, vInfo, reader->nsamples, getMAF(), getMinVariance() );
244 }
245
246 chunk = DataChunk<vector<double>>( matDosage, vInfo );
247
248 return ret;
249 }
250
255 static string variantToString( const BcfRecord &record ) {
256 string s = record.CHROM() + ":" + to_string(record.POS()) + " " + record.ID() + " " + record.REF() + " " + record.ALT();
257 return s;
258 }
259
260 private:
261 BcfReader *reader = nullptr;
262 BcfRecord *record = nullptr;
263 VariantInfo *vInfo = nullptr;
264 vector<string>::iterator itReg;
265 vector<string> validRegions;
266
267 bool continueIterating;
268 int fieldType;
269
270 // store genotype values
271 // type used based on fieldType
272 // 1: int;
273 // 2: float;
274 // 3: string;
275 // 0: error;
276 vector<int> values_int;
277 vector<float> values_fl;
278
279 vector<double> tmp;
280
281 // stores genotype dosage as doubles, with the next marker inserted at the end
282 // NOTE that when current size is exceeded, .insert() reallocates memory
283 // this can be slow
284 // set using reserve() to set initial capacity so avoid re-alloc
285 vector<double> matDosage;
286
287 bool getNextChunk_helper(){
288
289 // if no valid regions
290 if( validRegions.size() == 0) return false;
291
292 // if end of file reached, return false
293 if( ! continueIterating ) return continueIterating;
294
295 // clear data, but keep allocated capacity
296 matDosage.clear();
297 vInfo->clear();
298
299 // loop thru variant, updating the count each time
300 unsigned int j;
301 for(j=0; j < param.chunkSize; j++){
302
303 // get next variant
304 // if false, reached end of region
305 if( ! reader->getNextVariant( *record ) ){
306
307 // else go to next region
308 itReg++;
309
310 // if this was the last region
311 // set continueIterating so false is retured at next call to
312 // getNextChunk_helper()
313 // then break since no data left
314 if( itReg == validRegions.end()){
315 continueIterating = false;
316 break;
317 }
318
319 // else
320 // initialize the record for this region
321 reader->setRegion( *itReg );
322 reader->getNextVariant( *record );
323 }
324
325 // populate genotype with the values of the current variant
326 // If string, convert to dosage
327 // use values vector based on fieldType
328 switch(fieldType){
329 case 1: // int
330 record->getFORMAT( param.field, values_int);
331 matDosage.insert(matDosage.end(), values_int.begin(), values_int.end());
332 break;
333 case 2: // float
334 record->getFORMAT( param.field, values_fl);
335
336 if( param.field.compare("DS") == 0){
337 // dosage
338 matDosage.insert(matDosage.end(), values_fl.begin(), values_fl.end());
339 }else if( param.field.compare("GP") == 0){
340 // check if site ploidy > 2
341 if( record->ploidy() > 2 ){
342 throw std::runtime_error("GP is not supported for site with ploidy > 2\n " + variantToString(*record));
343 }
344
345 // genotype probabilities
346 vector<double> dsg = GP_to_dosage(values_fl, param.missingToMean);
347
348 matDosage.insert(matDosage.end(), dsg.begin(), dsg.end());
349 }
350 break;
351 case 3: // string. Convert GT to doubles
352
353 // check if site is multi-allelic
354 if( record->isMultiAllelics() ){
355 throw std::runtime_error("GT is not supported for multi-allelic site\n " + variantToString(*record));
356 }
357 // check if site ploidy > 2
358 if( record->ploidy() > 2 ){
359 throw std::runtime_error("GT is not supported for site with ploidy > 2\n " + variantToString(*record));
360 }
361
362 // get GT as int's with vector that is twice as long
363 record->getGenotypes(values_int);
364 tmp = intToDosage( values_int, param.missingToMean );
365 matDosage.insert(matDosage.end(), tmp.begin(), tmp.end());
366 break;
367 }
368
369 // store variant information
370 vInfo->addVariant(record->CHROM(),
371 record->POS(),
372 record->ID(),
373 record->REF(),
374 record->ALT() );
375 }
376
377 bool ret = true;
378
379 // if chunk is empty, return false
380 if( vInfo->size() == 0) ret = false;
381
382 return ret;
383 }
384
385 // check status of region
386 void checkStatus(const string & region){
387 switch( reader->getStatus( region ) ){
388 case 1: // region is vaild and not empty
389 break;
390
391 case 0: // the region is valid but empty.
392 break;
393
394 case -1: // there is no index file found.
395 throw runtime_error("Could not retrieve index file");
396 break;
397
398 case -2: // the region is not valid
399 Rcpp::Rcout << "region:" + region + ";\n";
400 throw runtime_error("region was not found: " + region );
401 break;
402 }
403 }
404};
405
406} // end namespace
407
408#endif
Definition GenomicDataStream_virtual.h:40
double getMinVariance()
Definition GenomicDataStream_virtual.h:219
double getMAF() const
Definition GenomicDataStream_virtual.h:225
Param param
Definition GenomicDataStream_virtual.h:273
GenomicDataStream()
Definition GenomicDataStream_virtual.h:187
Definition GenomicRanges.h:25
GenomicRanges()
Definition GenomicRanges.h:29
Definition VariantInfo.h:24
void clear()
Definition VariantInfo.h:142
GenomicRanges getChromRanges() override
Definition vcfstream.h:136
string getStreamType() override
Definition vcfstream.h:132
bool getNextChunk(DataChunk< arma::sp_mat > &chunk, const bool &useFilter=true) override
Definition vcfstream.h:160
bool getNextChunk(DataChunk< Rcpp::NumericMatrix > &chunk, const bool &useFilter=true) override
Definition vcfstream.h:216
bool getNextChunk(DataChunk< vector< double > > &chunk, const bool &useFilter=true) override
Definition vcfstream.h:236
vcfstream(const Param &param)
Definition vcfstream.h:44
static string variantToString(const BcfRecord &record)
Definition vcfstream.h:255
vector< string > getSampleNames() override
Definition vcfstream.h:126
vcfstream()
Definition vcfstream.h:40
bool getNextChunk(DataChunk< arma::mat > &chunk, const bool &useFilter=true) override
Definition vcfstream.h:140
bool getNextChunk(DataChunk< Eigen::SparseMatrix< double > > &chunk, const bool &useFilter=true) override
Definition vcfstream.h:197
~vcfstream() override
Definition vcfstream.h:86
void setRegions(const vector< string > &regions) override
Definition vcfstream.h:94
bool getNextChunk(DataChunk< Eigen::MatrixXd > &chunk, const bool &useFilter=true) override
Definition vcfstream.h:180
int n_samples() override
Definition vcfstream.h:120
Definition bgenstream.h:33
Definition GenomicDataStream_virtual.h:84
vector< string > regions
Definition GenomicDataStream_virtual.h:173
string samples
Definition GenomicDataStream_virtual.h:174
FileType fileType
Definition GenomicDataStream_virtual.h:178
string field
Definition GenomicDataStream_virtual.h:172
string file
Definition GenomicDataStream_virtual.h:171
int chunkSize
Definition GenomicDataStream_virtual.h:176
bool missingToMean
Definition GenomicDataStream_virtual.h:177