1 // ***************************************************************************
2 // BamReader_p.cpp (c) 2009 Derek Barnett
3 // Marth Lab, Department of Biology, Boston College
4 // All rights reserved.
5 // ---------------------------------------------------------------------------
6 // Last modified: 22 November 2010 (DB)
7 // ---------------------------------------------------------------------------
8 // Provides the basic functionality for reading BAM files
9 // ***************************************************************************
11 #include <api/BamReader.h>
13 #include <api/internal/BamReader_p.h>
14 #include <api/internal/BamStandardIndex_p.h>
15 #include <api/internal/BamToolsIndex_p.h>
16 using namespace BamTools;
17 using namespace BamTools::Internal;
26 BamReaderPrivate::BamReaderPrivate(BamReader* parent)
30 , AlignmentsBeginOffset(0)
32 , IndexCacheMode(BamIndex::LimitedIndexCaching)
33 , HasAlignmentsInRegion(true)
35 , DNA_LOOKUP("=ACMGRSVTWYHKDBN")
36 , CIGAR_LOOKUP("MIDNSHP")
38 IsBigEndian = SystemIsBigEndian();
42 BamReaderPrivate::~BamReaderPrivate(void) {
46 // adjusts requested region if necessary (depending on where data actually begins)
47 void BamReaderPrivate::AdjustRegion(BamRegion& region) {
49 // check for valid index first
50 if ( Index == 0 ) return;
52 // see if any references in region have alignments
53 HasAlignmentsInRegion = false;
54 int currentId = region.LeftRefID;
56 const int rightBoundRefId = ( region.isRightBoundSpecified() ? region.RightRefID : References.size() - 1 );
57 while ( currentId <= rightBoundRefId ) {
58 HasAlignmentsInRegion = Index->HasAlignments(currentId);
59 if ( HasAlignmentsInRegion ) break;
63 // if no data found on any reference in region
64 if ( !HasAlignmentsInRegion ) return;
66 // if left bound of desired region had no data, use first reference that had data
67 // otherwise, leave requested region as-is
68 if ( currentId != region.LeftRefID ) {
69 region.LeftRefID = currentId;
70 region.LeftPosition = 0;
74 // clear index data structure
75 void BamReaderPrivate::ClearIndex(void) {
81 // closes the BAM file
82 void BamReaderPrivate::Close(void) {
84 // close BGZF file stream
87 // clear out index data
90 // clear out header data
98 // clear out region flags
102 // creates index for BAM file, saves to file
103 // default behavior is to create the BAM standard index (".bai")
104 // set flag to false to create the BamTools-specific index (".bti")
105 bool BamReaderPrivate::CreateIndex(bool useStandardIndex) {
107 // clear out prior index data
110 // create index based on type requested
111 if ( useStandardIndex )
112 Index = new BamStandardIndex(&mBGZF, Parent);
114 Index = new BamToolsIndex(&mBGZF, Parent);
116 // set index cache mode to full for writing
117 Index->SetCacheMode(BamIndex::FullIndexCaching);
121 ok &= Index->Build();
124 // mark empty references
127 // attempt to save index data to file
128 ok &= Index->Write(Filename);
130 // set client's desired index cache mode
131 Index->SetCacheMode(IndexCacheMode);
133 // return success/fail of both building & writing index
137 const string BamReaderPrivate::GetHeaderText(void) const {
142 // return m_header->Text();
144 // return string("");
147 // get next alignment (from specified region, if given)
148 bool BamReaderPrivate::GetNextAlignment(BamAlignment& bAlignment) {
150 // if valid alignment found, attempt to parse char data, and return success/failure
151 if ( GetNextAlignmentCore(bAlignment) )
152 return bAlignment.BuildCharData();
154 // no valid alignment found
158 // retrieves next available alignment core data (returns success/fail)
159 // ** DOES NOT parse any character data (read name, bases, qualities, tag data)
160 // these can be accessed, if necessary, from the supportData
161 // useful for operations requiring ONLY positional or other alignment-related information
162 bool BamReaderPrivate::GetNextAlignmentCore(BamAlignment& bAlignment) {
164 // if region is set but has no alignments
165 if ( !Region.isNull() && !HasAlignmentsInRegion )
168 // if valid alignment available
169 if ( LoadNextAlignment(bAlignment) ) {
171 // set core-only flag
172 bAlignment.SupportData.HasCoreOnly = true;
174 // if region not specified with at least a left boundary, return success
175 if ( !Region.isLeftBoundSpecified() ) return true;
177 // determine region state (before, within, after)
178 BamReaderPrivate::RegionState state = IsOverlap(bAlignment);
180 // if alignment lies after region, return false
181 if ( state == AFTER_REGION ) return false;
183 while ( state != WITHIN_REGION ) {
184 // if no valid alignment available (likely EOF) return failure
185 if ( !LoadNextAlignment(bAlignment) ) return false;
186 // if alignment lies after region, return false (no available read within region)
187 state = IsOverlap(bAlignment);
188 if ( state == AFTER_REGION ) return false;
191 // return success (alignment found that overlaps region)
195 // no valid alignment
199 // returns RefID for given RefName (returns References.size() if not found)
200 int BamReaderPrivate::GetReferenceID(const string& refName) const {
202 // retrieve names from reference data
203 vector<string> refNames;
204 RefVector::const_iterator refIter = References.begin();
205 RefVector::const_iterator refEnd = References.end();
206 for ( ; refIter != refEnd; ++refIter)
207 refNames.push_back( (*refIter).RefName );
209 // return 'index-of' refName ( if not found, returns refNames.size() )
210 return distance(refNames.begin(), find(refNames.begin(), refNames.end(), refName));
213 // returns region state - whether alignment ends before, overlaps, or starts after currently specified region
214 // this *internal* method should ONLY called when (at least) IsLeftBoundSpecified == true
215 BamReaderPrivate::RegionState BamReaderPrivate::IsOverlap(BamAlignment& bAlignment) {
217 // if alignment is on any reference sequence before left bound
218 if ( bAlignment.RefID < Region.LeftRefID )
219 return BEFORE_REGION;
221 // if alignment starts on left bound reference
222 else if ( bAlignment.RefID == Region.LeftRefID ) {
224 // if alignment starts at or after left boundary
225 if ( bAlignment.Position >= Region.LeftPosition) {
227 // if right boundary is specified AND
228 // left/right boundaries are on same reference AND
229 // alignment starts past right boundary
230 if ( Region.isRightBoundSpecified() &&
231 Region.LeftRefID == Region.RightRefID &&
232 bAlignment.Position > Region.RightPosition )
235 // otherwise, alignment is within region
237 return WITHIN_REGION;
240 // alignment starts before left boundary
242 // check if alignment overlaps left boundary
243 if ( bAlignment.GetEndPosition() >= Region.LeftPosition )
244 return WITHIN_REGION;
246 return BEFORE_REGION;
250 // alignment starts on a reference after the left bound
253 // if region has a right boundary
254 if ( Region.isRightBoundSpecified() ) {
256 // alignment is on reference between boundaries
257 if ( bAlignment.RefID < Region.RightRefID )
258 return WITHIN_REGION;
260 // alignment is on reference after right boundary
261 else if ( bAlignment.RefID > Region.RightRefID )
264 // alignment is on right bound reference
266 // check if alignment starts before or at right boundary
267 if ( bAlignment.Position <= Region.RightPosition )
268 return WITHIN_REGION;
274 // otherwise, alignment is after left bound reference, but there is no right boundary
275 else return WITHIN_REGION;
279 // load BAM header data
280 void BamReaderPrivate::LoadHeaderData(void) {
282 // m_header = new BamHeader(&mBGZF);
283 // bool headerLoadedOk = m_header->Load();
284 // if ( !headerLoadedOk )
285 // cerr << "BamReader could not load header" << endl;
287 // check to see if proper BAM header
289 if (mBGZF.Read(buffer, 4) != 4) {
290 fprintf(stderr, "Could not read header type\n");
294 if (strncmp(buffer, "BAM\001", 4)) {
295 fprintf(stderr, "wrong header type!\n");
299 // get BAM header text length
300 mBGZF.Read(buffer, 4);
301 unsigned int headerTextLength = BgzfData::UnpackUnsignedInt(buffer);
302 if ( IsBigEndian ) SwapEndian_32(headerTextLength);
304 // get BAM header text
305 char* headerText = (char*)calloc(headerTextLength + 1, 1);
306 mBGZF.Read(headerText, headerTextLength);
307 HeaderText = (string)((const char*)headerText);
309 // clean up calloc-ed temp variable
313 // load existing index data from BAM index file (".bti" OR ".bai"), return success/fail
314 bool BamReaderPrivate::LoadIndex(const bool lookForIndex, const bool preferStandardIndex) {
316 // clear out any existing index data
319 // if no index filename provided, so we need to look for available index files
320 if ( IndexFilename.empty() ) {
322 // attempt to load BamIndex based on current Filename provided & preferStandardIndex flag
323 const BamIndex::PreferredIndexType type = (preferStandardIndex ? BamIndex::STANDARD : BamIndex::BAMTOOLS);
324 Index = BamIndex::FromBamFilename(Filename, &mBGZF, Parent, type);
326 // if null, return failure
327 if ( Index == 0 ) return false;
329 // generate proper IndexFilename based on type of index created
330 IndexFilename = Filename + Index->Extension();
335 // attempt to load BamIndex based on IndexFilename provided by client
336 Index = BamIndex::FromIndexFilename(IndexFilename, &mBGZF, Parent);
338 // if null, return failure
339 if ( Index == 0 ) return false;
342 // set cache mode for BamIndex
343 Index->SetCacheMode(IndexCacheMode);
345 // loading the index data from file
346 HasIndex = Index->Load(IndexFilename);
348 // mark empty references
351 // return index status
355 // populates BamAlignment with alignment data under file pointer, returns success/fail
356 bool BamReaderPrivate::LoadNextAlignment(BamAlignment& bAlignment) {
358 // read in the 'block length' value, make sure it's not zero
360 mBGZF.Read(buffer, 4);
361 bAlignment.SupportData.BlockLength = BgzfData::UnpackUnsignedInt(buffer);
362 if ( IsBigEndian ) { SwapEndian_32(bAlignment.SupportData.BlockLength); }
363 if ( bAlignment.SupportData.BlockLength == 0 ) return false;
365 // read in core alignment data, make sure the right size of data was read
366 char x[BAM_CORE_SIZE];
367 if ( mBGZF.Read(x, BAM_CORE_SIZE) != BAM_CORE_SIZE )
371 for ( int i = 0; i < BAM_CORE_SIZE; i+=sizeof(uint32_t) )
372 SwapEndian_32p(&x[i]);
375 // set BamAlignment 'core' and 'support' data
376 bAlignment.RefID = BgzfData::UnpackSignedInt(&x[0]);
377 bAlignment.Position = BgzfData::UnpackSignedInt(&x[4]);
379 unsigned int tempValue = BgzfData::UnpackUnsignedInt(&x[8]);
380 bAlignment.Bin = tempValue >> 16;
381 bAlignment.MapQuality = tempValue >> 8 & 0xff;
382 bAlignment.SupportData.QueryNameLength = tempValue & 0xff;
384 tempValue = BgzfData::UnpackUnsignedInt(&x[12]);
385 bAlignment.AlignmentFlag = tempValue >> 16;
386 bAlignment.SupportData.NumCigarOperations = tempValue & 0xffff;
388 bAlignment.SupportData.QuerySequenceLength = BgzfData::UnpackUnsignedInt(&x[16]);
389 bAlignment.MateRefID = BgzfData::UnpackSignedInt(&x[20]);
390 bAlignment.MatePosition = BgzfData::UnpackSignedInt(&x[24]);
391 bAlignment.InsertSize = BgzfData::UnpackSignedInt(&x[28]);
393 // set BamAlignment length
394 bAlignment.Length = bAlignment.SupportData.QuerySequenceLength;
396 // read in character data - make sure proper data size was read
397 bool readCharDataOK = false;
398 const unsigned int dataLength = bAlignment.SupportData.BlockLength - BAM_CORE_SIZE;
399 char* allCharData = (char*)calloc(sizeof(char), dataLength);
401 if ( mBGZF.Read(allCharData, dataLength) == (signed int)dataLength) {
403 // store 'allCharData' in supportData structure
404 bAlignment.SupportData.AllCharData.assign((const char*)allCharData, dataLength);
407 readCharDataOK = true;
410 // need to calculate this here so that BamAlignment::GetEndPosition() performs correctly,
411 // even when GetNextAlignmentCore() is called
412 const unsigned int cigarDataOffset = bAlignment.SupportData.QueryNameLength;
413 uint32_t* cigarData = (uint32_t*)(allCharData + cigarDataOffset);
415 bAlignment.CigarData.clear();
416 bAlignment.CigarData.reserve(bAlignment.SupportData.NumCigarOperations);
417 for (unsigned int i = 0; i < bAlignment.SupportData.NumCigarOperations; ++i) {
420 if ( IsBigEndian ) SwapEndian_32(cigarData[i]);
422 // build CigarOp structure
423 op.Length = (cigarData[i] >> BAM_CIGAR_SHIFT);
424 op.Type = CIGAR_LOOKUP[ (cigarData[i] & BAM_CIGAR_MASK) ];
427 bAlignment.CigarData.push_back(op);
432 return readCharDataOK;
435 // loads reference data from BAM file
436 void BamReaderPrivate::LoadReferenceData(void) {
438 // get number of reference sequences
440 mBGZF.Read(buffer, 4);
441 unsigned int numberRefSeqs = BgzfData::UnpackUnsignedInt(buffer);
442 if ( IsBigEndian ) SwapEndian_32(numberRefSeqs);
443 if ( numberRefSeqs == 0 ) return;
444 References.reserve((int)numberRefSeqs);
446 // iterate over all references in header
447 for (unsigned int i = 0; i != numberRefSeqs; ++i) {
449 // get length of reference name
450 mBGZF.Read(buffer, 4);
451 unsigned int refNameLength = BgzfData::UnpackUnsignedInt(buffer);
452 if ( IsBigEndian ) SwapEndian_32(refNameLength);
453 char* refName = (char*)calloc(refNameLength, 1);
455 // get reference name and reference sequence length
456 mBGZF.Read(refName, refNameLength);
457 mBGZF.Read(buffer, 4);
458 int refLength = BgzfData::UnpackSignedInt(buffer);
459 if ( IsBigEndian ) SwapEndian_32(refLength);
461 // store data for reference
463 aReference.RefName = (string)((const char*)refName);
464 aReference.RefLength = refLength;
465 References.push_back(aReference);
467 // clean up calloc-ed temp variable
472 // mark references with no alignment data
473 void BamReaderPrivate::MarkReferences(void) {
475 // ensure index is available
476 if ( !HasIndex ) return;
478 // mark empty references
479 for ( int i = 0; i < (int)References.size(); ++i )
480 References.at(i).RefHasAlignments = Index->HasAlignments(i);
483 // opens BAM file (and index)
484 bool BamReaderPrivate::Open(const string& filename,
485 const string& indexFilename,
486 const bool lookForIndex,
487 const bool preferStandardIndex)
491 IndexFilename = indexFilename;
493 // open the BGZF file for reading, return false on failure
494 if ( !mBGZF.Open(filename, "rb") ) return false;
496 // retrieve header text & reference data
500 // store file offset of first alignment
501 AlignmentsBeginOffset = mBGZF.Tell();
503 // if no index filename provided
504 if ( IndexFilename.empty() ) {
506 // client did not specify that index SHOULD be found
507 // useful for cases where sequential access is all that is required
508 if ( !lookForIndex ) return true;
510 // otherwise, look for index file, return success/fail
511 else return LoadIndex(lookForIndex, preferStandardIndex) ;
514 // client supplied an index filename
515 // attempt to load index data, return success/fail
516 return LoadIndex(lookForIndex, preferStandardIndex);
519 // returns BAM file pointer to beginning of alignment data
520 bool BamReaderPrivate::Rewind(void) {
522 // rewind to first alignment, return false if unable to seek
523 if ( !mBGZF.Seek(AlignmentsBeginOffset) ) return false;
525 // retrieve first alignment data, return false if unable to read
527 if ( !LoadNextAlignment(al) ) return false;
529 // reset default region info using first alignment in file
531 HasAlignmentsInRegion = true;
533 // rewind back to beginning of first alignment
534 // return success/fail of seek
535 return mBGZF.Seek(AlignmentsBeginOffset);
538 // change the index caching behavior
539 void BamReaderPrivate::SetIndexCacheMode(const BamIndex::BamIndexCacheMode mode) {
540 IndexCacheMode = mode;
541 if ( Index == 0 ) return;
542 Index->SetCacheMode(mode);
545 // asks Index to attempt a Jump() to specified region
546 // returns success/failure
547 bool BamReaderPrivate::SetRegion(const BamRegion& region) {
549 // clear out any prior BamReader region data
551 // N.B. - this is cleared so that BamIndex now has free reign to call
552 // GetNextAlignmentCore() and do overlap checking without worrying about BamReader
553 // performing any overlap checking of its own and moving on to the next read... Calls
554 // to GetNextAlignmentCore() with no Region set, simply return the next alignment.
555 // This ensures that the Index is able to do just that. (All without exposing
556 // LoadNextAlignment() to the public API, and potentially confusing clients with the nomenclature)
559 // check for existing index
560 if ( !HasIndex ) return false;
562 // adjust region if necessary to reflect where data actually begins
563 BamRegion adjustedRegion(region);
564 AdjustRegion(adjustedRegion);
566 // if no data present, return true
567 // not an error, but BamReader knows that no data is there for future alignment access
568 // (this is useful in a MultiBamReader setting where some BAM files may lack data in regions
569 // that other BAMs have data)
570 if ( !HasAlignmentsInRegion ) {
571 Region = adjustedRegion;
575 // attempt jump to user-specified region return false if jump could not be performed at all
576 // (invalid index, unknown reference, etc)
578 // Index::Jump() is allowed to modify the HasAlignmentsInRegion flag
579 // * This covers case where a region is requested that lies beyond the last alignment on a reference
580 // If this occurs, any subsequent calls to GetNexAlignment[Core] simply return false
581 // BamMultiReader is then able to successfully pull alignments from a region from multiple files
582 // even if one or more have no data.
583 if ( !Index->Jump(adjustedRegion, &HasAlignmentsInRegion) ) return false;
585 // save region and return success
586 Region = adjustedRegion;