]> git.donarmstrong.com Git - bamtools.git/blobdiff - src/toolkit/bamtools_split.cpp
Added support for custom reference prefix in split tool
[bamtools.git] / src / toolkit / bamtools_split.cpp
index 748127f08b061974b8a132ca0229d33b0fa407f6..e6602a9e590e250d3dd204fea990db579db20db4 100644 (file)
@@ -1,26 +1,29 @@
 // ***************************************************************************
 // bamtools_split.cpp (c) 2010 Derek Barnett, Erik Garrison
 // Marth Lab, Department of Biology, Boston College
-// All rights reserved.
 // ---------------------------------------------------------------------------
-// Last modified: 20 September 2010 (DB)
+// Last modified: 8 December 2011 (DB)
 // ---------------------------------------------------------------------------
-// 
+// Splits a BAM file on user-specified property, creating a new BAM output
+// file for each value found
 // ***************************************************************************
 
+#include "bamtools_split.h"
+
+#include <api/BamConstants.h>
+#include <api/BamReader.h>
+#include <api/BamWriter.h>
+#include <utils/bamtools_options.h>
+#include <utils/bamtools_variant.h>
+using namespace BamTools;
+
 #include <ctime>
 #include <iostream>
 #include <map>
 #include <sstream>
 #include <string>
 #include <vector>
-#include "bamtools_split.h"
-#include "bamtools_options.h"
-#include "bamtools_variant.h"
-#include "BamReader.h"
-#include "BamWriter.h"
 using namespace std;
-using namespace BamTools;
 
 namespace BamTools {
   
@@ -66,6 +69,7 @@ struct SplitTool::SplitSettings {
     // flags
     bool HasInputFilename;
     bool HasCustomOutputStub;
+    bool HasCustomRefPrefix;
     bool IsSplittingMapped;
     bool IsSplittingPaired;
     bool IsSplittingReference;
@@ -73,6 +77,7 @@ struct SplitTool::SplitSettings {
     
     // string args
     string CustomOutputStub;
+    string CustomRefPrefix;
     string InputFilename;
     string TagToSplit;
     
@@ -80,11 +85,13 @@ struct SplitTool::SplitSettings {
     SplitSettings(void)
         : HasInputFilename(false)
         , HasCustomOutputStub(false)
+        , HasCustomRefPrefix(false)
         , IsSplittingMapped(false)
         , IsSplittingPaired(false)
         , IsSplittingReference(false)
         , IsSplittingTag(false)
         , CustomOutputStub("")
+        , CustomRefPrefix("")
         , InputFilename(Options::StandardIn())
         , TagToSplit("")
     { } 
@@ -97,8 +104,13 @@ class SplitTool::SplitToolPrivate {
       
     // ctor & dtor
     public:
-        SplitToolPrivate(SplitTool::SplitSettings* settings);
-        ~SplitToolPrivate(void);
+        SplitToolPrivate(SplitTool::SplitSettings* settings)
+            : m_settings(settings)
+        { }
+
+        ~SplitToolPrivate(void) {
+            m_reader.Close();
+        }
         
     // 'public' interface
     public:
@@ -136,16 +148,6 @@ class SplitTool::SplitToolPrivate {
         RefVector m_references;
 };
 
-// constructor
-SplitTool::SplitToolPrivate::SplitToolPrivate(SplitTool::SplitSettings* settings) 
-    : m_settings(settings)
-{ }
-
-// destructor
-SplitTool::SplitToolPrivate::~SplitToolPrivate(void) { 
-    m_reader.Close();
-}
-
 void SplitTool::SplitToolPrivate::DetermineOutputFilenameStub(void) {
   
     // if user supplied output filename stub, use that
@@ -162,10 +164,14 @@ void SplitTool::SplitToolPrivate::DetermineOutputFilenameStub(void) {
 }
 
 bool SplitTool::SplitToolPrivate::OpenReader(void) {
+
+    // attempt to open BAM file
     if ( !m_reader.Open(m_settings->InputFilename) ) {
-        cerr << "ERROR: SplitTool could not open BAM file: " << m_settings->InputFilename << endl;
+        cerr << "bamtools split ERROR: could not open BAM file: " << m_settings->InputFilename << endl;
         return false;
     }
+
+    // save file 'metadata' & return success
     m_header     = m_reader.GetHeaderText();
     m_references = m_reader.GetReferenceData();
     return true;
@@ -177,7 +183,8 @@ bool SplitTool::SplitToolPrivate::Run(void) {
     DetermineOutputFilenameStub();
 
     // open up BamReader
-    if ( !OpenReader() ) return false;
+    if ( !OpenReader() )
+        return false;
     
     // determine split type from settings
     if ( m_settings->IsSplittingMapped )    return SplitMapped();
@@ -186,7 +193,8 @@ bool SplitTool::SplitToolPrivate::Run(void) {
     if ( m_settings->IsSplittingTag )       return SplitTag();
 
     // if we get here, no property was specified 
-    cerr << "No property given to split on... Please use -mapped, -paired, -reference, or -tag TAG to specifiy split behavior." << endl;
+    cerr << "bamtools split ERROR: no property given to split on... " << endl
+         << "Please use -mapped, -paired, -reference, or -tag TAG to specifiy desired split behavior." << endl;
     return false;
 }    
 
@@ -210,9 +218,15 @@ bool SplitTool::SplitToolPrivate::SplitMapped(void) {
         if ( writerIter == outputFiles.end() ) {
         
             // open new BamWriter
+            const string outputFilename = m_outputFilenameStub + ( isCurrentAlignmentMapped
+                                                                  ? SPLIT_MAPPED_TOKEN
+                                                                  : SPLIT_UNMAPPED_TOKEN ) + ".bam";
             writer = new BamWriter;
-            const string outputFilename = m_outputFilenameStub + ( isCurrentAlignmentMapped ? SPLIT_MAPPED_TOKEN : SPLIT_UNMAPPED_TOKEN ) + ".bam";
-            writer->Open(outputFilename, m_header, m_references);
+            if ( !writer->Open(outputFilename, m_header, m_references) ) {
+                cerr << "bamtools split ERROR: could not open " << outputFilename
+                     << " for writing." << endl;
+                return false;
+            }
           
             // store in map
             outputFiles.insert( make_pair(isCurrentAlignmentMapped, writer) );
@@ -222,7 +236,7 @@ bool SplitTool::SplitToolPrivate::SplitMapped(void) {
         else writer = (*writerIter).second;
         
         // store alignment in proper BAM output file 
-        if ( writer ) 
+        if ( writer )
             writer->SaveAlignment(al);
     }
     
@@ -253,9 +267,15 @@ bool SplitTool::SplitToolPrivate::SplitPaired(void) {
         if ( writerIter == outputFiles.end() ) {
         
             // open new BamWriter
+            const string outputFilename = m_outputFilenameStub + ( isCurrentAlignmentPaired
+                                                                  ? SPLIT_PAIRED_TOKEN
+                                                                  : SPLIT_SINGLE_TOKEN ) + ".bam";
             writer = new BamWriter;
-            const string outputFilename = m_outputFilenameStub + ( isCurrentAlignmentPaired ? SPLIT_PAIRED_TOKEN : SPLIT_SINGLE_TOKEN ) + ".bam";
-            writer->Open(outputFilename, m_header, m_references);
+            if ( !writer->Open(outputFilename, m_header, m_references) ) {
+                cerr << "bamtool split ERROR: could not open " << outputFilename
+                     << " for writing." << endl;
+                return false;
+            }
           
             // store in map
             outputFiles.insert( make_pair(isCurrentAlignmentPaired, writer) );
@@ -282,6 +302,16 @@ bool SplitTool::SplitToolPrivate::SplitReference(void) {
     map<int32_t, BamWriter*> outputFiles;
     map<int32_t, BamWriter*>::iterator writerIter;
     
+    // determine reference prefix
+    string refPrefix = SPLIT_REFERENCE_TOKEN;
+    if ( m_settings->HasCustomRefPrefix )
+        refPrefix = m_settings->CustomRefPrefix;
+
+    // make sure prefix starts with '.'
+    const size_t dotFound = refPrefix.find('.');
+    if ( dotFound != 0 )
+        refPrefix = string(".") + refPrefix;
+
     // iterate through alignments
     BamAlignment al;
     BamWriter* writer;
@@ -295,12 +325,24 @@ bool SplitTool::SplitToolPrivate::SplitReference(void) {
         // if no writer associated with this value
         if ( writerIter == outputFiles.end() ) {
         
+            // fetch reference name for ID
+            string refName;
+            if ( currentRefId == -1 )
+                refName = "unmapped";
+            else
+                refName = m_references.at(currentRefId).RefName;
+
+            // construct new output filename
+            const string outputFilename = m_outputFilenameStub + refPrefix + refName + ".bam";
+
             // open new BamWriter
             writer = new BamWriter;
-            const string refName = m_references.at(currentRefId).RefName;
-            const string outputFilename = m_outputFilenameStub + SPLIT_REFERENCE_TOKEN + refName + ".bam";
-            writer->Open(outputFilename, m_header, m_references);
-          
+            if ( !writer->Open(outputFilename, m_header, m_references) ) {
+                cerr << "bamtools split ERROR: could not open " << outputFilename
+                     << " for writing." << endl;
+                return false;
+            }
+
             // store in map
             outputFiles.insert( make_pair(currentRefId, writer) );
         } 
@@ -329,32 +371,37 @@ bool SplitTool::SplitToolPrivate::SplitTag(void) {
       
         // look for tag in this alignment and get tag type
         char tagType(0);
-        if ( !al.GetTagType(m_settings->TagToSplit, tagType) ) continue;
+        if ( !al.GetTagType(m_settings->TagToSplit, tagType) )
+            continue;
         
         // request split method based on tag type
         // pass it the current alignment found
-        switch (tagType) {
+        switch ( tagType ) {
           
-            case 'c' :
-            case 's' : 
-            case 'i' :
+            case (Constants::BAM_TAG_TYPE_INT8)  :
+            case (Constants::BAM_TAG_TYPE_INT16) :
+            case (Constants::BAM_TAG_TYPE_INT32) :
                 return SplitTagImpl<int32_t>(al);
                 
-            case 'C' :
-            case 'S' :
-            case 'I' : 
+            case (Constants::BAM_TAG_TYPE_UINT8)  :
+            case (Constants::BAM_TAG_TYPE_UINT16) :
+            case (Constants::BAM_TAG_TYPE_UINT32) :
                 return SplitTagImpl<uint32_t>(al);
               
-            case 'f' :
+            case (Constants::BAM_TAG_TYPE_FLOAT)  :
                 return SplitTagImpl<float>(al);
             
-            case 'A':
-            case 'Z':
-            case 'H':
+            case (Constants::BAM_TAG_TYPE_ASCII)  :
+            case (Constants::BAM_TAG_TYPE_STRING) :
+            case (Constants::BAM_TAG_TYPE_HEX)    :
                 return SplitTagImpl<string>(al);
+
+            case (Constants::BAM_TAG_TYPE_ARRAY) :
+                cerr << "bamtools split ERROR: array tag types are not supported" << endl;
+                return false;
           
             default:
-                fprintf(stderr, "ERROR: Unknown tag storage class encountered: [%c]\n", tagType);
+                cerr << "bamtools split ERROR: unknown tag type encountered: " << tagType << endl;
                 return false;
         }
     }
@@ -365,8 +412,9 @@ bool SplitTool::SplitToolPrivate::SplitTag(void) {
 
 // --------------------------------------------------------------------------------
 // template method implementation
-// N.B. - *technical note* - use of template methods defined in ".cpp" goes against normal practices
-// but works here because these are purely internal (no one can call from outside this file)
+// *Technical Note* - use of template methods declared & defined in ".cpp" file
+//                    goes against normal practices, but works here because these
+//                    are purely internal (no one can call from outside this file)
 
 // close BamWriters & delete pointers
 template<typename T>
@@ -375,15 +423,22 @@ void SplitTool::SplitToolPrivate::CloseWriters(map<T, BamWriter*>& writers) {
     typedef map<T, BamWriter*> WriterMap;
     typedef typename WriterMap::iterator WriterMapIterator;
   
+    // iterate over writers
     WriterMapIterator writerIter = writers.begin();
     WriterMapIterator writerEnd  = writers.end();
     for ( ; writerIter != writerEnd; ++writerIter ) {
         BamWriter* writer = (*writerIter).second;
-        if (writer == 0 ) continue;
+        if ( writer == 0 ) continue;
+
+        // close BamWriter
         writer->Close();
+
+        // destroy BamWriter
         delete writer;
         writer = 0;
     }
+
+    // clear the container (destroying the items doesn't remove them)
     writers.clear();
 }
 
@@ -409,9 +464,13 @@ bool SplitTool::SplitToolPrivate::SplitTagImpl(BamAlignment& al) {
     if ( al.GetTag(tag, currentValue) ) {
       
         // open new BamWriter, save first alignment
-        writer = new BamWriter;
         outputFilenameStream << m_outputFilenameStub << ".TAG_" << tag << "_" << currentValue << ".bam";
-        writer->Open(outputFilenameStream.str(), m_header, m_references);
+        writer = new BamWriter;
+        if ( !writer->Open(outputFilenameStream.str(), m_header, m_references) ) {
+            cerr << "bamtools split ERROR: could not open " << outputFilenameStream.str()
+                 << " for writing." << endl;
+            return false;
+        }
         writer->SaveAlignment(al);
         
         // store in map
@@ -434,10 +493,14 @@ bool SplitTool::SplitToolPrivate::SplitTagImpl(BamAlignment& al) {
         if ( writerIter == outputFiles.end() ) {
         
             // open new BamWriter
-            writer = new BamWriter;
             outputFilenameStream << m_outputFilenameStub << ".TAG_" << tag << "_" << currentValue << ".bam";
-            writer->Open(outputFilenameStream.str(), m_header, m_references);
-            
+            writer = new BamWriter;
+            if ( !writer->Open(outputFilenameStream.str(), m_header, m_references) ) {
+                cerr << "bamtool split ERROR: could not open " << outputFilenameStream.str()
+                     << " for writing." << endl;
+                return false;
+            }
+
             // store in map
             outputFiles.insert( make_pair(currentValue, writer) );
             
@@ -469,12 +532,18 @@ SplitTool::SplitTool(void)
     , m_impl(0)
 {
     // set program details
-    Options::SetProgramInfo("bamtools split", "splits a BAM file on user-specified property, creating a new BAM output file for each value found", "[-in <filename>] [-stub <filename stub>] < -mapped | -paired | -reference | -tag <TAG> > ");
+    const string name = "bamtools split";
+    const string description = "splits a BAM file on user-specified property, creating a new BAM output file for each value found";
+    const string args = "[-in <filename>] [-stub <filename stub>] < -mapped | -paired | -reference [-refPrefix <prefix>] | -tag <TAG> > ";
+    Options::SetProgramInfo(name, description, args);
     
     // set up options 
     OptionGroup* IO_Opts = Options::CreateOptionGroup("Input & Output");
     Options::AddValueOption("-in",   "BAM filename",  "the input BAM file",  "", m_settings->HasInputFilename,  m_settings->InputFilename,  IO_Opts, Options::StandardIn());
-    Options::AddValueOption("-stub", "filename stub", "prefix stub for output BAM files (default behavior is to use input filename, without .bam extension, as stub). If input is stdin and no stub provided, a timestamp is generated as the stub.", "", m_settings->HasCustomOutputStub, m_settings->CustomOutputStub, IO_Opts);
+    Options::AddValueOption("-refPrefix", "string", "custom prefix for splitting by references. Currently files end with REF_<refName>.bam. This option allows you to replace \"REF_\" with a prefix of your choosing.", "",
+                            m_settings->HasCustomRefPrefix, m_settings->CustomRefPrefix, IO_Opts);
+    Options::AddValueOption("-stub", "filename stub", "prefix stub for output BAM files (default behavior is to use input filename, without .bam extension, as stub). If input is stdin and no stub provided, a timestamp is generated as the stub.", "",
+                            m_settings->HasCustomOutputStub, m_settings->CustomOutputStub, IO_Opts);
     
     OptionGroup* SplitOpts = Options::CreateOptionGroup("Split Options");
     Options::AddOption("-mapped",    "split mapped/unmapped alignments",       m_settings->IsSplittingMapped,    SplitOpts);
@@ -503,10 +572,10 @@ int SplitTool::Run(int argc, char* argv[]) {
     // parse command line arguments
     Options::Parse(argc, argv, 1);
     
-    // initialize internal implementation
+    // initialize SplitTool with settings
     m_impl = new SplitToolPrivate(m_settings);
     
-    // run tool, return success/fail
+    // run SplitTool, return success/fail
     if ( m_impl->Run() ) 
         return 0;
     else