]> git.donarmstrong.com Git - bamtools.git/blob - src/api/BamReader.cpp
Fixed: Calls to GetEndPosition() rely on CIGAR data being parsed. Previously this...
[bamtools.git] / src / api / 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: 30 August 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 #include <iostream>\r
20 \r
21 // BamTools includes\r
22 #include "BGZF.h"\r
23 #include "BamReader.h"\r
24 #include "BamIndex.h"\r
25 using namespace BamTools;\r
26 using namespace std;\r
27 \r
28 struct BamReader::BamReaderPrivate {\r
29 \r
30     // -------------------------------\r
31     // structs, enums, typedefs\r
32     // -------------------------------\r
33     enum RegionState { BEFORE_REGION = 0\r
34                      , WITHIN_REGION\r
35                      , AFTER_REGION\r
36                      };\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     BamIndex* NewIndex;\r
47     RefVector References;\r
48     bool      IsIndexLoaded;\r
49     int64_t   AlignmentsBeginOffset;\r
50     string    Filename;\r
51     string    IndexFilename;\r
52     \r
53     // system data\r
54     bool IsBigEndian;\r
55 \r
56     // user-specified region values\r
57     BamRegion Region;\r
58     bool IsLeftBoundSpecified;\r
59     bool IsRightBoundSpecified;\r
60     \r
61     bool IsRegionSpecified;\r
62     int  CurrentRefID;\r
63     int  CurrentLeft;\r
64 \r
65     // parent BamReader\r
66     BamReader* Parent;\r
67     \r
68     // BAM character constants\r
69     const char* DNA_LOOKUP;\r
70     const char* CIGAR_LOOKUP;\r
71 \r
72     // -------------------------------\r
73     // constructor & destructor\r
74     // -------------------------------\r
75     BamReaderPrivate(BamReader* parent);\r
76     ~BamReaderPrivate(void);\r
77 \r
78     // -------------------------------\r
79     // "public" interface\r
80     // -------------------------------\r
81 \r
82     // file operations\r
83     void Close(void);\r
84     bool Jump(int refID, int position = 0);\r
85     bool Open(const string& filename, const string& indexFilename = "");\r
86     bool Rewind(void);\r
87     bool SetRegion(const BamRegion& region);\r
88 \r
89     // access alignment data\r
90     bool GetNextAlignment(BamAlignment& bAlignment);\r
91     bool GetNextAlignmentCore(BamAlignment& bAlignment);\r
92 \r
93     // access auxiliary data\r
94     int GetReferenceID(const string& refName) const;\r
95 \r
96     // index operations\r
97     bool CreateIndex(bool useDefaultIndex);\r
98 \r
99     // -------------------------------\r
100     // internal methods\r
101     // -------------------------------\r
102 \r
103     // *** reading alignments and auxiliary data *** //\r
104 \r
105     // fills out character data for BamAlignment data\r
106     bool BuildCharData(BamAlignment& bAlignment);\r
107     // checks to see if alignment overlaps current region\r
108     RegionState IsOverlap(BamAlignment& bAlignment);\r
109     // retrieves header text from BAM file\r
110     void LoadHeaderData(void);\r
111     // retrieves BAM alignment under file pointer\r
112     bool LoadNextAlignment(BamAlignment& bAlignment);\r
113     // builds reference data structure from BAM file\r
114     void LoadReferenceData(void);\r
115 \r
116     // *** index file handling *** //\r
117 \r
118     // clear out inernal index data structure\r
119     void ClearIndex(void);\r
120     // loads index from BAM index file\r
121     bool LoadIndex(void);\r
122 };\r
123 \r
124 // -----------------------------------------------------\r
125 // BamReader implementation (wrapper around BRPrivate)\r
126 // -----------------------------------------------------\r
127 // constructor\r
128 BamReader::BamReader(void) {\r
129     d = new BamReaderPrivate(this);\r
130 }\r
131 \r
132 // destructor\r
133 BamReader::~BamReader(void) {\r
134     delete d;\r
135     d = 0;\r
136 }\r
137 \r
138 // file operations\r
139 void BamReader::Close(void) { d->Close(); }\r
140 bool BamReader::IsOpen(void) const { return d->mBGZF.IsOpen; }\r
141 bool BamReader::Jump(int refID, int position) { \r
142     d->Region.LeftRefID = refID;\r
143     d->Region.LeftPosition = position;\r
144     d->IsLeftBoundSpecified = true;\r
145     d->IsRightBoundSpecified = false;\r
146     return d->Jump(refID, position); \r
147 }\r
148 bool BamReader::Open(const string& filename, const string& indexFilename) { return d->Open(filename, indexFilename); }\r
149 bool BamReader::Rewind(void) { return d->Rewind(); }\r
150 bool BamReader::SetRegion(const BamRegion& region) { return d->SetRegion(region); }\r
151 bool BamReader::SetRegion(const int& leftRefID, const int& leftBound, const int& rightRefID, const int& rightBound) {\r
152     return d->SetRegion( BamRegion(leftRefID, leftBound, rightRefID, rightBound) );\r
153 }\r
154 \r
155 // access alignment data\r
156 bool BamReader::GetNextAlignment(BamAlignment& bAlignment) { return d->GetNextAlignment(bAlignment); }\r
157 bool BamReader::GetNextAlignmentCore(BamAlignment& bAlignment) { return d->GetNextAlignmentCore(bAlignment); }\r
158 \r
159 // access auxiliary data\r
160 const string BamReader::GetHeaderText(void) const { return d->HeaderText; }\r
161 int BamReader::GetReferenceCount(void) const { return d->References.size(); }\r
162 const RefVector& BamReader::GetReferenceData(void) const { return d->References; }\r
163 int BamReader::GetReferenceID(const string& refName) const { return d->GetReferenceID(refName); }\r
164 const std::string BamReader::GetFilename(void) const { return d->Filename; }\r
165 \r
166 // index operations\r
167 bool BamReader::CreateIndex(bool useDefaultIndex) { return d->CreateIndex(useDefaultIndex); }\r
168 \r
169 // -----------------------------------------------------\r
170 // BamReaderPrivate implementation\r
171 // -----------------------------------------------------\r
172 \r
173 // constructor\r
174 BamReader::BamReaderPrivate::BamReaderPrivate(BamReader* parent)\r
175     : NewIndex(0)\r
176     , IsIndexLoaded(false)\r
177     , AlignmentsBeginOffset(0)\r
178     , IsLeftBoundSpecified(false)\r
179     , IsRightBoundSpecified(false)\r
180     , IsRegionSpecified(false)\r
181     , CurrentRefID(0)\r
182     , CurrentLeft(0)\r
183     , Parent(parent)\r
184     , DNA_LOOKUP("=ACMGRSVTWYHKDBN")\r
185     , CIGAR_LOOKUP("MIDNSHP")\r
186\r
187     IsBigEndian = SystemIsBigEndian();\r
188 }\r
189 \r
190 // destructor\r
191 BamReader::BamReaderPrivate::~BamReaderPrivate(void) {\r
192     Close();\r
193 }\r
194 \r
195 bool BamReader::BamReaderPrivate::BuildCharData(BamAlignment& bAlignment) {\r
196   \r
197     // calculate character lengths/offsets\r
198     const unsigned int dataLength      = bAlignment.SupportData.BlockLength - BAM_CORE_SIZE;\r
199 //     const unsigned int cigarDataOffset = bAlignment.SupportData.QueryNameLength;\r
200     const unsigned int seqDataOffset   = bAlignment.SupportData.QueryNameLength + (bAlignment.SupportData.NumCigarOperations * 4);\r
201     const unsigned int qualDataOffset  = seqDataOffset + (bAlignment.SupportData.QuerySequenceLength+1)/2;\r
202     const unsigned int tagDataOffset   = qualDataOffset + bAlignment.SupportData.QuerySequenceLength;\r
203     const unsigned int tagDataLength   = dataLength - tagDataOffset;\r
204       \r
205     // set up char buffers\r
206     const char*     allCharData = bAlignment.SupportData.AllCharData.data();\r
207 //           uint32_t* cigarData   = (uint32_t*)(allCharData + cigarDataOffset);\r
208     const char*     seqData     = ((const char*)allCharData) + seqDataOffset;\r
209     const char*     qualData    = ((const char*)allCharData) + qualDataOffset;\r
210           char*     tagData     = ((char*)allCharData) + tagDataOffset;\r
211   \r
212     // store alignment name (relies on null char in name as terminator)\r
213     bAlignment.Name.assign((const char*)(allCharData));    \r
214           \r
215 //     // save CigarOps \r
216 //     CigarOp op;\r
217 //     bAlignment.CigarData.clear();\r
218 //     bAlignment.CigarData.reserve(bAlignment.SupportData.NumCigarOperations);\r
219 //     for (unsigned int i = 0; i < bAlignment.SupportData.NumCigarOperations; ++i) {\r
220 // \r
221 //         // swap if necessary\r
222 //         if ( IsBigEndian ) { SwapEndian_32(cigarData[i]); }\r
223 //       \r
224 //         // build CigarOp structure\r
225 //         op.Length = (cigarData[i] >> BAM_CIGAR_SHIFT);\r
226 //         op.Type   = CIGAR_LOOKUP[ (cigarData[i] & BAM_CIGAR_MASK) ];\r
227 // \r
228 //         // save CigarOp\r
229 //         bAlignment.CigarData.push_back(op);\r
230 //     }\r
231           \r
232     // save query sequence\r
233     bAlignment.QueryBases.clear();\r
234     bAlignment.QueryBases.reserve(bAlignment.SupportData.QuerySequenceLength);\r
235     for (unsigned int i = 0; i < bAlignment.SupportData.QuerySequenceLength; ++i) {\r
236         char singleBase = DNA_LOOKUP[ ( ( seqData[(i/2)] >> (4*(1-(i%2)))) & 0xf ) ];\r
237         bAlignment.QueryBases.append(1, singleBase);\r
238     }\r
239   \r
240     // save qualities, converting from numeric QV to 'FASTQ-style' ASCII character\r
241     bAlignment.Qualities.clear();\r
242     bAlignment.Qualities.reserve(bAlignment.SupportData.QuerySequenceLength);\r
243     for (unsigned int i = 0; i < bAlignment.SupportData.QuerySequenceLength; ++i) {\r
244         char singleQuality = (char)(qualData[i]+33);\r
245         bAlignment.Qualities.append(1, singleQuality);\r
246     }\r
247     \r
248     // if QueryBases is empty (and this is a allowed case)\r
249     if ( bAlignment.QueryBases.empty() ) \r
250         bAlignment.AlignedBases = bAlignment.QueryBases;\r
251     \r
252     // if QueryBases contains data, then build AlignedBases using CIGAR data\r
253     else {\r
254     \r
255         // resize AlignedBases\r
256         bAlignment.AlignedBases.clear();\r
257         bAlignment.AlignedBases.reserve(bAlignment.SupportData.QuerySequenceLength);\r
258       \r
259         // iterate over CigarOps\r
260         int k = 0;\r
261         vector<CigarOp>::const_iterator cigarIter = bAlignment.CigarData.begin();\r
262         vector<CigarOp>::const_iterator cigarEnd  = bAlignment.CigarData.end();\r
263         for ( ; cigarIter != cigarEnd; ++cigarIter ) {\r
264             \r
265             const CigarOp& op = (*cigarIter);\r
266             switch(op.Type) {\r
267               \r
268                 case ('M') :\r
269                 case ('I') :\r
270                     bAlignment.AlignedBases.append(bAlignment.QueryBases.substr(k, op.Length)); // for 'M', 'I' - write bases\r
271                     // fall through\r
272                 \r
273                 case ('S') :\r
274                     k += op.Length;                                     // for 'S' - soft clip, skip over query bases\r
275                     break;\r
276                     \r
277                 case ('D') :\r
278                     bAlignment.AlignedBases.append(op.Length, '-');     // for 'D' - write gap character\r
279                     break;\r
280                     \r
281                 case ('P') :\r
282                     bAlignment.AlignedBases.append( op.Length, '*' );   // for 'P' - write padding character\r
283                     break;\r
284                     \r
285                 case ('N') :\r
286                     bAlignment.AlignedBases.append( op.Length, 'N' );  // for 'N' - write N's, skip bases in original query sequence\r
287                     break;\r
288                     \r
289                 case ('H') :\r
290                     break;  // for 'H' - hard clip, do nothing to AlignedBases, move to next op\r
291                     \r
292                 default:\r
293                     printf("ERROR: Invalid Cigar op type\n"); // shouldn't get here\r
294                     exit(1);\r
295             }\r
296         }\r
297     }\r
298  \r
299     // -----------------------\r
300     // Added: 3-25-2010 DB\r
301     // Fixed: endian-correctness for tag data\r
302     // -----------------------\r
303     if ( IsBigEndian ) {\r
304         int i = 0;\r
305         while ( (unsigned int)i < tagDataLength ) {\r
306           \r
307             i += 2; // skip tag type (e.g. "RG", "NM", etc)\r
308             uint8_t type = toupper(tagData[i]);     // lower & upper case letters have same meaning \r
309             ++i;                                    // skip value type\r
310     \r
311             switch (type) {\r
312                 \r
313                 case('A') :\r
314                 case('C') : \r
315                     ++i;\r
316                     break;\r
317 \r
318                 case('S') : \r
319                     SwapEndian_16p(&tagData[i]); \r
320                     i += sizeof(uint16_t);\r
321                     break;\r
322                     \r
323                 case('F') :\r
324                 case('I') : \r
325                     SwapEndian_32p(&tagData[i]);\r
326                     i += sizeof(uint32_t);\r
327                     break;\r
328                 \r
329                 case('D') : \r
330                     SwapEndian_64p(&tagData[i]);\r
331                     i += sizeof(uint64_t);\r
332                     break;\r
333                 \r
334                 case('H') :\r
335                 case('Z') : \r
336                     while (tagData[i]) { ++i; }\r
337                     ++i; // increment one more for null terminator\r
338                     break;\r
339                 \r
340                 default : \r
341                     printf("ERROR: Invalid tag value type\n"); // shouldn't get here\r
342                     exit(1);\r
343             }\r
344         }\r
345     }\r
346     \r
347     // store TagData\r
348     bAlignment.TagData.clear();\r
349     bAlignment.TagData.resize(tagDataLength);\r
350     memcpy((char*)bAlignment.TagData.data(), tagData, tagDataLength);\r
351     \r
352     // clear the core-only flag\r
353     bAlignment.SupportData.HasCoreOnly = false;\r
354     \r
355     // return success\r
356     return true;\r
357 }\r
358 \r
359 // clear index data structure\r
360 void BamReader::BamReaderPrivate::ClearIndex(void) {\r
361     delete NewIndex;\r
362     NewIndex = 0;\r
363 }\r
364 \r
365 // closes the BAM file\r
366 void BamReader::BamReaderPrivate::Close(void) {\r
367     \r
368     // close BGZF file stream\r
369     mBGZF.Close();\r
370     \r
371     // clear out index data\r
372     ClearIndex();\r
373     \r
374     // clear out header data\r
375     HeaderText.clear();\r
376     \r
377     // clear out region flags\r
378     IsLeftBoundSpecified  = false;\r
379     IsRightBoundSpecified = false;\r
380     IsRegionSpecified     = false;\r
381 }\r
382 \r
383 // create BAM index from BAM file (keep structure in memory) and write to default index output file\r
384 bool BamReader::BamReaderPrivate::CreateIndex(bool useDefaultIndex) {\r
385 \r
386     // clear out prior index data\r
387     ClearIndex();\r
388     \r
389     // create default index\r
390     if ( useDefaultIndex )\r
391         NewIndex = new BamDefaultIndex(&mBGZF, Parent, IsBigEndian);\r
392     // create BamTools 'custom' index\r
393     else\r
394         NewIndex = new BamToolsIndex(&mBGZF, Parent, IsBigEndian);\r
395     \r
396     bool ok = true;\r
397     ok &= NewIndex->Build();\r
398     ok &= NewIndex->Write(Filename); \r
399     \r
400     // return success/fail\r
401     return ok;\r
402 }\r
403 \r
404 // get next alignment (from specified region, if given)\r
405 bool BamReader::BamReaderPrivate::GetNextAlignment(BamAlignment& bAlignment) {\r
406 \r
407     // if valid alignment found, attempt to parse char data, and return success/failure\r
408     if ( GetNextAlignmentCore(bAlignment) )\r
409         return BuildCharData(bAlignment);\r
410     \r
411     // no valid alignment found\r
412     else\r
413         return false;\r
414 }\r
415 \r
416 // retrieves next available alignment core data (returns success/fail)\r
417 // ** DOES NOT parse any character data (read name, bases, qualities, tag data)\r
418 //    these can be accessed, if necessary, from the supportData \r
419 // useful for operations requiring ONLY positional or other alignment-related information\r
420 bool BamReader::BamReaderPrivate::GetNextAlignmentCore(BamAlignment& bAlignment) {\r
421 \r
422     // if valid alignment available\r
423     if ( LoadNextAlignment(bAlignment) ) {\r
424 \r
425         // set core-only flag\r
426         bAlignment.SupportData.HasCoreOnly = true;\r
427       \r
428         // if region not specified, return success\r
429         if ( !IsLeftBoundSpecified ) return true;\r
430 \r
431         // determine region state (before, within, after)\r
432         BamReader::BamReaderPrivate::RegionState state = IsOverlap(bAlignment);\r
433       \r
434         // if alignment lies after region, return false\r
435         if ( state == AFTER_REGION ) \r
436             return false;\r
437 \r
438         while ( state != WITHIN_REGION ) {\r
439             // if no valid alignment available (likely EOF) return failure\r
440             if ( !LoadNextAlignment(bAlignment) ) return false;\r
441             // if alignment lies after region, return false (no available read within region)\r
442             state = IsOverlap(bAlignment);\r
443             if ( state == AFTER_REGION) return false;\r
444             \r
445         }\r
446 \r
447         // return success (alignment found that overlaps region)\r
448         return true;\r
449     }\r
450 \r
451     // no valid alignment\r
452     else\r
453         return false;\r
454 }\r
455 \r
456 // returns RefID for given RefName (returns References.size() if not found)\r
457 int BamReader::BamReaderPrivate::GetReferenceID(const string& refName) const {\r
458 \r
459     // retrieve names from reference data\r
460     vector<string> refNames;\r
461     RefVector::const_iterator refIter = References.begin();\r
462     RefVector::const_iterator refEnd  = References.end();\r
463     for ( ; refIter != refEnd; ++refIter) {\r
464         refNames.push_back( (*refIter).RefName );\r
465     }\r
466 \r
467     // return 'index-of' refName ( if not found, returns refNames.size() )\r
468     return distance(refNames.begin(), find(refNames.begin(), refNames.end(), refName));\r
469 }\r
470 \r
471 // returns region state - whether alignment ends before, overlaps, or starts after currently specified region\r
472 // this *internal* method should ONLY called when (at least) IsLeftBoundSpecified == true\r
473 BamReader::BamReaderPrivate::RegionState BamReader::BamReaderPrivate::IsOverlap(BamAlignment& bAlignment) {\r
474     \r
475     // --------------------------------------------------\r
476     // check alignment start against right bound cutoff\r
477     \r
478     // if full region of interest was given\r
479     if ( IsRightBoundSpecified ) {\r
480       \r
481         // read starts on right bound reference, but AFTER right bound position\r
482         if ( bAlignment.RefID == Region.RightRefID && bAlignment.Position > Region.RightPosition )\r
483             return AFTER_REGION;\r
484       \r
485         // if read starts on reference AFTER right bound, return false\r
486         if ( bAlignment.RefID > Region.RightRefID ) \r
487             return AFTER_REGION;\r
488     }\r
489   \r
490     // --------------------------------------------------------\r
491     // no right bound given OR read starts before right bound\r
492     // so, check if it overlaps left bound \r
493   \r
494     // if read starts on left bound reference AND after left boundary, return success\r
495     if ( bAlignment.RefID == Region.LeftRefID && bAlignment.Position >= Region.LeftPosition)\r
496         return WITHIN_REGION;\r
497   \r
498     // if read is on any reference sequence before left bound, return false\r
499     if ( bAlignment.RefID < Region.LeftRefID )\r
500         return BEFORE_REGION;\r
501 \r
502     // --------------------------------------------------------\r
503     // read is on left bound reference, but starts before left bound position\r
504 \r
505     // if it overlaps, return WITHIN_REGION\r
506     if ( bAlignment.GetEndPosition() >= Region.LeftPosition )\r
507         return WITHIN_REGION;\r
508     // else begins before left bound position\r
509     else\r
510         return BEFORE_REGION;\r
511 }\r
512 \r
513 // jumps to specified region(refID, leftBound) in BAM file, returns success/fail\r
514 bool BamReader::BamReaderPrivate::Jump(int refID, int position) {\r
515 \r
516     // -----------------------------------------------------------------------\r
517     // check for existing index \r
518     if ( NewIndex == 0 ) return false; \r
519     // see if reference has alignments\r
520     if ( !NewIndex->HasAlignments(refID) ) return false; \r
521     // make sure position is valid\r
522     if ( position > References.at(refID).RefLength ) return false;\r
523     \r
524     // determine possible offsets\r
525     vector<int64_t> offsets;\r
526     if ( !NewIndex->GetOffsets(Region, IsRightBoundSpecified, offsets) ) {\r
527         printf("ERROR: Could not jump: unable to calculate offset for specified region.\n");\r
528         return false;\r
529     }\r
530       \r
531     // iterate through offsets\r
532     BamAlignment bAlignment;\r
533     bool result = true;\r
534     for ( vector<int64_t>::const_iterator o = offsets.begin(); o != offsets.end(); ++o) {\r
535         \r
536         // attempt seek & load first available alignment\r
537         result &= mBGZF.Seek(*o);\r
538         LoadNextAlignment(bAlignment);\r
539         \r
540         // if this alignment corresponds to desired position\r
541         // return success of seeking back to 'current offset'\r
542         if ( (bAlignment.RefID == refID && bAlignment.Position + bAlignment.Length > position) || (bAlignment.RefID > refID) ) {\r
543             if ( o != offsets.begin() ) --o;\r
544             return mBGZF.Seek(*o);\r
545         }\r
546     }\r
547     \r
548     return result;\r
549 }\r
550 \r
551 // load BAM header data\r
552 void BamReader::BamReaderPrivate::LoadHeaderData(void) {\r
553 \r
554     // check to see if proper BAM header\r
555     char buffer[4];\r
556     if (mBGZF.Read(buffer, 4) != 4) {\r
557         printf("Could not read header type\n");\r
558         exit(1);\r
559     }\r
560 \r
561     if (strncmp(buffer, "BAM\001", 4)) {\r
562         printf("wrong header type!\n");\r
563         exit(1);\r
564     }\r
565 \r
566     // get BAM header text length\r
567     mBGZF.Read(buffer, 4);\r
568     unsigned int headerTextLength = BgzfData::UnpackUnsignedInt(buffer);\r
569     if ( IsBigEndian ) { SwapEndian_32(headerTextLength); }\r
570     \r
571     // get BAM header text\r
572     char* headerText = (char*)calloc(headerTextLength + 1, 1);\r
573     mBGZF.Read(headerText, headerTextLength);\r
574     HeaderText = (string)((const char*)headerText);\r
575 \r
576     // clean up calloc-ed temp variable\r
577     free(headerText);\r
578 }\r
579 \r
580 // load existing index data from BAM index file (".bai"), return success/fail\r
581 bool BamReader::BamReaderPrivate::LoadIndex(void) {\r
582 \r
583     // clear out any existing index data\r
584     ClearIndex();\r
585 \r
586     // skip if index file empty\r
587     if ( IndexFilename.empty() )\r
588         return false;\r
589 \r
590     // check supplied filename for index type\r
591     size_t defaultExtensionFound = IndexFilename.find(".bai");\r
592     size_t customExtensionFound  = IndexFilename.find(".bti");\r
593     \r
594     // if SAM/BAM default (".bai")\r
595     if ( defaultExtensionFound != string::npos )\r
596         NewIndex = new BamDefaultIndex(&mBGZF, Parent, IsBigEndian);\r
597     \r
598     // if BamTools custom index (".bti")\r
599     else if ( customExtensionFound != string::npos )\r
600         NewIndex = new BamToolsIndex(&mBGZF, Parent, IsBigEndian);\r
601     \r
602     // else unknown\r
603     else {\r
604         printf("ERROR: Unknown index file extension.\n");\r
605         return false;\r
606     }\r
607     \r
608     // return success of loading index data\r
609     return NewIndex->Load(IndexFilename);\r
610 }\r
611 \r
612 // populates BamAlignment with alignment data under file pointer, returns success/fail\r
613 bool BamReader::BamReaderPrivate::LoadNextAlignment(BamAlignment& bAlignment) {\r
614 \r
615     // read in the 'block length' value, make sure it's not zero\r
616     char buffer[4];\r
617     mBGZF.Read(buffer, 4);\r
618     bAlignment.SupportData.BlockLength = BgzfData::UnpackUnsignedInt(buffer);\r
619     if ( IsBigEndian ) { SwapEndian_32(bAlignment.SupportData.BlockLength); }\r
620     if ( bAlignment.SupportData.BlockLength == 0 ) { return false; }\r
621 \r
622     // read in core alignment data, make sure the right size of data was read\r
623     char x[BAM_CORE_SIZE];\r
624     if ( mBGZF.Read(x, BAM_CORE_SIZE) != BAM_CORE_SIZE ) { return false; }\r
625 \r
626     if ( IsBigEndian ) {\r
627         for ( int i = 0; i < BAM_CORE_SIZE; i+=sizeof(uint32_t) ) { \r
628           SwapEndian_32p(&x[i]); \r
629         }\r
630     }\r
631     \r
632     // set BamAlignment 'core' and 'support' data\r
633     bAlignment.RefID    = BgzfData::UnpackSignedInt(&x[0]);  \r
634     bAlignment.Position = BgzfData::UnpackSignedInt(&x[4]);\r
635     \r
636     unsigned int tempValue = BgzfData::UnpackUnsignedInt(&x[8]);\r
637     bAlignment.Bin        = tempValue >> 16;\r
638     bAlignment.MapQuality = tempValue >> 8 & 0xff;\r
639     bAlignment.SupportData.QueryNameLength = tempValue & 0xff;\r
640 \r
641     tempValue = BgzfData::UnpackUnsignedInt(&x[12]);\r
642     bAlignment.AlignmentFlag = tempValue >> 16;\r
643     bAlignment.SupportData.NumCigarOperations = tempValue & 0xffff;\r
644 \r
645     bAlignment.SupportData.QuerySequenceLength = BgzfData::UnpackUnsignedInt(&x[16]);\r
646     bAlignment.MateRefID    = BgzfData::UnpackSignedInt(&x[20]);\r
647     bAlignment.MatePosition = BgzfData::UnpackSignedInt(&x[24]);\r
648     bAlignment.InsertSize   = BgzfData::UnpackSignedInt(&x[28]);\r
649     \r
650     // set BamAlignment length\r
651     bAlignment.Length = bAlignment.SupportData.QuerySequenceLength;\r
652     \r
653     // read in character data - make sure proper data size was read\r
654     bool readCharDataOK = false;\r
655     const unsigned int dataLength = bAlignment.SupportData.BlockLength - BAM_CORE_SIZE;\r
656     char* allCharData = (char*)calloc(sizeof(char), dataLength);\r
657     \r
658     if ( mBGZF.Read(allCharData, dataLength) == (signed int)dataLength) { \r
659       \r
660         // store 'allCharData' in supportData structure\r
661         bAlignment.SupportData.AllCharData.assign((const char*)allCharData, dataLength);\r
662         \r
663         // set success flag\r
664         readCharDataOK = true;\r
665         \r
666         // save CIGAR ops \r
667         // need to calculate this here so that  BamAlignment::GetEndPosition() performs correctly, \r
668         // even when BamReader::GetNextAlignmentCore() is called \r
669         const unsigned int cigarDataOffset = bAlignment.SupportData.QueryNameLength;\r
670         uint32_t* cigarData = (uint32_t*)(allCharData + cigarDataOffset);\r
671         CigarOp op;\r
672         bAlignment.CigarData.clear();\r
673         bAlignment.CigarData.reserve(bAlignment.SupportData.NumCigarOperations);\r
674         for (unsigned int i = 0; i < bAlignment.SupportData.NumCigarOperations; ++i) {\r
675 \r
676             // swap if necessary\r
677             if ( IsBigEndian ) SwapEndian_32(cigarData[i]);\r
678           \r
679             // build CigarOp structure\r
680             op.Length = (cigarData[i] >> BAM_CIGAR_SHIFT);\r
681             op.Type   = CIGAR_LOOKUP[ (cigarData[i] & BAM_CIGAR_MASK) ];\r
682 \r
683             // save CigarOp\r
684             bAlignment.CigarData.push_back(op);\r
685         }\r
686     }\r
687 \r
688     free(allCharData);\r
689     return readCharDataOK;\r
690 }\r
691 \r
692 // loads reference data from BAM file\r
693 void BamReader::BamReaderPrivate::LoadReferenceData(void) {\r
694 \r
695     // get number of reference sequences\r
696     char buffer[4];\r
697     mBGZF.Read(buffer, 4);\r
698     unsigned int numberRefSeqs = BgzfData::UnpackUnsignedInt(buffer);\r
699     if ( IsBigEndian ) { SwapEndian_32(numberRefSeqs); }\r
700     if (numberRefSeqs == 0) { return; }\r
701     References.reserve((int)numberRefSeqs);\r
702 \r
703     // iterate over all references in header\r
704     for (unsigned int i = 0; i != numberRefSeqs; ++i) {\r
705 \r
706         // get length of reference name\r
707         mBGZF.Read(buffer, 4);\r
708         unsigned int refNameLength = BgzfData::UnpackUnsignedInt(buffer);\r
709         if ( IsBigEndian ) { SwapEndian_32(refNameLength); }\r
710         char* refName = (char*)calloc(refNameLength, 1);\r
711 \r
712         // get reference name and reference sequence length\r
713         mBGZF.Read(refName, refNameLength);\r
714         mBGZF.Read(buffer, 4);\r
715         int refLength = BgzfData::UnpackSignedInt(buffer);\r
716         if ( IsBigEndian ) { SwapEndian_32(refLength); }\r
717 \r
718         // store data for reference\r
719         RefData aReference;\r
720         aReference.RefName   = (string)((const char*)refName);\r
721         aReference.RefLength = refLength;\r
722         References.push_back(aReference);\r
723 \r
724         // clean up calloc-ed temp variable\r
725         free(refName);\r
726     }\r
727 }\r
728 \r
729 // opens BAM file (and index)\r
730 bool BamReader::BamReaderPrivate::Open(const string& filename, const string& indexFilename) {\r
731 \r
732     Filename = filename;\r
733     IndexFilename = indexFilename;\r
734 \r
735     // open the BGZF file for reading, return false on failure\r
736     if ( !mBGZF.Open(filename, "rb") ) \r
737         return false;\r
738     \r
739     // retrieve header text & reference data\r
740     LoadHeaderData();\r
741     LoadReferenceData();\r
742 \r
743     // store file offset of first alignment\r
744     AlignmentsBeginOffset = mBGZF.Tell();\r
745 \r
746     // open index file & load index data (if exists)\r
747     if ( !IndexFilename.empty() )\r
748         LoadIndex();\r
749     \r
750     // return success\r
751     return true;\r
752 }\r
753 \r
754 // returns BAM file pointer to beginning of alignment data\r
755 bool BamReader::BamReaderPrivate::Rewind(void) {\r
756    \r
757     // rewind to first alignment\r
758     if ( !mBGZF.Seek(AlignmentsBeginOffset) ) return false;\r
759   \r
760     // retrieve first alignment data\r
761     BamAlignment al;\r
762     if ( !LoadNextAlignment(al) ) return false;\r
763       \r
764     // reset default region info using first alignment in file\r
765     Region.LeftRefID      = al.RefID;\r
766     Region.LeftPosition   = al.Position;\r
767     Region.RightRefID     = -1;\r
768     Region.RightPosition  = -1;\r
769     IsLeftBoundSpecified  = false;\r
770     IsRightBoundSpecified = false; \r
771 \r
772     // rewind back to before first alignment\r
773     // return success/fail of seek\r
774     return mBGZF.Seek(AlignmentsBeginOffset);\r
775 }\r
776 \r
777 // sets a region of interest (with left & right bound reference/position)\r
778 // attempts a Jump() to left bound as well\r
779 // returns success/failure of Jump()\r
780 bool BamReader::BamReaderPrivate::SetRegion(const BamRegion& region) {\r
781     \r
782     // save region of interest\r
783     Region = region;\r
784     \r
785     // set flags\r
786     if ( region.LeftRefID >= 0 && region.LeftPosition >= 0 ) \r
787         IsLeftBoundSpecified = true;\r
788     if ( region.RightRefID >= 0 && region.RightPosition >= 0 ) \r
789         IsRightBoundSpecified = true;\r
790     \r
791     // attempt jump to beginning of region, return success/fail of Jump()\r
792     return Jump( Region.LeftRefID, Region.LeftPosition );\r
793 }\r