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