From 53a791741d1918051ec9b7638490fe0c3f3994ec Mon Sep 17 00:00:00 2001 From: Marijon Pierre Date: Fri, 3 Feb 2017 15:00:31 +0100 Subject: [PATCH 01/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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 1eefc1a8df2d4ce01dfcd76faf7912798930b803 Mon Sep 17 00:00:00 2001 From: Anton Korobeynikov Date: Wed, 2 May 2018 21:17:43 +0300 Subject: [PATCH 25/31] Parse and store GFA paths --- graph/assemblygraph.cpp | 59 ++++++++++++++++++++++++++++++++++------- graph/assemblygraph.h | 3 +++ 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/graph/assemblygraph.cpp b/graph/assemblygraph.cpp index 152f9d69..1fd0e398 100644 --- a/graph/assemblygraph.cpp +++ b/graph/assemblygraph.cpp @@ -66,13 +66,26 @@ AssemblyGraph::~AssemblyGraph() void AssemblyGraph::cleanUp() { - QMapIterator i(m_deBruijnGraphNodes); - while (i.hasNext()) { - i.next(); - delete i.value(); + QMapIterator i(m_deBruijnGraphPaths); + while (i.hasNext()) + { + i.next(); + delete i.value(); + } + m_deBruijnGraphPaths.clear(); + } + + + { + QMapIterator i(m_deBruijnGraphNodes); + while (i.hasNext()) + { + i.next(); + delete i.value(); + } + m_deBruijnGraphNodes.clear(); } - m_deBruijnGraphNodes.clear(); QMapIterator, DeBruijnEdge*> j(m_deBruijnGraphEdges); while (j.hasNext()) @@ -396,6 +409,7 @@ void AssemblyGraph::determineGraphInfo() m_edgeCount = edgeCount; m_totalLength = totalLength; m_meanDepth = getMeanDepth(); + m_pathCount = m_deBruijnGraphPaths.size(); std::sort(nodeDepths.begin(), nodeDepths.end()); @@ -579,6 +593,7 @@ void AssemblyGraph::buildDeBruijnGraphFromGfa(QString fullFileName, bool *unsupp QMap colours; QMap labels; + QMap paths; QTextStream in(&inputFile); while (!in.atEnd()) { @@ -770,6 +785,14 @@ void AssemblyGraph::buildDeBruijnGraphFromGfa(QString fullFileName, bool *unsupp *unsupportedCigar = true; } } + + // Load paths + else if (lineParts.at(0) == "P") { + if (lineParts.size() < 4) + continue; + + paths.insert(lineParts.at(1), lineParts.at(2)); + } } //Pair up reverse complements, creating them if necessary. @@ -804,6 +827,22 @@ void AssemblyGraph::buildDeBruijnGraphFromGfa(QString fullFileName, bool *unsupp int overlap = edgeOverlaps[i]; createDeBruijnEdge(node1Name, node2Name, overlap, EXACT_OVERLAP); } + + // Create all the paths. + QMapIterator p(paths); + while (p.hasNext()) { + p.next(); + QString pathName = p.key(); + + QString pathStringFailure; + Path pp = Path::makeFromString(p.value(), false, &pathStringFailure); + + if (pp.isEmpty()) { + std::cout << pathStringFailure.toUtf8().constData() << std::endl; + } else { + m_deBruijnGraphPaths.insert(pathName, new Path(pp)); + } + } } if (m_deBruijnGraphNodes.size() == 0) @@ -3525,7 +3564,7 @@ void AssemblyGraph::getGraphComponentCountAndLargestComponentSize(int * componen QSet visitedNodes; QList< QList > connectedComponents; - + //Loop through all positive nodes. QMapIterator i(m_deBruijnGraphNodes); while (i.hasNext()) @@ -3534,12 +3573,12 @@ void AssemblyGraph::getGraphComponentCountAndLargestComponentSize(int * componen DeBruijnNode * v = i.value(); if (v->isNegativeNode()) continue; - + //If the node has not yet been visited, then it must be the start of a new connected component. if (!visitedNodes.contains(v)) { QList connectedComponent; - + QQueue q; q.enqueue(v); visitedNodes.insert(v); @@ -3562,9 +3601,9 @@ void AssemblyGraph::getGraphComponentCountAndLargestComponentSize(int * componen } connectedComponents.push_back(connectedComponent); - } + } } - + //Now that the list of connected components is built, we look for the //largest one (as measured by total node length). *componentCount = connectedComponents.size(); diff --git a/graph/assemblygraph.h b/graph/assemblygraph.h index 426e7532..e3524add 100644 --- a/graph/assemblygraph.h +++ b/graph/assemblygraph.h @@ -50,6 +50,8 @@ class AssemblyGraph : public QObject //pointers. QMap, DeBruijnEdge*> m_deBruijnGraphEdges; + QMap m_deBruijnGraphPaths; + ogdf::Graph * m_ogdfGraph; ogdf::EdgeArray * m_edgeArray; ogdf::GraphAttributes * m_graphAttributes; @@ -57,6 +59,7 @@ class AssemblyGraph : public QObject int m_kmer; int m_nodeCount; int m_edgeCount; + int m_pathCount; long long m_totalLength; long long m_shortestContig; long long m_longestContig; From ed8688106ea82ffcfbf870834481255069223f97 Mon Sep 17 00:00:00 2001 From: Anton Korobeynikov Date: Wed, 2 May 2018 21:18:00 +0300 Subject: [PATCH 26/31] Show loaded GFA paths --- ui/mainwindow.cpp | 2 ++ ui/mainwindow.ui | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 376276c7..ee38b95f 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -472,12 +472,14 @@ void MainWindow::displayGraphDetails() { ui->nodeCountLabel->setText(formatIntForDisplay(g_assemblyGraph->m_nodeCount)); ui->edgeCountLabel->setText(formatIntForDisplay(g_assemblyGraph->m_edgeCount)); + ui->pathCountLabel->setText(formatIntForDisplay(g_assemblyGraph->m_pathCount)); ui->totalLengthLabel->setText(formatIntForDisplay(g_assemblyGraph->m_totalLength)); } void MainWindow::clearGraphDetails() { ui->nodeCountLabel->setText("0"); ui->edgeCountLabel->setText("0"); + ui->pathCountLabel->setText("0"); ui->totalLengthLabel->setText("0"); } diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index f1e2b6b6..46abf741 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -121,6 +121,23 @@ + + + Paths: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0 + + + + Total length: @@ -130,7 +147,7 @@ - + 0 From 3f733e657cdc063807e9a074da554174e83aafae Mon Sep 17 00:00:00 2001 From: Anton Korobeynikov Date: Thu, 3 May 2018 01:12:43 +0300 Subject: [PATCH 27/31] Allow path selection via GUI --- command_line/image.cpp | 2 +- command_line/reduce.cpp | 2 +- graph/assemblygraph.cpp | 18 +++++- graph/assemblygraph.h | 3 +- program/globals.h | 2 +- ui/mainwindow.cpp | 67 ++++++++++++++++++-- ui/mainwindow.h | 2 + ui/mainwindow.ui | 132 +++++++++++++++++++++++++++------------- 8 files changed, 178 insertions(+), 50 deletions(-) diff --git a/command_line/image.cpp b/command_line/image.cpp index a8a8505f..b0bb15c6 100644 --- a/command_line/image.cpp +++ b/command_line/image.cpp @@ -128,7 +128,7 @@ int bandageImage(QStringList arguments) std::vector startingNodes = g_assemblyGraph->getStartingNodes(&errorTitle, &errorMessage, g_settings->doubleMode, g_settings->startingNodes, - "all"); + "all", ""); QString errormsg; QStringList columns; diff --git a/command_line/reduce.cpp b/command_line/reduce.cpp index 5b1de153..1b2ac6ed 100644 --- a/command_line/reduce.cpp +++ b/command_line/reduce.cpp @@ -105,7 +105,7 @@ int bandageReduce(QStringList arguments) std::vector startingNodes = g_assemblyGraph->getStartingNodes(&errorTitle, &errorMessage, g_settings->doubleMode, g_settings->startingNodes, - "all"); + "all", ""); if (errorMessage != "") { diff --git a/graph/assemblygraph.cpp b/graph/assemblygraph.cpp index 1fd0e398..fd3eebe2 100644 --- a/graph/assemblygraph.cpp +++ b/graph/assemblygraph.cpp @@ -2027,7 +2027,7 @@ void AssemblyGraph::addGraphicsItemsToScene(MyGraphicsScene * scene) std::vector AssemblyGraph::getStartingNodes(QString * errorTitle, QString * errorMessage, bool doubleMode, - QString nodesList, QString blastQueryName) + QString nodesList, QString blastQueryName, QString pathName) { std::vector startingNodes; @@ -2087,6 +2087,16 @@ std::vector AssemblyGraph::getStartingNodes(QString * errorTitle } } + else if (g_settings->graphScope == AROUND_PATHS) + { + if (m_deBruijnGraphPaths.count(pathName) == 0) + { + *errorTitle = "Invalid path"; + *errorMessage = "No path with such name is loaded"; + return startingNodes; + } + } + g_settings->doubleMode = doubleMode; clearOgdfGraphAndResetNodes(); @@ -2097,6 +2107,12 @@ std::vector AssemblyGraph::getStartingNodes(QString * errorTitle else if (g_settings->graphScope == DEPTH_RANGE) startingNodes = getNodesInDepthRange(g_settings->minDepthRange, g_settings->maxDepthRange); + else if (g_settings->graphScope == AROUND_PATHS) { + QList nodes = m_deBruijnGraphPaths[pathName]->getNodes(); + + for (QList::iterator i = nodes.begin(); i != nodes.end(); ++i) + startingNodes.push_back(*i); + } return startingNodes; } diff --git a/graph/assemblygraph.h b/graph/assemblygraph.h index e3524add..fe598c0e 100644 --- a/graph/assemblygraph.h +++ b/graph/assemblygraph.h @@ -118,7 +118,8 @@ class AssemblyGraph : public QObject QString * errorMessage, bool doubleMode, QString nodesList, - QString blastQueryName); + QString blastQueryName, + QString pathName); bool checkIfStringHasNodes(QString nodesString); QString generateNodesNotFoundErrorMessage(std::vector nodesNotInGraph, diff --git a/program/globals.h b/program/globals.h index 10b1bbbd..45f70025 100644 --- a/program/globals.h +++ b/program/globals.h @@ -34,7 +34,7 @@ class AssemblyGraph; enum NodeColourScheme {UNIFORM_COLOURS, RANDOM_COLOURS, DEPTH_COLOUR, BLAST_HITS_RAINBOW_COLOUR, BLAST_HITS_SOLID_COLOUR, CONTIGUITY_COLOUR, CUSTOM_COLOURS}; -enum GraphScope {WHOLE_GRAPH, AROUND_NODE, AROUND_BLAST_HITS, DEPTH_RANGE}; +enum GraphScope {WHOLE_GRAPH, AROUND_NODE, AROUND_PATHS, AROUND_BLAST_HITS, DEPTH_RANGE}; enum ContiguityStatus {STARTING, CONTIGUOUS_STRAND_SPECIFIC, CONTIGUOUS_EITHER_STRAND, MAYBE_CONTIGUOUS, NOT_CONTIGUOUS}; diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index ee38b95f..88be4329 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -609,6 +609,7 @@ void MainWindow::graphScopeChanged() setStartingNodesWidgetVisibility(false); setNodeDistanceWidgetVisibility(false); setDepthRangeWidgetVisibility(false); + setPathSelectionWidgetVisibility(false); ui->graphDrawingGridLayout->addWidget(ui->nodeStyleInfoText, 1, 0, 1, 1); ui->graphDrawingGridLayout->addWidget(ui->nodeStyleLabel, 1, 1, 1, 1); @@ -624,6 +625,7 @@ void MainWindow::graphScopeChanged() setStartingNodesWidgetVisibility(true); setNodeDistanceWidgetVisibility(true); setDepthRangeWidgetVisibility(false); + setPathSelectionWidgetVisibility(false); ui->nodeDistanceInfoText->setInfoText("Nodes will be drawn if they are specified in the above list or are " "within this many steps of those nodes.

" @@ -649,11 +651,41 @@ void MainWindow::graphScopeChanged() break; case 2: + g_settings->graphScope = AROUND_PATHS; + + setupPathSelectionComboBox(); + setStartingNodesWidgetVisibility(false); + setNodeDistanceWidgetVisibility(true); + setDepthRangeWidgetVisibility(false); + setPathSelectionWidgetVisibility(true); + + ui->nodeDistanceInfoText->setInfoText("Nodes will be drawn if they are specified in the above list or are " + "within this many steps of those nodes.

" + "A value of 0 will result in only the specified nodes being drawn. " + "A large value will result in large sections of the graph around " + "the specified nodes being drawn."); + + ui->graphDrawingGridLayout->addWidget(ui->pathSelectionInfoText, 1, 0, 1, 1); + ui->graphDrawingGridLayout->addWidget(ui->pathSelectionLabel, 1, 1, 1, 1); + ui->graphDrawingGridLayout->addWidget(ui->pathSelectionComboBox, 1, 2, 1, 1); + ui->graphDrawingGridLayout->addWidget(ui->nodeDistanceInfoText, 2, 0, 1, 1); + ui->graphDrawingGridLayout->addWidget(ui->nodeDistanceLabel, 2, 1, 1, 1); + ui->graphDrawingGridLayout->addWidget(ui->nodeDistanceSpinBox, 2, 2, 1, 1); + ui->graphDrawingGridLayout->addWidget(ui->nodeStyleInfoText, 3, 0, 1, 1); + ui->graphDrawingGridLayout->addWidget(ui->nodeStyleLabel, 3, 1, 1, 1); + ui->graphDrawingGridLayout->addWidget(ui->nodeStyleWidget, 3, 2, 1, 1); + ui->graphDrawingGridLayout->addWidget(ui->drawGraphInfoText, 4, 0, 1, 1); + ui->graphDrawingGridLayout->addWidget(ui->drawGraphButton, 4, 1, 1, 2); + + break; + + case 3: g_settings->graphScope = AROUND_BLAST_HITS; setStartingNodesWidgetVisibility(false); setNodeDistanceWidgetVisibility(true); setDepthRangeWidgetVisibility(false); + setPathSelectionWidgetVisibility(false); ui->nodeDistanceInfoText->setInfoText("Nodes will be drawn if they contain a BLAST hit or are within this " "many steps of nodes with a BLAST hit.

" @@ -672,12 +704,13 @@ void MainWindow::graphScopeChanged() break; - case 3: + case 4: g_settings->graphScope = DEPTH_RANGE; setStartingNodesWidgetVisibility(false); setNodeDistanceWidgetVisibility(false); setDepthRangeWidgetVisibility(true); + setPathSelectionWidgetVisibility(false); ui->graphDrawingGridLayout->addWidget(ui->minDepthInfoText, 1, 0, 1, 1); ui->graphDrawingGridLayout->addWidget(ui->minDepthLabel, 1, 1, 1, 1); @@ -721,6 +754,30 @@ void MainWindow::setDepthRangeWidgetVisibility(bool visible) ui->maxDepthLabel->setVisible(visible); ui->maxDepthSpinBox->setVisible(visible); } +void MainWindow::setPathSelectionWidgetVisibility(bool visible) +{ + ui->pathSelectionInfoText->setVisible(visible); + ui->pathSelectionLabel->setVisible(visible); + ui->pathSelectionComboBox->setVisible(visible); +} + +void MainWindow::setupPathSelectionComboBox() { + ui->pathSelectionComboBox->clear(); + + QStringList comboBoxItems; + for (QMap::key_iterator it = g_assemblyGraph->m_deBruijnGraphPaths.keyBegin(); + it != g_assemblyGraph->m_deBruijnGraphPaths.keyEnd(); ++it) + { + comboBoxItems.push_back(*it); + } + + if (comboBoxItems.size() > 0) + { + ui->pathSelectionComboBox->addItems(comboBoxItems); + ui->pathSelectionComboBox->setEnabled(true); + } +} + void MainWindow::drawDotplotPoweredByLogo(double x, double y, double w) { QPen pen; @@ -784,7 +841,8 @@ void MainWindow::drawGraph() std::vector startingNodes = g_assemblyGraph->getStartingNodes(&errorTitle, &errorMessage, ui->doubleNodesRadioButton->isChecked(), ui->startingNodesLineEdit->text(), - ui->blastQueryComboBox->currentText()); + ui->blastQueryComboBox->currentText(), + ui->pathSelectionComboBox->currentText()); if (errorMessage != "") { @@ -2352,8 +2410,9 @@ void MainWindow::setGraphScopeComboBox(GraphScope graphScope) { case WHOLE_GRAPH: ui->graphScopeComboBox->setCurrentIndex(0); break; case AROUND_NODE: ui->graphScopeComboBox->setCurrentIndex(1); break; - case AROUND_BLAST_HITS: ui->graphScopeComboBox->setCurrentIndex(2); break; - case DEPTH_RANGE: ui->graphScopeComboBox->setCurrentIndex(3); break; + case AROUND_PATHS: ui->graphScopeComboBox->setCurrentIndex(2); break; + case AROUND_BLAST_HITS: ui->graphScopeComboBox->setCurrentIndex(3); break; + case DEPTH_RANGE: ui->graphScopeComboBox->setCurrentIndex(4); break; } } diff --git a/ui/mainwindow.h b/ui/mainwindow.h index 04383ed1..de4b7bd1 100644 --- a/ui/mainwindow.h +++ b/ui/mainwindow.h @@ -86,6 +86,7 @@ class MainWindow : public QMainWindow void setNodeColourSchemeComboBox(NodeColourScheme nodeColourScheme); void setGraphScopeComboBox(GraphScope graphScope); void setupBlastQueryComboBox(); + void setupPathSelectionComboBox(); bool checkForImageSave(); QString convertGraphFileTypeToString(GraphFileType graphFileType); void setSelectedNodesWidgetsVisibility(bool visible); @@ -93,6 +94,7 @@ class MainWindow : public QMainWindow void setStartingNodesWidgetVisibility(bool visible); void setNodeDistanceWidgetVisibility(bool visible); void setDepthRangeWidgetVisibility(bool visible); + void setPathSelectionWidgetVisibility(bool visible); static QByteArray makeStringUrlSafe(QByteArray s); void removeGraphicsItemNodes(const std::vector * nodes, bool reverseComplement); void removeGraphicsItemEdges(const std::vector * edges, bool reverseComplement); diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 46abf741..a87ea27b 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -400,6 +400,51 @@
+ + + true + + + + 0 + 0 + + + + + 16 + 16 + + + + + + + + true + + + Path: + + + + + + + true + + + + 0 + 0 + + + + Qt::StrongFocus + + + + true @@ -418,7 +463,39 @@ - + + + + true + + + Distance: + + + + + + + true + + + + 0 + 0 + + + + Qt::StrongFocus + + + Qt::AlignCenter + + + 10000 + + + + @@ -434,7 +511,7 @@ - + @@ -500,7 +577,7 @@ - + @@ -513,28 +590,6 @@ - - - - true - - - - 0 - 0 - - - - Qt::StrongFocus - - - Qt::AlignCenter - - - 10000 - - - @@ -588,6 +643,11 @@ Around nodes + + + Around paths + + Around BLAST hits @@ -613,16 +673,6 @@
- - - - true - - - Distance: - - - @@ -639,7 +689,7 @@ - + Qt::StrongFocus @@ -655,7 +705,7 @@ - + @@ -687,7 +737,7 @@ - + @@ -700,7 +750,7 @@ - + @@ -716,7 +766,7 @@ - + @@ -729,7 +779,7 @@ - + Qt::StrongFocus From dc211a9a6bcf9b99991adbc54491c46329f716d2 Mon Sep 17 00:00:00 2001 From: Anton Korobeynikov Date: Thu, 3 May 2018 01:54:13 +0300 Subject: [PATCH 28/31] Allow select nodes along GFA path --- ui/mainwindow.cpp | 64 +++++++++++++------ ui/mainwindow.h | 3 + ui/mainwindow.ui | 158 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 204 insertions(+), 21 deletions(-) diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 88be4329..9cc7e2e2 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -157,6 +157,7 @@ MainWindow::MainWindow(QString fileToLoadOnStartup, bool drawGraphAfterLoad) : connect(ui->setNodeCustomLabelButton, SIGNAL(clicked()), this, SLOT(setNodeCustomLabel())); connect(ui->actionSettings, SIGNAL(triggered()), this, SLOT(openSettingsDialog())); connect(ui->selectNodesButton, SIGNAL(clicked()), this, SLOT(selectUserSpecifiedNodes())); + connect(ui->pathSelectButton, SIGNAL(clicked()), this, SLOT(selectPathNodes())); connect(ui->selectionSearchNodesLineEdit, SIGNAL(returnPressed()), this, SLOT(selectUserSpecifiedNodes())); connect(ui->actionAbout, SIGNAL(triggered()), this, SLOT(openAboutDialog())); connect(ui->blastSearchButton, SIGNAL(clicked()), this, SLOT(openBlastSearchDialog())); @@ -450,6 +451,7 @@ void MainWindow::loadGraph2(GraphFileType graphFileType, QString fullFileName) // to the default of 'Random colours'. if (!customColours && ui->coloursComboBox->currentIndex() == 6) ui->coloursComboBox->setCurrentIndex(0); + setupPathSelectionComboBox(); } catch (...) @@ -653,7 +655,6 @@ void MainWindow::graphScopeChanged() case 2: g_settings->graphScope = AROUND_PATHS; - setupPathSelectionComboBox(); setStartingNodesWidgetVisibility(false); setNodeDistanceWidgetVisibility(true); setDepthRangeWidgetVisibility(false); @@ -763,6 +764,7 @@ void MainWindow::setPathSelectionWidgetVisibility(bool visible) void MainWindow::setupPathSelectionComboBox() { ui->pathSelectionComboBox->clear(); + ui->pathSelectionComboBox2->clear(); QStringList comboBoxItems; for (QMap::key_iterator it = g_assemblyGraph->m_deBruijnGraphPaths.keyBegin(); @@ -775,6 +777,8 @@ void MainWindow::setupPathSelectionComboBox() { { ui->pathSelectionComboBox->addItems(comboBoxItems); ui->pathSelectionComboBox->setEnabled(true); + ui->pathSelectionComboBox2->addItems(comboBoxItems); + ui->pathSelectionComboBox2->setEnabled(true); } } @@ -1714,28 +1718,10 @@ void MainWindow::openSettingsDialog() } } -void MainWindow::selectUserSpecifiedNodes() -{ - if (g_assemblyGraph->checkIfStringHasNodes(ui->selectionSearchNodesLineEdit->text())) - { - QMessageBox::information(this, "No starting nodes", - "Please enter at least one node when drawing the graph using the 'Around node(s)' scope. " - "Separate multiple nodes with commas."); - return; - } - - if (ui->selectionSearchNodesLineEdit->text().length() == 0) - { - QMessageBox::information(this, "No nodes given", "Please enter the numbers of the nodes to find, separated by commas."); - return; - } - +void MainWindow::doSelectNodes(const std::vector &nodesToSelect, + const std::vector &nodesNotInGraph) { m_scene->blockSignals(true); m_scene->clearSelection(); - std::vector nodesNotInGraph; - std::vector nodesToSelect = getNodesFromLineEdit(ui->selectionSearchNodesLineEdit, - ui->selectionSearchNodesExactMatchRadioButton->isChecked(), - &nodesNotInGraph); //Select each node that actually has a GraphicsItemNode, and build a bounding //rectangle so the viewport can focus on the selected node. @@ -1791,6 +1777,42 @@ void MainWindow::selectUserSpecifiedNodes() selectionChanged(); } +void MainWindow::selectUserSpecifiedNodes() +{ + if (g_assemblyGraph->checkIfStringHasNodes(ui->selectionSearchNodesLineEdit->text())) + { + QMessageBox::information(this, "No starting nodes", + "Please enter at least one node when drawing the graph using the 'Around node(s)' scope. " + "Separate multiple nodes with commas."); + return; + } + + if (ui->selectionSearchNodesLineEdit->text().length() == 0) + { + QMessageBox::information(this, "No nodes given", "Please enter the numbers of the nodes to find, separated by commas."); + return; + } + + std::vector nodesNotInGraph; + std::vector nodesToSelect = getNodesFromLineEdit(ui->selectionSearchNodesLineEdit, + ui->selectionSearchNodesExactMatchRadioButton->isChecked(), + &nodesNotInGraph); + + doSelectNodes(nodesToSelect, nodesNotInGraph); +} + +void MainWindow::selectPathNodes() +{ + std::vector nodesNotInGraph; + std::vector nodesToSelect; + + QList nodes = g_assemblyGraph->m_deBruijnGraphPaths[ui->pathSelectionComboBox2->currentText()]->getNodes(); + for (QList::iterator i = nodes.begin(); i != nodes.end(); ++i) + nodesToSelect.push_back(*i); + + doSelectNodes(nodesToSelect, nodesNotInGraph); +} + void MainWindow::openAboutDialog() { diff --git a/ui/mainwindow.h b/ui/mainwindow.h index de4b7bd1..c465ac49 100644 --- a/ui/mainwindow.h +++ b/ui/mainwindow.h @@ -126,6 +126,9 @@ private slots: void hideNodes(); void openSettingsDialog(); void openAboutDialog(); + void doSelectNodes(const std::vector &nodesToSelect, + const std::vector &nodesNotInGraph); + void selectPathNodes(); void selectUserSpecifiedNodes(); void graphLayoutFinished(); void openBlastSearchDialog(); diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index a87ea27b..81d63e3e 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -1668,6 +1668,164 @@ + + + + + + 0 + 0 + + + + + 75 + true + + + + Find paths + + + + + + + Qt::Horizontal + + + + + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + 0 + 0 + + + + + 16 + 16 + + + + + + + + Qt::StrongFocus + + + Select + + + true + + + + + + + true + + + + 0 + 0 + + + + Path: + + + + + + + + 0 + 0 + + + + + 16 + 16 + + + + + + + + true + + + Action: + + + + + + + Qt::StrongFocus + + + Recolor + + + + + + + true + + + + 0 + 0 + + + + Qt::StrongFocus + + + + + + + + + + Qt::StrongFocus + + + Find path + + + From a0b960878edcd2c803f8514f2e993561e1a5ef6c Mon Sep 17 00:00:00 2001 From: Anton Korobeynikov Date: Thu, 3 May 2018 02:21:06 +0300 Subject: [PATCH 29/31] Implement recoloring --- ui/mainwindow.cpp | 14 ++++++++++++-- ui/mainwindow.h | 3 ++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 9cc7e2e2..3d7b404c 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -1719,7 +1719,8 @@ void MainWindow::openSettingsDialog() } void MainWindow::doSelectNodes(const std::vector &nodesToSelect, - const std::vector &nodesNotInGraph) { + const std::vector &nodesNotInGraph, + bool recolor) { m_scene->blockSignals(true); m_scene->clearSelection(); @@ -1727,6 +1728,7 @@ void MainWindow::doSelectNodes(const std::vector &nodesToSelect, //rectangle so the viewport can focus on the selected node. std::vector nodesNotFound; int foundNodes = 0; + QColor color; for (size_t i = 0; i < nodesToSelect.size(); ++i) { GraphicsItemNode * graphicsItemNode = nodesToSelect[i]->getGraphicsItemNode(); @@ -1738,6 +1740,14 @@ void MainWindow::doSelectNodes(const std::vector &nodesToSelect, if (graphicsItemNode != 0) { + if (recolor) + { + if (i == 0) + color = graphicsItemNode->m_colour; + else + graphicsItemNode->m_colour = color; + } + graphicsItemNode->setSelected(true); ++foundNodes; } @@ -1810,7 +1820,7 @@ void MainWindow::selectPathNodes() for (QList::iterator i = nodes.begin(); i != nodes.end(); ++i) nodesToSelect.push_back(*i); - doSelectNodes(nodesToSelect, nodesNotInGraph); + doSelectNodes(nodesToSelect, nodesNotInGraph, ui->pathSelectionRecolorRadioButton->isChecked()); } diff --git a/ui/mainwindow.h b/ui/mainwindow.h index c465ac49..580b8f8a 100644 --- a/ui/mainwindow.h +++ b/ui/mainwindow.h @@ -127,7 +127,8 @@ private slots: void openSettingsDialog(); void openAboutDialog(); void doSelectNodes(const std::vector &nodesToSelect, - const std::vector &nodesNotInGraph); + const std::vector &nodesNotInGraph, + bool recolor = false); void selectPathNodes(); void selectUserSpecifiedNodes(); void graphLayoutFinished(); From 863b3d7ed223044232bb721f9db522aa91afea3f Mon Sep 17 00:00:00 2001 From: Anton Korobeynikov Date: Thu, 3 May 2018 02:30:10 +0300 Subject: [PATCH 30/31] Properly recolor paths in double-stranded mode --- ui/mainwindow.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 3d7b404c..c7f08d6d 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -1728,24 +1728,32 @@ void MainWindow::doSelectNodes(const std::vector &nodesToSelect, //rectangle so the viewport can focus on the selected node. std::vector nodesNotFound; int foundNodes = 0; - QColor color; + QColor color1, color2; for (size_t i = 0; i < nodesToSelect.size(); ++i) { GraphicsItemNode * graphicsItemNode = nodesToSelect[i]->getGraphicsItemNode(); + GraphicsItemNode * rcgraphicsItemNode = nodesToSelect[i]->getReverseComplement()->getGraphicsItemNode(); //If the GraphicsItemNode isn't found, try the reverse complement. This //is only done for single node mode. if (graphicsItemNode == 0 && !g_settings->doubleMode) - graphicsItemNode = nodesToSelect[i]->getReverseComplement()->getGraphicsItemNode(); + graphicsItemNode = rcgraphicsItemNode; if (graphicsItemNode != 0) { if (recolor) { if (i == 0) - color = graphicsItemNode->m_colour; - else - graphicsItemNode->m_colour = color; + { + color1 = graphicsItemNode->m_colour; + if (g_settings->doubleMode) + color2 = rcgraphicsItemNode->m_colour; + } else { + graphicsItemNode->m_colour = color1; + if (g_settings->doubleMode) + rcgraphicsItemNode->m_colour = color2; + } + } graphicsItemNode->setSelected(true); From 80ea6f2a4f2bedf1dd0c4528cc174c61554f1ecb Mon Sep 17 00:00:00 2001 From: Erik Garrison Date: Tue, 15 Jun 2021 13:17:09 +0200 Subject: [PATCH 31/31] correct merge --- command_line/image.cpp | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/command_line/image.cpp b/command_line/image.cpp index 36046fb9..42956c3a 100644 --- a/command_line/image.cpp +++ b/command_line/image.cpp @@ -130,26 +130,6 @@ 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; - } - QString errormsg; QStringList columns; bool coloursLoaded = false;