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