]> git.donarmstrong.com Git - bamtools.git/blob - src/api/BamAlignment.h
d18b239d65f50b44703682184e760927fd181f81
[bamtools.git] / src / api / BamAlignment.h
1 // ***************************************************************************
2 // BamAlignment.h (c) 2009 Derek Barnett
3 // Marth Lab, Department of Biology, Boston College
4 // ---------------------------------------------------------------------------
5 // Last modified: 18 November 2012 (DB)
6 // ---------------------------------------------------------------------------
7 // Provides the BamAlignment data structure
8 // ***************************************************************************
9
10 #ifndef BAMALIGNMENT_H
11 #define BAMALIGNMENT_H
12
13 #include "api/api_global.h"
14 #include "api/BamAux.h"
15 #include "api/BamConstants.h"
16 #include <cstdlib>
17 #include <cstring>
18 #include <string>
19 #include <vector>
20
21 namespace BamTools {
22
23 //! \cond
24 // forward declaration of BamAlignment's "friends"
25 namespace Internal {
26     class BamReaderPrivate;
27     class BamWriterPrivate;
28 } // namespace Internal
29 //! \endcond
30
31 // BamAlignment data structure
32 struct API_EXPORT BamAlignment {
33
34     // constructors & destructor
35     public:
36         BamAlignment(void);
37         BamAlignment(const BamAlignment& other);
38         ~BamAlignment(void);
39
40     // queries against alignment flags
41     public:        
42         bool IsDuplicate(void) const;         // returns true if this read is a PCR duplicate
43         bool IsFailedQC(void) const;          // returns true if this read failed quality control
44         bool IsFirstMate(void) const;         // returns true if alignment is first mate on read
45         bool IsMapped(void) const;            // returns true if alignment is mapped
46         bool IsMateMapped(void) const;        // returns true if alignment's mate is mapped
47         bool IsMateReverseStrand(void) const; // returns true if alignment's mate mapped to reverse strand
48         bool IsPaired(void) const;            // returns true if alignment part of paired-end read
49         bool IsPrimaryAlignment(void) const;  // returns true if reported position is primary alignment
50         bool IsProperPair(void) const;        // returns true if alignment is part of read that satisfied paired-end resolution
51         bool IsReverseStrand(void) const;     // returns true if alignment mapped to reverse strand
52         bool IsSecondMate(void) const;        // returns true if alignment is second mate on read
53
54     // manipulate alignment flags
55     public:        
56         void SetIsDuplicate(bool ok);         // sets value of "PCR duplicate" flag
57         void SetIsFailedQC(bool ok);          // sets value of "failed quality control" flag
58         void SetIsFirstMate(bool ok);         // sets value of "alignment is first mate" flag
59         void SetIsMapped(bool ok);            // sets value of "alignment is mapped" flag
60         void SetIsMateMapped(bool ok);        // sets value of "alignment's mate is mapped" flag
61         void SetIsMateReverseStrand(bool ok); // sets value of "alignment's mate mapped to reverse strand" flag
62         void SetIsPaired(bool ok);            // sets value of "alignment part of paired-end read" flag
63         void SetIsPrimaryAlignment(bool ok);  // sets value of "position is primary alignment" flag
64         void SetIsProperPair(bool ok);        // sets value of "alignment is part of read that satisfied paired-end resolution" flag
65         void SetIsReverseStrand(bool ok);     // sets value of "alignment mapped to reverse strand" flag
66         void SetIsSecondMate(bool ok);        // sets value of "alignment is second mate on read" flag
67
68     // tag data access methods
69     public:
70
71         // add a new tag
72         template<typename T> bool AddTag(const std::string& tag, const std::string& type, const T& value);
73         template<typename T> bool AddTag(const std::string& tag, const std::vector<T>& values);
74
75         // edit (or append) tag
76         template<typename T> bool EditTag(const std::string& tag, const std::string& type, const T& value);
77         template<typename T> bool EditTag(const std::string& tag, const std::vector<T>& values);
78
79         // retrieves tag data
80         template<typename T> bool GetTag(const std::string& tag, T& destination) const;
81         template<typename T> bool GetTag(const std::string& tag, std::vector<T>& destination) const;
82
83         // retrieves all current tag names
84         std::vector<std::string> GetTagNames(void) const;
85
86         // retrieves the SAM/BAM type-code for requested tag name
87         bool GetTagType(const std::string& tag, char& type) const;
88
89         // returns true if alignment has a record for this tag name
90         bool HasTag(const std::string& tag) const;
91
92         // removes a tag
93         void RemoveTag(const std::string& tag);
94
95     // additional methods
96     public:
97         // populates alignment string fields
98         bool BuildCharData(void);
99
100         // calculates alignment end position
101         int GetEndPosition(bool usePadded = false, bool closedInterval = false) const;
102
103         // returns a description of the last error that occurred
104         std::string GetErrorString(void) const;
105
106         // retrieves the size, read locations and reference locations of soft-clip operations
107         bool GetSoftClips(std::vector<int>& clipSizes,
108                           std::vector<int>& readPositions,
109                           std::vector<int>& genomePositions,
110                           bool usePadded = false) const;
111
112     // public data fields
113     public:
114         std::string Name;               // read name
115         int32_t     Length;             // length of query sequence
116         std::string QueryBases;         // 'original' sequence (as reported from sequencing machine)
117         std::string AlignedBases;       // 'aligned' sequence (includes any indels, padding, clipping)
118         std::string Qualities;          // FASTQ qualities (ASCII characters, not numeric values)
119         std::string TagData;            // tag data (use provided methods to query/modify)
120         int32_t     RefID;              // ID number for reference sequence
121         int32_t     Position;           // position (0-based) where alignment starts
122         uint16_t    Bin;                // BAM (standard) index bin number for this alignment
123         uint16_t    MapQuality;         // mapping quality score
124         uint32_t    AlignmentFlag;      // alignment bit-flag (use provided methods to query/modify)
125         std::vector<CigarOp> CigarData; // CIGAR operations for this alignment
126         int32_t     MateRefID;          // ID number for reference sequence where alignment's mate was aligned
127         int32_t     MatePosition;       // position (0-based) where alignment's mate starts
128         int32_t     InsertSize;         // mate-pair insert size
129         std::string Filename;           // name of BAM file which this alignment comes from
130
131     //! \internal
132     // internal utility methods
133     private:
134         bool FindTag(const std::string& tag,
135                      char*& pTagData,
136                      const unsigned int& tagDataLength,
137                      unsigned int& numBytesParsed) const;
138         bool IsValidSize(const std::string& tag, const std::string& type) const;
139         void SetErrorString(const std::string& where, const std::string& what) const;
140         bool SkipToNextTag(const char storageType,
141                            char*& pTagData,
142                            unsigned int& numBytesParsed) const;
143
144     // internal data
145     private:
146
147         struct BamAlignmentSupportData {
148       
149             // data members
150             std::string AllCharData;
151             uint32_t    BlockLength;
152             uint32_t    NumCigarOperations;
153             uint32_t    QueryNameLength;
154             uint32_t    QuerySequenceLength;
155             bool        HasCoreOnly;
156             
157             // constructor
158             BamAlignmentSupportData(void)
159                 : BlockLength(0)
160                 , NumCigarOperations(0)
161                 , QueryNameLength(0)
162                 , QuerySequenceLength(0)
163                 , HasCoreOnly(false)
164             { }
165         };
166         BamAlignmentSupportData SupportData;
167         friend class Internal::BamReaderPrivate;
168         friend class Internal::BamWriterPrivate;
169
170         mutable std::string ErrorString; // mutable to allow updates even in logically const methods
171     //! \endinternal
172 };
173
174 // ---------------------------------------------------------
175 // BamAlignment tag access methods
176
177 /*! \fn bool AddTag(const std::string& tag, const std::string& type, const T& value)
178     \brief Adds a field to the BAM tags.
179
180     Does NOT modify an existing tag - use \link BamAlignment::EditTag() \endlink instead.
181
182     \param[in] tag   2-character tag name
183     \param[in] type  1-character tag type
184     \param[in] value data to store
185     \return \c true if the \b new tag was added successfully
186     \sa \samSpecURL for more details on reserved tag names, supported tag types, etc.
187 */
188 template<typename T>
189 inline bool BamAlignment::AddTag(const std::string& tag, const std::string& type, const T& value) {
190
191     // if char data not populated, do that first
192     if ( SupportData.HasCoreOnly )
193         BuildCharData();
194
195     // check tag/type size
196     if ( !IsValidSize(tag, type) ) {
197         // TODO: set error string?
198         return false;
199     }
200
201     // check that storage type code is OK for T
202     if ( !TagTypeHelper<T>::CanConvertTo(type.at(0)) ) {
203         // TODO: set error string?
204         return false;
205     }
206
207     // localize the tag data
208     char* pTagData = (char*)TagData.data();
209     const unsigned int tagDataLength = TagData.size();
210     unsigned int numBytesParsed = 0;
211
212     // if tag already exists, return false
213     // use EditTag explicitly instead
214     if ( FindTag(tag, pTagData, tagDataLength, numBytesParsed) ) {
215         // TODO: set error string?
216         return false;
217     }
218
219     // otherwise, convert value to string
220     union { T value; char valueBuffer[sizeof(T)]; } un;
221     un.value = value;
222
223     // copy original tag data to temp buffer
224     const std::string newTag = tag + type;
225     const size_t newTagDataLength = tagDataLength + newTag.size() + sizeof(T); // leave room for new T
226     RaiiBuffer originalTagData(newTagDataLength);
227     memcpy(originalTagData.Buffer, TagData.c_str(), tagDataLength + 1);    // '+1' for TagData null-term
228
229     // append newTag
230     strcat(originalTagData.Buffer + tagDataLength, newTag.data());
231     memcpy(originalTagData.Buffer + tagDataLength + newTag.size(), un.valueBuffer, sizeof(T));
232
233     // store temp buffer back in TagData
234     const char* newTagData = (const char*)originalTagData.Buffer;
235     TagData.assign(newTagData, newTagDataLength);
236     return true;
237 }
238
239 template<>
240 inline bool BamAlignment::AddTag<std::string>(const std::string& tag,
241                                               const std::string& type,
242                                               const std::string& value)
243 {
244     // if char data not populated, do that first
245     if ( SupportData.HasCoreOnly )
246         BuildCharData();
247
248     // check tag/type size
249     if ( !IsValidSize(tag, type) ) {
250         // TODO: set error string?
251         return false;
252     }
253
254     // check that storage type code is OK for string
255     if ( !TagTypeHelper<std::string>::CanConvertTo(type.at(0)) ) {
256         // TODO: set error string?
257         return false;
258     }
259
260     // localize the tag data
261     char* pTagData = (char*)TagData.data();
262     const unsigned int tagDataLength = TagData.size();
263     unsigned int numBytesParsed = 0;
264
265     // if tag already exists, return false
266     // use EditTag explicitly instead
267     if ( FindTag(tag, pTagData, tagDataLength, numBytesParsed) ) {
268         // TODO: set error string?
269         return false;
270     }
271
272     // otherwise, copy tag data to temp buffer
273     const std::string newTag = tag + type + value;
274     const size_t newTagDataLength = tagDataLength + newTag.size() + 1; // leave room for null-term
275     RaiiBuffer originalTagData(newTagDataLength);
276     memcpy(originalTagData.Buffer, TagData.c_str(), tagDataLength + 1);    // '+1' for TagData null-term
277
278     // append newTag (removes original null-term, then appends newTag + null-term)
279     strcat(originalTagData.Buffer + tagDataLength, newTag.data());
280
281     // store temp buffer back in TagData
282     const char* newTagData = (const char*)originalTagData.Buffer;
283     TagData.assign(newTagData, newTagDataLength);
284     return true;
285 }
286
287 /*! \fn template<typename T> bool AddTag(const std::string& tag, const std::vector<T>& values)
288     \brief Adds a numeric array field to the BAM tags.
289
290     Does NOT modify an existing tag - use \link BamAlignment::EditTag() \endlink instead.
291
292     \param[in] tag    2-character tag name
293     \param[in] values vector of data values to store
294     \return \c true if the \b new tag was added successfully
295     \sa \samSpecURL for more details on reserved tag names, supported tag types, etc.
296 */
297 template<typename T>
298 inline bool BamAlignment::AddTag(const std::string& tag, const std::vector<T>& values) {
299
300     // if char data not populated, do that first
301     if ( SupportData.HasCoreOnly )
302         BuildCharData();
303
304     // check for valid tag name length
305     if ( tag.size() != Constants::BAM_TAG_TAGSIZE )
306         return false;
307
308     // localize the tag data
309     char* pTagData = (char*)TagData.data();
310     const unsigned int tagDataLength = TagData.size();
311     unsigned int numBytesParsed = 0;
312
313     // if tag already exists, return false
314     // use EditTag explicitly instead
315     if ( FindTag(tag, pTagData, tagDataLength, numBytesParsed) ) {
316         // TODO: set error string?
317         return false;
318     }
319
320     // build new tag's base information
321     char newTagBase[Constants::BAM_TAG_ARRAYBASE_SIZE];
322     memcpy( newTagBase, tag.c_str(), Constants::BAM_TAG_TAGSIZE );
323     newTagBase[2] = Constants::BAM_TAG_TYPE_ARRAY;
324     newTagBase[3] = TagTypeHelper<T>::TypeCode();
325
326     // add number of array elements to newTagBase
327     const int32_t numElements  = values.size();
328     memcpy(newTagBase + 4, &numElements, sizeof(int32_t));
329
330     // copy current TagData string to temp buffer, leaving room for new tag's contents
331     const size_t newTagDataLength = tagDataLength +
332                                     Constants::BAM_TAG_ARRAYBASE_SIZE +
333                                     numElements*sizeof(T);
334     RaiiBuffer originalTagData(newTagDataLength);
335     memcpy(originalTagData.Buffer, TagData.c_str(), tagDataLength+1); // '+1' for TagData's null-term
336
337     // write newTagBase (removes old null term)
338     strcat(originalTagData.Buffer + tagDataLength, (const char*)newTagBase);
339
340     // add vector elements to tag
341     int elementsBeginOffset = tagDataLength + Constants::BAM_TAG_ARRAYBASE_SIZE;
342     for ( int i = 0 ; i < numElements; ++i ) {
343         const T& value = values.at(i);
344         memcpy(originalTagData.Buffer + elementsBeginOffset + i*sizeof(T), &value, sizeof(T));
345     }
346
347     // store temp buffer back in TagData
348     const char* newTagData = (const char*)originalTagData.Buffer;
349     TagData.assign(newTagData, newTagDataLength);
350     return true;
351 }
352
353 /*! \fn template<typename T> bool EditTag(const std::string& tag, const std::string& type, const T& value)
354     \brief Edits a BAM tag field.
355
356     If \a tag does not exist, a new entry is created.
357
358     \param tag[in]   2-character tag name
359     \param type[in]  1-character tag type (must be "Z" or "H")
360     \param value[in] new data value
361
362     \return \c true if the tag was modified/created successfully
363
364     \sa BamAlignment::RemoveTag()
365     \sa \samSpecURL for more details on reserved tag names, supported tag types, etc.
366 */
367 template<typename T>
368 inline bool BamAlignment::EditTag(const std::string& tag, const std::string& type, const T& value) {
369
370     // if char data not populated, do that first
371     if ( SupportData.HasCoreOnly )
372         BuildCharData();
373
374     // remove existing tag if present, then append tag with new value
375     if ( HasTag(tag) )
376         RemoveTag(tag);
377     return AddTag(tag, type, value);
378 }
379
380 /*! \fn template<typename T> bool EditTag(const std::string& tag, const std::vector<T>& values)
381     \brief Edits a BAM tag field containing a numeric array.
382
383     If \a tag does not exist, a new entry is created.
384
385     \param tag[in]   2-character tag name
386     \param value[in] vector of data values
387
388     \return \c true if the tag was modified/created successfully
389     \sa \samSpecURL for more details on reserved tag names, supported tag types, etc.
390 */
391 template<typename T>
392 inline bool BamAlignment::EditTag(const std::string& tag, const std::vector<T>& values) {
393
394     // if char data not populated, do that first
395     if ( SupportData.HasCoreOnly )
396         BuildCharData();
397
398     // remove existing tag if present, then append tag with new values
399     if ( HasTag(tag) )
400         RemoveTag(tag);
401     return AddTag(tag, values);
402 }
403
404
405 /*! \fn template<typename T> bool GetTag(const std::string& tag, T& destination) const
406     \brief Retrieves the value associated with a BAM tag.
407
408     \param tag[in]          2-character tag name
409     \param destination[out] retrieved value
410     \return \c true if found
411 */
412 template<typename T>
413 inline bool BamAlignment::GetTag(const std::string& tag, T& destination) const {
414
415     // skip if alignment is core-only
416     if ( SupportData.HasCoreOnly ) {
417         // TODO: set error string?
418         return false;
419     }
420
421     // skip if no tags present
422     if ( TagData.empty() ) {
423         // TODO: set error string?
424         return false;
425     }
426
427     // localize the tag data
428     char* pTagData = (char*)TagData.data();
429     const unsigned int tagDataLength = TagData.size();
430     unsigned int numBytesParsed = 0;
431
432     // return failure if tag not found
433     if ( !FindTag(tag, pTagData, tagDataLength, numBytesParsed) ) {
434         // TODO: set error string?
435         return false;
436     }
437
438     // fetch data type
439     const char type = *(pTagData - 1);
440     if ( !TagTypeHelper<T>::CanConvertFrom(type) ) {
441         // TODO: set error string ?
442         return false;
443     }
444
445     // determine data length
446     int destinationLength = 0;
447     switch ( type ) {
448
449         // 1 byte data
450         case (Constants::BAM_TAG_TYPE_ASCII) :
451         case (Constants::BAM_TAG_TYPE_INT8)  :
452         case (Constants::BAM_TAG_TYPE_UINT8) :
453             destinationLength = 1;
454             break;
455
456         // 2 byte data
457         case (Constants::BAM_TAG_TYPE_INT16)  :
458         case (Constants::BAM_TAG_TYPE_UINT16) :
459             destinationLength = 2;
460             break;
461
462         // 4 byte data
463         case (Constants::BAM_TAG_TYPE_INT32)  :
464         case (Constants::BAM_TAG_TYPE_UINT32) :
465         case (Constants::BAM_TAG_TYPE_FLOAT)  :
466             destinationLength = 4;
467             break;
468
469         // var-length types not supported for numeric destination
470         case (Constants::BAM_TAG_TYPE_STRING) :
471         case (Constants::BAM_TAG_TYPE_HEX)    :
472         case (Constants::BAM_TAG_TYPE_ARRAY)  :
473             SetErrorString("BamAlignment::GetTag",
474                            "cannot store variable length tag data into a numeric destination");
475             return false;
476
477         // unrecognized tag type
478         default:
479             const std::string message = std::string("invalid tag type: ") + type;
480             SetErrorString("BamAlignment::GetTag", message);
481             return false;
482     }
483
484     // store data in destination
485     destination = 0;
486     memcpy(&destination, pTagData, destinationLength);
487
488     // return success
489     return true;
490 }
491
492 template<>
493 inline bool BamAlignment::GetTag<std::string>(const std::string& tag,
494                                               std::string& destination) const
495 {
496     // skip if alignment is core-only
497     if ( SupportData.HasCoreOnly ) {
498         // TODO: set error string?
499         return false;
500     }
501
502     // skip if no tags present
503     if ( TagData.empty() ) {
504         // TODO: set error string?
505         return false;
506     }
507
508     // localize the tag data
509     char* pTagData = (char*)TagData.data();
510     const unsigned int tagDataLength = TagData.size();
511     unsigned int numBytesParsed = 0;
512
513     // return failure if tag not found
514     if ( !FindTag(tag, pTagData, tagDataLength, numBytesParsed) ) {
515         // TODO: set error string?
516         return false;
517     }
518
519     // otherwise copy data into destination
520     const unsigned int dataLength = strlen(pTagData);
521     destination.clear();
522     destination.resize(dataLength);
523     memcpy( (char*)destination.data(), pTagData, dataLength );
524
525     // return success
526     return true;
527 }
528
529 /*! \fn template<typename T> bool GetTag(const std::string& tag, std::vector<T>& destination) const
530     \brief Retrieves the numeric array associated with a BAM tag.
531
532     \param tag[in]          2-character tag name
533     \param destination[out] retrieved values
534     \return \c true if found
535 */
536 template<typename T>
537 inline bool BamAlignment::GetTag(const std::string& tag, std::vector<T>& destination) const {
538
539     // skip if alignment is core-only
540     if ( SupportData.HasCoreOnly ) {
541         // TODO: set error string?
542         return false;
543     }
544
545     // skip if no tags present
546     if ( TagData.empty() ) {
547         // TODO: set error string?
548         return false;
549     }
550
551     // localize the tag data
552     char* pTagData = (char*)TagData.data();
553     const unsigned int tagDataLength = TagData.size();
554     unsigned int numBytesParsed = 0;
555
556     // return false if tag not found
557     if ( !FindTag(tag, pTagData, tagDataLength, numBytesParsed) ) {
558         // TODO: set error string?
559         return false;
560     }
561
562     // check that tag is array type
563     const char tagType = *(pTagData - 1);
564     if ( tagType != Constants::BAM_TAG_TYPE_ARRAY ) {
565         SetErrorString("BamAlignment::GetTag", "cannot store a non-array tag in array destination");
566         return false;
567     }
568
569     // fetch element type
570     const char elementType = *pTagData;
571     if ( !TagTypeHelper<T>::CanConvertFrom(elementType) ) {
572         // TODO: set error string ?
573         return false;
574     }
575     ++pTagData;
576
577     // calculate length of each element in tag's array
578     int elementLength = 0;
579     switch ( elementType ) {
580         case (Constants::BAM_TAG_TYPE_ASCII) :
581         case (Constants::BAM_TAG_TYPE_INT8)  :
582         case (Constants::BAM_TAG_TYPE_UINT8) :
583             elementLength = sizeof(uint8_t);
584             break;
585
586         case (Constants::BAM_TAG_TYPE_INT16)  :
587         case (Constants::BAM_TAG_TYPE_UINT16) :
588             elementLength = sizeof(uint16_t);
589             break;
590
591         case (Constants::BAM_TAG_TYPE_INT32)  :
592         case (Constants::BAM_TAG_TYPE_UINT32) :
593         case (Constants::BAM_TAG_TYPE_FLOAT)  :
594             elementLength = sizeof(uint32_t);
595             break;
596
597         // var-length types not supported for numeric destination
598         case (Constants::BAM_TAG_TYPE_STRING) :
599         case (Constants::BAM_TAG_TYPE_HEX)    :
600         case (Constants::BAM_TAG_TYPE_ARRAY)  :
601             SetErrorString("BamAlignment::GetTag",
602                            "invalid array data, variable-length elements are not allowed");
603             return false;
604
605         // unknown tag type
606         default:
607             const std::string message = std::string("invalid array element type: ") + elementType;
608             SetErrorString("BamAlignment::GetTag", message);
609             return false;
610     }
611
612     // get number of elements
613     int32_t numElements;
614     memcpy(&numElements, pTagData, sizeof(int32_t));
615     pTagData += 4;
616     destination.clear();
617     destination.reserve(numElements);
618
619     // read in elements
620     T value;
621     for ( int i = 0 ; i < numElements; ++i ) {
622         memcpy(&value, pTagData, sizeof(T));
623         pTagData += sizeof(T);
624         destination.push_back(value);
625     }
626
627     // return success
628     return true;
629 }
630
631 typedef std::vector<BamAlignment> BamAlignmentVector;
632
633 } // namespace BamTools
634
635 #endif // BAMALIGNMENT_H