]> git.donarmstrong.com Git - bamtools.git/blob - BamReader.cpp
Major speedup of BamReader - removed full character data building until alignment...
[bamtools.git] / BamReader.cpp
1 // ***************************************************************************\r
2 // BamReader.cpp (c) 2009 Derek Barnett, Michael Str�mberg\r
3 // Marth Lab, Department of Biology, Boston College\r
4 // All rights reserved.\r
5 // ---------------------------------------------------------------------------\r
6 // Last modified: 6 April 2010 (DB)\r
7 // ---------------------------------------------------------------------------\r
8 // Uses BGZF routines were adapted from the bgzf.c code developed at the Broad\r
9 // Institute.\r
10 // ---------------------------------------------------------------------------\r
11 // Provides the basic functionality for reading BAM files\r
12 // ***************************************************************************\r
13 \r
14 // C++ includes\r
15 #include <algorithm>\r
16 #include <iterator>\r
17 #include <string>\r
18 #include <vector>\r
19 \r
20 // BamTools includes\r
21 #include "BGZF.h"\r
22 #include "BamReader.h"\r
23 using namespace BamTools;\r
24 using namespace std;\r
25 \r
26 namespace BamTools {\r
27   struct BamAlignmentSupportData {\r
28       string   AllCharData;\r
29       uint32_t BlockLength;\r
30       uint32_t NumCigarOperations;\r
31       uint32_t QueryNameLength;\r
32       uint32_t QuerySequenceLength;\r
33   };\r
34 } // namespace BamTools\r
35 \r
36 struct BamReader::BamReaderPrivate {\r
37 \r
38     // -------------------------------\r
39     // data members\r
40     // -------------------------------\r
41 \r
42     // general data\r
43     BgzfData  mBGZF;\r
44     string    HeaderText;\r
45     BamIndex  Index;\r
46     RefVector References;\r
47     bool      IsIndexLoaded;\r
48     int64_t   AlignmentsBeginOffset;\r
49     string    Filename;\r
50     string    IndexFilename;\r
51     \r
52     bool IsBigEndian;\r
53 \r
54     // user-specified region values\r
55     bool IsRegionSpecified;\r
56     int  CurrentRefID;\r
57     int  CurrentLeft;\r
58 \r
59     // BAM character constants\r
60     const char* DNA_LOOKUP;\r
61     const char* CIGAR_LOOKUP;\r
62 \r
63     // -------------------------------\r
64     // constructor & destructor\r
65     // -------------------------------\r
66     BamReaderPrivate(void);\r
67     ~BamReaderPrivate(void);\r
68 \r
69     // -------------------------------\r
70     // "public" interface\r
71     // -------------------------------\r
72 \r
73     // flie operations\r
74     void Close(void);\r
75     bool Jump(int refID, int position = 0);\r
76     void Open(const string& filename, const string& indexFilename = "");\r
77     bool Rewind(void);\r
78 \r
79     // access alignment data\r
80     bool GetNextAlignment(BamAlignment& bAlignment);\r
81 \r
82     // access auxiliary data\r
83     int GetReferenceID(const string& refName) const;\r
84 \r
85     // index operations\r
86     bool CreateIndex(void);\r
87 \r
88     // -------------------------------\r
89     // internal methods\r
90     // -------------------------------\r
91 \r
92     // *** reading alignments and auxiliary data *** //\r
93 \r
94     // calculate bins that overlap region ( left to reference end for now )\r
95     int BinsFromRegion(int refID, int left, uint16_t[MAX_BIN]);\r
96     // fills out character data for BamAlignment data\r
97     bool BuildCharData(BamAlignment& bAlignment, const BamAlignmentSupportData& supportData);\r
98     // calculates alignment end position based on starting position and provided CIGAR operations\r
99     int CalculateAlignmentEnd(const int& position, const std::vector<CigarOp>& cigarData);\r
100     // calculate file offset for first alignment chunk overlapping 'left'\r
101     int64_t GetOffset(int refID, int left);\r
102     // checks to see if alignment overlaps current region\r
103     bool IsOverlap(BamAlignment& bAlignment);\r
104     // retrieves header text from BAM file\r
105     void LoadHeaderData(void);\r
106     // retrieves BAM alignment under file pointer\r
107     bool LoadNextAlignment(BamAlignment& bAlignment, BamAlignmentSupportData& supportData);\r
108     // builds reference data structure from BAM file\r
109     void LoadReferenceData(void);\r
110 \r
111     // *** index file handling *** //\r
112 \r
113     // calculates index for BAM file\r
114     bool BuildIndex(void);\r
115     // clear out inernal index data structure\r
116     void ClearIndex(void);\r
117     // saves BAM bin entry for index\r
118     void InsertBinEntry(BamBinMap& binMap, const uint32_t& saveBin, const uint64_t& saveOffset, const uint64_t& lastOffset);\r
119     // saves linear offset entry for index\r
120     void InsertLinearOffset(LinearOffsetVector& offsets, const BamAlignment& bAlignment, const uint64_t& lastOffset);\r
121     // loads index from BAM index file\r
122     bool LoadIndex(void);\r
123     // simplifies index by merging 'chunks'\r
124     void MergeChunks(void);\r
125     // saves index to BAM index file\r
126     bool WriteIndex(void);\r
127 };\r
128 \r
129 // -----------------------------------------------------\r
130 // BamReader implementation (wrapper around BRPrivate)\r
131 // -----------------------------------------------------\r
132 \r
133 // constructor\r
134 BamReader::BamReader(void) {\r
135     d = new BamReaderPrivate;\r
136 }\r
137 \r
138 // destructor\r
139 BamReader::~BamReader(void) {\r
140     delete d;\r
141     d = 0;\r
142 }\r
143 \r
144 // file operations\r
145 void BamReader::Close(void) { d->Close(); }\r
146 bool BamReader::Jump(int refID, int position) { return d->Jump(refID, position); }\r
147 void BamReader::Open(const string& filename, const string& indexFilename) { d->Open(filename, indexFilename); }\r
148 bool BamReader::Rewind(void) { return d->Rewind(); }\r
149 \r
150 // access alignment data\r
151 bool BamReader::GetNextAlignment(BamAlignment& bAlignment) { return d->GetNextAlignment(bAlignment); }\r
152 \r
153 // access auxiliary data\r
154 const string BamReader::GetHeaderText(void) const { return d->HeaderText; }\r
155 int BamReader::GetReferenceCount(void) const { return d->References.size(); }\r
156 const RefVector BamReader::GetReferenceData(void) const { return d->References; }\r
157 int BamReader::GetReferenceID(const string& refName) const { return d->GetReferenceID(refName); }\r
158 \r
159 // index operations\r
160 bool BamReader::CreateIndex(void) { return d->CreateIndex(); }\r
161 \r
162 // -----------------------------------------------------\r
163 // BamReaderPrivate implementation\r
164 // -----------------------------------------------------\r
165 \r
166 // constructor\r
167 BamReader::BamReaderPrivate::BamReaderPrivate(void)\r
168     : IsIndexLoaded(false)\r
169     , AlignmentsBeginOffset(0)\r
170     , IsRegionSpecified(false)\r
171     , CurrentRefID(0)\r
172     , CurrentLeft(0)\r
173     , DNA_LOOKUP("=ACMGRSVTWYHKDBN")\r
174     , CIGAR_LOOKUP("MIDNSHP")\r
175\r
176     IsBigEndian = SystemIsBigEndian();\r
177 }\r
178 \r
179 // destructor\r
180 BamReader::BamReaderPrivate::~BamReaderPrivate(void) {\r
181     Close();\r
182 }\r
183 \r
184 // calculate bins that overlap region ( left to reference end for now )\r
185 int BamReader::BamReaderPrivate::BinsFromRegion(int refID, int left, uint16_t list[MAX_BIN]) {\r
186 \r
187     // get region boundaries\r
188     uint32_t begin = (unsigned int)left;\r
189     uint32_t end   = (unsigned int)References.at(refID).RefLength - 1;\r
190 \r
191     // initialize list, bin '0' always a valid bin\r
192     int i = 0;\r
193     list[i++] = 0;\r
194 \r
195     // get rest of bins that contain this region\r
196     unsigned int k;\r
197     for (k =    1 + (begin>>26); k <=    1 + (end>>26); ++k) { list[i++] = k; }\r
198     for (k =    9 + (begin>>23); k <=    9 + (end>>23); ++k) { list[i++] = k; }\r
199     for (k =   73 + (begin>>20); k <=   73 + (end>>20); ++k) { list[i++] = k; }\r
200     for (k =  585 + (begin>>17); k <=  585 + (end>>17); ++k) { list[i++] = k; }\r
201     for (k = 4681 + (begin>>14); k <= 4681 + (end>>14); ++k) { list[i++] = k; }\r
202 \r
203     // return number of bins stored\r
204     return i;\r
205 }\r
206 \r
207 bool BamReader::BamReaderPrivate::BuildCharData(BamAlignment& bAlignment, const BamAlignmentSupportData& supportData) {\r
208   \r
209     // calculate character lengths/offsets\r
210     const unsigned int dataLength     = supportData.BlockLength - BAM_CORE_SIZE;\r
211     const unsigned int seqDataOffset  = supportData.QueryNameLength + (supportData.NumCigarOperations * 4);\r
212     const unsigned int qualDataOffset = seqDataOffset + (supportData.QuerySequenceLength+1)/2;\r
213     const unsigned int tagDataOffset  = qualDataOffset + supportData.QuerySequenceLength;\r
214     const unsigned int tagDataLength  = dataLength - tagDataOffset;\r
215       \r
216     // set up char buffers\r
217     const char* allCharData = supportData.AllCharData.data();\r
218     const char* seqData     = ((const char*)allCharData) + seqDataOffset;\r
219     const char* qualData    = ((const char*)allCharData) + qualDataOffset;\r
220     char* tagData     = ((char*)allCharData) + tagDataOffset;\r
221   \r
222     // save query sequence\r
223     bAlignment.QueryBases.clear();\r
224     bAlignment.QueryBases.reserve(supportData.QuerySequenceLength);\r
225     for (unsigned int i = 0; i < supportData.QuerySequenceLength; ++i) {\r
226         char singleBase = DNA_LOOKUP[ ( ( seqData[(i/2)] >> (4*(1-(i%2)))) & 0xf ) ];\r
227         bAlignment.QueryBases.append(1, singleBase);\r
228     }\r
229   \r
230     // save qualities, converting from numeric QV to 'FASTQ-style' ASCII character\r
231     bAlignment.Qualities.clear();\r
232     bAlignment.Qualities.reserve(supportData.QuerySequenceLength);\r
233     for (unsigned int i = 0; i < supportData.QuerySequenceLength; ++i) {\r
234         char singleQuality = (char)(qualData[i]+33);\r
235         bAlignment.Qualities.append(1, singleQuality);\r
236     }\r
237     \r
238     // parse CIGAR to build 'AlignedBases'\r
239     bAlignment.AlignedBases.clear();\r
240     bAlignment.AlignedBases.reserve(supportData.QuerySequenceLength);\r
241     \r
242     int k = 0;\r
243     vector<CigarOp>::const_iterator cigarIter = bAlignment.CigarData.begin();\r
244     vector<CigarOp>::const_iterator cigarEnd  = bAlignment.CigarData.end();\r
245     for ( ; cigarIter != cigarEnd; ++cigarIter ) {\r
246         \r
247         const CigarOp& op = (*cigarIter);\r
248         switch(op.Type) {\r
249           \r
250             case ('M') :\r
251             case ('I') :\r
252                 bAlignment.AlignedBases.append(bAlignment.QueryBases.substr(k, op.Length)); // for 'M', 'I' - write bases\r
253                 // fall through\r
254             \r
255             case ('S') :\r
256                 k += op.Length;                                     // for 'S' - soft clip, skip over query bases\r
257                 break;\r
258                 \r
259             case ('D') :\r
260                 bAlignment.AlignedBases.append(op.Length, '-');     // for 'D' - write gap character\r
261                 break;\r
262                 \r
263             case ('P') :\r
264                 bAlignment.AlignedBases.append( op.Length, '*' );   // for 'P' - write padding character\r
265                 break;\r
266                 \r
267             case ('N') :\r
268                 bAlignment.AlignedBases.append( op.Length, 'N' );  // for 'N' - write N's, skip bases in original query sequence\r
269                 // k+=op.Length; \r
270                 break;\r
271                 \r
272             case ('H') :\r
273                 break;  // for 'H' - hard clip, do nothing to AlignedBases, move to next op\r
274                 \r
275             default:\r
276                 printf("ERROR: Invalid Cigar op type\n"); // shouldn't get here\r
277                 exit(1);\r
278         }\r
279     }\r
280  \r
281     // -----------------------\r
282     // Added: 3-25-2010 DWB\r
283     // Fixed: endian-correctness for tag data\r
284     // -----------------------\r
285     if ( IsBigEndian ) {\r
286         int i = 0;\r
287         while ( (unsigned int)i < tagDataLength ) {\r
288           \r
289             i += 2; // skip tag type (e.g. "RG", "NM", etc)\r
290             uint8_t type = toupper(tagData[i]);     // lower & upper case letters have same meaning \r
291             ++i;                                    // skip value type\r
292     \r
293             switch (type) {\r
294                 \r
295                 case('A') :\r
296                 case('C') : \r
297                     ++i;\r
298                     break;\r
299 \r
300                 case('S') : \r
301                     SwapEndian_16p(&tagData[i]); \r
302                     i+=2; // sizeof(uint16_t)\r
303                     break;\r
304                     \r
305                 case('F') :\r
306                 case('I') : \r
307                     SwapEndian_32p(&tagData[i]);\r
308                     i+=4; // sizeof(uint32_t)\r
309                     break;\r
310                 \r
311                 case('D') : \r
312                     SwapEndian_64p(&tagData[i]);\r
313                     i+=8; // sizeof(uint64_t) \r
314                     break;\r
315                 \r
316                 case('H') :\r
317                 case('Z') : \r
318                     while (tagData[i]) { ++i; }\r
319                     ++i; // increment one more for null terminator\r
320                     break;\r
321                 \r
322                 default : \r
323                     printf("ERROR: Invalid tag value type\n"); // shouldn't get here\r
324                     exit(1);\r
325             }\r
326         }\r
327     }\r
328     \r
329     // store TagData\r
330     bAlignment.TagData.clear();\r
331     bAlignment.TagData.resize(tagDataLength);\r
332     memcpy((char*)bAlignment.TagData.data(), tagData, tagDataLength);\r
333     \r
334     // return success\r
335     return true;\r
336 }\r
337 \r
338 // populates BAM index data structure from BAM file data\r
339 bool BamReader::BamReaderPrivate::BuildIndex(void) {\r
340 \r
341     // check to be sure file is open\r
342     if (!mBGZF.IsOpen) { return false; }\r
343 \r
344     // move file pointer to beginning of alignments\r
345     Rewind();\r
346 \r
347     // get reference count, reserve index space\r
348     int numReferences = References.size();\r
349     for ( int i = 0; i < numReferences; ++i ) {\r
350         Index.push_back(ReferenceIndex());\r
351     }\r
352 \r
353     // sets default constant for bin, ID, offset, coordinate variables\r
354     const uint32_t defaultValue = 0xffffffffu;\r
355 \r
356     // bin data\r
357     uint32_t saveBin(defaultValue);\r
358     uint32_t lastBin(defaultValue);\r
359 \r
360     // reference ID data\r
361     int32_t saveRefID(defaultValue);\r
362     int32_t lastRefID(defaultValue);\r
363 \r
364     // offset data\r
365     uint64_t saveOffset = mBGZF.Tell();\r
366     uint64_t lastOffset = saveOffset;\r
367 \r
368     // coordinate data\r
369     int32_t lastCoordinate = defaultValue;\r
370 \r
371     BamAlignment bAlignment;\r
372     while( GetNextAlignment(bAlignment) ) {\r
373 \r
374         // change of chromosome, save ID, reset bin\r
375         if ( lastRefID != bAlignment.RefID ) {\r
376             lastRefID = bAlignment.RefID;\r
377             lastBin   = defaultValue;\r
378         }\r
379 \r
380         // if lastCoordinate greater than BAM position - file not sorted properly\r
381         else if ( lastCoordinate > bAlignment.Position ) {\r
382             printf("BAM file not properly sorted:\n");\r
383             printf("Alignment %s : %d > %d on reference (id = %d)", bAlignment.Name.c_str(), lastCoordinate, bAlignment.Position, bAlignment.RefID);\r
384             exit(1);\r
385         }\r
386 \r
387         // if valid reference && BAM bin spans some minimum cutoff (smaller bin ids span larger regions)\r
388         if ( (bAlignment.RefID >= 0) && (bAlignment.Bin < 4681) ) {\r
389 \r
390             // save linear offset entry (matched to BAM entry refID)\r
391             ReferenceIndex& refIndex = Index.at(bAlignment.RefID);\r
392             LinearOffsetVector& offsets = refIndex.Offsets;\r
393             InsertLinearOffset(offsets, bAlignment, lastOffset);\r
394         }\r
395 \r
396         // if current BamAlignment bin != lastBin, "then possibly write the binning index"\r
397         if ( bAlignment.Bin != lastBin ) {\r
398 \r
399             // if not first time through\r
400             if ( saveBin != defaultValue ) {\r
401 \r
402                 // save Bam bin entry\r
403                 ReferenceIndex& refIndex = Index.at(saveRefID);\r
404                 BamBinMap& binMap = refIndex.Bins;\r
405                 InsertBinEntry(binMap, saveBin, saveOffset, lastOffset);\r
406             }\r
407 \r
408             // update saveOffset\r
409             saveOffset = lastOffset;\r
410 \r
411             // update bin values\r
412             saveBin = bAlignment.Bin;\r
413             lastBin = bAlignment.Bin;\r
414 \r
415             // update saveRefID\r
416             saveRefID = bAlignment.RefID;\r
417 \r
418             // if invalid RefID, break out (why?)\r
419             if ( saveRefID < 0 ) { break; }\r
420         }\r
421 \r
422         // make sure that current file pointer is beyond lastOffset\r
423         if ( mBGZF.Tell() <= (int64_t)lastOffset  ) {\r
424             printf("Error in BGZF offsets.\n");\r
425             exit(1);\r
426         }\r
427 \r
428         // update lastOffset\r
429         lastOffset = mBGZF.Tell();\r
430 \r
431         // update lastCoordinate\r
432         lastCoordinate = bAlignment.Position;\r
433     }\r
434 \r
435     // save any leftover BAM data (as long as refID is valid)\r
436     if ( saveRefID >= 0 ) {\r
437         // save Bam bin entry\r
438         ReferenceIndex& refIndex = Index.at(saveRefID);\r
439         BamBinMap& binMap = refIndex.Bins;\r
440         InsertBinEntry(binMap, saveBin, saveOffset, lastOffset);\r
441     }\r
442 \r
443     // simplify index by merging chunks\r
444     MergeChunks();\r
445 \r
446     // iterate over references\r
447     BamIndex::iterator indexIter = Index.begin();\r
448     BamIndex::iterator indexEnd  = Index.end();\r
449     for ( int i = 0; indexIter != indexEnd; ++indexIter, ++i ) {\r
450 \r
451         // get reference index data\r
452         ReferenceIndex& refIndex = (*indexIter);\r
453         BamBinMap& binMap = refIndex.Bins;\r
454         LinearOffsetVector& offsets = refIndex.Offsets;\r
455 \r
456         // store whether reference has alignments or no\r
457         References[i].RefHasAlignments = ( binMap.size() > 0 );\r
458 \r
459         // sort linear offsets\r
460         sort(offsets.begin(), offsets.end());\r
461     }\r
462 \r
463 \r
464     // rewind file pointer to beginning of alignments, return success/fail\r
465     return Rewind();\r
466 }\r
467 \r
468 // calculates alignment end position based on starting position and provided CIGAR operations\r
469 int BamReader::BamReaderPrivate::CalculateAlignmentEnd(const int& position, const vector<CigarOp>& cigarData) {\r
470 \r
471     // initialize alignment end to starting position\r
472     int alignEnd = position;\r
473 \r
474     // iterate over cigar operations\r
475     vector<CigarOp>::const_iterator cigarIter = cigarData.begin();\r
476     vector<CigarOp>::const_iterator cigarEnd  = cigarData.end();\r
477     for ( ; cigarIter != cigarEnd; ++cigarIter) {\r
478         char cigarType = (*cigarIter).Type;\r
479         if ( cigarType == 'M' || cigarType == 'D' || cigarType == 'N' ) {\r
480             alignEnd += (*cigarIter).Length;\r
481         }\r
482     }\r
483     return alignEnd;\r
484 }\r
485 \r
486 \r
487 // clear index data structure\r
488 void BamReader::BamReaderPrivate::ClearIndex(void) {\r
489     Index.clear(); // sufficient ??\r
490 }\r
491 \r
492 // closes the BAM file\r
493 void BamReader::BamReaderPrivate::Close(void) {\r
494     mBGZF.Close();\r
495     ClearIndex();\r
496     HeaderText.clear();\r
497     IsRegionSpecified = false;\r
498 }\r
499 \r
500 // create BAM index from BAM file (keep structure in memory) and write to default index output file\r
501 bool BamReader::BamReaderPrivate::CreateIndex(void) {\r
502 \r
503     // clear out index\r
504     ClearIndex();\r
505 \r
506     // build (& save) index from BAM file\r
507     bool ok = true;\r
508     ok &= BuildIndex();\r
509     ok &= WriteIndex();\r
510 \r
511     // return success/fail\r
512     return ok;\r
513 }\r
514 \r
515 // returns RefID for given RefName (returns References.size() if not found)\r
516 int BamReader::BamReaderPrivate::GetReferenceID(const string& refName) const {\r
517 \r
518     // retrieve names from reference data\r
519     vector<string> refNames;\r
520     RefVector::const_iterator refIter = References.begin();\r
521     RefVector::const_iterator refEnd  = References.end();\r
522     for ( ; refIter != refEnd; ++refIter) {\r
523         refNames.push_back( (*refIter).RefName );\r
524     }\r
525 \r
526     // return 'index-of' refName ( if not found, returns refNames.size() )\r
527     return distance(refNames.begin(), find(refNames.begin(), refNames.end(), refName));\r
528 }\r
529 \r
530 // get next alignment (from specified region, if given)\r
531 bool BamReader::BamReaderPrivate::GetNextAlignment(BamAlignment& bAlignment) {\r
532 \r
533     BamAlignmentSupportData supportData;\r
534   \r
535     // if valid alignment available\r
536     if ( LoadNextAlignment(bAlignment, supportData) ) {\r
537 \r
538         // if region not specified, return success\r
539         if ( !IsRegionSpecified ) { \r
540           bool ok = BuildCharData(bAlignment, supportData);\r
541           return ok; \r
542         }\r
543 \r
544         // load next alignment until region overlap is found\r
545         while ( !IsOverlap(bAlignment) ) {\r
546             // if no valid alignment available (likely EOF) return failure\r
547             if ( !LoadNextAlignment(bAlignment, supportData) ) { return false; }\r
548         }\r
549 \r
550         // return success (alignment found that overlaps region)\r
551         bool ok = BuildCharData(bAlignment, supportData);\r
552         return ok;\r
553     }\r
554 \r
555     // no valid alignment\r
556     else { return false; }\r
557 }\r
558 \r
559 // calculate closest indexed file offset for region specified\r
560 int64_t BamReader::BamReaderPrivate::GetOffset(int refID, int left) {\r
561 \r
562     // calculate which bins overlap this region\r
563     uint16_t* bins = (uint16_t*)calloc(MAX_BIN, 2);\r
564     int numBins = BinsFromRegion(refID, left, bins);\r
565 \r
566     // get bins for this reference\r
567     const ReferenceIndex& refIndex = Index.at(refID);\r
568     const BamBinMap& binMap        = refIndex.Bins;\r
569 \r
570     // get minimum offset to consider\r
571     const LinearOffsetVector& offsets = refIndex.Offsets;\r
572     uint64_t minOffset = ( (unsigned int)(left>>BAM_LIDX_SHIFT) >= offsets.size() ) ? 0 : offsets.at(left>>BAM_LIDX_SHIFT);\r
573 \r
574     // store offsets to beginning of alignment 'chunks'\r
575     std::vector<int64_t> chunkStarts;\r
576 \r
577     // store all alignment 'chunk' starts for bins in this region\r
578     for (int i = 0; i < numBins; ++i ) {\r
579         uint16_t binKey = bins[i];\r
580 \r
581         map<uint32_t, ChunkVector>::const_iterator binIter = binMap.find(binKey);\r
582         if ( (binIter != binMap.end()) && ((*binIter).first == binKey) ) {\r
583 \r
584             const ChunkVector& chunks = (*binIter).second;\r
585             std::vector<Chunk>::const_iterator chunksIter = chunks.begin();\r
586             std::vector<Chunk>::const_iterator chunksEnd  = chunks.end();\r
587             for ( ; chunksIter != chunksEnd; ++chunksIter) {\r
588                 const Chunk& chunk = (*chunksIter);\r
589                 if ( chunk.Stop > minOffset ) {\r
590                     chunkStarts.push_back( chunk.Start );\r
591                 }\r
592             }\r
593         }\r
594     }\r
595 \r
596     // clean up memory\r
597     free(bins);\r
598 \r
599     // if no alignments found, else return smallest offset for alignment starts\r
600     if ( chunkStarts.size() == 0 ) { return -1; }\r
601     else { return *min_element(chunkStarts.begin(), chunkStarts.end()); }\r
602 }\r
603 \r
604 // saves BAM bin entry for index\r
605 void BamReader::BamReaderPrivate::InsertBinEntry(BamBinMap&      binMap,\r
606                                                  const uint32_t& saveBin,\r
607                                                  const uint64_t& saveOffset,\r
608                                                  const uint64_t& lastOffset)\r
609 {\r
610     // look up saveBin\r
611     BamBinMap::iterator binIter = binMap.find(saveBin);\r
612 \r
613     // create new chunk\r
614     Chunk newChunk(saveOffset, lastOffset);\r
615 \r
616     // if entry doesn't exist\r
617     if ( binIter == binMap.end() ) {\r
618         ChunkVector newChunks;\r
619         newChunks.push_back(newChunk);\r
620         binMap.insert( pair<uint32_t, ChunkVector>(saveBin, newChunks));\r
621     }\r
622 \r
623     // otherwise\r
624     else {\r
625         ChunkVector& binChunks = (*binIter).second;\r
626         binChunks.push_back( newChunk );\r
627     }\r
628 }\r
629 \r
630 // saves linear offset entry for index\r
631 void BamReader::BamReaderPrivate::InsertLinearOffset(LinearOffsetVector& offsets,\r
632                                                      const BamAlignment& bAlignment,\r
633                                                      const uint64_t&     lastOffset)\r
634 {\r
635     // get converted offsets\r
636     int beginOffset = bAlignment.Position >> BAM_LIDX_SHIFT;\r
637     int endOffset   = ( CalculateAlignmentEnd(bAlignment.Position, bAlignment.CigarData) - 1) >> BAM_LIDX_SHIFT;\r
638 \r
639     // resize vector if necessary\r
640     int oldSize = offsets.size();\r
641     int newSize = endOffset + 1;\r
642     if ( oldSize < newSize ) { offsets.resize(newSize, 0); }\r
643 \r
644     // store offset\r
645     for(int i = beginOffset + 1; i <= endOffset ; ++i) {\r
646         if ( offsets[i] == 0) {\r
647             offsets[i] = lastOffset;\r
648         }\r
649     }\r
650 }\r
651 \r
652 // returns whether alignment overlaps currently specified region (refID, leftBound)\r
653 bool BamReader::BamReaderPrivate::IsOverlap(BamAlignment& bAlignment) {\r
654 \r
655     // if on different reference sequence, quit\r
656     if ( bAlignment.RefID != CurrentRefID ) { return false; }\r
657 \r
658     // read starts after left boundary\r
659     if ( bAlignment.Position >= CurrentLeft) { return true; }\r
660 \r
661     // return whether alignment end overlaps left boundary\r
662     return ( CalculateAlignmentEnd(bAlignment.Position, bAlignment.CigarData) >= CurrentLeft );\r
663 }\r
664 \r
665 // jumps to specified region(refID, leftBound) in BAM file, returns success/fail\r
666 bool BamReader::BamReaderPrivate::Jump(int refID, int position) {\r
667 \r
668     // if data exists for this reference and position is valid    \r
669     if ( References.at(refID).RefHasAlignments && (position <= References.at(refID).RefLength) ) {\r
670 \r
671         // set current region\r
672         CurrentRefID = refID;\r
673         CurrentLeft  = position;\r
674         IsRegionSpecified = true;\r
675 \r
676         // calculate offset\r
677         int64_t offset = GetOffset(CurrentRefID, CurrentLeft);\r
678 \r
679         // if in valid offset, return failure\r
680         if ( offset == -1 ) { return false; }\r
681 \r
682         // otherwise return success of seek operation\r
683         else { return mBGZF.Seek(offset); }\r
684     }\r
685 \r
686     // invalid jump request parameters, return failure\r
687     return false;\r
688 }\r
689 \r
690 // load BAM header data\r
691 void BamReader::BamReaderPrivate::LoadHeaderData(void) {\r
692 \r
693     // check to see if proper BAM header\r
694     char buffer[4];\r
695     if (mBGZF.Read(buffer, 4) != 4) {\r
696         printf("Could not read header type\n");\r
697         exit(1);\r
698     }\r
699 \r
700     if (strncmp(buffer, "BAM\001", 4)) {\r
701         printf("wrong header type!\n");\r
702         exit(1);\r
703     }\r
704 \r
705     // get BAM header text length\r
706     mBGZF.Read(buffer, 4);\r
707     unsigned int headerTextLength = BgzfData::UnpackUnsignedInt(buffer);\r
708     if ( IsBigEndian ) { SwapEndian_32(headerTextLength); }\r
709     \r
710     // get BAM header text\r
711     char* headerText = (char*)calloc(headerTextLength + 1, 1);\r
712     mBGZF.Read(headerText, headerTextLength);\r
713     HeaderText = (string)((const char*)headerText);\r
714 \r
715     // clean up calloc-ed temp variable\r
716     free(headerText);\r
717 }\r
718 \r
719 // load existing index data from BAM index file (".bai"), return success/fail\r
720 bool BamReader::BamReaderPrivate::LoadIndex(void) {\r
721 \r
722     // clear out index data\r
723     ClearIndex();\r
724 \r
725     // skip if index file empty\r
726     if ( IndexFilename.empty() ) { return false; }\r
727 \r
728     // open index file, abort on error\r
729     FILE* indexStream = fopen(IndexFilename.c_str(), "rb");\r
730     if(!indexStream) {\r
731         printf("ERROR: Unable to open the BAM index file %s for reading.\n", IndexFilename.c_str() );\r
732         return false;\r
733     }\r
734 \r
735     size_t elementsRead = 0;\r
736         \r
737     // see if index is valid BAM index\r
738     char magic[4];\r
739     elementsRead = fread(magic, 1, 4, indexStream);\r
740     if (strncmp(magic, "BAI\1", 4)) {\r
741         printf("Problem with index file - invalid format.\n");\r
742         fclose(indexStream);\r
743         return false;\r
744     }\r
745 \r
746     // get number of reference sequences\r
747     uint32_t numRefSeqs;\r
748     elementsRead = fread(&numRefSeqs, 4, 1, indexStream);\r
749     if ( IsBigEndian ) { SwapEndian_32(numRefSeqs); }\r
750     \r
751     // intialize space for BamIndex data structure\r
752     Index.reserve(numRefSeqs);\r
753 \r
754     // iterate over reference sequences\r
755     for (unsigned int i = 0; i < numRefSeqs; ++i) {\r
756 \r
757         // get number of bins for this reference sequence\r
758         int32_t numBins;\r
759         elementsRead = fread(&numBins, 4, 1, indexStream);\r
760         if ( IsBigEndian ) { SwapEndian_32(numBins); }\r
761 \r
762         if (numBins > 0) {\r
763             RefData& refEntry = References[i];\r
764             refEntry.RefHasAlignments = true;\r
765         }\r
766 \r
767         // intialize BinVector\r
768         BamBinMap binMap;\r
769 \r
770         // iterate over bins for that reference sequence\r
771         for (int j = 0; j < numBins; ++j) {\r
772 \r
773             // get binID\r
774             uint32_t binID;\r
775             elementsRead = fread(&binID, 4, 1, indexStream);\r
776 \r
777             // get number of regionChunks in this bin\r
778             uint32_t numChunks;\r
779             elementsRead = fread(&numChunks, 4, 1, indexStream);\r
780 \r
781             if ( IsBigEndian ) { \r
782               SwapEndian_32(binID);\r
783               SwapEndian_32(numChunks);\r
784             }\r
785             \r
786             // intialize ChunkVector\r
787             ChunkVector regionChunks;\r
788             regionChunks.reserve(numChunks);\r
789 \r
790             // iterate over regionChunks in this bin\r
791             for (unsigned int k = 0; k < numChunks; ++k) {\r
792 \r
793                 // get chunk boundaries (left, right)\r
794                 uint64_t left;\r
795                 uint64_t right;\r
796                 elementsRead = fread(&left, 8, 1, indexStream);\r
797                 elementsRead = fread(&right, 8, 1, indexStream);\r
798 \r
799                 if ( IsBigEndian ) {\r
800                     SwapEndian_64(left);\r
801                     SwapEndian_64(right);\r
802                 }\r
803                 \r
804                 // save ChunkPair\r
805                 regionChunks.push_back( Chunk(left, right) );\r
806             }\r
807 \r
808             // sort chunks for this bin\r
809             sort( regionChunks.begin(), regionChunks.end(), ChunkLessThan );\r
810 \r
811             // save binID, chunkVector for this bin\r
812             binMap.insert( pair<uint32_t, ChunkVector>(binID, regionChunks) );\r
813         }\r
814 \r
815         // load linear index for this reference sequence\r
816 \r
817         // get number of linear offsets\r
818         int32_t numLinearOffsets;\r
819         elementsRead = fread(&numLinearOffsets, 4, 1, indexStream);\r
820         if ( IsBigEndian ) { SwapEndian_32(numLinearOffsets); }\r
821 \r
822         // intialize LinearOffsetVector\r
823         LinearOffsetVector offsets;\r
824         offsets.reserve(numLinearOffsets);\r
825 \r
826         // iterate over linear offsets for this reference sequeence\r
827         uint64_t linearOffset;\r
828         for (int j = 0; j < numLinearOffsets; ++j) {\r
829             // read a linear offset & store\r
830             elementsRead = fread(&linearOffset, 8, 1, indexStream);\r
831             if ( IsBigEndian ) { SwapEndian_64(linearOffset); }\r
832             offsets.push_back(linearOffset);\r
833         }\r
834 \r
835         // sort linear offsets\r
836         sort( offsets.begin(), offsets.end() );\r
837 \r
838         // store index data for that reference sequence\r
839         Index.push_back( ReferenceIndex(binMap, offsets) );\r
840     }\r
841 \r
842     // close index file (.bai) and return\r
843     fclose(indexStream);\r
844     return true;\r
845 }\r
846 \r
847 // populates BamAlignment with alignment data under file pointer, returns success/fail\r
848 bool BamReader::BamReaderPrivate::LoadNextAlignment(BamAlignment& bAlignment, BamAlignmentSupportData& supportData) {\r
849 \r
850     // read in the 'block length' value, make sure it's not zero\r
851     char buffer[4];\r
852     mBGZF.Read(buffer, 4);\r
853     supportData.BlockLength = BgzfData::UnpackUnsignedInt(buffer);\r
854     if ( IsBigEndian ) { SwapEndian_32(supportData.BlockLength); }\r
855     if ( supportData.BlockLength == 0 ) { return false; }\r
856 \r
857     // read in core alignment data, make sure the right size of data was read\r
858     char x[BAM_CORE_SIZE];\r
859     if ( mBGZF.Read(x, BAM_CORE_SIZE) != BAM_CORE_SIZE ) { return false; }\r
860 \r
861     if ( IsBigEndian ) {\r
862         for ( int i = 0; i < BAM_CORE_SIZE; i+=sizeof(uint32_t) ) { \r
863           SwapEndian_32p(&x[i]); \r
864         }\r
865     }\r
866     \r
867     // set BamAlignment 'core' and 'support' data\r
868     bAlignment.RefID    = BgzfData::UnpackSignedInt(&x[0]);  \r
869     bAlignment.Position = BgzfData::UnpackSignedInt(&x[4]);\r
870     \r
871     unsigned int tempValue = BgzfData::UnpackUnsignedInt(&x[8]);\r
872     bAlignment.Bin        = tempValue >> 16;\r
873     bAlignment.MapQuality = tempValue >> 8 & 0xff;\r
874     supportData.QueryNameLength = tempValue & 0xff;\r
875 \r
876     tempValue = BgzfData::UnpackUnsignedInt(&x[12]);\r
877     bAlignment.AlignmentFlag = tempValue >> 16;\r
878     supportData.NumCigarOperations = tempValue & 0xffff;\r
879 \r
880     supportData.QuerySequenceLength = BgzfData::UnpackUnsignedInt(&x[16]);\r
881     bAlignment.MateRefID    = BgzfData::UnpackSignedInt(&x[20]);\r
882     bAlignment.MatePosition = BgzfData::UnpackSignedInt(&x[24]);\r
883     bAlignment.InsertSize   = BgzfData::UnpackSignedInt(&x[28]);\r
884     \r
885     // store 'all char data' and cigar ops\r
886     const unsigned int dataLength      = supportData.BlockLength - BAM_CORE_SIZE;\r
887     const unsigned int cigarDataOffset = supportData.QueryNameLength;\r
888     \r
889     char*     allCharData = (char*)calloc(sizeof(char), dataLength);\r
890     uint32_t* cigarData   = (uint32_t*)(allCharData + cigarDataOffset);\r
891     \r
892     // read in character data - make sure proper data size was read\r
893     if ( mBGZF.Read(allCharData, dataLength) != (signed int)dataLength) { return false; }\r
894     else {\r
895      \r
896         // store alignment name and length\r
897 //         bAlignment.Name.clear();\r
898         bAlignment.Name.assign((const char*)(allCharData));\r
899         bAlignment.Length = supportData.QuerySequenceLength;\r
900       \r
901         // store remaining 'allCharData' in supportData structure\r
902 //         supportData.AllCharData.clear();\r
903         supportData.AllCharData.assign((const char*)allCharData, dataLength);\r
904         \r
905         // save CigarOps for BamAlignment\r
906         bAlignment.CigarData.clear();\r
907         for (unsigned int i = 0; i < supportData.NumCigarOperations; ++i) {\r
908 \r
909             // swap if necessary\r
910             if ( IsBigEndian ) { SwapEndian_32(cigarData[i]); }\r
911           \r
912             // build CigarOp structure\r
913             CigarOp op;\r
914             op.Length = (cigarData[i] >> BAM_CIGAR_SHIFT);\r
915             op.Type   = CIGAR_LOOKUP[ (cigarData[i] & BAM_CIGAR_MASK) ];\r
916 \r
917             // save CigarOp\r
918             bAlignment.CigarData.push_back(op);\r
919         }\r
920     }\r
921 \r
922     free(allCharData);\r
923     return true;\r
924 }\r
925 \r
926 // loads reference data from BAM file\r
927 void BamReader::BamReaderPrivate::LoadReferenceData(void) {\r
928 \r
929     // get number of reference sequences\r
930     char buffer[4];\r
931     mBGZF.Read(buffer, 4);\r
932     unsigned int numberRefSeqs = BgzfData::UnpackUnsignedInt(buffer);\r
933     if ( IsBigEndian ) { SwapEndian_32(numberRefSeqs); }\r
934     if (numberRefSeqs == 0) { return; }\r
935     References.reserve((int)numberRefSeqs);\r
936 \r
937     // iterate over all references in header\r
938     for (unsigned int i = 0; i != numberRefSeqs; ++i) {\r
939 \r
940         // get length of reference name\r
941         mBGZF.Read(buffer, 4);\r
942         unsigned int refNameLength = BgzfData::UnpackUnsignedInt(buffer);\r
943         if ( IsBigEndian ) { SwapEndian_32(refNameLength); }\r
944         char* refName = (char*)calloc(refNameLength, 1);\r
945 \r
946         // get reference name and reference sequence length\r
947         mBGZF.Read(refName, refNameLength);\r
948         mBGZF.Read(buffer, 4);\r
949         int refLength = BgzfData::UnpackSignedInt(buffer);\r
950         if ( IsBigEndian ) { SwapEndian_32(refLength); }\r
951 \r
952         // store data for reference\r
953         RefData aReference;\r
954         aReference.RefName   = (string)((const char*)refName);\r
955         aReference.RefLength = refLength;\r
956         References.push_back(aReference);\r
957 \r
958         // clean up calloc-ed temp variable\r
959         free(refName);\r
960     }\r
961 }\r
962 \r
963 // merges 'alignment chunks' in BAM bin (used for index building)\r
964 void BamReader::BamReaderPrivate::MergeChunks(void) {\r
965 \r
966     // iterate over reference enties\r
967     BamIndex::iterator indexIter = Index.begin();\r
968     BamIndex::iterator indexEnd  = Index.end();\r
969     for ( ; indexIter != indexEnd; ++indexIter ) {\r
970 \r
971         // get BAM bin map for this reference\r
972         ReferenceIndex& refIndex = (*indexIter);\r
973         BamBinMap& bamBinMap = refIndex.Bins;\r
974 \r
975         // iterate over BAM bins\r
976         BamBinMap::iterator binIter = bamBinMap.begin();\r
977         BamBinMap::iterator binEnd  = bamBinMap.end();\r
978         for ( ; binIter != binEnd; ++binIter ) {\r
979 \r
980             // get chunk vector for this bin\r
981             ChunkVector& binChunks = (*binIter).second;\r
982             if ( binChunks.size() == 0 ) { continue; }\r
983 \r
984             ChunkVector mergedChunks;\r
985             mergedChunks.push_back( binChunks[0] );\r
986 \r
987             // iterate over chunks\r
988             int i = 0;\r
989             ChunkVector::iterator chunkIter = binChunks.begin();\r
990             ChunkVector::iterator chunkEnd  = binChunks.end();\r
991             for ( ++chunkIter; chunkIter != chunkEnd; ++chunkIter) {\r
992 \r
993                 // get 'currentChunk' based on numeric index\r
994                 Chunk& currentChunk = mergedChunks[i];\r
995 \r
996                 // get iteratorChunk based on vector iterator\r
997                 Chunk& iteratorChunk = (*chunkIter);\r
998 \r
999                 // if currentChunk.Stop(shifted) == iterator Chunk.Start(shifted)\r
1000                 if ( currentChunk.Stop>>16 == iteratorChunk.Start>>16 ) {\r
1001 \r
1002                     // set currentChunk.Stop to iteratorChunk.Stop\r
1003                     currentChunk.Stop = iteratorChunk.Stop;\r
1004                 }\r
1005 \r
1006                 // otherwise\r
1007                 else {\r
1008                     // set currentChunk + 1 to iteratorChunk\r
1009                     mergedChunks.push_back(iteratorChunk);\r
1010                     ++i;\r
1011                 }\r
1012             }\r
1013 \r
1014             // saved merged chunk vector\r
1015             (*binIter).second = mergedChunks;\r
1016         }\r
1017     }\r
1018 }\r
1019 \r
1020 // opens BAM file (and index)\r
1021 void BamReader::BamReaderPrivate::Open(const string& filename, const string& indexFilename) {\r
1022 \r
1023     Filename = filename;\r
1024     IndexFilename = indexFilename;\r
1025 \r
1026     // open the BGZF file for reading, retrieve header text & reference data\r
1027     mBGZF.Open(filename, "rb");\r
1028     LoadHeaderData();\r
1029     LoadReferenceData();\r
1030 \r
1031     // store file offset of first alignment\r
1032     AlignmentsBeginOffset = mBGZF.Tell();\r
1033 \r
1034     // open index file & load index data (if exists)\r
1035     if ( !IndexFilename.empty() ) {\r
1036         LoadIndex();\r
1037     }\r
1038 }\r
1039 \r
1040 // returns BAM file pointer to beginning of alignment data\r
1041 bool BamReader::BamReaderPrivate::Rewind(void) {\r
1042 \r
1043     // find first reference that has alignments in the BAM file\r
1044     int refID = 0;\r
1045     int refCount = References.size();\r
1046     for ( ; refID < refCount; ++refID ) {\r
1047         if ( References.at(refID).RefHasAlignments ) { break; }\r
1048     }\r
1049 \r
1050     // store default bounds for first alignment\r
1051     CurrentRefID = refID;\r
1052     CurrentLeft = 0;\r
1053     IsRegionSpecified = false;\r
1054 \r
1055     // return success/failure of seek\r
1056     return mBGZF.Seek(AlignmentsBeginOffset);\r
1057 }\r
1058 \r
1059 // saves index data to BAM index file (".bai"), returns success/fail\r
1060 bool BamReader::BamReaderPrivate::WriteIndex(void) {\r
1061 \r
1062     IndexFilename = Filename + ".bai";\r
1063     FILE* indexStream = fopen(IndexFilename.c_str(), "wb");\r
1064     if ( indexStream == 0 ) {\r
1065         printf("ERROR: Could not open file to save index\n");\r
1066         return false;\r
1067     }\r
1068 \r
1069     // write BAM index header\r
1070     fwrite("BAI\1", 1, 4, indexStream);\r
1071 \r
1072     // write number of reference sequences\r
1073     int32_t numReferenceSeqs = Index.size();\r
1074     if ( IsBigEndian ) { SwapEndian_32(numReferenceSeqs); }\r
1075     fwrite(&numReferenceSeqs, 4, 1, indexStream);\r
1076 \r
1077     // iterate over reference sequences\r
1078     BamIndex::const_iterator indexIter = Index.begin();\r
1079     BamIndex::const_iterator indexEnd  = Index.end();\r
1080     for ( ; indexIter != indexEnd; ++ indexIter ) {\r
1081 \r
1082         // get reference index data\r
1083         const ReferenceIndex& refIndex = (*indexIter);\r
1084         const BamBinMap& binMap = refIndex.Bins;\r
1085         const LinearOffsetVector& offsets = refIndex.Offsets;\r
1086 \r
1087         // write number of bins\r
1088         int32_t binCount = binMap.size();\r
1089         if ( IsBigEndian ) { SwapEndian_32(binCount); }\r
1090         fwrite(&binCount, 4, 1, indexStream);\r
1091 \r
1092         // iterate over bins\r
1093         BamBinMap::const_iterator binIter = binMap.begin();\r
1094         BamBinMap::const_iterator binEnd  = binMap.end();\r
1095         for ( ; binIter != binEnd; ++binIter ) {\r
1096 \r
1097             // get bin data (key and chunk vector)\r
1098             uint32_t binKey = (*binIter).first;\r
1099             const ChunkVector& binChunks = (*binIter).second;\r
1100 \r
1101             // save BAM bin key\r
1102             if ( IsBigEndian ) { SwapEndian_32(binKey); }\r
1103             fwrite(&binKey, 4, 1, indexStream);\r
1104 \r
1105             // save chunk count\r
1106             int32_t chunkCount = binChunks.size();\r
1107             if ( IsBigEndian ) { SwapEndian_32(chunkCount); }\r
1108             fwrite(&chunkCount, 4, 1, indexStream);\r
1109 \r
1110             // iterate over chunks\r
1111             ChunkVector::const_iterator chunkIter = binChunks.begin();\r
1112             ChunkVector::const_iterator chunkEnd  = binChunks.end();\r
1113             for ( ; chunkIter != chunkEnd; ++chunkIter ) {\r
1114 \r
1115                 // get current chunk data\r
1116                 const Chunk& chunk    = (*chunkIter);\r
1117                 uint64_t start = chunk.Start;\r
1118                 uint64_t stop  = chunk.Stop;\r
1119 \r
1120                 if ( IsBigEndian ) {\r
1121                     SwapEndian_64(start);\r
1122                     SwapEndian_64(stop);\r
1123                 }\r
1124                 \r
1125                 // save chunk offsets\r
1126                 fwrite(&start, 8, 1, indexStream);\r
1127                 fwrite(&stop,  8, 1, indexStream);\r
1128             }\r
1129         }\r
1130 \r
1131         // write linear offsets size\r
1132         int32_t offsetSize = offsets.size();\r
1133         if ( IsBigEndian ) { SwapEndian_32(offsetSize); }\r
1134         fwrite(&offsetSize, 4, 1, indexStream);\r
1135 \r
1136         // iterate over linear offsets\r
1137         LinearOffsetVector::const_iterator offsetIter = offsets.begin();\r
1138         LinearOffsetVector::const_iterator offsetEnd  = offsets.end();\r
1139         for ( ; offsetIter != offsetEnd; ++offsetIter ) {\r
1140 \r
1141             // write linear offset value\r
1142             uint64_t linearOffset = (*offsetIter);\r
1143             if ( IsBigEndian ) { SwapEndian_64(linearOffset); }\r
1144             fwrite(&linearOffset, 8, 1, indexStream);\r
1145         }\r
1146     }\r
1147 \r
1148     // flush buffer, close file, and return success\r
1149     fflush(indexStream);\r
1150     fclose(indexStream);\r
1151     return true;\r
1152 }\r