From f36d6db42bde718b1f8e08acd175d55cffd77e56 Mon Sep 17 00:00:00 2001 From: nkindlon <nek3d@virginia.edu> Date: Mon, 7 Apr 2014 22:32:49 -0400 Subject: [PATCH] Merge converted to PFM, first check-in --- Makefile | 2 +- src/intersectFile/intersectFile.cpp | 2 +- src/mergeBed/Makefile | 35 -- src/mergeBed/mergeBed.cpp | 359 ------------------ src/mergeBed/mergeBed.h | 72 ---- src/mergeFile/Makefile | 46 +++ src/mergeFile/mergeFile.cpp | 39 ++ src/mergeFile/mergeFile.h | 37 ++ src/{mergeBed => mergeFile}/mergeMain.cpp | 118 +----- src/nekSandbox1/nekSandboxMain.cpp | 6 +- src/sampleFile/SampleFile.cpp | 2 +- src/utils/BinTree/BinTree.cpp | 2 +- src/utils/Contexts/ContextBase.cpp | 30 +- src/utils/Contexts/ContextBase.h | 47 ++- src/utils/Contexts/ContextMap.cpp | 10 - src/utils/Contexts/ContextMap.h | 4 - src/utils/Contexts/ContextMerge.cpp | 164 ++++++++ src/utils/Contexts/ContextMerge.h | 31 ++ src/utils/Contexts/Makefile | 8 +- .../FileRecordTools/FileRecordMergeMgr.cpp | 204 ++++++++++ .../FileRecordTools/FileRecordMergeMgr.h | 55 +++ src/utils/FileRecordTools/FileRecordMgr.cpp | 185 +-------- src/utils/FileRecordTools/FileRecordMgr.h | 77 +--- src/utils/FileRecordTools/Makefile | 10 +- src/utils/FileRecordTools/Records/Makefile | 6 +- src/utils/FileRecordTools/Records/Record.h | 5 +- .../FileRecordTools/Records/StrandQueue.cpp | 131 +++++++ .../FileRecordTools/Records/StrandQueue.h | 47 +++ .../FileRecordTools/Records/recordsTar.tar.gz | Bin 54443 -> 0 bytes src/utils/KeyListOps/KeyListOps.h | 14 + src/utils/NewChromsweep/NewChromsweep.cpp | 4 +- src/utils/RecordOutputMgr/RecordOutputMgr.cpp | 22 +- src/utils/RecordOutputMgr/RecordOutputMgr.h | 1 + src/utils/general/DualQueue.h | 124 ------ src/utils/general/ParseTools.cpp | 3 + src/utils/general/ParseTools.h | 2 +- test/merge/test-merge.sh | 48 +-- 37 files changed, 930 insertions(+), 1022 deletions(-) delete mode 100644 src/mergeBed/Makefile delete mode 100644 src/mergeBed/mergeBed.cpp delete mode 100644 src/mergeBed/mergeBed.h create mode 100644 src/mergeFile/Makefile create mode 100644 src/mergeFile/mergeFile.cpp create mode 100644 src/mergeFile/mergeFile.h rename src/{mergeBed => mergeFile}/mergeMain.cpp (52%) create mode 100644 src/utils/Contexts/ContextMerge.cpp create mode 100644 src/utils/Contexts/ContextMerge.h create mode 100644 src/utils/FileRecordTools/FileRecordMergeMgr.cpp create mode 100644 src/utils/FileRecordTools/FileRecordMergeMgr.h create mode 100644 src/utils/FileRecordTools/Records/StrandQueue.cpp create mode 100644 src/utils/FileRecordTools/Records/StrandQueue.h delete mode 100644 src/utils/FileRecordTools/Records/recordsTar.tar.gz delete mode 100644 src/utils/general/DualQueue.h diff --git a/Makefile b/Makefile index 26767488..8a7911fa 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,7 @@ SUBDIRS = $(SRC_DIR)/annotateBed \ $(SRC_DIR)/linksBed \ $(SRC_DIR)/maskFastaFromBed \ $(SRC_DIR)/mapFile \ - $(SRC_DIR)/mergeBed \ + $(SRC_DIR)/mergeFile \ $(SRC_DIR)/multiBamCov \ $(SRC_DIR)/multiIntersectBed \ $(SRC_DIR)/nekSandbox1 \ diff --git a/src/intersectFile/intersectFile.cpp b/src/intersectFile/intersectFile.cpp index 928d1d9a..e61f4d23 100644 --- a/src/intersectFile/intersectFile.cpp +++ b/src/intersectFile/intersectFile.cpp @@ -81,7 +81,7 @@ bool FileIntersect::processUnsortedFiles() while (!queryFRM->eof()) { - Record *queryRecord = queryFRM->allocateAndGetNextRecord(); + Record *queryRecord = queryFRM->getNextRecord(); if (queryRecord == NULL) { continue; } diff --git a/src/mergeBed/Makefile b/src/mergeBed/Makefile deleted file mode 100644 index 0eaaa645..00000000 --- a/src/mergeBed/Makefile +++ /dev/null @@ -1,35 +0,0 @@ -UTILITIES_DIR = ../utils/ -OBJ_DIR = ../../obj/ -BIN_DIR = ../../bin/ - -# ------------------- -# define our includes -# ------------------- -INCLUDES = -I$(UTILITIES_DIR)/bedFile/ \ - -I$(UTILITIES_DIR)/lineFileUtilities/ \ - -I$(UTILITIES_DIR)/gzstream/ \ - -I$(UTILITIES_DIR)/fileType/ \ - -I$(UTILITIES_DIR)/VectorOps/ \ - -I$(UTILITIES_DIR)/version/ - -# ---------------------------------- -# define our source and object files -# ---------------------------------- -SOURCES= mergeMain.cpp mergeBed.cpp mergeBed.h -OBJECTS= mergeMain.o mergeBed.o -BUILT_OBJECTS= $(patsubst %,$(OBJ_DIR)/%,$(OBJECTS)) - - -all: $(BUILT_OBJECTS) - -.PHONY: all - -$(BUILT_OBJECTS): $(SOURCES) - @echo " * compiling" $(*F).cpp - @$(CXX) -c -o $@ $(*F).cpp $(LDFLAGS) $(CXXFLAGS) $(INCLUDES) - -clean: - @echo "Cleaning up." - @rm -f $(OBJ_DIR)/mergeMain.o $(OBJ_DIR)/mergeBed.o - -.PHONY: clean \ No newline at end of file diff --git a/src/mergeBed/mergeBed.cpp b/src/mergeBed/mergeBed.cpp deleted file mode 100644 index dae9887b..00000000 --- a/src/mergeBed/mergeBed.cpp +++ /dev/null @@ -1,359 +0,0 @@ -/***************************************************************************** - mergeBed.cpp - - (c) 2009 - Aaron Quinlan - Hall Laboratory - Department of Biochemistry and Molecular Genetics - University of Virginia - aaronquinlan@gmail.com - - Licenced under the GNU General Public License 2.0 license. -******************************************************************************/ -#include "lineFileUtilities.h" -#include "mergeBed.h" - - - -void BedMerge::ReportMergedNames(const vector<string> &names) { - if (names.size() > 0) { - printf("\t"); - vector<string>::const_iterator nameItr = names.begin(); - vector<string>::const_iterator nameEnd = names.end(); - for (; nameItr != nameEnd; ++nameItr) { - if (nameItr < (nameEnd - 1)) - cout << *nameItr << _delimiter; - else - cout << *nameItr; - } - } - else { - cerr << endl - << "*****" << endl - << "*****ERROR: " - << "No names found to report for the -names option. Exiting." - << endl - << "*****" << endl; - exit(1); - } -} - - -void BedMerge::ReportMergedScores(const vector<string> &scores) { - - // setup a VectorOps instances for the list of scores. - // VectorOps methods used for each possible operation. - VectorOps vo(scores); - std::stringstream buffer; - if (scores.size() > 0) { - if (_scoreOp == "sum") - buffer << setprecision (PRECISION) << vo.GetSum(); - else if (_scoreOp == "min") - buffer << setprecision (PRECISION) << vo.GetMin(); - else if (_scoreOp == "max") - buffer << setprecision (PRECISION) << vo.GetMax(); - else if (_scoreOp == "mean") - buffer << setprecision (PRECISION) << vo.GetMean(); - else if (_scoreOp == "median") - buffer << setprecision (PRECISION) << vo.GetMedian(); - else if (_scoreOp == "mode") - buffer << setprecision (PRECISION) << vo.GetMode(); - else if (_scoreOp == "antimode") - buffer << setprecision (PRECISION) << vo.GetAntiMode(); - else if (_scoreOp == "collapse") - buffer << setprecision (PRECISION) << vo.GetCollapse(_delimiter); - cout << "\t" << buffer.str(); - } - else { - cerr << endl - << "*****" << endl - << "*****ERROR: No scores found to report for the -scores option. Exiting." << endl - << "*****" << endl; - exit(1); - } -} - -// =============== -// = Constructor = -// =============== -BedMerge::BedMerge(string &bedFile, - bool numEntries, - int maxDistance, - bool forceStrand, - bool reportNames, - bool reportScores, - const string &scoreOp, - const string &delimiter) : - _bedFile(bedFile), - _numEntries(numEntries), - _forceStrand(forceStrand), - _reportNames(reportNames), - _reportScores(reportScores), - _scoreOp(scoreOp), - _maxDistance(maxDistance), - _delimiter(delimiter) -{ - _bed = new BedFile(bedFile); - - if (_forceStrand == false) - MergeBed(); - else - MergeBedStranded(); -} - - -// ================= -// = Destructor = -// ================= -BedMerge::~BedMerge(void) { -} - - -// =============================================== -// Convenience method for reporting merged blocks -// ================================================ -void BedMerge::Report(string chrom, int start, - int end, const vector<string> &names, - const vector<string> &scores, int mergeCount) -{ - // ARQ: removed to force all output to be zero-based, BED format, reagrdless of input type - //if (_bed->isZeroBased == false) {start++;} - - printf("%s\t%d\t%d", chrom.c_str(), start, end); - // just the merged intervals - if (_numEntries == false && _reportNames == false && - _reportScores == false) { - printf("\n"); - } - // merged intervals and counts - else if (_numEntries == true && _reportNames == false && - _reportScores == false) { - printf("\t%d\n", mergeCount); - } - // merged intervals, counts, and scores - else if (_numEntries == true && _reportNames == false && - _reportScores == true) { - printf("\t%d", mergeCount); - ReportMergedScores(scores); - printf("\n"); - } - // merged intervals, counts, and names - else if (_numEntries == true && _reportNames == true && - _reportScores == false) { - ReportMergedNames(names); - printf("\t%d\n", mergeCount); - } - // merged intervals, counts, names, and scores - else if (_numEntries == true && _reportNames == true && - _reportScores == true) { - ReportMergedNames(names); - ReportMergedScores(scores); - printf("\t%d\n", mergeCount); - } - // merged intervals and names - else if (_numEntries == false && _reportNames == true && - _reportScores == false) { - ReportMergedNames(names); - printf("\n"); - } - // merged intervals and scores - else if (_numEntries == false && _reportNames == false && - _reportScores == true) { - ReportMergedScores(scores); - printf("\n"); - } - // merged intervals, names, and scores - else if (_numEntries == false && _reportNames == true && - _reportScores == true) { - ReportMergedNames(names); - ReportMergedScores(scores); - printf("\n"); - } -} - - -// ========================================================= -// Convenience method for reporting merged blocks by strand -// ========================================================= -void BedMerge::ReportStranded(string chrom, int start, - int end, const vector<string> &names, - const vector<string> &scores, int mergeCount, - string strand) -{ - // ARQ: removed to force all output to be zero-based, BED format, reagrdless of input type - //if (_bed->isZeroBased == false) {start++;} - - printf("%s\t%d\t%d", chrom.c_str(), start, end); - // just the merged intervals - if (_numEntries == false && _reportNames == false && - _reportScores == false) { - printf("\t\t\t%s\n", strand.c_str()); - } - // merged intervals and counts - else if (_numEntries == true && _reportNames == false && - _reportScores == false) { - printf("\t\t%d\t%s\n", mergeCount, strand.c_str()); - } - // merged intervals, counts, and scores - else if (_numEntries == true && _reportNames == false && - _reportScores == true) { - printf("\t%d", mergeCount); - ReportMergedScores(scores); - printf("\t%s\n", strand.c_str()); - } - // merged intervals, counts, and names - else if (_numEntries == true && _reportNames == true && - _reportScores == false) { - ReportMergedNames(names); - printf("\t%d\t%s", mergeCount, strand.c_str()); - printf("\n"); - } - // merged intervals, counts, names, and scores - else if (_numEntries == true && _reportNames == true && - _reportScores == true) { - ReportMergedNames(names); - ReportMergedScores(scores); - printf("\t%s\t%d", strand.c_str(), mergeCount); - printf("\n"); - } - // merged intervals and names - else if (_numEntries == false && _reportNames == true && - _reportScores == false) { - ReportMergedNames(names); - printf("\t.\t%s\n", strand.c_str()); - } - // merged intervals and scores - else if (_numEntries == false && _reportNames == false && - _reportScores == true) { - printf("\t"); - ReportMergedScores(scores); - printf("\t%s\n", strand.c_str()); - } - // merged intervals, names, and scores - else if (_numEntries == false && _reportNames == true && - _reportScores == true) { - ReportMergedNames(names); - ReportMergedScores(scores); - printf("\t%s\n", strand.c_str()); - } -} - - -// ===================================================== -// = Merge overlapping BED entries into a single entry = -// ===================================================== -void BedMerge::MergeBed() { - int mergeCount = 1; - vector<string> names; - vector<string> scores; - int start = -1; - int end = -1; - BED prev, curr; - - _bed->Open(); - while (_bed->GetNextBed(curr, true)) { // true = force sorted intervals - if (_bed->_status != BED_VALID) - continue; - // new block, no overlap - if ( (((int) curr.start - end) > _maxDistance) || - (curr.chrom != prev.chrom)) - { - if (start >= 0) { - Report(prev.chrom, start, end, names, scores, mergeCount); - // reset - mergeCount = 1; - names.clear(); - scores.clear(); - } - start = curr.start; - end = curr.end; - if (!curr.name.empty()) - names.push_back(curr.name); - if (!curr.score.empty()) - scores.push_back(curr.score); - } - // same block, overlaps - else { - if ((int) curr.end > end) - end = curr.end; - if (!curr.name.empty()) - names.push_back(curr.name); - if (!curr.score.empty()) - scores.push_back(curr.score); - mergeCount++; - } - prev = curr; - } - if (start >= 0) { - Report(prev.chrom, start, end, names, scores, mergeCount); - } -} - - -// =============================================================================== -// = Merge overlapping BED entries into a single entry, accounting for strandedness = -// ================================================================================ -void BedMerge::MergeBedStranded() { - - // load the "B" bed file into a map so - // that we can easily compare "A" to it for overlaps - _bed->loadBedFileIntoMapNoBin(); - - // loop through each chromosome and merge their BED entries - masterBedMapNoBin::const_iterator m = _bed->bedMapNoBin.begin(); - masterBedMapNoBin::const_iterator mEnd = _bed->bedMapNoBin.end(); - for (; m != mEnd; ++m) { - - // bedList is already sorted by start position. - string chrom = m->first; - - // make a list of the two strands to merge separately. - vector<string> strands(2); - strands[0] = "+"; - strands[1] = "-"; - // do two passes, one for each strand. - for (unsigned int s = 0; s < strands.size(); s++) { - int mergeCount = 1; - int numOnStrand = 0; - vector<string> names; - vector<string> scores; - - // merge overlapping features for this chromosome. - int start = -1; - int end = -1; - vector<BED>::const_iterator bedItr = m->second.begin(); - vector<BED>::const_iterator bedEnd = m->second.end(); - for (; bedItr != bedEnd; ++bedItr) { - // if forcing strandedness, move on if the hit - // is not on the current strand. - if (bedItr->strand != strands[s]) { continue; } - else { numOnStrand++; } - if ( (((int) bedItr->start - end) > _maxDistance) || - (end < 0)) - { - if (start >= 0) { - ReportStranded(chrom, start, end, names, - scores, mergeCount, strands[s]); - // reset - mergeCount = 1; - names.clear(); - scores.clear(); - } - start = bedItr->start; - end = bedItr->end; - if (!bedItr->name.empty()) names.push_back(bedItr->name); - if (!bedItr->score.empty()) scores.push_back(bedItr->score); - } - else { - if ((int) bedItr-> end > end) end = bedItr->end; - mergeCount++; - if (!bedItr->name.empty()) names.push_back(bedItr->name); - if (!bedItr->score.empty()) scores.push_back(bedItr->score); - } - } - if (start >= 0) { - ReportStranded(chrom, start, end, names, - scores, mergeCount, strands[s]); - } - } - } -} diff --git a/src/mergeBed/mergeBed.h b/src/mergeBed/mergeBed.h deleted file mode 100644 index d9b0c143..00000000 --- a/src/mergeBed/mergeBed.h +++ /dev/null @@ -1,72 +0,0 @@ -/***************************************************************************** - mergeBed.h - - (c) 2009 - Aaron Quinlan - Hall Laboratory - Department of Biochemistry and Molecular Genetics - University of Virginia - aaronquinlan@gmail.com - - Licenced under the GNU General Public License 2.0 license. -******************************************************************************/ -#include "bedFile.h" -#include <vector> -#include <algorithm> -#include <numeric> -#include <iostream> -#include <iomanip> -#include <fstream> -#include <limits.h> -#include <stdlib.h> -#include "VectorOps.h" - -using namespace std; - -const int PRECISION = 21; - -//************************************************ -// Class methods and elements -//************************************************ -class BedMerge { - -public: - - // constructor - BedMerge(string &bedFile, bool numEntries, - int maxDistance, bool forceStrand, - bool reportNames, bool reportScores, - const string &scoreOp, const string &delimiter); - - // destructor - ~BedMerge(void); - - void MergeBed(); - void MergeBedStranded(); - -private: - - string _bedFile; - bool _numEntries; - bool _forceStrand; - bool _reportNames; - bool _reportScores; - string _scoreOp; - int _maxDistance; - string _delimiter; - // instance of a bed file class. - BedFile *_bed; - - void Report(string chrom, int start, int end, - const vector<string> &names, - const vector<string> &scores, - int mergeCount); - - void ReportStranded(string chrom, int start, int end, - const vector<string> &names, - const vector<string> &scores, - int mergeCount, - string strand); - void ReportMergedNames(const vector<string> &names); - void ReportMergedScores(const vector<string> &scores); - -}; diff --git a/src/mergeFile/Makefile b/src/mergeFile/Makefile new file mode 100644 index 00000000..50f40042 --- /dev/null +++ b/src/mergeFile/Makefile @@ -0,0 +1,46 @@ +UTILITIES_DIR = ../utils/ +OBJ_DIR = ../../obj/ +BIN_DIR = ../../bin/ + +# ------------------- +# define our includes +# ------------------- +INCLUDES = -I$(UTILITIES_DIR)/Contexts/ \ + -I$(UTILITIES_DIR)/general/ \ + -I$(UTILITIES_DIR)/fileType/ \ + -I$(UTILITIES_DIR)/gzstream/ \ + -I$(UTILITIES_DIR)/GenomeFile/ \ + -I$(UTILITIES_DIR)/BamTools/include \ + -I$(UTILITIES_DIR)/BamTools/src \ + -I$(UTILITIES_DIR)/BlockedIntervals \ + -I$(UTILITIES_DIR)/BamTools-Ancillary \ + -I$(UTILITIES_DIR)/FileRecordTools/ \ + -I$(UTILITIES_DIR)/FileRecordTools/FileReaders/ \ + -I$(UTILITIES_DIR)/FileRecordTools/Records/ \ + -I$(UTILITIES_DIR)/KeyListOps/ \ + -I$(UTILITIES_DIR)/RecordOutputMgr/ \ + -I$(UTILITIES_DIR)/NewChromsweep \ + -I$(UTILITIES_DIR)/BinTree \ + -I$(UTILITIES_DIR)/version/ + +# ---------------------------------- +# define our source and object files +# ---------------------------------- +SOURCES= mergeMain.cpp mergeFile.cpp mergeFile.h +OBJECTS= mergeMain.o mergeFile.o +BUILT_OBJECTS= $(patsubst %,$(OBJ_DIR)/%,$(OBJECTS)) + + +all: $(BUILT_OBJECTS) + +.PHONY: all + +$(BUILT_OBJECTS): $(SOURCES) + @echo " * compiling" $(*F).cpp + @$(CXX) -c -o $@ $(*F).cpp $(LDFLAGS) $(CXXFLAGS) $(INCLUDES) + +clean: + @echo "Cleaning up." + @rm -f $(OBJ_DIR)/mergeMain.o $(OBJ_DIR)/mergeFile.o + +.PHONY: clean \ No newline at end of file diff --git a/src/mergeFile/mergeFile.cpp b/src/mergeFile/mergeFile.cpp new file mode 100644 index 00000000..f772629c --- /dev/null +++ b/src/mergeFile/mergeFile.cpp @@ -0,0 +1,39 @@ +/***************************************************************************** + mergeFile.cpp + + (c) 2009 - Aaron Quinlan + Hall Laboratory + Department of Biochemistry and Molecular Genetics + University of Virginia + aaronquinlan@gmail.com + + Licenced under the GNU General Public License 2.0 license. +******************************************************************************/ +#include "mergeFile.h" + + +MergeFile::MergeFile(ContextMerge *context) +: _context(context), + _recordOutputMgr(NULL) +{ + _recordOutputMgr = new RecordOutputMgr(); + _recordOutputMgr->init(_context); +} + +MergeFile::~MergeFile() +{ + delete _recordOutputMgr; + _recordOutputMgr = NULL; +} + +bool MergeFile::merge() +{ + RecordKeyList hitSet; + FileRecordMgr *frm = _context->getFile(0); + while (!frm->eof()) { + Record *key = frm->getNextRecord(&hitSet); + if (key == NULL) continue; + _recordOutputMgr->printRecord(hitSet.getKey(), _context->getColumnOpsVal(hitSet)); + } + return true; +} diff --git a/src/mergeFile/mergeFile.h b/src/mergeFile/mergeFile.h new file mode 100644 index 00000000..0d876c67 --- /dev/null +++ b/src/mergeFile/mergeFile.h @@ -0,0 +1,37 @@ +/***************************************************************************** + mergeFile.h + + (c) 2009 - Aaron Quinlan + Hall Laboratory + Department of Biochemistry and Molecular Genetics + University of Virginia + aaronquinlan@gmail.com + + Licenced under the GNU General Public License 2.0 license. +******************************************************************************/ + +#ifndef MERGE_FILE_H_ +#define MERGE_FILE_H_ + +//************************************************ +// Class methods and elements +//************************************************ + +#include "ContextMerge.h" +#include "RecordOutputMgr.h" + +class MergeFile { + +public: + MergeFile(ContextMerge *context); + ~MergeFile(); + + bool merge(); + +private: + ContextMerge *_context; + RecordOutputMgr *_recordOutputMgr; + +}; + +#endif diff --git a/src/mergeBed/mergeMain.cpp b/src/mergeFile/mergeMain.cpp similarity index 52% rename from src/mergeBed/mergeMain.cpp rename to src/mergeFile/mergeMain.cpp index 28b869af..3f443e27 100644 --- a/src/mergeBed/mergeMain.cpp +++ b/src/mergeFile/mergeMain.cpp @@ -9,7 +9,7 @@ Licenced under the GNU General Public License 2.0 license. ******************************************************************************/ -#include "mergeBed.h" +#include "mergeFile.h" #include "version.h" using namespace std; @@ -26,113 +26,21 @@ void merge_help(void); int merge_main(int argc, char* argv[]) { - // our configuration variables - bool showHelp = false; - - // input files - string bedFile = "stdin"; - int maxDistance = 0; - string scoreOp = ""; - - // input arguments - bool haveBed = true; - bool numEntries = false; - bool haveMaxDistance = false; - bool forceStrand = false; - bool reportNames = false; - bool reportScores = false; - string delimiter = ","; - - for(int i = 1; i < argc; i++) { - int parameterLength = (int)strlen(argv[i]); - - if((PARAMETER_CHECK("-h", 2, parameterLength)) || - (PARAMETER_CHECK("--help", 5, parameterLength))) { - showHelp = true; + ContextMerge *context = new ContextMerge(); + if (!context->parseCmdArgs(argc, argv, 1) || context->getShowHelp() || !context->isValidState()) { + if (!context->getErrorMsg().empty()) { + cerr << context->getErrorMsg() << endl; } - } - - if(showHelp) merge_help(); - - // do some parsing (all of these parameters require 2 strings) - for(int i = 1; i < argc; i++) { - - int parameterLength = (int)strlen(argv[i]); - - if(PARAMETER_CHECK("-i", 2, parameterLength)) { - if ((i+1) < argc) { - bedFile = argv[i + 1]; - i++; - } - } - else if(PARAMETER_CHECK("-n", 2, parameterLength)) { - numEntries = true; - } - else if(PARAMETER_CHECK("-d", 2, parameterLength)) { - if ((i+1) < argc) { - haveMaxDistance = true; - maxDistance = atoi(argv[i + 1]); - i++; - } - } - else if (PARAMETER_CHECK("-s", 2, parameterLength)) { - forceStrand = true; - } - else if (PARAMETER_CHECK("-nms", 4, parameterLength)) { - reportNames = true; - } - else if (PARAMETER_CHECK("-scores", 7, parameterLength)) { - reportScores = true; - if ((i+1) < argc) { - scoreOp = argv[i + 1]; - i++; - } - } - else if (PARAMETER_CHECK("-delim", 6, parameterLength)) { - if ((i+1) < argc) { - delimiter = argv[i + 1]; - i++; - } - } - else { - cerr << endl << "*****ERROR: Unrecognized parameter: " << argv[i] << " *****" << endl << endl; - showHelp = true; - } - } - - // make sure we have both input files - if (!haveBed) { - cerr << endl << "*****" << endl << "*****ERROR: Need -i BED file. " << endl << "*****" << endl; - showHelp = true; - } - if ((reportScores == true) && (scoreOp != "sum") - && (scoreOp != "max") && (scoreOp != "min") - && (scoreOp != "mean") && (scoreOp != "mode") - && (scoreOp != "median") && (scoreOp != "antimode") - && (scoreOp != "collapse")) - { - cerr << endl - << "*****" - << endl - << "*****ERROR: Invalid scoreOp selection \"" - << scoreOp - << endl - << "\" *****" - << endl; - showHelp = true; - } - - if (!showHelp) { - BedMerge *bm = new BedMerge(bedFile, numEntries, - maxDistance, forceStrand, - reportNames, reportScores, - scoreOp, delimiter); - delete bm; - } - else { merge_help(); + delete context; + return 0; } - return 0; + MergeFile *mergeFile = new MergeFile(context); + + bool retVal = mergeFile->merge(); + delete mergeFile; + delete context; + return retVal ? 0 : 1; } void merge_help(void) { diff --git a/src/nekSandbox1/nekSandboxMain.cpp b/src/nekSandbox1/nekSandboxMain.cpp index ac3fb319..6e895b2e 100644 --- a/src/nekSandbox1/nekSandboxMain.cpp +++ b/src/nekSandbox1/nekSandboxMain.cpp @@ -6,7 +6,7 @@ using namespace std; #include <cstdio> #include "RecordKeyList.h" #include "NewChromsweep.h" -#include "DualQueue.h" +//#include "DualQueue.h" #include "ParseTools.h" #include <sstream> #include <iomanip> @@ -145,7 +145,7 @@ int nek_sandbox1_main(int argc,char** argv) // bool headerFound = false; // QuickString outbuf; // while (!frm.eof()) { -// Record *record = frm.allocateAndGetNextRecord(); +// Record *record = frm.getNextRecord(); // if (!headerFound && frm.hasHeader()) { // cout << frm.getHeader() << endl; // headerFound = true; @@ -287,7 +287,7 @@ void testDualQueue(Context *context) { printf("Original record order is:\n"); while (!frm.eof()) { - Record *record = frm.allocateAndGetNextRecord(); + Record *record = frm.getNextRecord(); if (record == NULL) { continue; } diff --git a/src/sampleFile/SampleFile.cpp b/src/sampleFile/SampleFile.cpp index 8291824c..096327e2 100644 --- a/src/sampleFile/SampleFile.cpp +++ b/src/sampleFile/SampleFile.cpp @@ -51,7 +51,7 @@ bool SampleFile::takeSample() while (!_inputFile->eof()) { - Record *record = _inputFile->allocateAndGetNextRecord(); + Record *record = _inputFile->getNextRecord(); if (record == NULL) { continue; } diff --git a/src/utils/BinTree/BinTree.cpp b/src/utils/BinTree/BinTree.cpp index 54696f69..f5cddd83 100644 --- a/src/utils/BinTree/BinTree.cpp +++ b/src/utils/BinTree/BinTree.cpp @@ -74,7 +74,7 @@ void BinTree::loadDB() Record *record = NULL; while (!_databaseFile->eof()) { - record = _databaseFile->allocateAndGetNextRecord(); + record = _databaseFile->getNextRecord(); //In addition to NULL records, we also don't want to add unmapped reads. if (record == NULL || record->isUnmapped()) { continue; diff --git a/src/utils/Contexts/ContextBase.cpp b/src/utils/Contexts/ContextBase.cpp index 16d2402c..c76d29b4 100644 --- a/src/utils/Contexts/ContextBase.cpp +++ b/src/utils/Contexts/ContextBase.cpp @@ -13,7 +13,6 @@ ContextBase::ContextBase() : _program(UNSPECIFIED_PROGRAM), _allFilesOpened(false), - _useMergedIntervals(false), _genomeFile(NULL), _outputFileType(FileRecordTypeChecker::UNKNOWN_FILE_TYPE), _outputTypeDetermined(false), @@ -45,7 +44,6 @@ ContextBase::ContextBase() _maxNumDatabaseFields(0), _useFullBamTags(false), _reportCount(false), - _maxDistance(0), _reportNames(false), _reportScores(false), _numOutputRecords(0), @@ -53,11 +51,16 @@ ContextBase::ContextBase() _seed(0), _forwardOnly(false), _reverseOnly(false), - _hasColumnOpsMethods(false) + _hasColumnOpsMethods(false), + _desiredStrand(FileRecordMergeMgr::ANY_STRAND), + _maxDistance(0), + _useMergedIntervals(false) + { _programNames["intersect"] = INTERSECT; _programNames["sample"] = SAMPLE; _programNames["map"] = MAP; + _programNames["merge"] = MERGE; if (hasColumnOpsMethods()) { _keyListOps = new KeyListOps(); @@ -233,11 +236,12 @@ bool ContextBase::openFiles() { _files.resize(_fileNames.size()); for (int i = 0; i < (int)_fileNames.size(); i++) { - FileRecordMgr *frm = new FileRecordMgr(_fileNames[i], _sortedInput); + FileRecordMgr *frm = getNewFRM(_fileNames[i]); if (hasGenomeFile()) { frm->setGenomeFile(_genomeFile); } frm->setFullBamFlags(_useFullBamTags); + frm->setIsSorted(_sortedInput); if (!frm->open()) { return false; } @@ -391,8 +395,9 @@ bool ContextBase::handle_c() markUsed(_i - _skipFirstArgs); _i++; markUsed(_i - _skipFirstArgs); + return true; } - return true; + return false; } @@ -412,7 +417,7 @@ bool ContextBase::handle_o() } -// for col ops, -null is a NULL vakue assigned +// for col ops, -null is a NULL value assigned // when no overlaps are detected. bool ContextBase::handle_null() { @@ -424,8 +429,9 @@ bool ContextBase::handle_null() markUsed(_i - _skipFirstArgs); _i++; markUsed(_i - _skipFirstArgs); + return true; } - return true; + return false; } //for col ops, delimStr will appear between each item in @@ -459,3 +465,13 @@ const QuickString &ContextBase::getColumnOpsVal(RecordKeyList &keyList) const { return _keyListOps->getOpVals(keyList); } +FileRecordMgr *ContextBase::getNewFRM(const QuickString &filename) { + if (!_useMergedIntervals) { + return new FileRecordMgr(filename); + } else { + FileRecordMergeMgr *frm = new FileRecordMergeMgr(filename); + frm->setStrandType(_desiredStrand); + frm->setMaxDistance(_maxDistance); + return frm; + } +} diff --git a/src/utils/Contexts/ContextBase.h b/src/utils/Contexts/ContextBase.h index b4bf1227..2c194a68 100644 --- a/src/utils/Contexts/ContextBase.h +++ b/src/utils/Contexts/ContextBase.h @@ -20,7 +20,7 @@ #include "version.h" #include "BedtoolsTypes.h" #include "FileRecordTypeChecker.h" -#include "FileRecordMgr.h" +#include "FileRecordMergeMgr.h" #include "NewGenomeFile.h" #include "api/BamReader.h" #include "api/BamAux.h" @@ -59,6 +59,7 @@ public: bool getUseMergedIntervals() const { return _useMergedIntervals; } void setUseMergedIntervals(bool val) { _useMergedIntervals = val; } + FileRecordMergeMgr::WANTED_STRAND_TYPE getDesiredStrand() const { return _desiredStrand; } void openGenomeFile(const QuickString &genomeFilename); void openGenomeFile(const BamTools::RefVector &refVector); @@ -106,23 +107,23 @@ public: virtual bool getUseFullBamTags() const { return _useFullBamTags; } virtual void setUseFullBamTags(bool val) { _useFullBamTags = val; } - // - // MERGE METHODS - // - virtual bool getReportCount() const { return _reportCount; } - virtual void setReportCount(bool val) { _reportCount = val; } - - virtual int getMaxDistance() const { return _maxDistance; } - virtual void setMaxDistance(int distance) { _maxDistance = distance; } - - virtual bool getReportNames() const { return _reportNames; } - virtual void setReportNames(bool val) { _reportNames = val; } - - virtual bool getReportScores() const { return _reportScores; } - virtual void setReportScores(bool val) { _reportScores = val; } - - virtual const QuickString &getScoreOp() const { return _scoreOp; } - virtual void setScoreOp(const QuickString &op) { _scoreOp = op; } +// // +// // MERGE METHODS +// // +// virtual bool getReportCount() const { return _reportCount; } +// virtual void setReportCount(bool val) { _reportCount = val; } +// +// virtual int getMaxDistance() const { return _maxDistance; } +// virtual void setMaxDistance(int distance) { _maxDistance = distance; } +// +// virtual bool getReportNames() const { return _reportNames; } +// virtual void setReportNames(bool val) { _reportNames = val; } +// +// virtual bool getReportScores() const { return _reportScores; } +// virtual void setReportScores(bool val) { _reportScores = val; } +// +// virtual const QuickString &getScoreOp() const { return _scoreOp; } +// virtual void setScoreOp(const QuickString &op) { _scoreOp = op; } // METHODS FOR PROGRAMS WITH USER_SPECIFIED NUMBER @@ -160,7 +161,6 @@ protected: bool _allFilesOpened; map<QuickString, PROGRAM_TYPE> _programNames; - bool _useMergedIntervals; NewGenomeFile *_genomeFile; ContextFileType _outputFileType; @@ -200,7 +200,6 @@ protected: int _maxNumDatabaseFields; bool _useFullBamTags; bool _reportCount; - int _maxDistance; bool _reportNames; bool _reportScores; QuickString _scoreOp; @@ -212,14 +211,22 @@ protected: bool _forwardOnly; bool _reverseOnly; + //Members for column operations bool _hasColumnOpsMethods; KeyListOps *_keyListOps; QuickString _nullStr; //placeholder return value when col ops aren't valid. + //Members for merged records + FileRecordMergeMgr::WANTED_STRAND_TYPE _desiredStrand; + int _maxDistance; + bool _useMergedIntervals; + + void markUsed(int i) { _argsProcessed[i] = true; } bool isUsed(int i) const { return _argsProcessed[i]; } bool cmdArgsValid(); bool openFiles(); + virtual FileRecordMgr *getNewFRM(const QuickString &filename); //set cmd line params and counter, i, as members so code //is more readable (as opposed to passing all 3 everywhere). diff --git a/src/utils/Contexts/ContextMap.cpp b/src/utils/Contexts/ContextMap.cpp index e3f82417..e6abc519 100644 --- a/src/utils/Contexts/ContextMap.cpp +++ b/src/utils/Contexts/ContextMap.cpp @@ -47,13 +47,3 @@ bool ContextMap::parseCmdArgs(int argc, char **argv, int skipFirstArgs) { } return ContextIntersect::parseCmdArgs(argc, argv, _skipFirstArgs); } -// -// -//bool ContextMap::isValidState() -//{ -// if (!ContextIntersect::isValidState()) { -// return false; -// } -//} -// -// diff --git a/src/utils/Contexts/ContextMap.h b/src/utils/Contexts/ContextMap.h index 9b7280e5..b5bf5959 100644 --- a/src/utils/Contexts/ContextMap.h +++ b/src/utils/Contexts/ContextMap.h @@ -15,12 +15,8 @@ class ContextMap : public ContextIntersect { public: ContextMap(); virtual ~ContextMap(); -// virtual bool isValidState(); -// virtual bool parseCmdArgs(int argc, char **argv, int skipFirstArgs); -// virtual bool hasIntersectMethods() const { return true; } -// private: diff --git a/src/utils/Contexts/ContextMerge.cpp b/src/utils/Contexts/ContextMerge.cpp new file mode 100644 index 00000000..5d6c58aa --- /dev/null +++ b/src/utils/Contexts/ContextMerge.cpp @@ -0,0 +1,164 @@ +/* + * ContextMerge.cpp + * + * Created on: Mar 26, 2014 + * Author: nek3d + */ + + +#include "ContextMerge.h" + +ContextMerge::ContextMerge() +{ + setUseMergedIntervals(true); + setColumnOpsMethods(true); + + //merge has no default columnOps the way map does, so we'll need to clear those. + _keyListOps->setColumns(""); + _keyListOps->setOperations(""); + +} + +ContextMerge::~ContextMerge() +{ + +} + + +bool ContextMerge::parseCmdArgs(int argc, char **argv, int skipFirstArgs) +{ + _argc = argc; + _argv = argv; + _skipFirstArgs = skipFirstArgs; + if (_argc < 2) { + setShowHelp(true); + return false; + } + + setProgram(_programNames[argv[0]]); + + _argsProcessed.resize(_argc - _skipFirstArgs, false); + + for (_i=_skipFirstArgs; _i < argc; _i++) { + if (isUsed(_i - _skipFirstArgs)) { + continue; + } + else if (strcmp(_argv[_i], "-n") == 0) { + if (!handle_n()) return false; + } + else if (strcmp(_argv[_i], "-nms") == 0) { + if (!handle_nms()) return false; + } + else if (strcmp(_argv[_i], "-scores") == 0) { + if (!handle_scores()) return false; + } + else if (strcmp(_argv[_i], "-delim") == 0) { + if (!handle_delim()) return false; + } + else if (strcmp(_argv[_i], "-d") == 0) { + if (!handle_d()) return false; + } + else if (strcmp(_argv[_i], "-s") == 0) { + if (!handle_s()) return false; + } + else if (strcmp(_argv[_i], "-S") == 0) { + if (!handle_S()) return false; + } + } + return ContextBase::parseCmdArgs(argc, argv, _skipFirstArgs); +} + +bool ContextMerge::isValidState() +{ + // Special: The merge program does not have default + //column operations, so if none were entered, disable column ops. + if (_keyListOps->getColumns().empty() && _keyListOps->getOperations().empty()) { + setColumnOpsMethods(false); + delete _keyListOps; + _keyListOps = NULL; + } + if (!ContextBase::isValidState()) { + return false; + } + if (_files.size() != 1) { + _errorMsg = "\n***** ERROR: input file not specified. *****"; + // Allow one and only input file for now + return false; + } + return true; +} + + +bool ContextMerge::handle_d() { + if ((_i+1) < _argc) { + if (isNumeric(_argv[_i+1])) { + int dist = str2chrPos(_argv[_i+1]); + if (dist >=0 ) { + _maxDistance = dist; + markUsed(_i - _skipFirstArgs); + _i++; + markUsed(_i - _skipFirstArgs); + return true; + } + } + } + _errorMsg = "\n***** ERROR: -d option must be followed by an integer value *****"; + return false; +} + +bool ContextMerge::handle_n() +{ + //This is the same as telling map "-c any -o count" + _keyListOps->addColumns("1"); //doesn't really matter which column, but the default column + //for keyListOps is score, which not every record necessarily has. + _keyListOps->addOperations("count"); + markUsed(_i - _skipFirstArgs); + return true; +} + +bool ContextMerge::handle_nms() +{ + //This is the same as telling map "-c 4 -o collapse" + _keyListOps->addColumns("4"); + _keyListOps->addOperations("collapse"); + markUsed(_i - _skipFirstArgs); + return true; +} + + +bool ContextMerge::handle_scores() +{ + if ((_i+1) < _argc) { + _keyListOps->addColumns("5"); + _keyListOps->addOperations(_argv[_i+1]); + markUsed(_i - _skipFirstArgs); + _i++; + markUsed(_i - _skipFirstArgs); + return true; + } + _errorMsg = "\n***** ERROR: -scores option given, but no operation specified. *****"; + + return false; +} + +bool ContextMerge::handle_s() { + _desiredStrand = FileRecordMergeMgr::SAME_STRAND_EITHER; + markUsed(_i - _skipFirstArgs); + return true; +} + +bool ContextMerge::handle_S() { + if ((_i+1) < _argc) { + if (_argv[_i+1][0] == '+') { + _desiredStrand = FileRecordMergeMgr::SAME_STRAND_FORWARD; + } else if (_argv[_i+1][0] == '-') { + _desiredStrand = FileRecordMergeMgr::SAME_STRAND_REVERSE; + } + markUsed(_i - _skipFirstArgs); + _i++; + markUsed(_i - _skipFirstArgs); + return true; + } + _errorMsg = "\n***** ERROR: -S option must be followed by + or -. *****"; + return false; +} diff --git a/src/utils/Contexts/ContextMerge.h b/src/utils/Contexts/ContextMerge.h new file mode 100644 index 00000000..f2690833 --- /dev/null +++ b/src/utils/Contexts/ContextMerge.h @@ -0,0 +1,31 @@ +/* + * ContextMerge.h + * + * Created on: Mar 26, 2014 + * Author: nek3d + */ + +#ifndef CONTEXTMERGE_H_ +#define CONTEXTMERGE_H_ + +#include "ContextBase.h" +#include "FileRecordMergeMgr.h" + +class ContextMerge: public ContextBase { +public: + ContextMerge(); + ~ContextMerge(); + virtual bool parseCmdArgs(int argc, char **argv, int skipFirstArgs); + virtual bool isValidState(); + +protected: + bool handle_d(); + bool handle_n(); + bool handle_nms(); + bool handle_scores(); + bool handle_s(); + bool handle_S(); +}; + + +#endif /* CONTEXTMERGE_H_ */ diff --git a/src/utils/Contexts/Makefile b/src/utils/Contexts/Makefile index 4b2ed429..1856b82b 100644 --- a/src/utils/Contexts/Makefile +++ b/src/utils/Contexts/Makefile @@ -19,8 +19,9 @@ INCLUDES = -I$(UTILITIES_DIR)/general/ \ # ---------------------------------- # define our source and object files # ---------------------------------- -SOURCES= ContextBase.cpp ContextBase.h ContextIntersect.cpp ContextIntersect.h ContextMap.cpp ContextMap.h ContextSample.cpp ContextSample.h -OBJECTS= ContextBase.o ContextIntersect.o ContextMap.o ContextSample.o +SOURCES= ContextBase.cpp ContextBase.h ContextIntersect.cpp ContextIntersect.h ContextMap.cpp \ + ContextMap.h ContextSample.cpp ContextSample.h ContextMerge.h ContextMerge.cpp +OBJECTS= ContextBase.o ContextIntersect.o ContextMap.o ContextSample.o ContextMerge.o _EXT_OBJECTS=ParseTools.o QuickString.o EXT_OBJECTS=$(patsubst %,$(OBJ_DIR)/%,$(_EXT_OBJECTS)) BUILT_OBJECTS= $(patsubst %,$(OBJ_DIR)/%,$(OBJECTS)) @@ -38,6 +39,7 @@ clean: @rm -f $(OBJ_DIR)/ContextBase.o \ $(OBJ_DIR)/ContextIntersect.o \ $(OBJ_DIR)/ContextMap.o \ - $(OBJ_DIR)/ContextSample.o + $(OBJ_DIR)/ContextSample.o \ + $(OBJ_DIR)/ContextMerge.o \ .PHONY: clean \ No newline at end of file diff --git a/src/utils/FileRecordTools/FileRecordMergeMgr.cpp b/src/utils/FileRecordTools/FileRecordMergeMgr.cpp new file mode 100644 index 00000000..c82206b3 --- /dev/null +++ b/src/utils/FileRecordTools/FileRecordMergeMgr.cpp @@ -0,0 +1,204 @@ +/* + * FileRecordMergeMgr.cpp + * + * Created on: Mar 19, 2014 + * Author: nek3d + */ + + +#include "FileRecordMergeMgr.h" + +FileRecordMergeMgr::FileRecordMergeMgr(const QuickString & filename) +: FileRecordMgr(filename), + _desiredStrand(ANY_STRAND), + _maxDistance(0) +{ +} + +//Record *FileRecordMergeMgr::allocateAndGetNextMergedRecord(WANT_STRAND_TYPE desiredStrand, int maxDistance) { +// RecordKeyList recList; +// if (!allocateAndGetNextMergedRecord(recList, desiredStrand, maxDistance)) { +// return NULL; +// } +// deleteAllMergedItemsButKey(recList); +// return const_cast<Record *>(recList.getKey()); //want key to be non-const +//} + +Record *FileRecordMergeMgr::getNextRecord(RecordKeyList *recList) +{ + if (!recList->allClear()) { + deleteMergedRecord(*recList); + } + + _mustBeForward = _desiredStrand == SAME_STRAND_FORWARD; + _mustBeReverse = _desiredStrand == SAME_STRAND_REVERSE; + + Record *startRecord = tryToTakeFromStorage(); + + // if we couldn't use a previously stored record for starters, + //then begin with a new one that matches strand criteria. + while (startRecord == NULL) { + startRecord = FileRecordMgr::getNextRecord(); + if (startRecord == NULL) { //hit EOF!! + return NULL; + } + + if ((_mustBeForward && (startRecord->getStrandVal() != Record::FORWARD)) || (_mustBeReverse && (startRecord->getStrandVal() != Record::REVERSE))) { + //record is reverse, only want forward, OR record is forward, wanted reverse + deleteRecord(startRecord); + startRecord = NULL; + } + if (startRecord->getStrandVal() == Record::UNKNOWN && _desiredStrand != ANY_STRAND) { + //there is an unknown strand, but the user specified strandedness. + deleteRecord(startRecord); + startRecord = NULL; + } + } + + // OK!! We have a start record! Re-evaluate strand requirements for next recored. + + _mustBeForward = _desiredStrand == SAME_STRAND_FORWARD || (_desiredStrand == SAME_STRAND_EITHER && (startRecord->getStrandVal() == Record::FORWARD)); + _mustBeReverse = _desiredStrand == SAME_STRAND_REVERSE || (_desiredStrand == SAME_STRAND_EITHER && (startRecord->getStrandVal() == Record::REVERSE)); + bool mustKeepOpposite = (_desiredStrand == SAME_STRAND_EITHER); + + const QuickString &currChrom = startRecord->getChrName(); + _foundChroms.insert(currChrom); + + bool madeComposite = false; + recList->push_back(startRecord); + recList->setKey(startRecord); //key of recList will just be the startRecord unless we're able to merge more. + + Record::strandType currStrand = startRecord->getStrandVal(); + bool mustMatchStrand = _desiredStrand != ANY_STRAND; + + int currEnd = startRecord->getEndPos(); + //now look for more records to merge with this one. + //stop when they're out of range, not on the same chromosome, or we hit EOF. + //ignore if they don't comply with strand. + Record *nextRecord = NULL; + while (nextRecord == NULL) { + bool takenFromStorage = false; + nextRecord = mustMatchStrand ? tryToTakeFromStorage(currStrand) : tryToTakeFromStorage(); + if (nextRecord == NULL) { + nextRecord = FileRecordMgr::getNextRecord(); + } else { + takenFromStorage = true; + } + if (nextRecord == NULL) { // EOF hit + break; + } + //delete any record from file with an unknown strand if we are doing stranded merge, but first check + //that it's chrom was the same and it's not out of range. If either is true, stop scanning. + bool mustDelete = (mustMatchStrand && nextRecord->getStrandVal() == Record::UNKNOWN); + + //check that we are still on the same chromosome. + const QuickString &newChrom = nextRecord->getChrName(); + if (newChrom != currChrom) { //hit a different chromosome. + if (_foundChroms.find(newChrom) == _foundChroms.end() || takenFromStorage) { + //haven't seen this chromosome before, sort order is already enforced in the base class method. + if (!mustDelete) { + addToStorage(nextRecord); + } else { + deleteRecord(nextRecord); + } + nextRecord = NULL; + break; + } + } + + //check whether it's in range + int nextStart = nextRecord->getStartPos(); + if (nextStart > currEnd + _maxDistance) { + //no, it's out of range. + if (!mustDelete) { + addToStorage(nextRecord); + } else { + deleteRecord(nextRecord); + } + nextRecord = NULL; + break; + } + + // NOW, going back, we can delete any unknown strand records. But don't stop scanning. + if (mustDelete) { + deleteRecord(nextRecord); + nextRecord = NULL; + continue; + } + //if taken from file, and wrong strand, store or delete. + if (!takenFromStorage && ((_mustBeForward && (nextRecord->getStrandVal() != Record::FORWARD)) || (_mustBeReverse && (nextRecord->getStrandVal() != Record::REVERSE)))) { + if (mustKeepOpposite) { + addToStorage(nextRecord); + } else { + deleteRecord(nextRecord); + } + nextRecord = NULL; + continue; //get the next record + } + //ok, they're on the same chrom and in range, and the strand is good. Do a merge. + recList->push_back(nextRecord); + madeComposite = true; + int nextEnd = nextRecord->getEndPos(); + if (nextEnd > currEnd) { + currEnd = nextEnd; + } + nextRecord = NULL; + } + if (madeComposite) { + Record *newKey = _recordMgr->allocateRecord(); + (*newKey) = (*startRecord); + newKey->setEndPos(currEnd); + recList->setKey(newKey); + } + _totalMergedRecordLength += (unsigned long)(recList->getKey()->getEndPos() - recList->getKey()->getStartPos()); + return const_cast<Record *>(recList->getKey()); +} + +void FileRecordMergeMgr::addToStorage(Record *record) { + //if the strand requirements are strict, and the record doesn't match, + //store in the "round file". + + if ((_desiredStrand == SAME_STRAND_FORWARD && record->getStrandVal() != Record::FORWARD) || + (_desiredStrand == SAME_STRAND_REVERSE && record->getStrandVal() != Record::REVERSE) || + (_desiredStrand != ANY_STRAND && record->getStrandVal() == Record::UNKNOWN)) { + deleteRecord(record); + return; + } + _storedRecords.push(record); +} + +Record *FileRecordMergeMgr::tryToTakeFromStorage() { + Record *record = _storedRecords.top(); + if (record != NULL) { + _storedRecords.pop(); + } + return record; +} + +Record *FileRecordMergeMgr::tryToTakeFromStorage(Record::strandType strand) { + Record *record = _storedRecords.top(strand); + if (record != NULL) { + _storedRecords.pop(strand); + } + return record; +} + +void FileRecordMergeMgr::deleteMergedRecord(RecordKeyList &recList) +{ + deleteAllMergedItemsButKey(recList); + deleteRecord(recList.getKey()); + recList.setKey(NULL); +} + +void FileRecordMergeMgr::deleteAllMergedItemsButKey(RecordKeyList &recList) { + //if the key is also in the list, this method won't delete it. + for (RecordKeyList::const_iterator_type iter = recList.begin(); iter != recList.end(); iter = recList.next()) { + if (iter->value() == recList.getKey()) { + continue; + } + deleteRecord(iter->value()); + } + recList.clearList(); +} + + diff --git a/src/utils/FileRecordTools/FileRecordMergeMgr.h b/src/utils/FileRecordTools/FileRecordMergeMgr.h new file mode 100644 index 00000000..5bbfb529 --- /dev/null +++ b/src/utils/FileRecordTools/FileRecordMergeMgr.h @@ -0,0 +1,55 @@ +/* + * FileRecordMergeMgr.h + * + * Created on: Mar 19, 2014 + * Author: nek3d + */ + +#ifndef FILERECORDMERGEMGR_H_ +#define FILERECORDMERGEMGR_H_ + +#include "FileRecordMgr.h" +#include "StrandQueue.h" + +class FileRecordMergeMgr : public FileRecordMgr { + +public: + FileRecordMergeMgr(const QuickString & filename); + + ////////////////////////////////////////////////////////////////////////////////// + // + // MERGED RECORDS + // + // This will give a single "meta" record containing "flattened" or merged records. + // + // Pass an empty RecordKeyList. When done, will have a pair: 1st is the final merged record, + // second is list of constituent Records merged. + // + /////////////////////////////////////////////////////////////////////////////////// + + Record *getNextRecord(RecordKeyList *keyList = NULL); + void deleteMergedRecord(RecordKeyList &recList); // MUST use this method for cleanup! + + typedef enum { SAME_STRAND_FORWARD, //must all be forward strand + SAME_STRAND_REVERSE, //must all be reverse strand + SAME_STRAND_EITHER, //must be same strand, but can be either forward or reverse + ANY_STRAND } //do no care about strand (Default value) + WANTED_STRAND_TYPE; + + void setStrandType(WANTED_STRAND_TYPE strand) { _desiredStrand = strand; } + void setMaxDistance(int maxDistance) { _maxDistance = maxDistance; } + +private: + + WANTED_STRAND_TYPE _desiredStrand; + int _maxDistance; + StrandQueue _storedRecords; + + void deleteAllMergedItemsButKey(RecordKeyList &recList); + void addToStorage(Record *record); + Record *tryToTakeFromStorage(); + Record *tryToTakeFromStorage(Record::strandType strand); +}; + + +#endif /* FILERECORDMERGEMGR_H_ */ diff --git a/src/utils/FileRecordTools/FileRecordMgr.cpp b/src/utils/FileRecordTools/FileRecordMgr.cpp index 38fc056b..0edf73a6 100644 --- a/src/utils/FileRecordTools/FileRecordMgr.cpp +++ b/src/utils/FileRecordTools/FileRecordMgr.cpp @@ -4,7 +4,7 @@ #include "Record.h" #include "NewGenomeFile.h" -FileRecordMgr::FileRecordMgr(const QuickString &filename, bool isSorted) +FileRecordMgr::FileRecordMgr(const QuickString &filename) : _filename(filename), _bufStreamMgr(NULL), @@ -12,7 +12,7 @@ FileRecordMgr::FileRecordMgr(const QuickString &filename, bool isSorted) _fileType(FileRecordTypeChecker::UNKNOWN_FILE_TYPE), _recordType(FileRecordTypeChecker::UNKNOWN_RECORD_TYPE), _recordMgr(NULL), - _isSortedInput(isSorted), + _isSortedInput(false), _freeListBlockSize(512), _useFullBamTags(false), _prevStart(INT_MAX), @@ -88,7 +88,7 @@ bool FileRecordMgr::eof(){ return _fileReader->eof(); } -Record *FileRecordMgr::allocateAndGetNextRecord() +Record *FileRecordMgr::getNextRecord(RecordKeyList *keyList) { if (!_fileReader->isOpen()) { return NULL; @@ -120,6 +120,9 @@ Record *FileRecordMgr::allocateAndGetNextRecord() } assignChromId(record); _totalRecordLength += (unsigned long)(record->getEndPos() - record->getStartPos()); + if (keyList != NULL) { + keyList->setKey(record); + } return record; } @@ -198,6 +201,10 @@ void FileRecordMgr::deleteRecord(const Record *record) { _recordMgr->deleteRecord(record); } +void FileRecordMgr::deleteRecord(RecordKeyList *keyList) { + _recordMgr->deleteRecord(keyList->getKey()); +} + void FileRecordMgr::allocateFileReader() { switch (_fileType) { @@ -224,175 +231,3 @@ const BamTools::RefVector & FileRecordMgr::getBamReferences() { } return static_cast<BamFileReader *>(_fileReader)->getReferences(); } - -#ifdef false - -Record *FileRecordMgr::allocateAndGetNextMergedRecord(WANT_STRAND_TYPE desiredStrand, int maxDistance) { - RecordKeyList recList; - if (!allocateAndGetNextMergedRecord(recList, desiredStrand, maxDistance)) { - return NULL; - } - deleteAllMergedItemsButKey(recList); - return const_cast<Record *>(recList.getKey()); //want key to be non-const -} - -bool FileRecordMgr::allocateAndGetNextMergedRecord(RecordKeyList & recList, WANT_STRAND_TYPE desiredStrand, int maxDistance) -{ - if (!recList.allClear()) { - deleteMergedRecord(recList); - } - - _mustBeForward = desiredStrand == SAME_STRAND_FORWARD; - _mustBeReverse = desiredStrand == SAME_STRAND_REVERSE; - - Record *startRecord = tryToTakeFromStorage(); - - // if we couldn't use a previously stored record for starters, - //then begin with a new one that matches strand criteria. - while (startRecord == NULL) { - startRecord = allocateAndGetNextRecord(); - if (startRecord == NULL) { //hit EOF!! - return false; - } - - if (_mustBeForward && !startRecord->getStrand()) { - //record is reverse, wanted forward. - addToStorage(startRecord); - startRecord = NULL; - } else if (_mustBeReverse && startRecord->getStrand()) { - //record is forward, wanted reverse - addToStorage(startRecord); - startRecord = NULL; - } - } - - // OK!! We have a start record! - - _mustBeForward = desiredStrand == SAME_STRAND_FORWARD || (desiredStrand == SAME_STRAND_EITHER && startRecord->getStrand()); - _mustBeReverse = desiredStrand == SAME_STRAND_REVERSE || (desiredStrand == SAME_STRAND_EITHER && !startRecord->getStrand()); - - const QuickString &currChrom = startRecord->getChrName(); - _foundChroms.insert(currChrom); - - bool madeComposite = false; - recList.push_back(startRecord); - recList.setKey(startRecord); //key of recList will just be the startRecord unless we're able to merge more. - - bool currStrand = startRecord->getStrand(); - bool mustMatchStrand = desiredStrand != ANY_STRAND; - - int currEnd = startRecord->getEndPos(); - //now look for more records to merge with this one. - //stop when they're out of range, not on the same chromosome, or we hit EOF. - //ignore if they don't comply with strand. - Record *nextRecord = NULL; - while (nextRecord == NULL) { - bool takenFromStorage = false; - nextRecord = mustMatchStrand ? tryToTakeFromStorage(currStrand) : tryToTakeFromStorage(); - if (nextRecord == NULL) { - nextRecord = allocateAndGetNextRecord(); - } else { - takenFromStorage = true; - } - if (nextRecord == NULL) { // EOF hit - break; - } - const QuickString &newChrom = nextRecord->getChrName(); - if (newChrom != currChrom) { //hit a different chromosome. - if (_foundChroms.find(newChrom) == _foundChroms.end() || takenFromStorage) { - //haven't seen this chromosome before. - addToStorage(nextRecord); - break; - } else { - //different strand, but we've already seen this chrom. File is not sorted. - fprintf(stderr, "ERROR: Input file %s is not sorted by chromosome, startPos.\n", _context->getInputFileName(_contextFileIdx).c_str()); - deleteRecord(nextRecord); - deleteMergedRecord(recList); - exit(1); - } - } - int nextStart = nextRecord->getStartPos(); - //is the record out of range? - if (nextStart > currEnd + maxDistance) { - //yes, it's out of range. - addToStorage(nextRecord); - break; - } - - //ok, they're on the same chrom and in range. Are we happy with the strand? - if (mustMatchStrand && nextRecord->getStrand() != currStrand) { - //no, we're not. - addToStorage(nextRecord); - nextRecord = NULL; - continue; - } - //everything's good! do a merge. - recList.push_back(nextRecord); - madeComposite = true; - int nextEnd = nextRecord->getEndPos(); - if (nextEnd > currEnd) { - currEnd = nextEnd; - } - nextRecord = NULL; - } - if (madeComposite) { - Record *newKey = _recordMgr->allocateRecord(); - (*newKey) = (*startRecord); - newKey->setEndPos(currEnd); - recList.setKey(newKey); - } - _totalMergedRecordLength += (unsigned long)(recList.getKey()->getEndPos() - recList.getKey()->getStartPos()); - return true; -} - -void FileRecordMgr::addToStorage(Record *record) { - _storedRecords.push(record); -} - -Record *FileRecordMgr::tryToTakeFromStorage() { - Record *record = _storedRecords.empty() ? NULL : const_cast<Record *>(_storedRecords.top()); - if (record != NULL) { - _storedRecords.pop(); - } - return record; -} - -Record *FileRecordMgr::tryToTakeFromStorage(bool strand) { - Record *record = NULL; - if(strand) { - if (_storedRecords.emptyForward()) { - return NULL; - } else { - record = const_cast<Record *>(_storedRecords.topForward()); - _storedRecords.popForward(); - return record; - } - } else { - if (_storedRecords.emptyReverse()) { - return NULL; - } else { - record = const_cast<Record *>(_storedRecords.topReverse()); - _storedRecords.popReverse(); - return record; - } - } -} - -void FileRecordMgr::deleteMergedRecord(RecordKeyList &recList) -{ - deleteAllMergedItemsButKey(recList); - deleteRecord(recList.getKey()); - recList.setKey(NULL); -} - -void FileRecordMgr::deleteAllMergedItemsButKey(RecordKeyList &recList) { - //if the key is also in the list, this method won't delete it. - for (RecordKeyList::const_iterator_type iter = recList.begin(); iter != recList.end(); iter = recList.next()) { - if (iter->value() == recList.getKey()) { - continue; - } - deleteRecord(iter->value()); - } - recList.clearList(); -} -#endif diff --git a/src/utils/FileRecordTools/FileRecordMgr.h b/src/utils/FileRecordTools/FileRecordMgr.h index b4e76e92..0994a7c7 100644 --- a/src/utils/FileRecordTools/FileRecordMgr.h +++ b/src/utils/FileRecordTools/FileRecordMgr.h @@ -32,12 +32,20 @@ class NewGenomeFile; class FileRecordMgr { public: - FileRecordMgr(const QuickString & filename, bool isSorted = false); - ~FileRecordMgr(); + FileRecordMgr(const QuickString & filename); + virtual ~FileRecordMgr(); bool open(); void close(); bool eof(); + //This is an all-in-one method to give the user a new record that is initialized with + //the next entry in the data file. + //NOTE!! User MUST pass back the returned pointer to deleteRecord method for cleanup! + //Also Note! User must check for NULL returned, meaning we failed to get the next record. + virtual Record *getNextRecord(RecordKeyList *keyList = NULL); + void deleteRecord(const Record *); + virtual void deleteRecord(RecordKeyList *keyList); + const QuickString &getFileName() const { return _filename;} bool hasHeader() const { return _fileReader->hasHeader(); } const QuickString &getHeader() const { return _fileReader->getHeader(); } @@ -69,55 +77,6 @@ public: const BamTools::RefVector &getBamReferences(); int getNumFields() const { return _fileReader->getNumFields(); } - //This is an all-in-one method to give the user a new record that is initialized with - //the next entry in the data file. - //NOTE!! User MUST pass back the returned pointer to deleteRecord method for cleanup! - //Also Note! User must check for NULL returned, meaning we failed to get the next record. - Record *allocateAndGetNextRecord(); - void deleteRecord(const Record *); - -#ifdef false - ////////////////////////////////////////////////////////////////////////////////// - // - // MERGED RECORDS - // - //this will give a single "meta" record containing "flattened" or merged records. - // - // 1st ARG: Pass an empty RecordKeyList. When done, will have a pair: 1st is the final merged record, - // second is list of constituent Records merged. - // ** NOTE ** If the RecordKeyList is not empty, this method will empty it for you and delete all contents! - // - // 2nd ARG: Choose from WANT_STRAND_TYPE, defined below below - // - // 3rd ARG: allows for nearby records, i.e. maxDistance 100 will merge records <= 100 bases apart. Default 0 means only - // merge records that actually intersect. - // - // Return value: true if any records found. False if eof hit before records matching requested parameters found. - - typedef enum { SAME_STRAND_FORWARD, //must all be forward strand - SAME_STRAND_REVERSE, //must all be reverse strand - SAME_STRAND_EITHER, //must be same strand, but can be either forward or reverse - ANY_STRAND } //do no care about strand (Default value) - WANT_STRAND_TYPE; - - // - // WARNING!! Specifying a strand will keep all records on the other strand in memory!! - // This is done so that requests for records on that other strand can still be met. - // For now, use this method at any time to purge the kept records from memory, such as - // when changing chromosomes, for example. - void purgeKeepList(); - bool allocateAndGetNextMergedRecord(RecordKeyList & recList, WANT_STRAND_TYPE desiredStrand = ANY_STRAND, int maxDistance = 0); - void deleteMergedRecord(RecordKeyList &recList); // MUST use this method for cleanup! - - //this method will allocate a new record of merged records, but the returned record should only be passed to the deleteRecord method - //for cleanup, not to the delete mmerged record. - Record *allocateAndGetNextMergedRecord(WANT_STRAND_TYPE desiredStrand = ANY_STRAND, int maxDistance = 0); - - // - // END MERGED RECORDS - // - ////////////////////////////////////////////////////////////////////////////////// -#endif //File statistics unsigned long getTotalRecordLength() const { return _totalRecordLength; } //sum of length of all returned records @@ -140,7 +99,9 @@ public: _hasGenomeFile = true; } -private: + void setIsSorted(bool val) { _isSortedInput = val; } + +protected: QuickString _filename; BufferedStreamMgr *_bufStreamMgr; @@ -158,8 +119,6 @@ private: int _prevStart; int _prevChromId; - //members for handling merged records -// DualQueue<Record *, DualQueueAscending > _storedRecords; bool _mustBeForward; bool _mustBeReverse; @@ -177,16 +136,6 @@ private: void testInputSortOrder(Record *record); void assignChromId(Record *); void sortError(const Record *record, bool genomeFileError); - - -#ifdef false - void deleteAllMergedItemsButKey(RecordKeyList &recList); - void addToStorage(Record *record); - Record *tryToTakeFromStorage(); - Record *tryToTakeFromStorage(bool strand); -#endif - - }; diff --git a/src/utils/FileRecordTools/Makefile b/src/utils/FileRecordTools/Makefile index 9d021179..fce099b8 100644 --- a/src/utils/FileRecordTools/Makefile +++ b/src/utils/FileRecordTools/Makefile @@ -21,8 +21,8 @@ SUBDIRS = ./FileReaders \ # ---------------------------------- # define our source and object files # ---------------------------------- -SOURCES= FileRecordMgr.cpp FileRecordMgr.h -OBJECTS= FileRecordMgr.o RecordOutputMgr.o +SOURCES= FileRecordMgr.cpp FileRecordMgr.h FileRecordMergeMgr.cpp FileRecordMergeMgr.h +OBJECTS= FileRecordMgr.o FileRecordMergeMgr.o _EXT_OBJECTS=SingleLineDelimTextFileReader.o BamFileReader.o Bed3Interval.o Bed6Interval.o BedPlusInterval.o Bed12Interval.o BamRecord.o \ SingleLineDelimTransferBuffer.o FileRecordTypeChecker.o QuickString.o ParseTools.o RecordKeyList.o BufferedStreamMgr.o EXT_OBJECTS=$(patsubst %,$(OBJ_DIR)/%,$(_EXT_OBJECTS)) @@ -31,6 +31,8 @@ BUILT_OBJECTS= $(patsubst %,$(OBJ_DIR)/%,$(OBJECTS)) $(BUILT_OBJECTS): $(SOURCES) $(SUBDIRS) @echo " * compiling FileRecordMgr.cpp" @$(CXX) -c -o $(OBJ_DIR)/FileRecordMgr.o FileRecordMgr.cpp $(LDFLAGS) $(CXXFLAGS) $(INCLUDES) + @echo " * compiling FileRecordMergeMgr.cpp" + @$(CXX) -c -o $(OBJ_DIR)/FileRecordMergeMgr.o FileRecordMergeMgr.cpp $(LDFLAGS) $(CXXFLAGS) $(INCLUDES) @@ -42,10 +44,8 @@ $(SUBDIRS): $(OBJ_DIR) clean: @echo "Cleaning up." @rm -f $(OBJ_DIR)/FileRecordMgr.o - @rm -f $(OBJ_DIR)/RecordMgr.o @rm -f $(OBJ_DIR)/FileRecordTypeChecker.o - @rm -f $(OBJ_DIR)/SingleLineDelimTextFileReader.o - @rm -f $(OBJ_DIR)/SingleLineDelimTransferBuffer.o + @rm -f $(OBJ_DIR)/FileRecordMergeMgr.o .PHONY: clean \ No newline at end of file diff --git a/src/utils/FileRecordTools/Records/Makefile b/src/utils/FileRecordTools/Records/Makefile index 6bfd6106..556cc82d 100644 --- a/src/utils/FileRecordTools/Records/Makefile +++ b/src/utils/FileRecordTools/Records/Makefile @@ -21,9 +21,9 @@ SOURCES= RecordMgr.cpp RecordMgr.h Record.h Record.cpp Bed3Interval.h Bed3Interv Bed4Interval.h Bed4Interval.cpp BedGraphInterval.h BedGraphInterval.cpp Bed5Interval.h Bed5Interval.cpp \ Bed6Interval.h Bed6Interval.cpp \ BedPlusInterval.h BedPlusInterval.cpp Bed12Interval.h Bed12Interval.cpp BamRecord.h BamRecord.cpp VcfRecord.h VcfRecord.cpp \ - GffRecord.h GffRecord.cpp RecordKeyList.h RecordKeyList.cpp BlockMgr.h BlockMgr.cpp + GffRecord.h GffRecord.cpp RecordKeyList.h RecordKeyList.cpp BlockMgr.h BlockMgr.cpp StrandQueue.h StrandQueue.cpp OBJECTS= RecordMgr.o Record.o Bed3Interval.o Bed4Interval.o BedGraphInterval.o Bed5Interval.o Bed6Interval.o BedPlusInterval.o Bed12Interval.o BamRecord.o \ - VcfRecord.o GffRecord.o RecordKeyList.o BlockMgr.o + VcfRecord.o GffRecord.o RecordKeyList.o BlockMgr.o StrandQueue.o _EXT_OBJECTS=ParseTools.o QuickString.o ChromIdLookup.o EXT_OBJECTS=$(patsubst %,$(OBJ_DIR)/%,$(_EXT_OBJECTS)) BUILT_OBJECTS= $(patsubst %,$(OBJ_DIR)/%,$(OBJECTS)) @@ -40,6 +40,6 @@ clean: @echo "Cleaning up." @rm -f $(OBJ_DIR)/RecordMgr.o $(OBJ_DIR)/Record.o $(OBJ_DIR)/Bed3Interval.o $(OBJ_DIR)/Bed4Interval.o \ $(OBJ_DIR)/BedGraphInterval.o $(OBJ_DIR)/Bed5Interval.o $(OBJ_DIR)/Bed6Interval.o \ - $(OBJ_DIR)/BedPlusInterval.o $(OBJ_DIR)/Bed12Interval.o $(OBJ_DIR)/BamRecord.o $(OBJ_DIR)/VcfRecord.o $(OBJ_DIR)/GffRecord.o $(OBJ_DIR)/BlockMgr.o + $(OBJ_DIR)/BedPlusInterval.o $(OBJ_DIR)/Bed12Interval.o $(OBJ_DIR)/BamRecord.o $(OBJ_DIR)/VcfRecord.o $(OBJ_DIR)/GffRecord.o $(OBJ_DIR)/BlockMgr.o $(OBJ_DIR)/StrandQueue.o .PHONY: clean \ No newline at end of file diff --git a/src/utils/FileRecordTools/Records/Record.h b/src/utils/FileRecordTools/Records/Record.h index d8071c1e..8e4376c1 100644 --- a/src/utils/FileRecordTools/Records/Record.h +++ b/src/utils/FileRecordTools/Records/Record.h @@ -152,6 +152,9 @@ protected: bool _isMateUnmapped; }; - +class RecordPtrSortFunctor { +public: + bool operator()(const Record *rec1, const Record *rec2) const { return *rec1 > *rec2; } +}; #endif /* RECORD_H_ */ diff --git a/src/utils/FileRecordTools/Records/StrandQueue.cpp b/src/utils/FileRecordTools/Records/StrandQueue.cpp new file mode 100644 index 00000000..aa53313b --- /dev/null +++ b/src/utils/FileRecordTools/Records/StrandQueue.cpp @@ -0,0 +1,131 @@ +/* + * StrandQueue.cpp + * + * Created on: Mar 31, 2014 + * Author: nek3d + */ + +#include "StrandQueue.h" + +StrandQueue::StrandQueue() { + + for (int i=0; i < NUM_QUEUES; i++) { + queueType *queue = new queueType(); + _queues.push_back(queue); + } + _strandIdxs.resize(3); + _strandIdxs[0] = Record::FORWARD; + _strandIdxs[1] = Record::REVERSE; + _strandIdxs[2] = Record::UNKNOWN; +} + +StrandQueue::~StrandQueue() { + for (int i=0; i < NUM_QUEUES; i++) { + delete _queues[i]; + } +} + +Record *StrandQueue::top() const +{ + int minIdx = getMinIdx(); + if (minIdx == -1) return NULL; + return const_cast<Record *>(_queues[minIdx]->top()); +} + +void StrandQueue::pop() { + int minIdx = getMinIdx(); + if (minIdx == -1) return; + _queues[minIdx]->pop(); +} + +Record * StrandQueue::top(Record::strandType strand) const { + const Record *record = NULL; + switch (strand) { + case Record::FORWARD: + if (_queues[0]->empty()) return NULL; + record = _queues[0]->top(); + break; + case Record::REVERSE: + if (_queues[1]->empty()) return NULL; + + record = _queues[1]->top(); + break; + case Record::UNKNOWN: + if (_queues[0]->empty()) return NULL; + record = _queues[2]->top(); + break; + default: + break; + } + return const_cast<Record *>(record); +} + +void StrandQueue::pop(Record::strandType strand) const { + switch (strand) { + case Record::FORWARD: + if (_queues[0]->empty()) return; + _queues[0]->pop(); + break; + case Record::REVERSE: + if (_queues[1]->empty()) return; + _queues[1]->pop(); + break; + case Record::UNKNOWN: + if (_queues[2]->empty()) return; + _queues[2]->pop(); + break; + default: + break; + } +} + +void StrandQueue::push(Record *record) { + switch (record->getStrandVal()) { + case Record::FORWARD: + _queues[0]->push(record); + break; + case Record::REVERSE: + _queues[1]->push(record); + break; + case Record::UNKNOWN: + _queues[2]->push(record); + break; + default: + break; + } +} + +size_t StrandQueue::size() const { + size_t sumSize = 0; + for (int i = 0; i < NUM_QUEUES; i++) { + sumSize += _queues[i]->size(); + } + return sumSize; +} + +bool StrandQueue::empty() const { + for (int i = 0; i < NUM_QUEUES; i++) { + if (!_queues[i]->empty()) { + return false; + } + } + return true; +} + + +int StrandQueue::getMinIdx() const { + if (empty()) return -1; + const Record *minRec = NULL; + int minIdx = -1; + for (int i = 0; i < NUM_QUEUES; i++) { + if (_queues[i]->empty()) continue; + const Record *currTop = _queues[i]->top(); + if (currTop == NULL) continue; + if (minRec == NULL || *currTop < *minRec) { + minRec = currTop; + minIdx = i; + } + } + return minIdx; +} + diff --git a/src/utils/FileRecordTools/Records/StrandQueue.h b/src/utils/FileRecordTools/Records/StrandQueue.h new file mode 100644 index 00000000..e6f0bb49 --- /dev/null +++ b/src/utils/FileRecordTools/Records/StrandQueue.h @@ -0,0 +1,47 @@ +/* + * StrandQueue.h + * + * Created on: Jan 29, 2013 + * Author: nek3d + */ +#ifndef STRANDQUEUE_H_ +#define STRANDQUEUE_H_ + +using namespace std; + +#include <vector> +#include <queue> +#include <cstdio> +#include <cstdlib> +#include "Record.h" + +class StrandQueue { +public: + StrandQueue(); + ~StrandQueue(); + + Record * top() const; + void pop(); + Record * top(Record::strandType strand) const; + void pop(Record::strandType strand) const; + void push(Record *record); + size_t size() const; + bool empty() const; + +private: +// static RecordPtrSortFunctor _recSortFunctor; + typedef priority_queue<Record *, vector<const Record *>, RecordPtrSortFunctor > queueType; + vector<queueType *> _queues; + static const int NUM_QUEUES = 3; + + //we want to be able to iterate over the enumerated strand types in Record.h, + //which are FORWARD, REVERSE, and UNKNOWN. However, iterating over an enum is hard to + //do, so we'll use a suggestion found in a forum, and put the enum values into a vector. + vector<Record::strandType> _strandIdxs; + + int getMinIdx() const; //will return the idx of queue with the current min val. + +}; + + +#endif // STRANDQUEUE_H_ diff --git a/src/utils/FileRecordTools/Records/recordsTar.tar.gz b/src/utils/FileRecordTools/Records/recordsTar.tar.gz deleted file mode 100644 index 321a88e4935d73f7034cce0611f9191d27bfeaa2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54443 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%0Dd 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 YFZzrAqQB@b`u{ZjU!B1#1po*F00@zL#sB~S diff --git a/src/utils/KeyListOps/KeyListOps.h b/src/utils/KeyListOps/KeyListOps.h index 3c26d2c2..5046ec18 100644 --- a/src/utils/KeyListOps/KeyListOps.h +++ b/src/utils/KeyListOps/KeyListOps.h @@ -18,10 +18,24 @@ public: KeyListOps(); void setColumns(const QuickString &columns) { _columns = columns; } + void addColumns(const QuickString &newCols) { + if (!_columns.empty()) _columns += ","; + _columns += newCols; + } void setOperations(const QuickString & operation) { _operations = operation; } + void addOperations(const QuickString &newOps) { + if (!_operations.empty()) _operations += ","; + _operations += newOps; + } + void setNullValue(const QuickString & nullValue) { _methods.setNullValue(nullValue); } void setDelimStr(const QuickString & delimStr) { _methods.setDelimStr(delimStr); } + const QuickString &getColumns() { return _columns; } + const QuickString &getOperations() { return _operations; } + const QuickString &getNullValue() { return _methods.getNullValue(); } + const QuickString &getDelimStr() { return _methods.getDelimStr(); } + void setKeyList(RecordKeyList *keyList) { _methods.setKeyList(keyList); } typedef enum { SUM, MEAN, STDDEV, SAMPLE_STDDEV, MEDIAN, MODE, ANTIMODE, MIN, MAX, ABSMIN, ABSMAX, COUNT, DISTINCT, COUNT_DISTINCT, diff --git a/src/utils/NewChromsweep/NewChromsweep.cpp b/src/utils/NewChromsweep/NewChromsweep.cpp index 5e6045be..c04fefb1 100644 --- a/src/utils/NewChromsweep/NewChromsweep.cpp +++ b/src/utils/NewChromsweep/NewChromsweep.cpp @@ -185,7 +185,7 @@ bool NewChromSweep::next(RecordKeyList &next) { void NewChromSweep::nextRecord(bool query) { if (query) { // if (!_context->getUseMergedIntervals()) { - _currQueryRec = _queryFRM->allocateAndGetNextRecord(); + _currQueryRec = _queryFRM->getNextRecord(); // } else { // _currQueryRec = _queryFRM->allocateAndGetNextMergedRecord(_context->getSameStrand() ? FileRecordMgr::SAME_STRAND_EITHER : FileRecordMgr::ANY_STRAND); // } @@ -194,7 +194,7 @@ void NewChromSweep::nextRecord(bool query) { } } else { //database // if (!_context->getUseMergedIntervals()) { - _currDatabaseRec = _databaseFRM->allocateAndGetNextRecord(); + _currDatabaseRec = _databaseFRM->getNextRecord(); // } else { // _currDatabaseRec = _databaseFRM->allocateAndGetNextMergedRecord(_context->getSameStrand() ? FileRecordMgr::SAME_STRAND_EITHER : FileRecordMgr::ANY_STRAND); // } diff --git a/src/utils/RecordOutputMgr/RecordOutputMgr.cpp b/src/utils/RecordOutputMgr/RecordOutputMgr.cpp index 55cb70ec..9ab8b52b 100644 --- a/src/utils/RecordOutputMgr/RecordOutputMgr.cpp +++ b/src/utils/RecordOutputMgr/RecordOutputMgr.cpp @@ -76,9 +76,15 @@ bool RecordOutputMgr::printKeyAndTerminate(RecordKeyList &keyList) { if (bamCode == BAM_AS_BAM) { return true; } else if (bamCode == NOT_BAM) { - keyList.getKey()->print(_outBuf); + if (_context->getProgram() == ContextBase::MERGE) { + //when printing merged records, we want to force the printing into + //bed3 format, which is surprisingly difficult to do. Had to use the following: + const Bed3Interval *bed3 = static_cast<const Bed3Interval *>(keyList.getKey()); + bed3->Bed3Interval::print(_outBuf); + } else { + keyList.getKey()->print(_outBuf); + } return false; - } //otherwise, it was BAM_AS_BED, and the key was printed. return false; @@ -114,6 +120,7 @@ void RecordOutputMgr::printRecord(const Record *record) void RecordOutputMgr::printRecord(const Record *record, const QuickString & value) { + _afterVal = value; printRecord(record); _outBuf.append(value); newline(); @@ -206,6 +213,17 @@ void RecordOutputMgr::printRecord(RecordKeyList &keyList, RecordKeyList *blockLi } _currBamBlockList = NULL; return; + } else if (_context->getProgram() == ContextBase::MERGE) { + if (!printKeyAndTerminate(keyList)) { + if (_context->getDesiredStrand() != FileRecordMergeMgr::ANY_STRAND) { + //add the sign of the record + tab(); + _outBuf.append(keyList.getKey()->getStrand()); + } + if (!_afterVal.empty()) tab(); + } + _currBamBlockList = NULL; + return; } } diff --git a/src/utils/RecordOutputMgr/RecordOutputMgr.h b/src/utils/RecordOutputMgr/RecordOutputMgr.h index b6296fda..07891cc2 100644 --- a/src/utils/RecordOutputMgr/RecordOutputMgr.h +++ b/src/utils/RecordOutputMgr/RecordOutputMgr.h @@ -44,6 +44,7 @@ private: // BlockMgr *_bamBlockMgr; const BlockMgr *_splitInfo; + QuickString _afterVal; //to store values to be printed after record, such as column operations. //some helper functions to neaten the code. void tab() { _outBuf.append('\t'); } void newline() { _outBuf.append('\n'); } diff --git a/src/utils/general/DualQueue.h b/src/utils/general/DualQueue.h deleted file mode 100644 index 1cee4158..00000000 --- a/src/utils/general/DualQueue.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - * DualQueue.h - * - * Created on: Jan 29, 2013 - * Author: nek3d - */ -#ifdef false -#ifndef DUALQUEUE_H_ -#define DUALQUEUE_H_ - -using namespace std; - -#include <vector> -#include <queue> -#include <cstdio> -#include <cstdlib> - -template <class T> class DualQueueAscending { -public: - bool operator() ( const T &item1, const T &item2) const { - - printf("\n\nIn comparison method:\n item1=\n"); -// item1->print(); - printf("\nitem2=\n"); -// item2->print(); - printf("\n"); - - if( *(item1) < *(item2) ) { - printf("Item1 less than item2. Returning false.\n"); - return false; - } - printf("Item1 not less than item2. Returning true.\n"); - return true; - } -}; - -template <class T> class DualQueueDescending { -public: - bool operator() ( const T &item1, const T &item2) const { - if( *(item2) < *(item1) ) { - return false; - } - return true; - } -}; - - -template <class T, template<class T> class CompareFunc> class DualQueue { -public: - DualQueue() {} - ~DualQueue() {} - - const T & top() const { - if (empty()) { - fprintf(stderr, "ERROR. Tried to top from empty dualQueue.\n"); - exit(1); - } - if (emptyForward()) { - return topReverse(); - } - if (emptyReverse()) { - return topForward(); - } - - return (topFowardHigherPriorityThanTopReverse() ? topForward() : topReverse()); - } - void pop() { - if (empty()) { - fprintf(stderr, "ERROR. Tried to pop from empty dualQueue.\n"); - exit(1); - } - if (emptyForward()) { - popReverse(); - return; - } - if (emptyReverse()) { - popForward(); - return; - } - topFowardHigherPriorityThanTopReverse() ? popForward() : popReverse(); - } - void push(const T &item) { item->getStrand() ? pushForward(item) : pushReverse(item); } - size_t size() const { return sizeForward() + sizeReverse(); } - bool empty() const { return _forwardQueue.empty() && _reverseQueue.empty(); } - - const T & topForward() const { return _forwardQueue.top(); } - void popForward() { _forwardQueue.pop(); } - void pushForward(const T &item) { _forwardQueue.push(item); } - size_t sizeForward() const { return _forwardQueue.size(); } - bool emptyForward() const { return _forwardQueue.empty(); } - - - const T & topReverse() const { return _reverseQueue.top(); } - void popReverse() { _reverseQueue.pop(); } - void pushReverse(const T &item) { _reverseQueue.push(item); } - size_t sizeReverse() const { return _reverseQueue.size(); } - bool emptyReverse() const { return _reverseQueue.empty(); } - - -private: - typedef priority_queue<T, vector<T>, CompareFunc<T> > queueType; - queueType _forwardQueue; - queueType _reverseQueue; - - bool topFowardHigherPriorityThanTopReverse() const { - printf("\n\nIn priority method:\n TopForward=\n"); -// topForward()->print(); - printf("\nTopReverse=\n"); -// topReverse()->print(); - printf("\n"); - if (CompareFunc<T>()(topForward(), topReverse())) { - printf("Forward higher priority than reverse.\n"); - return true; - } else { - printf("Reverse higher priority than forward.\n"); - return false; - } - } - -}; - - -#endif /* DUALQUEUE_H_ */ -#endif diff --git a/src/utils/general/ParseTools.cpp b/src/utils/general/ParseTools.cpp index ad5764b4..bef426a4 100644 --- a/src/utils/general/ParseTools.cpp +++ b/src/utils/general/ParseTools.cpp @@ -19,6 +19,9 @@ int str2chrPos(const QuickString &str) { } int str2chrPos(const char *str, size_t ulen) { + if (ulen == 0) { + ulen = strlen(str); + } int len=(int)ulen; if (len < 1 || len > 10) { return INT_MIN; //can't do more than 9 digits and a minus sign diff --git a/src/utils/general/ParseTools.h b/src/utils/general/ParseTools.h index 2132d0e4..405d631a 100644 --- a/src/utils/general/ParseTools.h +++ b/src/utils/general/ParseTools.h @@ -22,7 +22,7 @@ bool isNumeric(const QuickString &str); //Empty strings, too long strings, or strings containing anything other than //digits (with the excpetion of a minus sign in the first position) //will result in error. Errors return INT_MIN. -int str2chrPos(const char *str, size_t len); +int str2chrPos(const char *str, size_t len = 0); int str2chrPos(const QuickString &str); diff --git a/test/merge/test-merge.sh b/test/merge/test-merge.sh index ee7f6290..165e1e86 100644 --- a/test/merge/test-merge.sh +++ b/test/merge/test-merge.sh @@ -30,18 +30,22 @@ $BT merge -i a.bed > obs check obs exp rm obs exp - +########################################################### +# +# NOTE: Testing for sorted input is now deprecated, as the +# FileRecordMgr is already testing for that. +# ########################################################### # Test #2 # Enforce coordinate sorted input. ########################################################### -echo " merge.t2...\c" -command -v tac 2>/dev/null || alias tac="sed '1!G;h;\$!d'" -tac a.bed | $BT merge -i - 2> obs -echo "ERROR: input file: (-) is not sorted by chrom then start. - The start coordinate at line 3 is less than the start at line 2" > exp -check obs exp -rm obs exp +#echo " merge.t2...\c" +#command -v tac 2>/dev/null || alias tac="sed '1!G;h;\$!d'" +#tac a.bed | $BT merge -i - 2> obs +#echo "ERROR: input file: (-) is not sorted by chrom then start. +# The start coordinate at line 3 is less than the start at line 2" > exp +#check obs exp +#rm obs exp ########################################################### @@ -64,11 +68,9 @@ rm obs exp ########################################################### echo " merge.t4...\c" echo \ -"chr1 10 20 -***** -*****ERROR: No names found to report for the -names option. Exiting. -*****" > exp -$BT merge -i a.bed -nms > obs 2>&1 +"***** +***** ERROR: Requested column 4, but database file a.bed only has fields 1 - 3." > exp +$BT merge -i a.bed -nms 2>&1 > /dev/null | head -3 | tail -2 > obs check obs exp rm obs exp @@ -130,7 +132,7 @@ chr1 30 100 a2,a3,a4 9 3 chr2 10 20 a1 5 1 chr2 30 40 a2 6 1 chr2 42 100 a3,a4 15 2" > exp -$BT merge -i a.full.bed -nms -n -scores sum> obs +$BT merge -i a.full.bed -nms -scores sum -n> obs check obs exp rm obs exp @@ -139,15 +141,15 @@ rm obs exp ########################################################### echo " merge.t9...\c" echo \ -"chr1 10 20 a1 1 + 1 -chr1 30 40 a2 2 + 1 -chr1 45 100 a4 4 + 1 -chr1 40 50 a3 3 - 1 -chr2 10 20 a1 5 + 1 -chr2 30 40 a2 6 + 1 -chr2 42 50 a3 7 + 1 -chr2 45 100 a4 8 - 1" > exp -$BT merge -i a.full.bed -s -nms -n -scores sum> obs +"chr1 10 20 + a1 1 1 +chr1 30 40 + a2 2 1 +chr1 40 50 - a3 3 1 +chr1 45 100 + a4 4 1 +chr2 10 20 + a1 5 1 +chr2 30 40 + a2 6 1 +chr2 42 50 + a3 7 1 +chr2 45 100 - a4 8 1" > exp +$BT merge -i a.full.bed -s -nms -scores sum -n> obs check obs exp rm obs exp -- GitLab