]> git.donarmstrong.com Git - bamtools.git/blob - src/api/internal/bam/BamReader_p.cpp
24e54fd08278b9f6b757322d78e2a6aa80986246
[bamtools.git] / src / api / internal / bam / BamReader_p.cpp
1 // ***************************************************************************
2 // BamReader_p.cpp (c) 2009 Derek Barnett
3 // Marth Lab, Department of Biology, Boston College
4 // ---------------------------------------------------------------------------
5 // Last modified: 28 November 2011 (DB)
6 // ---------------------------------------------------------------------------
7 // Provides the basic functionality for reading BAM files
8 // ***************************************************************************
9
10 #include "api/BamConstants.h"
11 #include "api/BamReader.h"
12 #include "api/IBamIODevice.h"
13 #include "api/internal/bam/BamHeader_p.h"
14 #include "api/internal/bam/BamRandomAccessController_p.h"
15 #include "api/internal/bam/BamReader_p.h"
16 #include "api/internal/index/BamStandardIndex_p.h"
17 #include "api/internal/index/BamToolsIndex_p.h"
18 #include "api/internal/io/BamDeviceFactory_p.h"
19 #include "api/internal/utils/BamException_p.h"
20 using namespace BamTools;
21 using namespace BamTools::Internal;
22
23 #include <algorithm>
24 #include <cassert>
25 #include <iostream>
26 #include <iterator>
27 #include <vector>
28 using namespace std;
29
30 // constructor
31 BamReaderPrivate::BamReaderPrivate(BamReader* parent)
32     : m_alignmentsBeginOffset(0)
33     , m_parent(parent)
34 {
35     m_isBigEndian = BamTools::SystemIsBigEndian();
36 }
37
38 // destructor
39 BamReaderPrivate::~BamReaderPrivate(void) {
40     Close();
41 }
42
43 // closes the BAM file
44 bool BamReaderPrivate::Close(void) {
45
46     // clear BAM metadata
47     m_references.clear();
48     m_header.Clear();
49
50     // clear filename
51     m_filename.clear();
52
53     // close random access controller
54     m_randomAccessController.Close();
55
56     // if stream is open, attempt close
57     if ( IsOpen() ) {
58         try {
59             m_stream.Close();
60         } catch ( BamException& e ) {
61             const string streamError = e.what();
62             const string message = string("encountered error closing BAM file: \n\t") + streamError;
63             SetErrorString("BamReader::Close", message);
64             return false;
65         }
66     }
67
68     // return success
69     return true;
70 }
71
72 // creates an index file of requested type on current BAM file
73 bool BamReaderPrivate::CreateIndex(const BamIndex::IndexType& type) {
74
75     // skip if BAM file not open
76     if ( !IsOpen() ) {
77         SetErrorString("BamReader::CreateIndex", "cannot create index on unopened BAM file");
78         return false;
79     }
80
81     // attempt to create index
82     if ( m_randomAccessController.CreateIndex(this, type) )
83         return true;
84     else {
85         const string bracError = m_randomAccessController.GetErrorString();
86         const string message = string("could not create index: \n\t") + bracError;
87         SetErrorString("BamReader::CreateIndex", message);
88         return false;
89     }
90 }
91
92 // return path & filename of current BAM file
93 const string BamReaderPrivate::Filename(void) const {
94     return m_filename;
95 }
96
97 string BamReaderPrivate::GetErrorString(void) const {
98     return m_errorString;
99 }
100
101 // return header data as std::string
102 string BamReaderPrivate::GetHeaderText(void) const {
103     return m_header.ToString();
104 }
105
106 // return header data as SamHeader object
107 SamHeader BamReaderPrivate::GetSamHeader(void) const {
108     return m_header.ToSamHeader();
109 }
110
111 // get next alignment (with character data fully parsed)
112 bool BamReaderPrivate::GetNextAlignment(BamAlignment& alignment) {
113
114     // if valid alignment found
115     if ( GetNextAlignmentCore(alignment) ) {
116
117         // store alignment's "source" filename
118         alignment.Filename = m_filename;
119
120         // return success/failure of parsing char data
121         if ( alignment.BuildCharData() )
122             return true;
123         else {
124             const string alError = alignment.GetErrorString();
125             const string message = string("could not populate alignment data: \n\t") + alError;
126             SetErrorString("BamReader::GetNextAlignment", message);
127             return false;
128         }
129     }
130
131     // no valid alignment found
132     return false;
133 }
134
135 // retrieves next available alignment core data (returns success/fail)
136 // ** DOES NOT populate any character data fields (read name, bases, qualities, tag data, filename)
137 //    these can be accessed, if necessary, from the supportData
138 // useful for operations requiring ONLY positional or other alignment-related information
139 bool BamReaderPrivate::GetNextAlignmentCore(BamAlignment& alignment) {
140
141     // skip if stream not opened
142     if ( !m_stream.IsOpen() )
143         return false;
144
145     try {
146
147         // skip if region is set but has no alignments
148         if ( m_randomAccessController.HasRegion() &&
149              !m_randomAccessController.RegionHasAlignments() )
150         {
151             return false;
152         }
153
154         // if can't read next alignment
155         if ( !LoadNextAlignment(alignment) )
156             return false;
157
158         // check alignment's region-overlap state
159         BamRandomAccessController::RegionState state = m_randomAccessController.AlignmentState(alignment);
160
161         // if alignment starts after region, no need to keep reading
162         if ( state == BamRandomAccessController::AfterRegion )
163             return false;
164
165         // read until overlap is found
166         while ( state != BamRandomAccessController::OverlapsRegion ) {
167
168             // if can't read next alignment
169             if ( !LoadNextAlignment(alignment) )
170                 return false;
171
172             // check alignment's region-overlap state
173             state = m_randomAccessController.AlignmentState(alignment);
174
175             // if alignment starts after region, no need to keep reading
176             if ( state == BamRandomAccessController::AfterRegion )
177                 return false;
178         }
179
180         // if we get here, we found the next 'valid' alignment
181         // (e.g. overlaps current region if one was set, simply the next alignment if not)
182         alignment.SupportData.HasCoreOnly = true;
183         return true;
184
185     } catch ( BamException& e ) {
186         const string streamError = e.what();
187         const string message = string("encountered error reading BAM alignment: \n\t") + streamError;
188         SetErrorString("BamReader::GetNextAlignmentCore", message);
189         return false;
190     }
191 }
192
193 int BamReaderPrivate::GetReferenceCount(void) const {
194     return m_references.size();
195 }
196
197 const RefVector& BamReaderPrivate::GetReferenceData(void) const {
198     return m_references;
199 }
200
201 // returns RefID for given RefName (returns References.size() if not found)
202 int BamReaderPrivate::GetReferenceID(const string& refName) const {
203
204     // retrieve names from reference data
205     vector<string> refNames;
206     RefVector::const_iterator refIter = m_references.begin();
207     RefVector::const_iterator refEnd  = m_references.end();
208     for ( ; refIter != refEnd; ++refIter)
209         refNames.push_back( (*refIter).RefName );
210
211     // return 'index-of' refName (or -1 if not found)
212     int index = distance(refNames.begin(), find(refNames.begin(), refNames.end(), refName));
213     if ( index == (int)m_references.size() ) return -1;
214     else return index;
215 }
216
217 bool BamReaderPrivate::HasIndex(void) const {
218     return m_randomAccessController.HasIndex();
219 }
220
221 bool BamReaderPrivate::IsOpen(void) const {
222     return m_stream.IsOpen();
223 }
224
225 // load BAM header data
226 void BamReaderPrivate::LoadHeaderData(void) {
227     m_header.Load(&m_stream);
228 }
229
230 // populates BamAlignment with alignment data under file pointer, returns success/fail
231 bool BamReaderPrivate::LoadNextAlignment(BamAlignment& alignment) {
232
233     // read in the 'block length' value, make sure it's not zero
234     char buffer[sizeof(uint32_t)];
235     fill_n(buffer, sizeof(uint32_t), 0);
236     m_stream.Read(buffer, sizeof(uint32_t));
237     alignment.SupportData.BlockLength = BamTools::UnpackUnsignedInt(buffer);
238     if ( m_isBigEndian ) BamTools::SwapEndian_32(alignment.SupportData.BlockLength);
239     if ( alignment.SupportData.BlockLength == 0 )
240         return false;
241
242     // read in core alignment data, make sure the right size of data was read
243     char x[Constants::BAM_CORE_SIZE];
244     if ( m_stream.Read(x, Constants::BAM_CORE_SIZE) != Constants::BAM_CORE_SIZE )
245         return false;
246
247     // swap core endian-ness if necessary
248     if ( m_isBigEndian ) {
249         for ( unsigned int i = 0; i < Constants::BAM_CORE_SIZE; i+=sizeof(uint32_t) )
250             BamTools::SwapEndian_32p(&x[i]);
251     }
252
253     // set BamAlignment 'core' and 'support' data
254     alignment.RefID    = BamTools::UnpackSignedInt(&x[0]);
255     alignment.Position = BamTools::UnpackSignedInt(&x[4]);
256
257     unsigned int tempValue = BamTools::UnpackUnsignedInt(&x[8]);
258     alignment.Bin        = tempValue >> 16;
259     alignment.MapQuality = tempValue >> 8 & 0xff;
260     alignment.SupportData.QueryNameLength = tempValue & 0xff;
261
262     tempValue = BamTools::UnpackUnsignedInt(&x[12]);
263     alignment.AlignmentFlag = tempValue >> 16;
264     alignment.SupportData.NumCigarOperations = tempValue & 0xffff;
265
266     alignment.SupportData.QuerySequenceLength = BamTools::UnpackUnsignedInt(&x[16]);
267     alignment.MateRefID    = BamTools::UnpackSignedInt(&x[20]);
268     alignment.MatePosition = BamTools::UnpackSignedInt(&x[24]);
269     alignment.InsertSize   = BamTools::UnpackSignedInt(&x[28]);
270
271     // set BamAlignment length
272     alignment.Length = alignment.SupportData.QuerySequenceLength;
273
274     // read in character data - make sure proper data size was read
275     bool readCharDataOK = false;
276     const unsigned int dataLength = alignment.SupportData.BlockLength - Constants::BAM_CORE_SIZE;
277     RaiiBuffer allCharData(dataLength);
278
279     if ( m_stream.Read(allCharData.Buffer, dataLength) == dataLength ) {
280
281         // store 'allCharData' in supportData structure
282         alignment.SupportData.AllCharData.assign((const char*)allCharData.Buffer, dataLength);
283
284         // set success flag
285         readCharDataOK = true;
286
287         // save CIGAR ops
288         // need to calculate this here so that  BamAlignment::GetEndPosition() performs correctly,
289         // even when GetNextAlignmentCore() is called
290         const unsigned int cigarDataOffset = alignment.SupportData.QueryNameLength;
291         uint32_t* cigarData = (uint32_t*)(allCharData.Buffer + cigarDataOffset);
292         CigarOp op;
293         alignment.CigarData.clear();
294         alignment.CigarData.reserve(alignment.SupportData.NumCigarOperations);
295         for ( unsigned int i = 0; i < alignment.SupportData.NumCigarOperations; ++i ) {
296
297             // swap endian-ness if necessary
298             if ( m_isBigEndian ) BamTools::SwapEndian_32(cigarData[i]);
299
300             // build CigarOp structure
301             op.Length = (cigarData[i] >> Constants::BAM_CIGAR_SHIFT);
302             op.Type   = Constants::BAM_CIGAR_LOOKUP[ (cigarData[i] & Constants::BAM_CIGAR_MASK) ];
303
304             // save CigarOp
305             alignment.CigarData.push_back(op);
306         }
307     }
308
309     // return success/failure
310     return readCharDataOK;
311 }
312
313 // loads reference data from BAM file
314 bool BamReaderPrivate::LoadReferenceData(void) {
315
316     // get number of reference sequences
317     char buffer[sizeof(uint32_t)];
318     m_stream.Read(buffer, sizeof(uint32_t));
319     uint32_t numberRefSeqs = BamTools::UnpackUnsignedInt(buffer);
320     if ( m_isBigEndian ) BamTools::SwapEndian_32(numberRefSeqs);
321     m_references.reserve((int)numberRefSeqs);
322
323     // iterate over all references in header
324     for ( unsigned int i = 0; i != numberRefSeqs; ++i ) {
325
326         // get length of reference name
327         m_stream.Read(buffer, sizeof(uint32_t));
328         uint32_t refNameLength = BamTools::UnpackUnsignedInt(buffer);
329         if ( m_isBigEndian ) BamTools::SwapEndian_32(refNameLength);
330         RaiiBuffer refName(refNameLength);
331
332         // get reference name and reference sequence length
333         m_stream.Read(refName.Buffer, refNameLength);
334         m_stream.Read(buffer, sizeof(int32_t));
335         int32_t refLength = BamTools::UnpackSignedInt(buffer);
336         if ( m_isBigEndian ) BamTools::SwapEndian_32(refLength);
337
338         // store data for reference
339         RefData aReference;
340         aReference.RefName   = (string)((const char*)refName.Buffer);
341         aReference.RefLength = refLength;
342         m_references.push_back(aReference);
343     }
344
345     // return success
346     return true;
347 }
348
349 bool BamReaderPrivate::LocateIndex(const BamIndex::IndexType& preferredType) {
350
351     if ( m_randomAccessController.LocateIndex(this, preferredType) )
352         return true;
353     else {
354         const string bracError = m_randomAccessController.GetErrorString();
355         const string message = string("could not locate index: \n\t") + bracError;
356         SetErrorString("BamReader::LocateIndex", message);
357         return false;
358     }
359 }
360
361 // opens BAM file (and index)
362 bool BamReaderPrivate::Open(const string& filename) {
363
364     try {
365
366         // make sure we're starting with fresh state
367         Close();
368
369         // open BgzfStream
370         m_stream.Open(filename, IBamIODevice::ReadOnly);
371
372         // load BAM metadata
373         LoadHeaderData();
374         LoadReferenceData();
375
376         // store filename & offset of first alignment
377         m_filename = filename;
378         m_alignmentsBeginOffset = m_stream.Tell();
379
380         // return success
381         return true;
382
383     } catch ( BamException& e ) {
384         const string error = e.what();
385         const string message = string("could not open file: ") + filename +
386                                "\n\t" + error;
387         SetErrorString("BamReader::Open", message);
388         return false;
389     }
390 }
391
392 bool BamReaderPrivate::OpenIndex(const std::string& indexFilename) {
393
394     if ( m_randomAccessController.OpenIndex(indexFilename, this) )
395         return true;
396     else {
397         const string bracError = m_randomAccessController.GetErrorString();
398         const string message = string("could not open index: \n\t") + bracError;
399         SetErrorString("BamReader::OpenIndex", message);
400         return false;
401     }
402 }
403
404 // returns BAM file pointer to beginning of alignment data
405 bool BamReaderPrivate::Rewind(void) {
406
407     // reset region
408     m_randomAccessController.ClearRegion();
409
410     // return status of seeking back to first alignment
411     if ( Seek(m_alignmentsBeginOffset) )
412         return true;
413     else {
414         const string currentError = m_errorString;
415         const string message = string("could not rewind: \n\t") + currentError;
416         SetErrorString("BamReader::Rewind", message);
417         return false;
418     }
419 }
420
421 bool BamReaderPrivate::Seek(const int64_t& position) {
422
423     // skip if BAM file not open
424     if ( !IsOpen() ) {
425         SetErrorString("BamReader::Seek", "cannot seek on unopened BAM file");
426         return false;
427     }
428
429     try {
430         m_stream.Seek(position);
431         return true;
432     }
433     catch ( BamException& e ) {
434         const string streamError = e.what();
435         const string message = string("could not seek in BAM file: \n\t") + streamError;
436         SetErrorString("BamReader::Seek", message);
437         return false;
438     }
439 }
440
441 void BamReaderPrivate::SetErrorString(const string& where, const string& what) {
442     static const string SEPARATOR = ": ";
443     m_errorString = where + SEPARATOR + what;
444 }
445
446 void BamReaderPrivate::SetIndex(BamIndex* index) {
447     m_randomAccessController.SetIndex(index);
448 }
449
450 // sets current region & attempts to jump to it
451 // returns success/failure
452 bool BamReaderPrivate::SetRegion(const BamRegion& region) {
453
454     if ( m_randomAccessController.SetRegion(region, m_references.size()) )
455         return true;
456     else {
457         const string bracError = m_randomAccessController.GetErrorString();
458         const string message = string("could not set region: \n\t") + bracError;
459         SetErrorString("BamReader::SetRegion", message);
460         return false;
461     }
462 }
463
464 int64_t BamReaderPrivate::Tell(void) const {
465     return m_stream.Tell();
466 }