]> git.donarmstrong.com Git - bamtools.git/blob - src/toolkit/bamtools_stats.cpp
f66c462d34e2a43cbadf1c8b2cdf012778055f52
[bamtools.git] / src / toolkit / bamtools_stats.cpp
1 // ***************************************************************************
2 // bamtools_cpp (c) 2010 Derek Barnett, Erik Garrison
3 // Marth Lab, Department of Biology, Boston College
4 // All rights reserved.
5 // ---------------------------------------------------------------------------
6 // Last modified: 7 April 2011
7 // ---------------------------------------------------------------------------
8 // Prints general alignment statistics for BAM file(s).
9 // ***************************************************************************
10
11 #include "bamtools_stats.h"
12
13 #include <api/BamMultiReader.h>
14 #include <utils/bamtools_options.h>
15 using namespace BamTools;
16
17 #include <cmath>
18 #include <algorithm>
19 #include <functional>
20 #include <iostream>
21 #include <numeric>
22 #include <string>
23 #include <vector>
24 using namespace std;
25
26 // ---------------------------------------------
27 // StatsSettings implementation
28
29 struct StatsTool::StatsSettings {
30
31     // flags
32     bool HasInput;
33     bool IsShowingInsertSizeSummary;
34
35     // filenames
36     vector<string> InputFiles;
37     
38     // constructor
39     StatsSettings(void)
40         : HasInput(false)
41         , IsShowingInsertSizeSummary(false)
42     { }
43 };  
44
45 // ---------------------------------------------
46 // StatsToolPrivate implementation
47
48 struct StatsTool::StatsToolPrivate {
49   
50     // ctor & dtor
51     public:
52         StatsToolPrivate(StatsTool::StatsSettings* _settings);
53         ~StatsToolPrivate(void) { }
54   
55     // 'public' interface
56     public:
57         bool Run(void);
58         
59     // internal methods
60     private:
61         bool CalculateMedian(vector<int>& data, double& median); 
62         void PrintStats(void);
63         void ProcessAlignment(const BamAlignment& al);
64         
65     // data members
66     private:
67         StatsTool::StatsSettings* m_settings;
68         unsigned int m_numReads;
69         unsigned int m_numPaired;
70         unsigned int m_numProperPair;
71         unsigned int m_numMapped;
72         unsigned int m_numBothMatesMapped;
73         unsigned int m_numForwardStrand;
74         unsigned int m_numReverseStrand;
75         unsigned int m_numFirstMate;
76         unsigned int m_numSecondMate;
77         unsigned int m_numSingletons;
78         unsigned int m_numFailedQC;
79         unsigned int m_numDuplicates;
80         vector<int> m_insertSizes;
81 };
82
83 StatsTool::StatsToolPrivate::StatsToolPrivate(StatsTool::StatsSettings* settings)
84     : m_settings(settings)
85     , m_numReads(0)
86     , m_numPaired(0)
87     , m_numProperPair(0)
88     , m_numMapped(0)
89     , m_numBothMatesMapped(0)
90     , m_numForwardStrand(0)
91     , m_numReverseStrand(0)
92     , m_numFirstMate(0)
93     , m_numSecondMate(0)
94     , m_numSingletons(0)
95     , m_numFailedQC(0)
96     , m_numDuplicates(0)
97
98     m_insertSizes.reserve(100000);
99 }
100
101 // median is of type double because in the case of even number of data elements,
102 // we need to return the average of middle 2 elements
103 bool StatsTool::StatsToolPrivate::CalculateMedian(vector<int>& data, double& median) { 
104   
105     // skip if data empty
106     if ( data.empty() ) return false;
107
108     // find middle element
109     size_t middleIndex = data.size() / 2;
110     vector<int>::iterator target = data.begin() + middleIndex;
111     nth_element(data.begin(), target, data.end());
112     
113     // odd number of elements
114     if ( (data.size() % 2) != 0) {
115         median = (double)(*target);
116         return true;
117     }
118     
119     // even number of elements
120     else {
121         double rightTarget = (double)(*target);
122         vector<int>::iterator leftTarget = target - 1;
123         nth_element(data.begin(), leftTarget, data.end());
124         median = (double)((rightTarget+*leftTarget)/2.0);
125         return true;
126     }
127 }
128
129 // print BAM file alignment stats
130 void StatsTool::StatsToolPrivate::PrintStats(void) {
131   
132     cout << endl;
133     cout << "**********************************************" << endl;
134     cout << "Stats for BAM file(s): " << endl;
135     cout << "**********************************************" << endl;
136     cout << endl;
137     cout << "Total reads:       " << m_numReads << endl;
138     cout << "Mapped reads:      " << m_numMapped << "\t(" << ((float)m_numMapped/m_numReads)*100 << "%)" << endl;
139     cout << "Forward strand:    " << m_numForwardStrand << "\t(" << ((float)m_numForwardStrand/m_numReads)*100 << "%)" << endl;
140     cout << "Reverse strand:    " << m_numReverseStrand << "\t(" << ((float)m_numReverseStrand/m_numReads)*100 << "%)" << endl;
141     cout << "Failed QC:         " << m_numFailedQC << "\t(" << ((float)m_numFailedQC/m_numReads)*100 << "%)" << endl;
142     cout << "Duplicates:        " << m_numDuplicates << "\t(" << ((float)m_numDuplicates/m_numReads)*100 << "%)" << endl;
143     cout << "Paired-end reads:  " << m_numPaired << "\t(" << ((float)m_numPaired/m_numReads)*100 << "%)" << endl;
144     
145     if ( m_numPaired != 0 ) {
146         cout << "'Proper-pairs':    " << m_numProperPair << "\t(" << ((float)m_numProperPair/m_numPaired)*100 << "%)" << endl;
147         cout << "Both pairs mapped: " << m_numBothMatesMapped << "\t(" << ((float)m_numBothMatesMapped/m_numPaired)*100 << "%)" << endl;
148         cout << "Read 1:            " << m_numFirstMate << endl;
149         cout << "Read 2:            " << m_numSecondMate << endl;
150         cout << "Singletons:        " << m_numSingletons << "\t(" << ((float)m_numSingletons/m_numPaired)*100 << "%)" << endl;
151     }
152     
153     if ( m_settings->IsShowingInsertSizeSummary ) {
154       
155         double avgInsertSize = 0.0;
156         if ( !m_insertSizes.empty() ) {
157             avgInsertSize = ( accumulate(m_insertSizes.begin(), m_insertSizes.end(), 0.0) / (double)m_insertSizes.size() );
158             cout << "Average insert size (absolute value): " << avgInsertSize << endl;
159         }
160         
161         double medianInsertSize = 0.0;
162         if ( CalculateMedian(m_insertSizes, medianInsertSize) )
163             cout << "Median insert size (absolute value): " << medianInsertSize << endl;
164     }
165     cout << endl;
166 }
167
168 // use current input alignment to update BAM file alignment stats
169 void StatsTool::StatsToolPrivate::ProcessAlignment(const BamAlignment& al) {
170   
171     // increment total alignment counter
172     ++m_numReads;
173     
174     // incrememt counters for pairing-independent flags
175     if ( al.IsDuplicate() ) ++m_numDuplicates;
176     if ( al.IsFailedQC()  ) ++m_numFailedQC;
177     if ( al.IsMapped()    ) ++m_numMapped;
178     
179     // increment strand counters
180     if ( al.IsReverseStrand() ) 
181         ++m_numReverseStrand;
182     else 
183         ++m_numForwardStrand;
184     
185     // if alignment is paired-end
186     if ( al.IsPaired() ) {
187       
188         // increment PE counter
189         ++m_numPaired;
190       
191         // increment first mate/second mate counters
192         if ( al.IsFirstMate()  ) ++m_numFirstMate;
193         if ( al.IsSecondMate() ) ++m_numSecondMate;
194         
195         // if alignment is mapped, check mate status
196         if ( al.IsMapped() ) {
197             // if mate mapped
198             if ( al.IsMateMapped() ) 
199                 ++m_numBothMatesMapped;
200             // else singleton
201             else 
202                 ++m_numSingletons;
203         }
204         
205         // check for explicit proper pair flag
206         if ( al.IsProperPair() ) ++m_numProperPair;
207         
208         // store insert size for first mate 
209         if ( m_settings->IsShowingInsertSizeSummary && al.IsFirstMate() && (al.InsertSize != 0) ) {
210             int insertSize = abs(al.InsertSize);
211             m_insertSizes.push_back( insertSize );
212         }
213     }
214 }
215
216 bool StatsTool::StatsToolPrivate::Run() {
217   
218     // set to default input if none provided
219     if ( !m_settings->HasInput )
220         m_settings->InputFiles.push_back(Options::StandardIn());
221
222     // open the BAM files
223     BamMultiReader reader;
224     if ( !reader.Open(m_settings->InputFiles) ) {
225         cerr << "bamtools stats ERROR: could not open input BAM file(s)... Aborting." << endl;
226         reader.Close();
227         return false;
228     }
229     
230     // plow through alignments, keeping track of stats
231     BamAlignment al;
232     while ( reader.GetNextAlignmentCore(al) )
233         ProcessAlignment(al);
234     reader.Close();
235     
236     // print stats & exit
237     PrintStats();
238     return true; 
239 }
240
241 // ---------------------------------------------
242 // StatsTool implementation
243
244 StatsTool::StatsTool(void)
245     : AbstractTool()
246     , m_settings(new StatsSettings)
247     , m_impl(0)
248 {
249     // set program details
250     Options::SetProgramInfo("bamtools stats", "prints general alignment statistics", "[-in <filename> -in <filename> ...] [statsOptions]");
251     
252     // set up options 
253     OptionGroup* IO_Opts = Options::CreateOptionGroup("Input & Output");
254     Options::AddValueOption("-in", "BAM filename", "the input BAM file", "", m_settings->HasInput,  m_settings->InputFiles,  IO_Opts, Options::StandardIn());
255     
256     OptionGroup* AdditionalOpts = Options::CreateOptionGroup("Additional Stats");
257     Options::AddOption("-insert", "summarize insert size data", m_settings->IsShowingInsertSizeSummary, AdditionalOpts);
258 }
259
260 StatsTool::~StatsTool(void) {
261
262     delete m_settings;
263     m_settings = 0;
264     
265     delete m_impl;
266     m_impl = 0;
267 }
268
269 int StatsTool::Help(void) {
270     Options::DisplayHelp();
271     return 0;
272 }
273
274 int StatsTool::Run(int argc, char* argv[]) {
275   
276     // parse command line arguments
277     Options::Parse(argc, argv, 1);
278     
279     // initialize StatsTool with settings
280     m_impl = new StatsToolPrivate(m_settings);
281     
282     // run StatsTool, return success/fail
283     if ( m_impl->Run() )
284         return 0;
285     else
286         return 1;
287 }