From 53a791741d1918051ec9b7638490fe0c3f3994ec Mon Sep 17 00:00:00 2001 From: Marijon Pierre Date: Fri, 3 Feb 2017 15:00:31 +0100 Subject: [PATCH 01/26] Add posibility to colors node in cli image generation --- command_line/image.cpp | 36 ++++++++++++++++++++++++++++++++++++ command_line/image.h | 1 + 2 files changed, 37 insertions(+) diff --git a/command_line/image.cpp b/command_line/image.cpp index fcd89861..a8a8505f 100644 --- a/command_line/image.cpp +++ b/command_line/image.cpp @@ -130,6 +130,26 @@ int bandageImage(QStringList arguments) g_settings->startingNodes, "all"); + QString errormsg; + QStringList columns; + bool coloursLoaded = false; + QString csvPath = parseColorsOption(arguments); + if (csvPath != "") + { + if(!g_assemblyGraph->loadCSV(csvPath, &columns, &errormsg, &coloursLoaded)) + { + err << errormsg << endl; + return 1; + } + + if(coloursLoaded == false) + { + err << csvPath << " didn't contains color" << endl; + return 1; + } + g_settings->nodeColourScheme = CUSTOM_COLOURS; + } + if (errorMessage != "") { err << errorMessage << endl; @@ -212,6 +232,7 @@ void printImageUsage(QTextStream * out, bool all) text << ""; text << "Options: --height Image height (default: 1000)"; text << "--width Image width (default: not set)"; + text << "--color csv file with 2 column first the node name second the node color"; text << ""; text << "If only height or width is set, the other will be determined automatically. If both are set, the image will be exactly that size."; text << ""; @@ -232,6 +253,9 @@ QString checkForInvalidImageOptions(QStringList arguments) error = checkOptionForInt("--width", &arguments, IntSetting(0, 1, 32767), false); if (error.length() > 0) return error; + error = checkOptionForString("--colors", &arguments, QStringList(), "a path of csv file"); + if (error.length() > 0) return error; + return checkForInvalidOrExcessSettings(&arguments); } @@ -251,3 +275,15 @@ void parseImageOptions(QStringList arguments, int * width, int * height) parseSettings(arguments); } +//This function parses the command line options. It assumes that the options +//have already been checked for correctness. +QString parseColorsOption(QStringList arguments) +{ + QString path = ""; + if (isOptionPresent("--colors", &arguments)) + path = getStringOption("--colors", &arguments); + + parseSettings(arguments); + + return path; +} diff --git a/command_line/image.h b/command_line/image.h index 41d26291..59af21c9 100644 --- a/command_line/image.h +++ b/command_line/image.h @@ -28,5 +28,6 @@ int bandageImage(QStringList arguments); void printImageUsage(QTextStream * out, bool all); QString checkForInvalidImageOptions(QStringList arguments); void parseImageOptions(QStringList arguments, int * width, int * height); +QString parseColorsOption(QStringList arguments); #endif // IMAGE_H From d9c665ae389ad06dca24722c77a0282fb1b19a75 Mon Sep 17 00:00:00 2001 From: Ryan Wick Date: Sat, 11 Mar 2017 08:44:33 +1100 Subject: [PATCH 02/26] Fix crash when adding new BLAST queries but not immediately running search --- ui/blastsearchdialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/blastsearchdialog.cpp b/ui/blastsearchdialog.cpp index 9594c9db..a223fb96 100644 --- a/ui/blastsearchdialog.cpp +++ b/ui/blastsearchdialog.cpp @@ -159,6 +159,7 @@ void BlastSearchDialog::clearBlastHits() ui->blastHitsTableWidget->clearContents(); while (ui->blastHitsTableWidget->rowCount() > 0) ui->blastHitsTableWidget->removeRow(0); + g_assemblyGraph->clearAllBlastHitPointers(); } void BlastSearchDialog::fillTablesAfterBlastSearch() From 2db0b0cd52bbfd0b42d921167157c594fac08942 Mon Sep 17 00:00:00 2001 From: Ryan Wick Date: Fri, 17 Mar 2017 14:18:36 +1100 Subject: [PATCH 03/26] Refuse to load GFA/FASTG with duplicate node names --- graph/assemblygraph.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/graph/assemblygraph.cpp b/graph/assemblygraph.cpp index a839d528..c829097a 100644 --- a/graph/assemblygraph.cpp +++ b/graph/assemblygraph.cpp @@ -616,7 +616,9 @@ void AssemblyGraph::buildDeBruijnGraphFromGfa(QString fullFileName, bool *unsupp QString nodeName = lineParts.at(1); if (nodeName.isEmpty()) - nodeName = "node"; + nodeName = getUniqueNodeName("node"); + if (m_deBruijnGraphNodes.contains(nodeName + "+")) + throw "load error"; QByteArray sequence = lineParts.at(2).toLocal8Bit(); @@ -972,6 +974,8 @@ void AssemblyGraph::buildDeBruijnGraphFromFastg(QString fullFileName) nodeName += "-"; else nodeName += "+"; + if (m_deBruijnGraphNodes.contains(nodeName)) + throw "load error"; QString nodeDepthString = thisNodeDetails.at(5); if (negativeNode) From 109c3cb6935f5d1c7f88e627b84fec5a2b2ee362 Mon Sep 17 00:00:00 2001 From: Ryan Wick Date: Fri, 17 Mar 2017 14:32:02 +1100 Subject: [PATCH 04/26] Remove unnecessary lines in project file --- Bandage.pro | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Bandage.pro b/Bandage.pro index 05378e53..8b8db843 100644 --- a/Bandage.pro +++ b/Bandage.pro @@ -259,10 +259,6 @@ FORMS += \ RESOURCES += \ images/images.qrc - -unix:INCLUDEPATH += /usr/include/ -unix:LIBS += -L/usr/lib - # The following settings are compatible with OGDF being built in 64 bit release mode using Visual Studio 2013 win32:LIBS += -lpsapi win32:RC_FILE = images/myapp.rc From d1b59ff95bcee005f7461b5d9a36e2453f61496e Mon Sep 17 00:00:00 2001 From: Ryan Wick Date: Fri, 7 Apr 2017 12:52:23 +1000 Subject: [PATCH 05/26] Add select nodes with dead ends --- ui/mainwindow.cpp | 49 +++++++++++++++++++++++++++++++++++++++++++++++ ui/mainwindow.h | 1 + ui/mainwindow.ui | 6 ++++++ 3 files changed, 56 insertions(+) diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 9d1008c5..67aec68e 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -165,6 +165,7 @@ MainWindow::MainWindow(QString fileToLoadOnStartup, bool drawGraphAfterLoad) : connect(ui->contiguityButton, SIGNAL(clicked()), this, SLOT(determineContiguityFromSelectedNode())); connect(ui->actionBring_selected_nodes_to_front, SIGNAL(triggered()), this, SLOT(bringSelectedNodesToFront())); connect(ui->actionSelect_nodes_with_BLAST_hits, SIGNAL(triggered()), this, SLOT(selectNodesWithBlastHits())); + connect(ui->actionSelect_nodes_with_dead_ends, SIGNAL(triggered()), this, SLOT(selectNodesWithDeadEnds())); connect(ui->actionSelect_all, SIGNAL(triggered()), this, SLOT(selectAll())); connect(ui->actionSelect_none, SIGNAL(triggered()), this, SLOT(selectNone())); connect(ui->actionInvert_selection, SIGNAL(triggered()), this, SLOT(invertSelection())); @@ -1897,6 +1898,54 @@ void MainWindow::selectNodesWithBlastHits() } +void MainWindow::selectNodesWithDeadEnds() +{ + m_scene->blockSignals(true); + m_scene->clearSelection(); + + bool atLeastOneNodeHasDeadEnd = false; + bool atLeastOneNodeSelected = false; + + QMapIterator i(g_assemblyGraph->m_deBruijnGraphNodes); + while (i.hasNext()) + { + i.next(); + DeBruijnNode * node = i.value(); + + bool nodeHasDeadEnd = node->getDeadEndCount() > 0; + if (nodeHasDeadEnd) + atLeastOneNodeHasDeadEnd = true; + + GraphicsItemNode * graphicsItemNode = node->getGraphicsItemNode(); + + if (graphicsItemNode == 0) + continue; + + if (nodeHasDeadEnd) + { + graphicsItemNode->setSelected(true); + atLeastOneNodeSelected = true; + } + } + m_scene->blockSignals(false); + g_graphicsView->viewport()->update(); + selectionChanged(); + + if (!atLeastOneNodeHasDeadEnd) + { + QMessageBox::information(this, "No dead ends", "Nothing was selected because this graph has no dead ends."); + return; + } + + if (!atLeastOneNodeSelected) + QMessageBox::information(this, "No dead ends in visible nodes", + "Nothing was selected because no dead ends are currently visible. " + "Adjust the graph scope to make the nodes with dead ends hits visible."); + else + zoomToSelection(); +} + + void MainWindow::selectAll() { m_scene->blockSignals(true); diff --git a/ui/mainwindow.h b/ui/mainwindow.h index fafc0864..803fb91b 100644 --- a/ui/mainwindow.h +++ b/ui/mainwindow.h @@ -132,6 +132,7 @@ private slots: void graphLayoutCancelled(); void bringSelectedNodesToFront(); void selectNodesWithBlastHits(); + void selectNodesWithDeadEnds(); void selectAll(); void selectNone(); void invertSelection(); diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 7ac8182f..d2070c1d 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -1828,6 +1828,7 @@ + @@ -2226,6 +2227,11 @@ Change node depth + + + Select nodes with dead ends + + From 9acfcf3231d3436177bfafd80fe35c08ee8fee79 Mon Sep 17 00:00:00 2001 From: Ryan Wick Date: Mon, 8 May 2017 14:36:01 +0100 Subject: [PATCH 06/26] Allow FASTQ files for BLAST search --- blast/blastsearch.cpp | 2 +- graph/assemblygraph.cpp | 50 ++++++++++++++++++++++++++++++++++++++++- graph/assemblygraph.h | 4 ++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/blast/blastsearch.cpp b/blast/blastsearch.cpp index 3275cc45..d37a21ac 100644 --- a/blast/blastsearch.cpp +++ b/blast/blastsearch.cpp @@ -296,7 +296,7 @@ int BlastSearch::loadBlastQueriesFromFastaFile(QString fullFileName) std::vector queryNames; std::vector querySequences; - AssemblyGraph::readFastaFile(fullFileName, &queryNames, &querySequences); + AssemblyGraph::readFastaOrFastqFile(fullFileName, &queryNames, &querySequences); for (size_t i = 0; i < queryNames.size(); ++i) { diff --git a/graph/assemblygraph.cpp b/graph/assemblygraph.cpp index c829097a..88bba2a1 100644 --- a/graph/assemblygraph.cpp +++ b/graph/assemblygraph.cpp @@ -2379,7 +2379,25 @@ QString AssemblyGraph::getOppositeNodeName(QString nodeName) } -void AssemblyGraph::readFastaFile(QString filename, std::vector * names, std::vector *sequences) +void AssemblyGraph::readFastaOrFastqFile(QString filename, std::vector * names, + std::vector * sequences) { + QChar firstChar = 0; + QFile inputFile(filename); + if (inputFile.open(QIODevice::ReadOnly)) { + QTextStream in(&inputFile); + QString firstLine = in.readLine(); + firstChar = firstLine.at(0); + inputFile.close(); + } + if (firstChar == '>') + readFastaFile(filename, names, sequences); + else if (firstChar == '@') + readFastqFile(filename, names, sequences); +} + + + +void AssemblyGraph::readFastaFile(QString filename, std::vector * names, std::vector * sequences) { QFile inputFile(filename); if (inputFile.open(QIODevice::ReadOnly)) @@ -2427,6 +2445,36 @@ void AssemblyGraph::readFastaFile(QString filename, std::vector * names } +void AssemblyGraph::readFastqFile(QString filename, std::vector * names, std::vector * sequences) +{ + QFile inputFile(filename); + if (inputFile.open(QIODevice::ReadOnly)) + { + QTextStream in(&inputFile); + while (!in.atEnd()) + { + QApplication::processEvents(); + + QString name = in.readLine().simplified(); + QByteArray sequence = in.readLine().simplified().toLocal8Bit(); + in.readLine(); // separator + in.readLine(); // qualities + + if (name.length() == 0) + continue; + if (sequence.length() == 0) + continue; + if (name.at(0) != '@') + continue; + name.remove(0, 1); //Remove '@' from start + names->push_back(name); + sequences->push_back(sequence); + } + inputFile.close(); + } +} + + void AssemblyGraph::recalculateAllDepthsRelativeToDrawnMean() { double meanDrawnDepth = getMeanDepth(true); diff --git a/graph/assemblygraph.h b/graph/assemblygraph.h index d1a63227..426e7532 100644 --- a/graph/assemblygraph.h +++ b/graph/assemblygraph.h @@ -128,8 +128,12 @@ class AssemblyGraph : public QObject void setAllEdgesExactOverlap(int overlap); void autoDetermineAllEdgesExactOverlap(); + static void readFastaOrFastqFile(QString filename, std::vector * names, + std::vector * sequences); static void readFastaFile(QString filename, std::vector * names, std::vector * sequences); + static void readFastqFile(QString filename, std::vector * names, + std::vector * sequences); int getDrawnNodeCount() const; void deleteNodes(std::vector * nodes); From c774cc506f720c93c07cf3052826ebc4adcb5ee0 Mon Sep 17 00:00:00 2001 From: Ryan Wick Date: Fri, 26 May 2017 16:49:38 +1000 Subject: [PATCH 07/26] Small fix for CSV parsing --- graph/assemblygraph.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/graph/assemblygraph.cpp b/graph/assemblygraph.cpp index 88bba2a1..152f9d69 100644 --- a/graph/assemblygraph.cpp +++ b/graph/assemblygraph.cpp @@ -1696,6 +1696,12 @@ bool AssemblyGraph::loadCSV(QString filename, QStringList * columns, QString * e //If the node name it finds does not end in a '+' or '-', it will add '+'. QString AssemblyGraph::getNodeNameFromString(QString string) { + // First check for the most obvious case, where the string is already a node name. + if (m_deBruijnGraphNodes.contains(string)) + return string; + if (m_deBruijnGraphNodes.contains(string + "+")) + return string + "+"; + QStringList parts = string.split("_"); if (parts.size() == 0) return ""; From 3cbd21b7989285a16142eb55059a3342910e2320 Mon Sep 17 00:00:00 2001 From: Ryan Wick Date: Fri, 14 Jul 2017 09:43:02 +1000 Subject: [PATCH 08/26] Fix Bandage info tab-delimited output --- command_line/info.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command_line/info.cpp b/command_line/info.cpp index 815f4750..830815ce 100644 --- a/command_line/info.cpp +++ b/command_line/info.cpp @@ -118,7 +118,7 @@ int bandageInfo(QStringList arguments) out << median << "\t"; out << thirdQuartile << "\t"; out << longestNode << "\t"; - out << medianDepthByBase << "\n"; + out << medianDepthByBase << "\t"; out << estimatedSequenceLength << "\n"; } else From 4f1417105704c71584adf6a1ee69e3d71e57a328 Mon Sep 17 00:00:00 2001 From: Ivan Sovic Date: Mon, 16 Oct 2017 22:19:12 -0700 Subject: [PATCH 09/26] Added dotplot.cpp and dotplot.h to calculate k-mer seed hits. Also, updated the .pro file to compile the new sources. --- Bandage.pro | 2 + program/dotplot.cpp | 141 ++++++++++++++++++++++++++++++++++++++++++++ program/dotplot.h | 42 +++++++++++++ 3 files changed, 185 insertions(+) create mode 100644 program/dotplot.cpp create mode 100644 program/dotplot.h diff --git a/Bandage.pro b/Bandage.pro index 05378e53..93e9aa22 100644 --- a/Bandage.pro +++ b/Bandage.pro @@ -31,6 +31,7 @@ INCLUDEPATH += ui SOURCES += \ program/main.cpp\ + program/dotplot.cpp \ program/settings.cpp \ program/globals.cpp \ program/graphlayoutworker.cpp \ @@ -121,6 +122,7 @@ SOURCES += \ HEADERS += \ program/settings.h \ + program/dotplot.h \ program/globals.h \ program/graphlayoutworker.h \ graph/debruijnnode.h \ diff --git a/program/dotplot.cpp b/program/dotplot.cpp new file mode 100644 index 00000000..3680d29e --- /dev/null +++ b/program/dotplot.cpp @@ -0,0 +1,141 @@ +/* + * dotplot.cpp + * + * Created on: Oct 16, 2017 + * Author: Ivan Sovic + * GitHub: @isovic + * Copyright: Ivan Sovic, 2017 + * Licence: MIT + * + * Simple tool that collects all kmer hits between + * two sequences. If drawn, this represents a dotplot + * between two sequences. Can be used for very simple + * mapping as well. + */ + +#include "dotplot.h" + +#include +#include +#include +#include + +const int8_t nuc_to_2bit[256] = { + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 0 - 15 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 16 - 31 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 32 - 47 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 48 - 63 + 4, 0, 4, 1, 4, 4, 4, 2, 4, 4, 4, 4, 4, 4, 4, 4, // 64 - 79 (A, C, G) + 4, 4, 4, 4, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 80 - 95 (T) + 4, 0, 4, 1, 4, 4, 4, 2, 4, 4, 4, 4, 4, 4, 4, 4, // 96 - 111 + 4, 4, 4, 4, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 112 - 127 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 128 - 143 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 144 - 159 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 160 - 176 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 176 - 191 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 192 - 208 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 208 - 223 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 224 - 239 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 // 240 - 256 +}; + +const int8_t nuc_to_complement[256] = { + 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 0 - 15 + 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 16 - 31 + 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 32 - 47 + 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 48 - 63 + 78, 84, 78, 71, 78, 78, 78, 67, 78, 78, 78, 78, 78, 78, 78, 78, // 64 - 79 (A, C, G) + 78, 78, 78, 78, 65, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 80 - 95 (T) + 78, 84, 78, 71, 78, 78, 78, 67, 78, 78, 78, 78, 78, 78, 78, 78, // 96 - 111 + 78, 78, 78, 78, 65, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 112 - 127 + 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 128 - 143 + 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 144 - 159 + 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 160 - 176 + 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 176 - 191 + 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 192 - 208 + 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 208 - 223 + 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 224 - 239 + 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78 // 240 - 256 +}; + + +std::string reverseComplement(const std::string& seq) { + std::stringstream ss; + for (int32_t i = ((int32_t) seq.size()) - 1; i >= 0; i--) { + ss << nuc_to_complement[(int32_t) seq[i]]; + } + return ss.str(); +} + +std::vector hashKmers(const std::string& seq, int32_t k, bool seq_is_rev) { + std::vector ret; + + int64_t buff = 0x0; + int64_t buff_mask = (((int64_t) 1) << (2 * k)) - 1; // Clear the upper bits. + + ret.reserve(seq.size() - k + 1); + + // Initialize the buffer. + for (int32_t i = 0; i < (k - 1); i++) { + int64_t conv_val = nuc_to_2bit[(int32_t) seq[i]]; + assert(conv_val < 4); + buff = (((int64_t) buff) << 2) | (conv_val & 0x03); + } + + for (int32_t i = (k - 1); i < (int32_t) seq.size(); i++) { + // Update the buffer + int64_t conv_val = nuc_to_2bit[(int32_t) seq[i]]; + assert(conv_val < 4); + buff = (((int64_t) buff) << 2) | (conv_val & 0x03); + buff &= buff_mask; + + int32_t pos = (seq_is_rev == false) ? (i - k) : (seq.size() - (i - k + 1)); + ret.emplace_back(KmerPos(buff, pos)); + } + + return ret; +} + +std::vector findHits(const std::vector& sorted_kmers_seq1, const std::vector& sorted_kmers_seq2) { + int32_t k1 = 0, k2 = 0; + + int32_t n_kmers1 = sorted_kmers_seq1.size(); + int32_t n_kmers2 = sorted_kmers_seq2.size(); + + std::vector hits; + + while (k1 < n_kmers1 && k2 < n_kmers2) { + while (k1 < n_kmers1 && sorted_kmers_seq1[k1].kmer < sorted_kmers_seq2[k2].kmer) { + k1 += 1; + } + if (k1 >= n_kmers1) { break; } + + while (k2 < n_kmers2 && sorted_kmers_seq2[k2].kmer < sorted_kmers_seq1[k1].kmer) { + k2 += 1; + } + if (k2 >= n_kmers2) { break; } + + hits.emplace_back(KmerHit(sorted_kmers_seq1[k1].pos, sorted_kmers_seq2[k2].pos)); + k1 += 1; + k2 += 1; + } + + return hits; +} + +std::vector findKmerMatches(const std::string& seq1, const std::string& seq2, int32_t k) { + auto kmers_seq1 = hashKmers(seq1, k, false); + auto kmers_seq2 = hashKmers(seq2, k, false); + auto kmers_seq2_rev = hashKmers(reverseComplement(seq2), k, true); + + std::sort(kmers_seq1.begin(), kmers_seq1.end(), [](const KmerPos& a, const KmerPos& b) { return (a.kmer < b.kmer || (a.kmer == b.kmer && a.pos < b.pos)); } ); + std::sort(kmers_seq2.begin(), kmers_seq2.end(), [](const KmerPos& a, const KmerPos& b) { return (a.kmer < b.kmer || (a.kmer == b.kmer && a.pos < b.pos)); } ); + std::sort(kmers_seq2_rev.begin(), kmers_seq2_rev.end(), [](const KmerPos& a, const KmerPos& b) { return (a.kmer < b.kmer || (a.kmer == b.kmer && a.pos < b.pos)); } ); + + auto hits = findHits(kmers_seq1, kmers_seq2); + auto hits_rev = findHits(kmers_seq1, kmers_seq2_rev); + + hits.insert(hits.end(), hits_rev.begin(), hits_rev.end()); + + return hits; +} diff --git a/program/dotplot.h b/program/dotplot.h new file mode 100644 index 00000000..8356feb8 --- /dev/null +++ b/program/dotplot.h @@ -0,0 +1,42 @@ +/* + * dotplot.h + * + * Created on: Oct 16, 2017 + * Author: Ivan Sovic + * GitHub: @isovic + * Copyright: Ivan Sovic, 2017 + * Licence: MIT + * + * Simple tool that collects all kmer hits between + * two sequences. If drawn, this represents a dotplot + * between two sequences. Can be used for very simple + * mapping as well. + */ + +#ifndef SRC_PROGRAM_DOTPLOT_H_ +#define SRC_PROGRAM_DOTPLOT_H_ + +#include +#include +#include + +class KmerPos { + public: + KmerPos() : kmer(0), pos(0) {} + KmerPos(int64_t _kmer, int32_t _pos) : kmer(_kmer), pos(_pos) { } + + int64_t kmer; + int32_t pos; +}; + +struct KmerHit { + KmerHit() : x(0), y(0) { } + KmerHit(int32_t _x, int32_t _y) : x(_x), y(_y) { } + + int32_t x; + int32_t y; +}; + +std::vector findKmerMatches(const std::string& seq1, const std::string& seq2, int32_t k); + +#endif From a18aaca7dc695cb6e763660f801e715b2bbd3ae6 Mon Sep 17 00:00:00 2001 From: Ivan Sovic Date: Mon, 16 Oct 2017 22:19:56 -0700 Subject: [PATCH 10/26] Added the placeholder for the dotplot, and removed the vertical spacers to make the GUI more compact. --- ui/mainwindow.ui | 100 ++++++++++++++++++++++++++++++----------------- 1 file changed, 65 insertions(+), 35 deletions(-) diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 7ac8182f..1c7c87c9 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -1354,9 +1354,9 @@ 0 - 0 - 249 - 899 + -294 + 324 + 1158 @@ -1511,6 +1511,68 @@ + + + + Draw dotplot + + + + + + + + 0 + 25 + + + + + + 10 + 1 + 71 + 16 + + + + k-mer size: + + + + + + 90 + 0 + 131 + 21 + + + + 15 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + 300 + 300 + + + + + 300 + 300 + + + + @@ -1526,22 +1588,6 @@ 0 - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 60 - - - - @@ -1692,22 +1738,6 @@ 0 - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 60 - - - - From a8622dd1c3b5cde5459f0bdc51e213d0ced6d551 Mon Sep 17 00:00:00 2001 From: Ivan Sovic Date: Mon, 16 Oct 2017 22:21:15 -0700 Subject: [PATCH 11/26] Added a shared pointer to hold the dotplot scene, and the method which will be signalled by the slot. --- ui/mainwindow.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/mainwindow.h b/ui/mainwindow.h index fafc0864..3baf9b6a 100644 --- a/ui/mainwindow.h +++ b/ui/mainwindow.h @@ -62,6 +62,7 @@ class MainWindow : public QMainWindow UiState m_uiState; BlastSearchDialog * m_blastSearchDialog; bool m_alreadyShown; + std::shared_ptr m_dotplotScene; void cleanUp(); void displayGraphDetails(); @@ -159,6 +160,7 @@ private slots: void changeNodeName(); void changeNodeDepth(); void openGraphInfoDialog(); + void drawDotplot(); protected: void showEvent(QShowEvent *ev); From 821dd3ffda427af11e174caf65da92069f761b43 Mon Sep 17 00:00:00 2001 From: Ivan Sovic Date: Mon, 16 Oct 2017 22:25:09 -0700 Subject: [PATCH 12/26] Added the signal handling and plotting of the dotplot between two selected nodes. If more (or less) than two nodes are selected, Bandage will notify the user and won't draw. --- ui/mainwindow.cpp | 132 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 1 deletion(-) diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 9d1008c5..6e492424 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -67,6 +67,7 @@ #include "changenodedepthdialog.h" #include #include "graphinfodialog.h" +#include "program/dotplot.h" MainWindow::MainWindow(QString fileToLoadOnStartup, bool drawGraphAfterLoad) : QMainWindow(0), @@ -194,6 +195,7 @@ MainWindow::MainWindow(QString fileToLoadOnStartup, bool drawGraphAfterLoad) : connect(ui->actionChange_node_name, SIGNAL(triggered(bool)), this, SLOT(changeNodeName())); connect(ui->actionChange_node_depth, SIGNAL(triggered(bool)), this, SLOT(changeNodeDepth())); connect(ui->moreInfoButton, SIGNAL(clicked(bool)), this, SLOT(openGraphInfoDialog())); + connect(ui->drawDotplotButton, SIGNAL(clicked()), this, SLOT(drawDotplot())); connect(this, SIGNAL(windowLoaded()), this, SLOT(afterMainWindowShow()), Qt::ConnectionType(Qt::QueuedConnection | Qt::UniqueConnection)); } @@ -738,6 +740,134 @@ void MainWindow::drawGraph() layoutGraph(); } +void MainWindow::drawDotplot() +{ + std::vector selectedNodes = m_scene->getSelectedNodes(); + + if (selectedNodes.size() != 2) { + QString infoTitle = "Draw dotplot"; + QString infoMessage = "Select exactly two nodes to dotplot."; + QMessageBox::information(this, infoTitle, infoMessage); + return; + } + + std::vector seqs; + std::vector headers; + for (size_t i=0; isequenceIsMissing()) { + QString infoTitle = "Draw dotplot"; + QString infoMessage = "Error: The GFA node does not contain a valid sequence!"; + QMessageBox::information(this, infoTitle, infoMessage); + return; + } + + QByteArray nodeSequence = node->getSequence(); + QString nodeHeader = node->getName(); + std::string seq(nodeSequence.constData(), nodeSequence.length()); + std::string header = nodeHeader.toLocal8Bit().constData(); + + seqs.push_back(seq); + headers.push_back(header); + } + + // Parse the k-mer size. + int32_t k = 15; + std::stringstream iss(std::string(ui->kmerSizeInput->text().toLocal8Bit().constData())); + iss >> k; + + if (k > 31) { + QString infoTitle = "Draw dotplot"; + QString infoMessage = "Error: k-mer size should not exceed 31."; + QMessageBox::information(this, infoTitle, infoMessage); + return; + } + + // Calculate the dotplot. + auto hits = findKmerMatches(seqs[0], seqs[1], k); + + // The rest of the method is just plotting. + m_dotplotScene = std::shared_ptr(new QGraphicsScene()); + ui->graphicsView->setScene(m_dotplotScene.get()); + + // Calculate the starts and ends of the dotplot coordinate system. + int32_t max_len = std::max(seqs[0].size(), seqs[1].size()); + double begin_offset = 40; + double x_begin = begin_offset; + double y_begin = begin_offset; + double max_size = (float) std::min(ui->graphicsView->maximumWidth(), ui->graphicsView->maximumHeight()) - 10 - begin_offset; + double scale = max_size / ((double) max_len); + double x_end = x_begin + seqs[0].size() * scale; + double y_end = y_begin + seqs[1].size() * scale; + + // Make the scene not move. + ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_dotplotScene->setSceneRect(0, 0, 300, 300); + + // Add bounds to the dotplot graph. + double overhang = 5; + m_dotplotScene->addLine(x_begin - overhang, y_begin, x_end, y_begin); + m_dotplotScene->addLine(x_end, y_begin - overhang, x_end, y_end); + m_dotplotScene->addLine(x_begin - overhang, y_end, x_end, y_end); + m_dotplotScene->addLine(x_begin, y_begin - overhang, x_begin, y_end); + + // Annotate the graph. + QFont font; + font.setPixelSize(8); + font.setFamily("Monospace"); + + { + QGraphicsTextItem *text = m_dotplotScene->addText(QString("%1").arg(seqs[0].size())); + text->setFont(font); + text->setPos(x_end - text->boundingRect().width(), y_begin - text->boundingRect().height()); + } + + { + QGraphicsTextItem *text = m_dotplotScene->addText(QString("%1").arg(0)); + text->setFont(font); + text->setPos(x_begin + 1, y_begin - text->boundingRect().height()); + } + + { + QGraphicsTextItem *text = m_dotplotScene->addText(QString("%1").arg(0)); + text->setFont(font); + text->setPos(x_begin - text->boundingRect().width(), y_begin); + } + + { + QGraphicsTextItem *text = m_dotplotScene->addText(QString("%1").arg(seqs[1].size())); + text->setFont(font); + text->setPos(x_begin - text->boundingRect().width(), y_end - text->boundingRect().height()); + } + + { + std::string trimmed_header = (headers[0].size() > 20) ? headers[0].substr(0, 20) : headers[0]; + QGraphicsTextItem *text = m_dotplotScene->addText(QString(trimmed_header.c_str())); + text->setFont(font); + text->setPos((x_end + x_begin - text->boundingRect().width()) / 2.0, y_begin - text->boundingRect().height()); + } + + { + std::string trimmed_header = (headers[1].size() > 20) ? (headers[1].substr(0, 20)) : (headers[1]); + QGraphicsTextItem *text = m_dotplotScene->addText(QString(trimmed_header.c_str())); + text->setFont(font); + QTransform t; + t.rotate(270); + text->setTransform(t); + text->setPos(x_begin - text->boundingRect().height(), (y_begin + y_end + text->boundingRect().width()) / 2.0); + } + + // Generate the actual dotplot. + for (auto& hit: hits) { + m_dotplotScene->addEllipse(hit.x * scale + x_begin, hit.y * scale + y_begin, 2.0 * scale, 2.0 * scale); + } + + ui->graphicsView->show(); +} + + void MainWindow::graphLayoutFinished() { @@ -2252,7 +2382,7 @@ void MainWindow::webBlastSelectedNodes() QByteArray urlSafeFasta = makeStringUrlSafe(selectedNodesFasta); QByteArray url = "http://blast.ncbi.nlm.nih.gov/Blast.cgi?PROGRAM=blastn&PAGE_TYPE=BlastSearch&LINK_LOC=blasthome&QUERY=" + urlSafeFasta; - + if (url.length() < 8190) QDesktopServices::openUrl(QUrl(url)); From 8439ab17e6c8593766bea57d376657ad8cfe92a5 Mon Sep 17 00:00:00 2001 From: Ivan Sovic Date: Tue, 17 Oct 2017 21:46:23 -0700 Subject: [PATCH 13/26] Added declarations to dotplot.h. --- program/dotplot.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/program/dotplot.h b/program/dotplot.h index 8356feb8..3fc17cd2 100644 --- a/program/dotplot.h +++ b/program/dotplot.h @@ -37,6 +37,10 @@ struct KmerHit { int32_t y; }; +std::string reverseComplement(const std::string& seq); +std::vector hashKmers(const std::string& seq, int32_t k, bool seq_is_rev); +std::vector findHits(const std::vector& sorted_kmers_seq1, const std::vector& sorted_kmers_seq2); +std::vector findKmerMatches(const std::string& seq1, const std::string& seq2, int32_t k); std::vector findKmerMatches(const std::string& seq1, const std::string& seq2, int32_t k); #endif From 9bccb545cc03c8f4cf779d95007208cc2c4b61cd Mon Sep 17 00:00:00 2001 From: Ivan Sovic Date: Tue, 17 Oct 2017 21:46:53 -0700 Subject: [PATCH 14/26] Allowed self-dotplots. --- ui/mainwindow.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 6e492424..9c43b1c1 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -744,17 +744,24 @@ void MainWindow::drawDotplot() { std::vector selectedNodes = m_scene->getSelectedNodes(); - if (selectedNodes.size() != 2) { + if (selectedNodes.size() < 1 || selectedNodes.size() > 2) { QString infoTitle = "Draw dotplot"; - QString infoMessage = "Select exactly two nodes to dotplot."; + QString infoMessage = "Select either one (for self-dotplot) or two nodes to dotplot."; QMessageBox::information(this, infoTitle, infoMessage); return; } std::vector seqs; std::vector headers; - for (size_t i=0; i nodes_to_process = selectedNodes; + if (selectedNodes.size() == 1) { + nodes_to_process.push_back(selectedNodes[0]); + } + + for (size_t i=0; isequenceIsMissing()) { QString infoTitle = "Draw dotplot"; From 8870cb9cc52b20775bba49573a4b44def3baff9f Mon Sep 17 00:00:00 2001 From: Ivan Sovic Date: Wed, 18 Oct 2017 22:22:45 -0700 Subject: [PATCH 15/26] Added unit tests for the new methods. --- BandageTests.pro | 2 + tests/bandagetests.cpp | 227 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 228 insertions(+), 1 deletion(-) diff --git a/BandageTests.pro b/BandageTests.pro index dcfb5d9a..adbc84e0 100644 --- a/BandageTests.pro +++ b/BandageTests.pro @@ -33,6 +33,7 @@ SOURCES += \ program/settings.cpp \ program/globals.cpp \ program/graphlayoutworker.cpp \ + program/dotplot.cpp \ graph/debruijnnode.cpp \ graph/debruijnedge.cpp \ graph/graphicsitemnode.cpp \ @@ -123,6 +124,7 @@ HEADERS += \ program/settings.h \ program/globals.h \ program/graphlayoutworker.h \ + program/dotplot.h \ graph/debruijnnode.h \ graph/debruijnedge.h \ graph/graphicsitemnode.h \ diff --git a/tests/bandagetests.cpp b/tests/bandagetests.cpp index 388cd427..86f4dccd 100644 --- a/tests/bandagetests.cpp +++ b/tests/bandagetests.cpp @@ -29,6 +29,7 @@ #include "../graph/debruijnedge.h" #include "../program/globals.h" #include "../command_line/commoncommandlinefunctions.h" +#include "../program/dotplot.h" class BandageTests : public QObject { @@ -58,7 +59,10 @@ private slots: void changeNodeDepths(); void blastQueryPaths(); void bandageInfo(); - + void testReverseComplement(); + void testHashKmers(); + void testFindHits(); + void testFindKmerMatches(); private: void createGlobals(); @@ -1480,6 +1484,227 @@ void BandageTests::bandageInfo() QCOMPARE(9398, largestComponentLength); } +void BandageTests::testReverseComplement() +{ + QCOMPARE( reverseComplement("A"), std::string("T")); + QCOMPARE( reverseComplement("C"), std::string("G")); + QCOMPARE( reverseComplement("T"), std::string("A")); + QCOMPARE( reverseComplement("G"), std::string("C")); + QCOMPARE( reverseComplement(""), std::string("")); + QCOMPARE( reverseComplement("ACTG"), std::string("CAGT")); +} + +void BandageTests::testHashKmers() +{ + { // Test for an empty string. + std::string seq = ""; + int32_t k = 10; + bool seq_is_rev = false; + std::vector expected_kmers = { }; + std::vector kmers = hashKmers(seq, k, seq_is_rev); + QCOMPARE((int64_t) kmers.size(), (int64_t) 0); + } + + { // Test the behaviour when a non [ACTG] character is given. + std::string seq = "AAAAANAAAAA"; + int32_t k = 10; + bool seq_is_rev = false; + std::vector expected_kmers = { }; + std::vector kmers = hashKmers(seq, k, seq_is_rev); + QCOMPARE((int64_t) kmers.size(), (int64_t) 0); + } + + { // A simple normal test case. Entire seq should be only one kmer. + std::string seq = "AAAAAAAAAA"; + int32_t k = 10; + bool seq_is_rev = false; + std::vector expected_kmers = { KmerPos(0x0, 0) }; + std::vector kmers = hashKmers(seq, k, seq_is_rev); + QCOMPARE(kmers, expected_kmers); + } + + { // Similar to before, but 6 kmers. + std::string seq = "AAAAAAAAAAAAAAA"; + int32_t k = 10; + bool seq_is_rev = false; + std::vector expected_kmers = { KmerPos(0x0, 0), KmerPos(0x0, 1), + KmerPos(0x0, 2), KmerPos(0x0, 3), + KmerPos(0x0, 4), KmerPos(0x0, 5) }; + std::vector kmers = hashKmers(seq, k, seq_is_rev); + QCOMPARE(kmers, expected_kmers); + } + + { // Test the reverse complement. + std::string seq = "AAAAAAAAAA"; + int32_t k = 10; + bool seq_is_rev = true; + std::vector expected_kmers = { KmerPos(0x0, 9) }; + std::vector kmers = hashKmers(seq, k, seq_is_rev); + QCOMPARE(kmers, expected_kmers); + } + + { // Test what happens if an invalid kmer size is given. + std::string seq = "AAAAAAAAAA"; + int32_t k = 0; + bool seq_is_rev = false; + std::vector kmers = hashKmers(seq, k, seq_is_rev); + QCOMPARE((int64_t) kmers.size(), (int64_t) 0); + } + + { // Test for a normal sequence, but a more complex variation. + std::string seq = "ACTGAAAGACT"; + int32_t k = 10; + bool seq_is_rev = false; + std::vector expected_kmers = { KmerPos(0x1E021, 0), KmerPos(0x78087, 1) }; + std::vector kmers = hashKmers(seq, k, seq_is_rev); + QCOMPARE(kmers, expected_kmers); + } + + { // Similar as before, but in reverse. Hash keys should be same, but positions different. + std::string seq = "ACTGAAAGACT"; + int32_t k = 10; + bool seq_is_rev = true; + std::vector expected_kmers = { KmerPos(0x1E021, 10), KmerPos(0x78087, 9) }; + std::vector kmers = hashKmers(seq, k, seq_is_rev); + QCOMPARE(kmers, expected_kmers); + } + + { // Test a normal sequence and a normal (smaller) kmer size. + std::string seq = "ACTGAAAGACT"; + int32_t k = 4; + bool seq_is_rev = false; + std::vector expected_kmers = { KmerPos(0x1E, 0), KmerPos(0x78, 1), KmerPos(0xE0, 2), + KmerPos(0x80, 3), KmerPos(0x2, 4), KmerPos(0x8, 5), + KmerPos(0x21, 6), KmerPos(0x87, 7) }; + std::vector kmers = hashKmers(seq, k, seq_is_rev); + QCOMPARE(kmers, expected_kmers); + } +} + +void BandageTests::testFindHits() +{ + { // Test on empty inputs. + std::vector sorted_kmers_seq1 = { }; + std::vector sorted_kmers_seq2 = { }; + std::vector result = findHits(sorted_kmers_seq1, sorted_kmers_seq2); + QCOMPARE((int64_t) result.size(), (int64_t) 0); + } + + { // No hits are output if any of the input arrays are not sorted. + std::vector sorted_kmers_seq1 = {KmerPos(1, 0), KmerPos(0, 1)}; + std::vector sorted_kmers_seq2 = {KmerPos(0, 0), KmerPos(1, 1)}; + std::vector result = findHits(sorted_kmers_seq1, sorted_kmers_seq2); + QCOMPARE((int64_t) result.size(), (int64_t) 0); + } + + { /// Sorted but no hits. + std::vector sorted_kmers_seq1 = {KmerPos(2, 0), KmerPos(3, 1)}; + std::vector sorted_kmers_seq2 = {KmerPos(0, 0), KmerPos(1, 1)}; + std::vector result = findHits(sorted_kmers_seq1, sorted_kmers_seq2); + QCOMPARE((int64_t) result.size(), (int64_t) 0); + } + + { // There should be two hits. + std::vector sorted_kmers_seq1 = {KmerPos(0, 0), KmerPos(1, 1)}; + std::vector sorted_kmers_seq2 = {KmerPos(0, 0), KmerPos(1, 1)}; + std::vector result = findHits(sorted_kmers_seq1, sorted_kmers_seq2); + std::vector expected = {KmerHit(0, 0), KmerHit(1, 1)}; + QCOMPARE(result, expected); + } + + { // One is empty, the other is not. + std::vector sorted_kmers_seq1 = { }; + std::vector sorted_kmers_seq2 = {KmerPos(0, 0), KmerPos(1, 1)}; + std::vector result = findHits(sorted_kmers_seq1, sorted_kmers_seq2); + QCOMPARE((int64_t) result.size(), (int64_t) 0); + } + + { // One is empty, the other is not. + std::vector sorted_kmers_seq1 = {KmerPos(0, 0), KmerPos(1, 1)}; + std::vector sorted_kmers_seq2 = { }; + std::vector result = findHits(sorted_kmers_seq1, sorted_kmers_seq2); + QCOMPARE((int64_t) result.size(), (int64_t) 0); + } + + { // Only a subset matches. + std::vector sorted_kmers_seq1 = {KmerPos(3, 1), KmerPos(4, 2)}; + std::vector sorted_kmers_seq2 = {KmerPos(0, 0), KmerPos(1, 1), + KmerPos(3, 3), KmerPos(4, 7), + KmerPos(5, 8) }; + std::vector result = findHits(sorted_kmers_seq1, sorted_kmers_seq2); + std::vector expected = {KmerHit(1, 3), KmerHit(2, 7)}; + QCOMPARE(result, expected); + } + +} + +void BandageTests::testFindKmerMatches() +{ + { // Test a simple basic match case. + std::string seq1 = "CT"; + std::string seq2 = "CT"; + int32_t k = 2; + std::vector result = findKmerMatches(seq1, seq2, k); + std::vector expected = {KmerHit(0, 0)}; + QCOMPARE(result, expected); + } + + { // Test a case with no hits. + std::string seq1 = "AAAAA"; + std::string seq2 = "CT"; + int32_t k = 2; + std::vector result = findKmerMatches(seq1, seq2, k); + std::vector expected = { }; + QCOMPARE((int64_t) result.size(), (int64_t) expected.size()); + } + + { // Test a normal one-hit case. + std::string seq1 = "ACTTGGGA"; + std::string seq2 = "CT"; + int32_t k = 2; + std::vector result = findKmerMatches(seq1, seq2, k); + std::vector expected = {KmerHit(1, 0)}; + QCOMPARE((int64_t) result.size(), (int64_t) expected.size()); + QCOMPARE(result, expected); + } + + { // Test matching empty sequences. + std::string seq1 = ""; + std::string seq2 = ""; + int32_t k = 10; + std::vector result = findKmerMatches(seq1, seq2, k); + std::vector expected = { }; + QCOMPARE((int64_t) result.size(), (int64_t) expected.size()); + QCOMPARE(result, expected); + } + + { // Test finding of hits in a larger sequence. + std::string seq1 = "CTCGCACTTGGGGAATCGCGCAGACCTCACCCGGTTTGCAGGCTTGCGCCGGGCGGTAGATGCGCCGCCAGGCGAAAAACAGCGCGACCAGCGCTGCGCC"; + std::string seq2 = "CTCGCACTTG" "AGACCTCACC" "TAGATGCGCCG"; + // Reverse complement: + // CGGCGCATCTA GGTGAGGTCT CAAGTGCGAG + int32_t k = 10; + std::vector result = findKmerMatches(seq1, seq2, k); + std::vector expected = {KmerHit(0, 0), KmerHit(21, 10), + KmerHit(56, 20), KmerHit(57, 21) }; + std::sort(result.begin(), result.end()); + std::sort(expected.begin(), expected.end()); + QCOMPARE((int64_t) result.size(), (int64_t) expected.size()); + QCOMPARE(result, expected); + } + + { // Test the reverse complement matching. + std::string seq1 = "CTCGCACTTG"; + std::string seq2 = "CAAGTGCGAG"; + int32_t k = 10; + std::vector result = findKmerMatches(seq1, seq2, k); + std::vector expected = {KmerHit(0, 9) }; + std::sort(result.begin(), result.end()); + std::sort(expected.begin(), expected.end()); + QCOMPARE((int64_t) result.size(), (int64_t) expected.size()); + QCOMPARE(result, expected); + } +} From 7899b417b83d5c23af0b1ca62211f0224bb491dd Mon Sep 17 00:00:00 2001 From: Ivan Sovic Date: Wed, 18 Oct 2017 22:23:58 -0700 Subject: [PATCH 16/26] Fixed the dotplot.cpp and dotplot.h according to the results discovered by unit tests. --- program/dotplot.cpp | 38 +++++++++++++++++++++++++++++++++++--- program/dotplot.h | 12 ++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/program/dotplot.cpp b/program/dotplot.cpp index 3680d29e..1f981622 100644 --- a/program/dotplot.cpp +++ b/program/dotplot.cpp @@ -70,6 +70,23 @@ std::string reverseComplement(const std::string& seq) { std::vector hashKmers(const std::string& seq, int32_t k, bool seq_is_rev) { std::vector ret; + if (((int32_t) seq.size()) < k) { + return ret; + } + + if (k <= 0 || k >= 31) { + return ret; + } + + // Pre-scan the sequence and check whether it contains + // only [ACTG] bases. We do not allow other bases, because + // they will be packed as 2-bit values in a hash key. + for (size_t i = 0; i < seq.size(); i++) { + if (nuc_to_2bit[(int32_t) seq[i]] > 3) { + return ret; + } + } + int64_t buff = 0x0; int64_t buff_mask = (((int64_t) 1) << (2 * k)) - 1; // Clear the upper bits. @@ -78,18 +95,16 @@ std::vector hashKmers(const std::string& seq, int32_t k, bool seq_is_re // Initialize the buffer. for (int32_t i = 0; i < (k - 1); i++) { int64_t conv_val = nuc_to_2bit[(int32_t) seq[i]]; - assert(conv_val < 4); buff = (((int64_t) buff) << 2) | (conv_val & 0x03); } for (int32_t i = (k - 1); i < (int32_t) seq.size(); i++) { // Update the buffer int64_t conv_val = nuc_to_2bit[(int32_t) seq[i]]; - assert(conv_val < 4); buff = (((int64_t) buff) << 2) | (conv_val & 0x03); buff &= buff_mask; - int32_t pos = (seq_is_rev == false) ? (i - k) : (seq.size() - (i - k + 1)); + int32_t pos = (seq_is_rev == false) ? (i - k + 1) : (seq.size() - (i - k + 1) - 1); ret.emplace_back(KmerPos(buff, pos)); } @@ -104,6 +119,18 @@ std::vector findHits(const std::vector& sorted_kmers_seq1, con std::vector hits; + // Check the sortedness of input. + for (size_t i = 1; i < sorted_kmers_seq1.size(); i++) { + if(sorted_kmers_seq1[i].kmer < sorted_kmers_seq1[i - 1].kmer) { + return hits; + } + } + for (size_t i = 1; i < sorted_kmers_seq2.size(); i++) { + if(sorted_kmers_seq2[i].kmer < sorted_kmers_seq2[i - 1].kmer) { + return hits; + } + } + while (k1 < n_kmers1 && k2 < n_kmers2) { while (k1 < n_kmers1 && sorted_kmers_seq1[k1].kmer < sorted_kmers_seq2[k2].kmer) { k1 += 1; @@ -115,6 +142,11 @@ std::vector findHits(const std::vector& sorted_kmers_seq1, con } if (k2 >= n_kmers2) { break; } + // If the values are not the same, just keep on gliding. + if (sorted_kmers_seq1[k1].kmer != sorted_kmers_seq2[k2].kmer) { + continue; + } + hits.emplace_back(KmerHit(sorted_kmers_seq1[k1].pos, sorted_kmers_seq2[k2].pos)); k1 += 1; k2 += 1; diff --git a/program/dotplot.h b/program/dotplot.h index 3fc17cd2..2d33188e 100644 --- a/program/dotplot.h +++ b/program/dotplot.h @@ -24,6 +24,12 @@ class KmerPos { public: KmerPos() : kmer(0), pos(0) {} KmerPos(int64_t _kmer, int32_t _pos) : kmer(_kmer), pos(_pos) { } + bool operator==(const KmerPos& b) const { + return (this->kmer == b.kmer && this->pos == b.pos); + } + bool operator<(const KmerPos& b) const { + return (this->kmer < b.kmer || (this->kmer == b.kmer && this->pos < b.pos)); + } int64_t kmer; int32_t pos; @@ -32,6 +38,12 @@ class KmerPos { struct KmerHit { KmerHit() : x(0), y(0) { } KmerHit(int32_t _x, int32_t _y) : x(_x), y(_y) { } + bool operator==(const KmerHit& b) const { + return (this->x == b.x && this->y == b.y); + } + bool operator<(const KmerHit& b) const { + return (this->x < b.x || (this->x == b.x && this->y < b.y)); + } int32_t x; int32_t y; From 6246fd3938113999b35871d71903990981ef2e7f Mon Sep 17 00:00:00 2001 From: Ivan Sovic Date: Wed, 18 Oct 2017 22:28:50 -0700 Subject: [PATCH 17/26] Changed the upper limit to k-mer size as checked by the Main window. --- ui/mainwindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 9c43b1c1..4183be26 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -784,9 +784,9 @@ void MainWindow::drawDotplot() std::stringstream iss(std::string(ui->kmerSizeInput->text().toLocal8Bit().constData())); iss >> k; - if (k > 31) { + if (k > 30) { QString infoTitle = "Draw dotplot"; - QString infoMessage = "Error: k-mer size should not exceed 31."; + QString infoMessage = "Error: k-mer size should not exceed 30."; QMessageBox::information(this, infoTitle, infoMessage); return; } From 7ed3e9f04dee2495f52e50b5e316f8076231ad02 Mon Sep 17 00:00:00 2001 From: Ivan Sovic Date: Wed, 18 Oct 2017 23:28:45 -0700 Subject: [PATCH 18/26] Minor aesthetic changes. --- ui/mainwindow.cpp | 75 +++++++++++++++++++++++++++++++++++++++++++---- ui/mainwindow.h | 1 + 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 4183be26..88740e79 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -719,6 +719,60 @@ void MainWindow::setDepthRangeWidgetVisibility(bool visible) ui->maxDepthSpinBox->setVisible(visible); } +void MainWindow::drawDotplotPoweredByLogo(double x, double y, double w) { + QPen pen; + pen.setWidth(0); + pen.setColor(QColor("#BBBBBB")); + QBrush brush(QColor("#BBBBBB")); + + QPen outline; + outline.setWidth(1); + outline.setColor(QColor("#888888")); + + m_dotplotScene->addRect(x + w, y, w, w, pen, brush); + m_dotplotScene->addRect(x + 3*w, y, w, w, pen, brush); + m_dotplotScene->addRect(x, y + w, 5 * w, w, pen, brush); + m_dotplotScene->addRect(x + w, y + 2 * w, w, w, pen, brush); + m_dotplotScene->addRect(x + 3*w, y + 2 * w, w, w, pen, brush); + m_dotplotScene->addRect(x, y + 3 * w, 5 * w, w, pen, brush); + m_dotplotScene->addRect(x, y + 4 * w, w, w, pen, brush); + m_dotplotScene->addRect(x + 2*w, y + 4 * w, w, w, pen, brush); + m_dotplotScene->addRect(x + 4*w, y + 4 * w, w, w, pen, brush); + + m_dotplotScene->addLine(x + 1 * w, y + 0 * w, x + 2 * w, y + 0 * w, outline); + m_dotplotScene->addLine(x + 2 * w, y + 0 * w, x + 2 * w, y + 1 * w, outline); + m_dotplotScene->addLine(x + 2 * w, y + 1 * w, x + 3 * w, y + 1 * w, outline); + m_dotplotScene->addLine(x + 3 * w, y + 1 * w, x + 3 * w, y + 0 * w, outline); + m_dotplotScene->addLine(x + 3 * w, y + 0 * w, x + 4 * w, y + 0 * w, outline); + m_dotplotScene->addLine(x + 4 * w, y + 0 * w, x + 4 * w, y + 1 * w, outline); + m_dotplotScene->addLine(x + 4 * w, y + 1 * w, x + 5 * w, y + 1 * w, outline); + m_dotplotScene->addLine(x + 5 * w, y + 1 * w, x + 5 * w, y + 2 * w, outline); + m_dotplotScene->addLine(x + 5 * w, y + 2 * w, x + 4 * w, y + 2 * w, outline); + m_dotplotScene->addLine(x + 4 * w, y + 2 * w, x + 4 * w, y + 3 * w, outline); + m_dotplotScene->addLine(x + 4 * w, y + 3 * w, x + 5 * w, y + 3 * w, outline); + m_dotplotScene->addLine(x + 5 * w, y + 3 * w, x + 5 * w, y + 5 * w, outline); + m_dotplotScene->addLine(x + 5 * w, y + 5 * w, x + 4 * w, y + 5 * w, outline); + m_dotplotScene->addLine(x + 4 * w, y + 5 * w, x + 4 * w, y + 4 * w, outline); + m_dotplotScene->addLine(x + 4 * w, y + 4 * w, x + 3 * w, y + 4 * w, outline); + m_dotplotScene->addLine(x + 3 * w, y + 4 * w, x + 3 * w, y + 5 * w, outline); + m_dotplotScene->addLine(x + 3 * w, y + 5 * w, x + 2 * w, y + 5 * w, outline); + m_dotplotScene->addLine(x + 2 * w, y + 5 * w, x + 2 * w, y + 4 * w, outline); + m_dotplotScene->addLine(x + 2 * w, y + 4 * w, x + 1 * w, y + 4 * w, outline); + m_dotplotScene->addLine(x + 1 * w, y + 4 * w, x + 1 * w, y + 5 * w, outline); + m_dotplotScene->addLine(x + 1 * w, y + 5 * w, x + 0 * w, y + 5 * w, outline); + m_dotplotScene->addLine(x + 0 * w, y + 5 * w, x + 0 * w, y + 3 * w, outline); + m_dotplotScene->addLine(x + 0 * w, y + 3 * w, x + 1 * w, y + 3 * w, outline); + m_dotplotScene->addLine(x + 1 * w, y + 3 * w, x + 1 * w, y + 2 * w, outline); + m_dotplotScene->addLine(x + 1 * w, y + 2 * w, x + 0 * w, y + 2 * w, outline); + m_dotplotScene->addLine(x + 0 * w, y + 2 * w, x + 0 * w, y + 1 * w, outline); + m_dotplotScene->addLine(x + 0 * w, y + 1 * w, x + 1 * w, y + 1 * w, outline); + m_dotplotScene->addLine(x + 1 * w, y + 1 * w, x + 1 * w, y + 0 * w, outline); + m_dotplotScene->addLine(x + 2 * w, y + 2 * w, x + 3 * w, y + 2 * w, outline); + m_dotplotScene->addLine(x + 3 * w, y + 2 * w, x + 3 * w, y + 3 * w, outline); + m_dotplotScene->addLine(x + 3 * w, y + 3 * w, x + 2 * w, y + 3 * w, outline); + m_dotplotScene->addLine(x + 2 * w, y + 3 * w, x + 2 * w, y + 2 * w, outline); + +} void MainWindow::drawGraph() { @@ -742,8 +796,10 @@ void MainWindow::drawGraph() void MainWindow::drawDotplot() { + // Fetch the selected nodes. std::vector selectedNodes = m_scene->getSelectedNodes(); + // Limit the number of nodes that need to be selected, and notify the user. if (selectedNodes.size() < 1 || selectedNodes.size() > 2) { QString infoTitle = "Draw dotplot"; QString infoMessage = "Select either one (for self-dotplot) or two nodes to dotplot."; @@ -751,8 +807,9 @@ void MainWindow::drawDotplot() return; } - std::vector seqs; + // Placeholder for the sequences which will be dotplotted. std::vector headers; + std::vector seqs; // Enable self-dotplots. std::vector nodes_to_process = selectedNodes; @@ -760,6 +817,7 @@ void MainWindow::drawDotplot() nodes_to_process.push_back(selectedNodes[0]); } + // Get the sequences and the headers of the nodes to draw. for (size_t i=0; ikmerSizeInput->text().toLocal8Bit().constData())); iss >> k; - if (k > 30) { + // Sanity check for the k-mer size. + if (k <= 0 || k > 30) { QString infoTitle = "Draw dotplot"; - QString infoMessage = "Error: k-mer size should not exceed 30."; + QString infoMessage = "Error: k-mer size should be > 0 and <= 30."; QMessageBox::information(this, infoTitle, infoMessage); return; } @@ -794,16 +853,17 @@ void MainWindow::drawDotplot() // Calculate the dotplot. auto hits = findKmerMatches(seqs[0], seqs[1], k); - // The rest of the method is just plotting. + // Prepare the scene and plot. m_dotplotScene = std::shared_ptr(new QGraphicsScene()); ui->graphicsView->setScene(m_dotplotScene.get()); // Calculate the starts and ends of the dotplot coordinate system. int32_t max_len = std::max(seqs[0].size(), seqs[1].size()); double begin_offset = 40; + double end_offset = 10; double x_begin = begin_offset; double y_begin = begin_offset; - double max_size = (float) std::min(ui->graphicsView->maximumWidth(), ui->graphicsView->maximumHeight()) - 10 - begin_offset; + double max_size = (float) std::min(ui->graphicsView->maximumWidth(), ui->graphicsView->maximumHeight()) - end_offset - begin_offset; double scale = max_size / ((double) max_len); double x_end = x_begin + seqs[0].size() * scale; double y_end = y_begin + seqs[1].size() * scale; @@ -820,6 +880,11 @@ void MainWindow::drawDotplot() m_dotplotScene->addLine(x_begin - overhang, y_end, x_end, y_end); m_dotplotScene->addLine(x_begin, y_begin - overhang, x_begin, y_end); + double logo_w = 2; + double logo_x = x_begin - 0 - 6 * logo_w; + double logo_y = x_begin - 0 - 6 * logo_w; + drawDotplotPoweredByLogo(logo_x, logo_y, logo_w); + // Annotate the graph. QFont font; font.setPixelSize(8); diff --git a/ui/mainwindow.h b/ui/mainwindow.h index 3baf9b6a..2a030491 100644 --- a/ui/mainwindow.h +++ b/ui/mainwindow.h @@ -160,6 +160,7 @@ private slots: void changeNodeName(); void changeNodeDepth(); void openGraphInfoDialog(); + void drawDotplotPoweredByLogo(double x, double y, double w); void drawDotplot(); protected: From ccad4b833e53a346403dd41896319da7bb56adf2 Mon Sep 17 00:00:00 2001 From: Ryan Wick Date: Fri, 20 Oct 2017 16:04:56 +1100 Subject: [PATCH 19/26] Adjust UI layout of dotplot --- ui/mainwindow.cpp | 12 ++-- ui/mainwindow.ui | 151 +++++++++++++++++++++++++++++++--------------- 2 files changed, 110 insertions(+), 53 deletions(-) diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 70f358de..5f49b680 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -108,6 +108,8 @@ MainWindow::MainWindow(QString fileToLoadOnStartup, bool drawGraphAfterLoad) : m_graphicsViewZoom = new GraphicsViewZoom(g_graphicsView); g_graphicsView->m_zoom = m_graphicsViewZoom; + ui->dotplotGraphicsView->setRenderHint(QPainter::Antialiasing, true); + m_scene = new MyGraphicsScene(this); g_graphicsView->setScene(m_scene); @@ -856,7 +858,7 @@ void MainWindow::drawDotplot() // Prepare the scene and plot. m_dotplotScene = std::shared_ptr(new QGraphicsScene()); - ui->graphicsView->setScene(m_dotplotScene.get()); + ui->dotplotGraphicsView->setScene(m_dotplotScene.get()); // Calculate the starts and ends of the dotplot coordinate system. int32_t max_len = std::max(seqs[0].size(), seqs[1].size()); @@ -864,14 +866,14 @@ void MainWindow::drawDotplot() double end_offset = 10; double x_begin = begin_offset; double y_begin = begin_offset; - double max_size = (float) std::min(ui->graphicsView->maximumWidth(), ui->graphicsView->maximumHeight()) - end_offset - begin_offset; + double max_size = (float) std::min(ui->dotplotGraphicsView->maximumWidth(), ui->dotplotGraphicsView->maximumHeight()) - end_offset - begin_offset; double scale = max_size / ((double) max_len); double x_end = x_begin + seqs[0].size() * scale; double y_end = y_begin + seqs[1].size() * scale; // Make the scene not move. - ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui->dotplotGraphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui->dotplotGraphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_dotplotScene->setSceneRect(0, 0, 300, 300); // Add bounds to the dotplot graph. @@ -937,7 +939,7 @@ void MainWindow::drawDotplot() m_dotplotScene->addEllipse(hit.x * scale + x_begin, hit.y * scale + y_begin, 2.0 * scale, 2.0 * scale); } - ui->graphicsView->show(); + ui->dotplotGraphicsView->show(); } diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 10e84a81..ee4e7051 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -1354,9 +1354,9 @@ 0 - -294 - 324 - 1158 + 0 + 326 + 1201 @@ -1512,67 +1512,122 @@ - + + + + 0 + 0 + + + + + 75 + true + + - Draw dotplot + Dotplot - - - - 0 - 25 - + + + Qt::Horizontal - - - - 10 - 1 - 71 - 16 - - - - k-mer size: + + + + + + + 0 - - - - - 90 - 0 - 131 - 21 - + + 0 - - 15 + + 0 - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + 0 - + + + + k-mer size: + + + + + + + 15 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + - - - - 300 - 300 - - - - - 300 - 300 - + + + Draw dotplot + + + + 0 + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 300 + 300 + + + + + 300 + 300 + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + From cd243f928392403ce6bae19ef7d97bd39573b50a Mon Sep 17 00:00:00 2001 From: Ryan Wick Date: Fri, 20 Oct 2017 16:45:23 +1100 Subject: [PATCH 20/26] Tweaks to dotplot --- ui/mainwindow.cpp | 15 +++++++++++---- ui/mainwindow.ui | 3 +++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 5f49b680..0ade5c81 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -108,8 +108,6 @@ MainWindow::MainWindow(QString fileToLoadOnStartup, bool drawGraphAfterLoad) : m_graphicsViewZoom = new GraphicsViewZoom(g_graphicsView); g_graphicsView->m_zoom = m_graphicsViewZoom; - ui->dotplotGraphicsView->setRenderHint(QPainter::Antialiasing, true); - m_scene = new MyGraphicsScene(this); g_graphicsView->setScene(m_scene); @@ -874,7 +872,7 @@ void MainWindow::drawDotplot() // Make the scene not move. ui->dotplotGraphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui->dotplotGraphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - m_dotplotScene->setSceneRect(0, 0, 300, 300); + m_dotplotScene->setSceneRect(0, 0, 290, 290); // Add bounds to the dotplot graph. double overhang = 5; @@ -935,10 +933,19 @@ void MainWindow::drawDotplot() } // Generate the actual dotplot. + QPen pen(Qt::black); + QBrush brush(Qt::black); for (auto& hit: hits) { - m_dotplotScene->addEllipse(hit.x * scale + x_begin, hit.y * scale + y_begin, 2.0 * scale, 2.0 * scale); + m_dotplotScene->addEllipse(hit.x * scale + x_begin, hit.y * scale + y_begin, 2.0 * scale, 2.0 * scale, + pen, brush); } + // Scale the dotplot so it fits in the view with just a bit of margin. + QRectF sceneRectangle = m_dotplotScene->sceneRect(); + sceneRectangle.setWidth(sceneRectangle.width() * 1.05); + sceneRectangle.setHeight(sceneRectangle.height() * 1.05); + ui->dotplotGraphicsView->fitInView(sceneRectangle); + ui->dotplotGraphicsView->show(); } diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index ee4e7051..42105279 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -1611,6 +1611,9 @@ 300 + + QPainter::Antialiasing|QPainter::TextAntialiasing + From e0aec113a8fb1c9dd3a89b18f9a9e1063db2b72b Mon Sep 17 00:00:00 2001 From: Ryan Wick Date: Fri, 20 Oct 2017 17:08:06 +1100 Subject: [PATCH 21/26] Use a spinbox for k-mer size input --- ui/mainwindow.cpp | 5 +---- ui/mainwindow.ui | 41 +++++++++++++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 0ade5c81..376276c7 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -838,10 +838,7 @@ void MainWindow::drawDotplot() headers.push_back(header); } - // Parse the k-mer size. - int32_t k = 15; - std::stringstream iss(std::string(ui->kmerSizeInput->text().toLocal8Bit().constData())); - iss >> k; + int32_t k = ui->kmerSizeInput->value(); // Sanity check for the k-mer size. if (k <= 0 || k > 30) { diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 42105279..0f33f8ab 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -1356,7 +1356,7 @@ 0 0 326 - 1201 + 1204 @@ -1552,6 +1552,19 @@ 0 + + + + Qt::Horizontal + + + + 40 + 20 + + + + @@ -1560,15 +1573,31 @@ - - - 15 + + + 1 - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + 30 + + + 15 + + + + Qt::Horizontal + + + + 0 + 20 + + + + From 76d98ba3814852e968b927f417601e28418fd5b2 Mon Sep 17 00:00:00 2001 From: Ryan Wick Date: Tue, 24 Oct 2017 12:43:47 +1100 Subject: [PATCH 22/26] Fix issues with tab order --- ui/mainwindow.ui | 116 ++++++++++++++++- ui/settingsdialog.ui | 301 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 411 insertions(+), 6 deletions(-) diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 0f33f8ab..f1e2b6b6 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -58,7 +58,7 @@ 0 - 0 + -194 289 1058 @@ -195,6 +195,9 @@ + + Qt::StrongFocus + More info @@ -349,6 +352,9 @@ 0 + + Qt::StrongFocus + Exact @@ -365,6 +371,9 @@ 0 + + Qt::StrongFocus + Partial @@ -434,6 +443,9 @@ 0 + + Qt::StrongFocus + Single @@ -450,6 +462,9 @@ 0 + + Qt::StrongFocus + Double @@ -460,6 +475,9 @@ + + Qt::StrongFocus + Draw graph @@ -489,6 +507,9 @@ 0 + + Qt::StrongFocus + Qt::AlignCenter @@ -524,6 +545,9 @@ 0 + + Qt::StrongFocus + @@ -534,6 +558,9 @@ 0 + + Qt::StrongFocus + Entire graph @@ -597,6 +624,9 @@ + + Qt::StrongFocus + Qt::AlignCenter @@ -684,6 +714,9 @@ + + Qt::StrongFocus + Qt::AlignCenter @@ -823,6 +856,9 @@ 0 + + Qt::StrongFocus + Qt::AlignCenter @@ -848,6 +884,9 @@ + + Qt::StrongFocus + Determine contiguity @@ -855,6 +894,9 @@ + + Qt::StrongFocus + Random colours @@ -936,6 +978,9 @@ 0 + + Qt::StrongFocus + Qt::AlignCenter @@ -1065,6 +1110,9 @@ + + Qt::StrongFocus + Font @@ -1072,6 +1120,9 @@ + + Qt::StrongFocus + Text outline @@ -1097,6 +1148,9 @@ + + Qt::StrongFocus + Custom @@ -1104,6 +1158,9 @@ + + Qt::StrongFocus + Name @@ -1111,6 +1168,9 @@ + + Qt::StrongFocus + Length @@ -1118,6 +1178,9 @@ + + Qt::StrongFocus + Depth @@ -1125,6 +1188,9 @@ + + Qt::StrongFocus + BLAST hits @@ -1135,10 +1201,16 @@ false + + Qt::StrongFocus + + + Qt::StrongFocus + CSV data: @@ -1272,6 +1344,9 @@ 0 + + Qt::StrongFocus + none @@ -1281,6 +1356,9 @@ + + Qt::StrongFocus + Create/view BLAST search @@ -1431,6 +1509,9 @@ + + Qt::StrongFocus + Exact @@ -1483,6 +1564,9 @@ + + Qt::StrongFocus + Partial @@ -1499,6 +1583,9 @@ 0 + + Qt::StrongFocus + @@ -1506,6 +1593,9 @@ + + Qt::StrongFocus + Find node(s) @@ -1574,6 +1664,9 @@ + + Qt::StrongFocus + 1 @@ -1603,6 +1696,9 @@ + + Qt::StrongFocus + Draw dotplot @@ -1789,6 +1885,9 @@ + + Qt::StrongFocus + Ctrl+L @@ -1799,6 +1898,9 @@ + + Qt::StrongFocus + Ctrl+O @@ -2367,11 +2469,14 @@ controlsScrollArea + moreInfoButton graphScopeComboBox startingNodesLineEdit startingNodesExactMatchRadioButton startingNodesPartialMatchRadioButton nodeDistanceSpinBox + minDepthSpinBox + maxDepthSpinBox singleNodesRadioButton doubleNodesRadioButton drawGraphButton @@ -2384,6 +2489,9 @@ nodeLengthsCheckBox nodeDepthCheckBox blastHitsCheckBox + setNodeCustomLabelButton + csvCheckBox + csvComboBox fontButton textOutlineCheckBox blastSearchButton @@ -2393,10 +2501,12 @@ selectionSearchNodesExactMatchRadioButton selectionSearchNodesPartialMatchRadioButton selectNodesButton + kmerSizeInput + drawDotplotButton + dotplotGraphicsView selectedNodesTextEdit - setNodeCustomColourButton - setNodeCustomLabelButton selectedEdgesTextEdit + setNodeCustomColourButton diff --git a/ui/settingsdialog.ui b/ui/settingsdialog.ui index 2c27a310..b3b6f2c4 100644 --- a/ui/settingsdialog.ui +++ b/ui/settingsdialog.ui @@ -31,6 +31,9 @@ + + Qt::NoFocus + true @@ -38,8 +41,8 @@ 0 - -62 - 400 + -2839 + 402 3466 @@ -155,6 +158,9 @@ per megabase: + + Qt::StrongFocus + Auto @@ -165,6 +171,9 @@ per megabase: + + Qt::StrongFocus + Manual @@ -188,6 +197,9 @@ per megabase: 0 + + Qt::StrongFocus + Qt::AlignCenter @@ -253,6 +265,9 @@ per megabase: 0 + + Qt::StrongFocus + Qt::AlignCenter @@ -304,6 +319,9 @@ per megabase: 0 + + Qt::StrongFocus + Qt::AlignCenter @@ -329,6 +347,9 @@ per megabase: 0 + + Qt::StrongFocus + Qt::AlignCenter @@ -473,6 +494,9 @@ per megabase: 0 + + Qt::StrongFocus + Qt::AlignCenter @@ -593,6 +617,9 @@ per megabase: + + Qt::StrongFocus + On @@ -603,6 +630,9 @@ per megabase: + + Qt::StrongFocus + Off @@ -648,6 +678,9 @@ per megabase: 0 + + Qt::StrongFocus + Qt::AlignCenter @@ -718,6 +751,9 @@ per megabase: 0 + + Qt::StrongFocus + 4 @@ -785,6 +821,9 @@ per megabase: 0 + + Qt::StrongFocus + Qt::AlignCenter @@ -935,6 +974,9 @@ per megabase: 0 + + Qt::StrongFocus + @@ -1009,6 +1051,9 @@ per megabase: 0 + + Qt::StrongFocus + @@ -1067,6 +1112,9 @@ per megabase: 0 + + Qt::StrongFocus + @@ -1080,6 +1128,9 @@ per megabase: 0 + + Qt::StrongFocus + Qt::AlignCenter @@ -1114,6 +1165,9 @@ per megabase: + + Qt::StrongFocus + On @@ -1124,6 +1178,9 @@ per megabase: + + Qt::StrongFocus + Off @@ -1158,6 +1215,9 @@ per megabase: + + Qt::StrongFocus + On @@ -1168,6 +1228,9 @@ per megabase: + + Qt::StrongFocus + Off @@ -1397,6 +1460,9 @@ single node style: 0 + + Qt::StrongFocus + @@ -1426,6 +1492,9 @@ single node style: 0 + + Qt::StrongFocus + Qt::AlignCenter @@ -1451,6 +1520,9 @@ single node style: 0 + + Qt::StrongFocus + @@ -1555,6 +1627,9 @@ single node style: + + Qt::StrongFocus + Over visible regions @@ -1565,6 +1640,9 @@ single node style: + + Qt::StrongFocus + On node centre @@ -1709,6 +1787,9 @@ single node style: 0 + + Qt::StrongFocus + Qt::AlignCenter @@ -1737,6 +1818,9 @@ single node style: 0 + + Qt::StrongFocus + Qt::AlignCenter @@ -2076,6 +2160,9 @@ single node style: 0 + + Qt::StrongFocus + 255 @@ -2092,6 +2179,9 @@ single node style: 0 + + Qt::StrongFocus + 255 @@ -2163,6 +2253,9 @@ single node style: 0 + + Qt::StrongFocus + 255 @@ -2173,6 +2266,9 @@ single node style: + + Qt::StrongFocus + Qt::AlignCenter @@ -2202,6 +2298,9 @@ single node style: 0 + + Qt::StrongFocus + 255 @@ -2257,6 +2356,9 @@ single node style: + + Qt::StrongFocus + Qt::AlignCenter @@ -2267,6 +2369,9 @@ single node style: + + Qt::StrongFocus + Qt::AlignCenter @@ -2299,6 +2404,9 @@ single node style: 0 + + Qt::StrongFocus + 255 @@ -2309,6 +2417,9 @@ single node style: + + Qt::StrongFocus + Qt::AlignCenter @@ -2319,6 +2430,9 @@ single node style: + + Qt::StrongFocus + Qt::AlignCenter @@ -2364,6 +2478,9 @@ single node style: 0 + + Qt::StrongFocus + 255 @@ -2374,6 +2491,9 @@ single node style: + + Qt::StrongFocus + Qt::AlignCenter @@ -2483,6 +2603,9 @@ single node style: + + Qt::StrongFocus + @@ -2519,6 +2642,9 @@ single node style: + + Qt::StrongFocus + @@ -2548,6 +2674,9 @@ single node style: 0 + + Qt::StrongFocus + @@ -2739,6 +2868,9 @@ single node style: 0 + + Qt::StrongFocus + @@ -2791,6 +2923,9 @@ single node style: + + Qt::StrongFocus + @@ -2944,6 +3079,9 @@ single node style: + + Qt::StrongFocus + Qt::AlignCenter @@ -2957,6 +3095,9 @@ single node style: + + Qt::StrongFocus + Qt::AlignCenter @@ -2987,6 +3128,9 @@ single node style: + + Qt::StrongFocus + Auto @@ -2997,6 +3141,9 @@ single node style: + + Qt::StrongFocus + Manual @@ -3144,6 +3291,9 @@ single node style: 0 + + Qt::StrongFocus + @@ -3333,6 +3483,9 @@ single node style: 0 + + Qt::StrongFocus + @@ -3401,6 +3554,9 @@ single node style: + + Qt::StrongFocus + @@ -3447,6 +3603,9 @@ single node style: + + Qt::StrongFocus + @@ -3467,6 +3626,9 @@ single node style: + + Qt::StrongFocus + @@ -3487,6 +3649,9 @@ single node style: + + Qt::StrongFocus + @@ -3494,6 +3659,9 @@ single node style: + + Qt::StrongFocus + Qt::AlignCenter @@ -3590,6 +3758,9 @@ single node style: + + Qt::StrongFocus + Qt::AlignCenter @@ -3622,6 +3793,9 @@ single node style: + + Qt::StrongFocus + Qt::AlignCenter @@ -3641,6 +3815,9 @@ single node style: + + Qt::StrongFocus + Qt::AlignCenter @@ -3654,6 +3831,9 @@ single node style: + + Qt::StrongFocus + Min mean hit identity: @@ -3661,6 +3841,9 @@ single node style: + + Qt::StrongFocus + Min query path hit coverage: @@ -3668,6 +3851,9 @@ single node style: + + Qt::StrongFocus + Maximum length discrepancy (bases): @@ -3676,6 +3862,9 @@ discrepancy (bases): + + Qt::StrongFocus + Max e-value product: @@ -3683,6 +3872,9 @@ discrepancy (bases): + + Qt::StrongFocus + Maximum path length: @@ -3697,6 +3889,9 @@ discrepancy (bases): + + Qt::StrongFocus + Minimum path length: @@ -3704,6 +3899,9 @@ discrepancy (bases): + + Qt::StrongFocus + Minimum length discrepancy (bases): @@ -3712,6 +3910,9 @@ discrepancy (bases): + + Qt::StrongFocus + Qt::AlignCenter @@ -3725,6 +3926,9 @@ discrepancy (bases): + + Qt::StrongFocus + Qt::AlignCenter @@ -3747,6 +3951,9 @@ discrepancy (bases): + + Qt::StrongFocus + Qt::AlignCenter @@ -3760,6 +3967,9 @@ discrepancy (bases): + + Qt::StrongFocus + Qt::AlignCenter @@ -3936,6 +4146,9 @@ discrepancy (bases): + + Qt::StrongFocus + Qt::AlignCenter @@ -3971,6 +4184,9 @@ discrepancy (bases): + + Qt::StrongFocus + 1 @@ -3981,6 +4197,9 @@ discrepancy (bases): + + Qt::StrongFocus + Qt::AlignCenter @@ -4010,6 +4229,9 @@ discrepancy (bases): + + Qt::StrongFocus + Qt::AlignCenter @@ -4072,6 +4294,9 @@ discrepancy (bases): + + Qt::StrongFocus + Restore defaults @@ -4079,6 +4304,9 @@ discrepancy (bases): + + Qt::NoFocus + Qt::Horizontal @@ -4123,12 +4351,79 @@ discrepancy (bases): - scrollArea + nodeLengthPerMegabaseAutoRadioButton + nodeLengthPerMegabaseManualRadioButton + nodeLengthPerMegabaseManualSpinBox + minimumNodeLengthSpinBox + edgeLengthSpinBox + edgeWidthSpinBox + doubleModeNodeSeparationSpinBox + nodeSegmentLengthSpinBox + graphLayoutQualitySlider + linearLayoutOnRadioButton + linearLayoutOffRadioButton + componentSeparationSpinBox + edgeColourButton + outlineColourButton + outlineThicknessSpinBox + selectionColourButton antialiasingOnRadioButton antialiasingOffRadioButton + singleNodeArrowHeadsOnRadioButton + singleNodeArrowHeadsOffRadioButton + textColourButton + textOutlineThicknessSpinBox + textOutlineColourButton positionVisibleRadioButton positionCentreRadioButton + depthEffectOnWidthSpinBox + depthPowerSpinBox + randomColourPositiveSaturationSlider + randomColourPositiveSaturationSpinBox + randomColourNegativeSaturationSlider + randomColourNegativeSaturationSpinBox + randomColourPositiveLightnessSlider + randomColourPositiveLightnessSpinBox + randomColourNegativeLightnessSlider + randomColourNegativeLightnessSpinBox + randomColourPositiveOpacitySlider + randomColourPositiveOpacitySpinBox + randomColourNegativeOpacitySlider + randomColourNegativeOpacitySpinBox + uniformPositiveNodeColourButton + uniformNegativeNodeColourButton + uniformNodeSpecialColourButton + lowDepthColourButton + highDepthColourButton depthValueAutoRadioButton + depthValueManualRadioButton + lowDepthValueSpinBox + highDepthValueSpinBox + noBlastHitsColourButton + contiguitySearchDepthSpinBox + contiguousStrandSpecificColourButton + contiguousEitherStrandColourButton + maybeContiguousColourButton + notContiguousColourButton + contiguityStartingColourButton + maxHitsForQueryPathSpinBox + maxPathNodesSpinBox + minQueryCoveredByPathSpinBox + minQueryCoveredByHitsCheckBox + minQueryCoveredByHitsSpinBox + minMeanHitIdentityCheckBox + minMeanHitIdentitySpinBox + maxEValueProductCheckBox + maxEValueCoefficientSpinBox + maxEValueExponentSpinBox + minLengthPercentageCheckBox + minLengthPercentageSpinBox + maxLengthPercentageCheckBox + maxLengthPercentageSpinBox + minLengthBaseDiscrepancyCheckBox + minLengthBaseDiscrepancySpinBox + maxLengthBaseDiscrepancyCheckBox + maxLengthBaseDiscrepancySpinBox restoreDefaultsButton From 542d69e52bbf6df52bd3d7157b69d8db963491c9 Mon Sep 17 00:00:00 2001 From: Ivan Sovic Date: Tue, 31 Oct 2017 12:14:43 +0100 Subject: [PATCH 23/26] Added a failing test case for the dotplot. --- tests/bandagetests.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/bandagetests.cpp b/tests/bandagetests.cpp index 86f4dccd..92d05875 100644 --- a/tests/bandagetests.cpp +++ b/tests/bandagetests.cpp @@ -1434,7 +1434,6 @@ void BandageTests::blastQueryPaths() QCOMPARE(query7Paths.size(), 1); } - void BandageTests::bandageInfo() { int n50 = 0; @@ -1636,6 +1635,15 @@ void BandageTests::testFindHits() QCOMPARE(result, expected); } + { // Test multiple successive hits. + std::vector sorted_kmers_seq1 = { KmerPos(2, 0), KmerPos(3, 1), KmerPos(4, 2) }; + std::vector sorted_kmers_seq2 = { KmerPos(0, 0), KmerPos(1, 1), + KmerPos(3, 3), KmerPos(3, 7), + KmerPos(3, 8), KmerPos(5, 9) }; + std::vector result = findHits(sorted_kmers_seq1, sorted_kmers_seq2); + std::vector expected = { KmerHit(1, 3), KmerHit(1, 7), KmerHit(1, 8)}; + QCOMPARE(result, expected); + } } void BandageTests::testFindKmerMatches() From 534844833ff1d36c1c34c21ba6608c909f404027 Mon Sep 17 00:00:00 2001 From: Ivan Sovic Date: Tue, 31 Oct 2017 12:17:06 +0100 Subject: [PATCH 24/26] Fixed the bug in findHits (dotplot). Previously, only the first hit would be reported. --- program/dotplot.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/program/dotplot.cpp b/program/dotplot.cpp index 1f981622..44911b7e 100644 --- a/program/dotplot.cpp +++ b/program/dotplot.cpp @@ -147,9 +147,11 @@ std::vector findHits(const std::vector& sorted_kmers_seq1, con continue; } - hits.emplace_back(KmerHit(sorted_kmers_seq1[k1].pos, sorted_kmers_seq2[k2].pos)); + // Find n^2 exact hits. + for (int32_t i = k2; i < n_kmers2 && sorted_kmers_seq2[i].kmer == sorted_kmers_seq1[k1].kmer; i++) { + hits.emplace_back(KmerHit(sorted_kmers_seq1[k1].pos, sorted_kmers_seq2[i].pos)); + } k1 += 1; - k2 += 1; } return hits; From 366e935b54eeaa5af5a48cc7d5ddf1065cf7cb97 Mon Sep 17 00:00:00 2001 From: Ryan Wick Date: Fri, 21 Feb 2020 17:03:03 +1100 Subject: [PATCH 25/26] Add support for SKESA fasta files --- graph/assemblygraph.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/graph/assemblygraph.cpp b/graph/assemblygraph.cpp index 152f9d69..cd30158f 100644 --- a/graph/assemblygraph.cpp +++ b/graph/assemblygraph.cpp @@ -1418,6 +1418,16 @@ void AssemblyGraph::buildDeBruijnGraphFromPlainFasta(QString fullFileName) m_depthTag = "KC"; } + // Check to see if the name matches SKESA format, in which case we can get the depth and node number. + else if (thisNodeDetails.size() >= 3 && thisNodeDetails[0] == "Contig" && thisNodeDetails[1].toInt() > 0) { + name = thisNodeDetails[1]; + bool ok; + double convertedDepth = thisNodeDetails[2].toDouble(&ok); + if (ok) + depth = convertedDepth; + m_depthTag = "KC"; + } + // If it doesn't match, then we will use the sequence name up to the first space. else { QStringList nameParts = name.split(" "); @@ -1443,6 +1453,10 @@ void AssemblyGraph::buildDeBruijnGraphFromPlainFasta(QString fullFileName) if (lowerName.contains("circular=true")) circularNodeNames.push_back(name); + // SKESA circularity + if (thisNodeDetails.size() == 4 and thisNodeDetails[3] == "Circ") + circularNodeNames.push_back(name); + if (name.length() < 1) throw "load error"; From ccfa041b6cdcb2984485991ac119fc687c39b5f6 Mon Sep 17 00:00:00 2001 From: nmendozam Date: Wed, 11 Nov 2020 17:20:52 -0500 Subject: [PATCH 26/26] Compilation and installation instructions added for Fedora --- README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index db53b3c1..b0a0048b 100644 --- a/README.md +++ b/README.md @@ -37,16 +37,26 @@ The Linux binaries comes in two varieties: dynamically-linked and statically-lin If the compiled binaries do not work for you, the instructions below should help you build Bandage on most common OSs. If you are having difficulties building Bandage for your OS, feel free to contact me (Ryan) at rrwick@gmail.com and I'll do my best to help you out! -### Ubuntu +### Ubuntu & Fedora -The following instructions successfully build Bandage on a fresh installation of Ubuntu 14.04: +The following instructions successfully build Bandage on a fresh installation of Ubuntu 14.04 or fedora 33: 1. Ensure the package lists are up-to-date: `sudo apt-get update` -2. Install prerequisite packages: `sudo apt-get install build-essential git qtbase5-dev libqt5svg5-dev` +2. Install prerequisite packages: + + For Ubuntu: + ``` + sudo apt-get install build-essential git qtbase5-dev libqt5svg5-dev + ``` + For Fedora: + ``` + sudo dnf groupinstall "Development Tools" "Development Libraries" + sudo dnf install qt5-qtsvg-devel git qt5-qtbase-devel + ``` 3. Download the Bandage code from GitHub: `git clone https://github.com/rrwick/Bandage.git` 4. Open a terminal in the Bandage directory. 5. Set the environment variable to specify that you will be using Qt 5, not Qt 4: `export QT_SELECT=5` -6. Run qmake to generate a Makefile: `qmake` +6. Run qmake to generate a Makefile: `qmake` or in Fedora `qmake-qt5` 7. Build the program: `make` 8. `Bandage` should now be an executable file. 9. Optionally, copy the program into /usr/local/bin: `sudo make install`. The Bandage build directory can then be deleted.