]> git.donarmstrong.com Git - bamtools.git/blob - src/toolkit/bamtools_convert.cpp
added reader.Open checks to a number of tools
[bamtools.git] / src / toolkit / bamtools_convert.cpp
1 // ***************************************************************************
2 // bamtools_convert.cpp (c) 2010 Derek Barnett, Erik Garrison
3 // Marth Lab, Department of Biology, Boston College
4 // All rights reserved.
5 // ---------------------------------------------------------------------------
6 // Last modified: 23 September 2010
7 // ---------------------------------------------------------------------------
8 // Converts between BAM and a number of other formats
9 // ***************************************************************************
10
11 #include <fstream>
12 #include <iostream>
13 #include <sstream>
14 #include <string>
15 #include <vector>
16 #include "bamtools_convert.h"
17 #include "bamtools_fasta.h"
18 #include "bamtools_options.h"
19 #include "bamtools_pileup_engine.h"
20 #include "bamtools_utilities.h"
21 #include "BGZF.h"
22 #include "BamMultiReader.h"
23 using namespace std;
24 using namespace BamTools;
25   
26 namespace BamTools { 
27   
28     // ---------------------------------------------
29     // ConvertTool constants
30   
31     // supported conversion format command-line names
32     static const string FORMAT_BED      = "bed";
33     static const string FORMAT_BEDGRAPH = "bedgraph";
34     static const string FORMAT_FASTA    = "fasta";
35     static const string FORMAT_FASTQ    = "fastq";
36     static const string FORMAT_JSON     = "json";
37     static const string FORMAT_SAM      = "sam";
38     static const string FORMAT_PILEUP   = "pileup";
39     static const string FORMAT_WIGGLE   = "wig";
40
41     // other constants
42     static const unsigned int FASTA_LINE_MAX = 50;
43     
44     // ---------------------------------------------
45     // ConvertPileupFormatVisitor declaration
46     
47     class ConvertPileupFormatVisitor : public PileupVisitor {
48       
49         // ctor & dtor
50         public:
51             ConvertPileupFormatVisitor(const RefVector& references, 
52                                        const string& fastaFilename,
53                                        const bool isPrintingMapQualities,
54                                        ostream* out);
55             ~ConvertPileupFormatVisitor(void);
56       
57         // PileupVisitor interface implementation
58         public:
59             void Visit(const PileupPosition& pileupData);
60             
61         // data members
62         private:
63             Fasta     m_fasta;
64             bool      m_hasFasta;
65             bool      m_isPrintingMapQualities;
66             ostream*  m_out;
67             RefVector m_references;
68     };
69     
70 } // namespace BamTools
71   
72 // ---------------------------------------------
73 // ConvertSettings implementation
74
75 struct ConvertTool::ConvertSettings {
76
77     // flags
78     bool HasInput;
79     bool HasOutput;
80     bool HasFormat;
81     bool HasRegion;
82
83     // pileup flags
84     bool HasFastaFilename;
85     bool IsOmittingSamHeader;
86     bool IsPrintingPileupMapQualities;
87     
88     // options
89     vector<string> InputFiles;
90     string OutputFilename;
91     string Format;
92     string Region;
93     
94     // pileup options
95     string FastaFilename;
96
97     // constructor
98     ConvertSettings(void)
99         : HasInput(false)
100         , HasOutput(false)
101         , HasFormat(false)
102         , HasRegion(false)
103         , HasFastaFilename(false)
104         , IsOmittingSamHeader(false)
105         , IsPrintingPileupMapQualities(false)
106         , OutputFilename(Options::StandardOut())
107         , FastaFilename("")
108     { } 
109 };    
110
111 // ---------------------------------------------
112 // ConvertToolPrivate implementation  
113   
114 struct ConvertTool::ConvertToolPrivate {
115   
116     // ctor & dtor
117     public:
118         ConvertToolPrivate(ConvertTool::ConvertSettings* settings);
119         ~ConvertToolPrivate(void);
120     
121     // interface
122     public:
123         bool Run(void);
124         
125     // internal methods
126     private:
127         void PrintBed(const BamAlignment& a);
128         void PrintBedGraph(const BamAlignment& a);
129         void PrintFasta(const BamAlignment& a);
130         void PrintFastq(const BamAlignment& a);
131         void PrintJson(const BamAlignment& a);
132         void PrintSam(const BamAlignment& a);
133         void PrintWiggle(const BamAlignment& a);
134         
135         // special case - uses the PileupEngine
136         bool RunPileupConversion(BamMultiReader* reader);
137         
138     // data members
139     private: 
140         ConvertTool::ConvertSettings* m_settings;
141         RefVector m_references;
142         ostream m_out;
143 };
144
145 ConvertTool::ConvertToolPrivate::ConvertToolPrivate(ConvertTool::ConvertSettings* settings)
146     : m_settings(settings)
147     , m_out(cout.rdbuf()) // default output to cout
148 { }
149
150 ConvertTool::ConvertToolPrivate::~ConvertToolPrivate(void) { }
151
152 bool ConvertTool::ConvertToolPrivate::Run(void) {
153  
154     // ------------------------------------
155     // initialize conversion input/output
156         
157     // set to default input if none provided
158     if ( !m_settings->HasInput ) 
159         m_settings->InputFiles.push_back(Options::StandardIn());
160     
161     // open input files
162     BamMultiReader reader;
163     if (!reader.Open(m_settings->InputFiles, false)) {
164         cerr << "Could not open input files" << endl;
165         return false;
166     }
167     m_references = reader.GetReferenceData();
168
169     // set region if specified
170     BamRegion region;
171     if ( m_settings->HasRegion ) {
172         if ( Utilities::ParseRegionString(m_settings->Region, reader, region) ) {
173             if ( !reader.SetRegion(region) ) {
174                 cerr << "Could not set BamReader region to REGION: " << m_settings->Region << endl;
175                 return false;
176             }
177         } else {
178             cerr << "Could not parse REGION: " << m_settings->Region << endl;
179             return false;
180         }
181     }
182         
183     // if output file given
184     ofstream outFile;
185     if ( m_settings->HasOutput ) {
186       
187         // open output file stream
188         outFile.open(m_settings->OutputFilename.c_str());
189         if ( !outFile ) {
190             cerr << "Could not open " << m_settings->OutputFilename << " for output" << endl; 
191             return false; 
192         }
193         
194         // set m_out to file's streambuf
195         m_out.rdbuf(outFile.rdbuf()); 
196     }
197     
198     // -------------------------------------
199     // do conversion based on format
200     
201      bool convertedOk = true;
202     
203     // pileup is special case
204     // conversion not done per alignment, like the other formats
205     if ( m_settings->Format == FORMAT_PILEUP )
206         convertedOk = RunPileupConversion(&reader);
207     
208     // all other formats
209     else {
210     
211         bool formatError = false;
212         
213         // set function pointer to proper conversion method
214         void (BamTools::ConvertTool::ConvertToolPrivate::*pFunction)(const BamAlignment&) = 0;
215         if      ( m_settings->Format == FORMAT_BED )      pFunction = &BamTools::ConvertTool::ConvertToolPrivate::PrintBed;
216         else if ( m_settings->Format == FORMAT_BEDGRAPH ) pFunction = &BamTools::ConvertTool::ConvertToolPrivate::PrintBedGraph;
217         else if ( m_settings->Format == FORMAT_FASTA )    pFunction = &BamTools::ConvertTool::ConvertToolPrivate::PrintFasta;
218         else if ( m_settings->Format == FORMAT_FASTQ )    pFunction = &BamTools::ConvertTool::ConvertToolPrivate::PrintFastq;
219         else if ( m_settings->Format == FORMAT_JSON )     pFunction = &BamTools::ConvertTool::ConvertToolPrivate::PrintJson;
220         else if ( m_settings->Format == FORMAT_SAM )      pFunction = &BamTools::ConvertTool::ConvertToolPrivate::PrintSam;
221         else if ( m_settings->Format == FORMAT_WIGGLE )   pFunction = &BamTools::ConvertTool::ConvertToolPrivate::PrintWiggle;
222         else { 
223             cerr << "Unrecognized format: " << m_settings->Format << endl;
224             cerr << "Please see help|README (?) for details on supported formats " << endl;
225             formatError = true;
226             convertedOk = false;
227         }
228         
229         // if format selected ok
230         if ( !formatError ) {
231         
232             // if SAM format & not omitting header, print SAM header first
233             if ( (m_settings->Format == FORMAT_SAM) && !m_settings->IsOmittingSamHeader ) 
234                 m_out << reader.GetHeaderText();
235             
236             // iterate through file, doing conversion
237             BamAlignment a;
238             while ( reader.GetNextAlignment(a) ) 
239               (this->*pFunction)(a);
240             
241             // set flag for successful conversion
242             convertedOk = true;
243         }
244     }
245     
246     // ------------------------
247     // clean up & exit
248     
249     reader.Close();
250     if ( m_settings->HasOutput ) outFile.close();
251     return convertedOk;   
252 }
253
254 // ----------------------------------------------------------
255 // Conversion/output methods
256 // ----------------------------------------------------------
257
258 void ConvertTool::ConvertToolPrivate::PrintBed(const BamAlignment& a) { 
259   
260     // tab-delimited, 0-based half-open 
261     // (e.g. a 50-base read aligned to pos 10 could have BED coordinates (10, 60) instead of BAM coordinates (10, 59) )
262     // <chromName> <chromStart> <chromEnd> <readName> <score> <strand>
263
264     m_out << m_references.at(a.RefID).RefName << "\t"
265           << a.Position << "\t"
266           << a.GetEndPosition() + 1 << "\t"
267           << a.Name << "\t"
268           << a.MapQuality << "\t"
269           << (a.IsReverseStrand() ? "-" : "+") << endl;
270 }
271
272 void ConvertTool::ConvertToolPrivate::PrintBedGraph(const BamAlignment& a) { 
273     ; 
274 }
275
276 // print BamAlignment in FASTA format
277 // N.B. - uses QueryBases NOT AlignedBases
278 void ConvertTool::ConvertToolPrivate::PrintFasta(const BamAlignment& a) { 
279     
280     // >BamAlignment.Name
281     // BamAlignment.QueryBases (up to FASTA_LINE_MAX bases per line)
282     // ...
283     //
284     // N.B. - QueryBases are reverse-complemented if aligned to reverse strand
285   
286     // print header
287     m_out << "> " << a.Name << endl;
288     
289     // handle reverse strand alignment - bases 
290     string sequence = a.QueryBases;
291     if ( a.IsReverseStrand() )
292         Utilities::ReverseComplement(sequence);
293     
294     // if sequence fits on single line
295     if ( sequence.length() <= FASTA_LINE_MAX )
296         m_out << sequence << endl;
297     
298     // else split over multiple lines
299     else {
300       
301         size_t position = 0;
302         size_t seqLength = sequence.length(); // handle reverse strand alignment - bases & qualitiesth();
303         
304         // write subsequences to each line
305         while ( position < (seqLength - FASTA_LINE_MAX) ) {
306             m_out << sequence.substr(position, FASTA_LINE_MAX) << endl;
307             position += FASTA_LINE_MAX;
308         }
309         
310         // write final subsequence
311         m_out << sequence.substr(position) << endl;
312     }
313 }
314
315 // print BamAlignment in FASTQ format
316 // N.B. - uses QueryBases NOT AlignedBases
317 void ConvertTool::ConvertToolPrivate::PrintFastq(const BamAlignment& a) { 
318   
319     // @BamAlignment.Name
320     // BamAlignment.QueryBases
321     // +
322     // BamAlignment.Qualities
323     //
324     // N.B. - QueryBases are reverse-complemented (& Qualities reversed) if aligned to reverse strand .
325     //        Name is appended "/1" or "/2" if paired-end, to reflect which mate this entry is.
326   
327     // handle paired-end alignments
328     string name = a.Name;
329     if ( a.IsPaired() )
330         name.append( (a.IsFirstMate() ? "/1" : "/2") );
331   
332     // handle reverse strand alignment - bases & qualities
333     string qualities = a.Qualities;
334     string sequence  = a.QueryBases;
335     if ( a.IsReverseStrand() ) {
336         Utilities::Reverse(qualities);
337         Utilities::ReverseComplement(sequence);
338     }
339   
340     // write to output stream
341     m_out << "@" << name << endl
342           << sequence    << endl
343           << "+"         << endl
344           << qualities   << endl;
345 }
346
347 // print BamAlignment in JSON format
348 void ConvertTool::ConvertToolPrivate::PrintJson(const BamAlignment& a) {
349   
350     // write name & alignment flag
351     m_out << "{\"name\":\"" << a.Name << "\",\"alignmentFlag\":\"" << a.AlignmentFlag << "\",";
352     
353     // write reference name
354     if ( (a.RefID >= 0) && (a.RefID < (int)m_references.size()) ) 
355         m_out << "\"reference\":\"" << m_references[a.RefID].RefName << "\",";
356     
357     // write position & map quality
358     m_out << "\"position\":" << a.Position+1 << ",\"mapQuality\":" << a.MapQuality << ",";
359     
360     // write CIGAR
361     const vector<CigarOp>& cigarData = a.CigarData;
362     if ( !cigarData.empty() ) {
363         m_out << "\"cigar\":[";
364         vector<CigarOp>::const_iterator cigarBegin = cigarData.begin();
365         vector<CigarOp>::const_iterator cigarIter = cigarBegin;
366         vector<CigarOp>::const_iterator cigarEnd  = cigarData.end();
367         for ( ; cigarIter != cigarEnd; ++cigarIter ) {
368             const CigarOp& op = (*cigarIter);
369             if (cigarIter != cigarBegin) m_out << ",";
370             m_out << "\"" << op.Length << op.Type << "\"";
371         }
372         m_out << "],";
373     }
374     
375     // write mate reference name, mate position, & insert size
376     if ( a.IsPaired() && (a.MateRefID >= 0) && (a.MateRefID < (int)m_references.size()) ) {
377         m_out << "\"mate\":{"
378               << "\"reference\":\"" << m_references[a.MateRefID].RefName << "\","
379               << "\"position\":" << a.MatePosition+1
380               << ",\"insertSize\":" << a.InsertSize << "},";
381     }
382     
383     // write sequence
384     if ( !a.QueryBases.empty() ) 
385         m_out << "\"queryBases\":\"" << a.QueryBases << "\",";
386     
387     // write qualities
388     if ( !a.Qualities.empty() ) {
389         string::const_iterator s = a.Qualities.begin();
390         m_out << "\"qualities\":[" << static_cast<short>(*s) - 33;
391         ++s;
392         for (; s != a.Qualities.end(); ++s) {
393             m_out << "," << static_cast<short>(*s) - 33;
394         }
395         m_out << "],";
396     }
397     
398     // write tag data
399     const char* tagData = a.TagData.c_str();
400     const size_t tagDataLength = a.TagData.length();
401     size_t index = 0;
402     if (index < tagDataLength) {
403
404         m_out << "\"tags\":{";
405         
406         while ( index < tagDataLength ) {
407
408             if (index > 0)
409                 m_out << ",";
410             
411             // write tag name
412             m_out << "\"" << a.TagData.substr(index, 2) << "\":";
413             index += 2;
414             
415             // get data type
416             char type = a.TagData.at(index);
417             ++index;
418             
419             switch (type) {
420                 case('A') : 
421                     m_out << "\"" << tagData[index] << "\"";
422                     ++index; 
423                     break;
424                 
425                 case('C') : 
426                     m_out << (int)tagData[index]; 
427                     ++index; 
428                     break;
429                 
430                 case('c') : 
431                     m_out << (int)tagData[index];
432                     ++index; 
433                     break;
434                 
435                 case('S') : 
436                     m_out << BgzfData::UnpackUnsignedShort(&tagData[index]); 
437                     index += 2; 
438                     break;
439                     
440                 case('s') : 
441                     m_out << BgzfData::UnpackSignedShort(&tagData[index]);
442                     index += 2; 
443                     break;
444                 
445                 case('I') : 
446                     m_out << BgzfData::UnpackUnsignedInt(&tagData[index]);
447                     index += 4; 
448                     break;
449                 
450                 case('i') : 
451                     m_out << BgzfData::UnpackSignedInt(&tagData[index]);
452                     index += 4; 
453                     break;
454                 
455                 case('f') : 
456                     m_out << BgzfData::UnpackFloat(&tagData[index]);
457                     index += 4; 
458                     break;
459                 
460                 case('d') : 
461                     m_out << BgzfData::UnpackDouble(&tagData[index]);
462                     index += 8; 
463                     break;
464                 
465                 case('Z') :
466                 case('H') : 
467                     m_out << "\""; 
468                     while (tagData[index]) {
469                         if (tagData[index] == '\"')
470                             m_out << "\\\""; // escape for json
471                         else
472                             m_out << tagData[index];
473                         ++index;
474                     }
475                     m_out << "\""; 
476                     ++index; 
477                     break;      
478             }
479             
480             if ( tagData[index] == '\0') 
481                 break;
482         }
483
484         m_out << "}";
485     }
486
487     m_out << "}" << endl;
488 }
489
490 // print BamAlignment in SAM format
491 void ConvertTool::ConvertToolPrivate::PrintSam(const BamAlignment& a) {
492   
493     // tab-delimited
494     // <QNAME> <FLAG> <RNAME> <POS> <MAPQ> <CIGAR> <MRNM> <MPOS> <ISIZE> <SEQ> <QUAL> [ <TAG>:<VTYPE>:<VALUE> [...] ]
495   
496     // write name & alignment flag
497     m_out << a.Name << "\t" << a.AlignmentFlag << "\t";
498     
499     // write reference name
500     if ( (a.RefID >= 0) && (a.RefID < (int)m_references.size()) ) 
501         m_out << m_references[a.RefID].RefName << "\t";
502     else 
503         m_out << "*\t";
504     
505     // write position & map quality
506     m_out << a.Position+1 << "\t" << a.MapQuality << "\t";
507     
508     // write CIGAR
509     const vector<CigarOp>& cigarData = a.CigarData;
510     if ( cigarData.empty() ) m_out << "*\t";
511     else {
512         vector<CigarOp>::const_iterator cigarIter = cigarData.begin();
513         vector<CigarOp>::const_iterator cigarEnd  = cigarData.end();
514         for ( ; cigarIter != cigarEnd; ++cigarIter ) {
515             const CigarOp& op = (*cigarIter);
516             m_out << op.Length << op.Type;
517         }
518         m_out << "\t";
519     }
520     
521     // write mate reference name, mate position, & insert size
522     if ( a.IsPaired() && (a.MateRefID >= 0) && (a.MateRefID < (int)m_references.size()) ) {
523         if ( a.MateRefID == a.RefID ) m_out << "=\t";
524         else m_out << m_references[a.MateRefID].RefName << "\t";
525         m_out << a.MatePosition+1 << "\t" << a.InsertSize << "\t";
526     } 
527     else m_out << "*\t0\t0\t";
528     
529     // write sequence
530     if ( a.QueryBases.empty() ) m_out << "*\t";
531     else m_out << a.QueryBases << "\t";
532     
533     // write qualities
534     if ( a.Qualities.empty() ) m_out << "*";
535     else m_out << a.Qualities;
536     
537     // write tag data
538     const char* tagData = a.TagData.c_str();
539     const size_t tagDataLength = a.TagData.length();
540     
541     size_t index = 0;
542     while ( index < tagDataLength ) {
543
544         // write tag name   
545         string tagName = a.TagData.substr(index, 2);
546         m_out << "\t" << tagName << ":";
547         index += 2;
548         
549         // get data type
550         char type = a.TagData.at(index);
551         ++index;
552         switch (type) {
553             case('A') : 
554                 m_out << "A:" << tagData[index]; 
555                 ++index; 
556                 break;
557             
558             case('C') : 
559                 m_out << "i:" << (int)tagData[index]; 
560                 ++index; 
561                 break;
562             
563             case('c') : 
564                 m_out << "i:" << (int)tagData[index];
565                 ++index; 
566                 break;
567             
568             case('S') : 
569                 m_out << "i:" << BgzfData::UnpackUnsignedShort(&tagData[index]);
570                 index += 2; 
571                 break;
572                 
573             case('s') : 
574                 m_out << "i:" << BgzfData::UnpackSignedShort(&tagData[index]);
575                 index += 2; 
576                 break;
577             
578             case('I') :
579                 m_out << "i:" << BgzfData::UnpackUnsignedInt(&tagData[index]);
580                 index += 4; 
581                 break;
582             
583             case('i') : 
584                 m_out << "i:" << BgzfData::UnpackSignedInt(&tagData[index]);
585                 index += 4; 
586                 break;
587             
588             case('f') : 
589                 m_out << "f:" << BgzfData::UnpackFloat(&tagData[index]);
590                 index += 4; 
591                 break;
592             
593             case('d') : 
594                 m_out << "d:" << BgzfData::UnpackDouble(&tagData[index]);
595                 index += 8; 
596                 break;
597             
598             case('Z') :
599             case('H') : 
600                 m_out << type << ":";
601                 while (tagData[index]) {
602                     m_out << tagData[index];
603                     ++index;
604                 }
605                 ++index; 
606                 break;      
607         }
608
609         if ( tagData[index] == '\0') 
610             break;
611     }
612
613     m_out << endl;
614 }
615
616 void ConvertTool::ConvertToolPrivate::PrintWiggle(const BamAlignment& a) { 
617     ; 
618 }
619
620 bool ConvertTool::ConvertToolPrivate::RunPileupConversion(BamMultiReader* reader) {
621   
622     // check for valid BamMultiReader
623     if ( reader == 0 ) return false;
624   
625     // set up our pileup format 'visitor'
626     ConvertPileupFormatVisitor* v = new ConvertPileupFormatVisitor(m_references, 
627                                                                    m_settings->FastaFilename,
628                                                                    m_settings->IsPrintingPileupMapQualities, 
629                                                                    &m_out);
630
631     // set up PileupEngine
632     PileupEngine pileup;
633     pileup.AddVisitor(v);
634     
635     // iterate through data
636     BamAlignment al;
637     while ( reader->GetNextAlignment(al) )
638         pileup.AddAlignment(al);
639     pileup.Flush();
640     
641     // clean up
642     delete v;
643     v = 0;
644     
645     // return success
646     return true;
647 }       
648
649 // ---------------------------------------------
650 // ConvertTool implementation
651
652 ConvertTool::ConvertTool(void)
653     : AbstractTool()
654     , m_settings(new ConvertSettings)
655     , m_impl(0)
656 {
657     // set program details
658     Options::SetProgramInfo("bamtools convert", "converts BAM to a number of other formats", "-format <FORMAT> [-in <filename> -in <filename> ...] [-out <filename>] [other options]");
659     
660     // set up options 
661     OptionGroup* IO_Opts = Options::CreateOptionGroup("Input & Output");
662     Options::AddValueOption("-in",     "BAM filename", "the input BAM file(s)", "", m_settings->HasInput,   m_settings->InputFiles,     IO_Opts, Options::StandardIn());
663     Options::AddValueOption("-out",    "BAM filename", "the output BAM file",   "", m_settings->HasOutput,  m_settings->OutputFilename, IO_Opts, Options::StandardOut());
664     Options::AddValueOption("-format", "FORMAT", "the output file format - see README for recognized formats", "", m_settings->HasFormat, m_settings->Format, IO_Opts);
665    
666     OptionGroup* FilterOpts = Options::CreateOptionGroup("Filters");
667     Options::AddValueOption("-region", "REGION", "genomic region. Index file is recommended for better performance, and is read automatically if it exists as <filename>.bai. See \'bamtools help index\' for more  details on creating one", "", m_settings->HasRegion, m_settings->Region, FilterOpts);
668     
669     OptionGroup* PileupOpts = Options::CreateOptionGroup("Pileup Options");
670     Options::AddValueOption("-fasta", "FASTA filename", "FASTA reference file", "", m_settings->HasFastaFilename, m_settings->FastaFilename, PileupOpts, "");
671     Options::AddOption("-mapqual", "print the mapping qualities", m_settings->IsPrintingPileupMapQualities, PileupOpts);
672     
673     OptionGroup* SamOpts = Options::CreateOptionGroup("SAM Options");
674     Options::AddOption("-noheader", "omit the SAM header from output", m_settings->IsOmittingSamHeader, SamOpts);
675 }
676
677 ConvertTool::~ConvertTool(void) {
678     delete m_settings;
679     m_settings = 0;
680     
681     delete m_impl;
682     m_impl = 0;
683 }
684
685 int ConvertTool::Help(void) {
686     Options::DisplayHelp();
687     return 0;
688 }
689
690 int ConvertTool::Run(int argc, char* argv[]) {
691   
692     // parse command line arguments
693     Options::Parse(argc, argv, 1);
694     
695     // run internal ConvertTool implementation, return success/fail
696     m_impl = new ConvertToolPrivate(m_settings);
697     
698     if ( m_impl->Run() ) 
699         return 0;
700     else 
701         return 1;
702 }
703
704 // ---------------------------------------------
705 // ConvertPileupFormatVisitor implementation
706
707 ConvertPileupFormatVisitor::ConvertPileupFormatVisitor(const RefVector& references, 
708                                                        const string& fastaFilename,
709                                                        const bool isPrintingMapQualities,
710                                                        ostream* out)
711     : PileupVisitor()
712     , m_hasFasta(false)
713     , m_isPrintingMapQualities(isPrintingMapQualities)
714     , m_out(out)
715     , m_references(references)
716
717     // set up Fasta reader if file is provided
718     if ( !fastaFilename.empty() ) {
719       
720         // check for FASTA index
721         string indexFilename = "";
722         if ( Utilities::FileExists(fastaFilename + ".fai") ) 
723             indexFilename = fastaFilename + ".fai";
724       
725         // open FASTA file
726         if ( m_fasta.Open(fastaFilename, indexFilename) ) 
727             m_hasFasta = true;
728     }
729 }
730
731 ConvertPileupFormatVisitor::~ConvertPileupFormatVisitor(void) { 
732     // be sure to close Fasta reader
733     if ( m_hasFasta ) {
734         m_fasta.Close();
735         m_hasFasta = false;
736     }
737 }
738
739 void ConvertPileupFormatVisitor::Visit(const PileupPosition& pileupData ) {
740   
741     // skip if no alignments at this position
742     if ( pileupData.PileupAlignments.empty() ) return;
743   
744     // retrieve reference name
745     const string& referenceName = m_references[pileupData.RefId].RefName;
746     const int& position   = pileupData.Position;
747     
748     // retrieve reference base from FASTA file, if one provided; otherwise default to 'N'
749     char referenceBase('N');
750     if ( m_hasFasta && (pileupData.Position < m_references[pileupData.RefId].RefLength) ) {
751         if ( !m_fasta.GetBase(pileupData.RefId, pileupData.Position, referenceBase ) ) {
752             cerr << "Pileup error : Could not read reference base from FASTA file" << endl;
753             return;
754         }
755     }
756     
757     // get count of alleles at this position
758     const int numberAlleles = pileupData.PileupAlignments.size();
759     
760     // -----------------------------------------------------------
761     // build strings based on alleles at this positionInAlignment
762     
763     stringstream bases("");
764     stringstream baseQualities("");
765     stringstream mapQualities("");
766     
767     // iterate over alignments at this pileup position
768     vector<PileupAlignment>::const_iterator pileupIter = pileupData.PileupAlignments.begin();
769     vector<PileupAlignment>::const_iterator pileupEnd  = pileupData.PileupAlignments.end();
770     for ( ; pileupIter != pileupEnd; ++pileupIter ) {
771         const PileupAlignment pa = (*pileupIter);
772         const BamAlignment& ba = pa.Alignment;
773         
774         // if beginning of read segment
775         if ( pa.IsSegmentBegin )
776             bases << '^' << ( ((int)ba.MapQuality > 93) ? (char)126 : (char)((int)ba.MapQuality+33) );
777         
778         // if current base is not a DELETION
779         if ( !pa.IsCurrentDeletion ) {
780           
781             // get base at current position
782             char base = ba.QueryBases.at(pa.PositionInAlignment);
783             
784             // if base matches reference
785             if ( base == '=' || 
786                  toupper(base) == toupper(referenceBase) ||
787                  tolower(base) == tolower(referenceBase) ) 
788             {
789                 base = (ba.IsReverseStrand() ? ',' : '.' );
790             }
791             
792             // mismatches reference
793             else base = (ba.IsReverseStrand() ? tolower(base) : toupper(base) );
794             
795             // store base
796             bases << base;
797           
798             // if next position contains insertion
799             if ( pa.IsNextInsertion ) {
800                 bases << '+' << pa.InsertionLength;
801                 for (int i = 1; i <= pa.InsertionLength; ++i) {
802                     char insertedBase = (char)ba.QueryBases.at(pa.PositionInAlignment + i);
803                     bases << (ba.IsReverseStrand() ? (char)tolower(insertedBase) : (char)toupper(insertedBase) );
804                 }
805             }
806             
807             // if next position contains DELETION
808             else if ( pa.IsNextDeletion ) {
809                 bases << '-' << pa.DeletionLength;
810                 for (int i = 1; i <= pa.DeletionLength; ++i) {
811                     char deletedBase('N');
812                     if ( m_hasFasta && (pileupData.Position+i < m_references[pileupData.RefId].RefLength) ) {
813                         if ( !m_fasta.GetBase(pileupData.RefId, pileupData.Position+i, deletedBase ) ) {
814                             cerr << "Pileup error : Could not read reference base from FASTA file" << endl;
815                             return;
816                         }
817                     }
818                     bases << (ba.IsReverseStrand() ? (char)tolower(deletedBase) : (char)toupper(deletedBase) );
819                 }
820             }
821         }
822         
823         // otherwise, DELETION
824         else bases << '*';
825         
826         // if end of read segment
827         if ( pa.IsSegmentEnd ) bases << '$';
828         
829         // store current base quality
830         baseQualities << ba.Qualities.at(pa.PositionInAlignment);
831         
832         // save alignment map quality if desired
833         if ( m_isPrintingMapQualities )
834             mapQualities << ( ((int)ba.MapQuality > 93) ? (char)126 : (char)((int)ba.MapQuality+33) );
835     }
836     
837     // ----------------------
838     // print results 
839     
840     // tab-delimited
841     // <refName> <1-based pos> <refBase> <numberAlleles> <bases> <qualities> [mapQuals]
842     
843     const string TAB = "\t";
844     *m_out << referenceName       << TAB 
845            << position + 1        << TAB 
846            << referenceBase       << TAB 
847            << numberAlleles       << TAB 
848            << bases.str()         << TAB 
849            << baseQualities.str() << TAB
850            << mapQualities.str()  << endl;
851 }