From: Diane Trout Date: Tue, 21 Mar 2006 08:46:58 +0000 (+0000) Subject: provide a user interface to edit motifs X-Git-Url: http://woldlab.caltech.edu/gitweb/?p=mussa.git;a=commitdiff_plain;h=b8fd78b2af2c93f8cdb9193e5fcf4591c97de4b9 provide a user interface to edit motifs --- diff --git a/alg/mussa.cpp b/alg/mussa.cpp index ea3fb23..09cb297 100644 --- a/alg/mussa.cpp +++ b/alg/mussa.cpp @@ -649,6 +649,21 @@ Mussa::load_old(char * load_file_path, int s_num) //the_paths.save("tmp.save"); } +void Mussa::add_motifs(const vector& motifs, + const vector& colors) +{ + if (motifs.size() != colors.size()) { + throw mussa_error("motif and color vectors must be the same size"); + } + + for(size_t i = 0; i != motifs.size(); ++i) + { + motif_sequences.insert(motifs[i]); + color_mapper.appendInstanceColor("motif", motifs[i], colors[i]); + } + update_sequences_motifs(); +} + // I mostly split the ifstream out so I can use a stringstream to test it. void Mussa::load_motifs(std::istream &in) { @@ -697,6 +712,18 @@ void Mussa::load_motifs(std::istream &in) continue; } } + update_sequences_motifs(); +} + +void Mussa::load_motifs(string filename) +{ + ifstream f; + f.open(filename.c_str(), ifstream::in); + load_motifs(f); +} + +void Mussa::update_sequences_motifs() +{ // once we've loaded all the motifs from the file, // lets attach them to the sequences for(vector::iterator seq_i = the_seqs.begin(); @@ -715,11 +742,9 @@ void Mussa::load_motifs(std::istream &in) } } -void Mussa::load_motifs(string filename) +const set& Mussa::motifs() const { - ifstream f; - f.open(filename.c_str(), ifstream::in); - load_motifs(f); + return motif_sequences; } AnnotationColors& Mussa::colorMapper() diff --git a/alg/mussa.hpp b/alg/mussa.hpp index 8cf8ffa..b4a6e26 100644 --- a/alg/mussa.hpp +++ b/alg/mussa.hpp @@ -116,6 +116,13 @@ class Mussa void load_old(char * load_file_path, int s_num); // manage motif lists + //! add vector of motifs and colors to our motif collection + /*! this depends on sets and color maps being unique + * (aka if you add the same item more than once it doesn't + * increase the size of the data structure + */ + void add_motifs(const std::vector& motifs, + const std::vector& colors); //! load motifs from an ifstream /*! The file should look something like * @@ -126,11 +133,16 @@ class Mussa void load_motifs(std::istream &); //! load a list of motifs from a file named filename void load_motifs(std::string filename); + //! return our motifs; + const std::set& motifs() const; //! return color mapper AnnotationColors& colorMapper(); private: + //! push motifs to our attached sequences + void update_sequences_motifs(); + // Private variables // parameters needed for a mussa analysis //! name of this analysis. (will also be used when saving an analysis) diff --git a/alg/test/test_mussa.cpp b/alg/test/test_mussa.cpp index 1ca85a4..97044d6 100644 --- a/alg/test/test_mussa.cpp +++ b/alg/test/test_mussa.cpp @@ -131,3 +131,20 @@ BOOST_AUTO_TEST_CASE( mussa_load_motif ) BOOST_CHECK( seq_i->motifs().size() > 0 ); } } + +BOOST_AUTO_TEST_CASE( mussa_add_motif ) +{ + vector motifs; + motifs.push_back("AAGG"); + vector colors; + colors.push_back(Color(1.0, 0.0, 0.0)); + + Mussa m1; + m1.add_a_seq("AAAAGGGGTTTT"); + m1.add_a_seq("GGGCCCCTTGGTT"); + m1.add_motifs(motifs, colors); + int first_size = m1.motifs().size(); + BOOST_CHECK_EQUAL( first_size, 1 ); + m1.add_motifs(motifs, colors); + BOOST_CHECK_EQUAL( first_size, m1.motifs().size() ); +} diff --git a/mussagl.pro b/mussagl.pro index c7e5aea..ab9f983 100644 --- a/mussagl.pro +++ b/mussagl.pro @@ -18,6 +18,8 @@ HEADERS += mussa_exceptions.hpp \ qui/ImageScaler.hpp \ qui/ImageSaveDialog.hpp \ qui/IntAction.hpp \ + qui/motif_editor/MotifDetail.hpp \ + qui/motif_editor/MotifEditor.hpp \ qui/seqbrowser/SequenceBrowserWidget.hpp \ qui/seqbrowser/SequenceBrowser.hpp \ qui/seqbrowser/SequenceBrowserSidebar.hpp \ @@ -40,6 +42,8 @@ SOURCES += mussagl.cpp \ qui/ImageScaler.cpp \ qui/ImageSaveDialog.cpp \ qui/IntAction.cpp \ + qui/motif_editor/MotifDetail.cpp \ + qui/motif_editor/MotifEditor.cpp \ qui/seqbrowser/SequenceBrowserWidget.cpp \ qui/seqbrowser/SequenceBrowser.cpp \ qui/seqbrowser/SequenceBrowserSidebar.cpp \ diff --git a/qui/MussaAlignedWindow.cpp b/qui/MussaAlignedWindow.cpp index 0657889..fbf2d6a 100644 --- a/qui/MussaAlignedWindow.cpp +++ b/qui/MussaAlignedWindow.cpp @@ -120,6 +120,11 @@ void MussaAlignedWindow::toggleViewAlignment(size_t alignment_index) computeMatchLines(); } +void MussaAlignedWindow::update() +{ + browser.update(); +} + void MussaAlignedWindow::computeMatchLines() { const vector& raw_sequence = analysis.sequences(); diff --git a/qui/MussaAlignedWindow.hpp b/qui/MussaAlignedWindow.hpp index d7b642e..a6d5844 100644 --- a/qui/MussaAlignedWindow.hpp +++ b/qui/MussaAlignedWindow.hpp @@ -24,6 +24,9 @@ public slots: //! toggle whether or not to show the aligned basepairs of a window void toggleViewAlignment(size_t alignment_index); + //! just force updating the window + void update(); + protected: void setSelectedPaths(Mussa &m, const std::set& sel_paths); //! set menus (must be called after setSelectedPaths) diff --git a/qui/MussaWindow.cpp b/qui/MussaWindow.cpp index c179b90..0dddbd8 100644 --- a/qui/MussaWindow.cpp +++ b/qui/MussaWindow.cpp @@ -20,6 +20,7 @@ using namespace std; MussaWindow::MussaWindow(Mussa *analysis_, QWidget *parent) : QMainWindow(parent), analysis(analysis_), + motif_editor(0), browser(this), mussaViewTB("Path Views"), zoomBox(), @@ -35,6 +36,9 @@ MussaWindow::MussaWindow(Mussa *analysis_, QWidget *parent) : // opengl widget. //scene->setWhatsThis(tr("Mussa in OpenGL!")); setCentralWidget(&browser); + // well updatePosition isn't quite right as we really just need + // to call update() + connect(this, SIGNAL(changedAnnotations()), &browser, SLOT(update())); mussaViewTB.addAction(toggleMotifsAction); @@ -91,6 +95,9 @@ void MussaWindow::setupActions() connect(createSubAnalysisAction, SIGNAL(triggered()), this, SLOT(createSubAnalysis())); + editMotifsAction = new QAction(tr("Edit Motifs"), this);; + connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs())); + loadMotifListAction = new QAction(tr("Load Motif List"), this); connect(loadMotifListAction, SIGNAL(triggered()), this, SLOT(loadMotifList())); @@ -162,6 +169,7 @@ void MussaWindow::setupMainMenu() newMenu->addAction(closeAction); newMenu = menuBar()->addMenu(tr("&View")); + newMenu->addAction(editMotifsAction); newMenu->addAction(viewMussaAlignmentAction); newMenu->addAction(showMussaViewToolbarAction); @@ -189,6 +197,17 @@ void MussaWindow::createSubAnalysis() NotImplementedBox(); } +void MussaWindow::editMotifs() +{ + if (motif_editor != 0) { + motif_editor->hide(); + delete motif_editor; + } + motif_editor = new MotifEditor(analysis); + connect(motif_editor, SIGNAL(changedMotifs()), + this, SLOT(updateAnnotations())); + motif_editor->show(); +} void MussaWindow::loadMotifList() { @@ -313,7 +332,10 @@ void MussaWindow::viewMussaAlignment() QObject::tr("you should probably select some paths " "first")); } else { - MussaAlignedWindow *ma_win = new MussaAlignedWindow(*analysis, selected_paths); + MussaAlignedWindow *ma_win = new MussaAlignedWindow(*analysis, + selected_paths); + connect(this, SIGNAL(changedAnnotations()), + ma_win, SLOT(update())); aligned_windows.push_back(ma_win); ma_win->show(); } @@ -339,6 +361,14 @@ void MussaWindow::updateAnalysis() zoomBox.setValue(browser.zoom()); } +void MussaWindow::updateAnnotations() +{ + // motifs were changed in the sequences by + // Mussa::update_sequences_motifs + emit changedAnnotations(); + browser.update(); +} + void MussaWindow::updateLinks() { browser.clear_links(); diff --git a/qui/MussaWindow.hpp b/qui/MussaWindow.hpp index 14c1e1f..0d5f412 100644 --- a/qui/MussaWindow.hpp +++ b/qui/MussaWindow.hpp @@ -9,6 +9,7 @@ #include #include "qui/MussaAlignedWindow.hpp" +#include "qui/motif_editor/MotifEditor.hpp" #include "qui/seqbrowser/SequenceBrowserWidget.hpp" #include "qui/ThresholdWidget.hpp" @@ -38,6 +39,7 @@ public slots: //! \defgroup MotifHandling Handling of motif lists //\@{ //! load motifs + void editMotifs(); void loadMotifList(); void saveMotifList(); void toggleMotifs(); @@ -54,10 +56,14 @@ public slots: //! open new window showing our alignment void viewMussaAlignment(); - + +signals: + void changedAnnotations(); + protected: Mussa *analysis; std::list aligned_windows; + MotifEditor *motif_editor; // display our wonderful mussa output SequenceBrowserWidget browser; @@ -65,11 +71,13 @@ protected: QSpinBox zoomBox; ThresholdWidget threshold; QLabel zoomLabel; + QAction *aboutAction; QAction *closeAction; QAction *createNewAnalysisAction; QAction *createSubAnalysisAction; + QAction *editMotifsAction; QAction *loadMotifListAction; QAction *loadMupaAction; QAction *loadSavedAnalysisAction; @@ -86,10 +94,14 @@ protected: void setupMainMenu(); //! stub function to fill in QActions void NotImplementedBox(); + +protected slots: //! update the SequenceBrowser with our analysis void updateAnalysis(); //! update the view of conserved windows void updateLinks(); + //! update annotations? + void updateAnnotations(); }; #endif diff --git a/qui/motif_editor/MotifDetail.cpp b/qui/motif_editor/MotifDetail.cpp new file mode 100644 index 0000000..333cb1f --- /dev/null +++ b/qui/motif_editor/MotifDetail.cpp @@ -0,0 +1,86 @@ +#include +#include +#include + +#include "qui/motif_editor/MotifDetail.hpp" + +#include +using namespace std; + +static const QRegExp iupacAlphabet("[ACTGUWRKYSMBHDVN]*", Qt::CaseInsensitive); +static const QRegExpValidator iupacValidator(iupacAlphabet, 0); + +MotifDetail::MotifDetail(QWidget *parent) + : QWidget(parent), + motif_color(1.0, 1.0, 1.0) +{ + setupWidget(); +} + +MotifDetail::MotifDetail(const MotifDetail &md) + : QWidget((QWidget *)md.parent()), + motif_color(md.motif_color), + motifText(md.motifText.displayText()) +{ +} + +MotifDetail::MotifDetail(std::string& m, Color& c, QWidget *parent) + : QWidget(parent), + motif_color(c), + motifText(m.c_str()) +{ + setupWidget(); +} + +void MotifDetail::setupWidget() +{ + QHBoxLayout *layout = new QHBoxLayout; + + colorButton.setFlat(true); + + colorButton.setPalette(QPalette(qcolor())); + colorButton.setAutoFillBackground(true); + connect(&colorButton, SIGNAL(clicked()), this, SLOT(promptColor())); + layout->addWidget(&colorButton); + motifText.setValidator(&iupacValidator); + layout->addWidget(&motifText); + + setLayout(layout); +} + +void MotifDetail::setMotif(const string &m) +{ + motifText.setText(m.c_str()); +} + +string MotifDetail::motif() const +{ + return motifText.text().toStdString(); +} + +void MotifDetail::setColor(const Color &c) +{ + motif_color = c; + colorButton.setPalette(QPalette(qcolor())); +} + +Color MotifDetail::color() const +{ + return motif_color; +} + +QColor MotifDetail::qcolor() const +{ + QColor qc; + qc.setRedF(motif_color.r()); + qc.setGreenF(motif_color.g()); + qc.setBlueF(motif_color.b()); + return qc; +} + +void MotifDetail::promptColor() +{ + QColor new_qcolor = QColorDialog::getColor(qcolor(), this); + Color new_color(new_qcolor.redF(), new_qcolor.greenF(), new_qcolor.blueF()); + setColor(new_color); +} diff --git a/qui/motif_editor/MotifDetail.hpp b/qui/motif_editor/MotifDetail.hpp new file mode 100644 index 0000000..0397976 --- /dev/null +++ b/qui/motif_editor/MotifDetail.hpp @@ -0,0 +1,43 @@ +#ifndef _MOTIF_DETAIL_H +#define _MOTIF_DETAIL_H + +#include + +#include +#include +#include +#include +#include + +//#include "alg/sequence.hpp" +#include "alg/color.hpp" + +class MotifDetail : public QWidget +{ + Q_OBJECT + +public: + MotifDetail(QWidget *parent=0); + MotifDetail(const MotifDetail &); + MotifDetail(std::string& m, Color& c, QWidget *parent=0); + + void setMotif(const std::string& m); + std::string motif() const; + + void setColor(const Color&); + Color color() const; + QColor qcolor() const; + +public slots: + void promptColor(); + +private: + void setupWidget(); + + Color motif_color; + + // widgets + QPushButton colorButton; + QLineEdit motifText; +}; +#endif diff --git a/qui/motif_editor/MotifEditor.cpp b/qui/motif_editor/MotifEditor.cpp new file mode 100644 index 0000000..c1df29e --- /dev/null +++ b/qui/motif_editor/MotifEditor.cpp @@ -0,0 +1,68 @@ +#include "qui/motif_editor/MotifEditor.hpp" +#include "alg/sequence.hpp" + +#include +#include + +using namespace std; + +MotifEditor::MotifEditor(Mussa *m, QWidget *parent) + : QWidget(parent), + analysis(m), + applyButton("set motifs") +{ + assert (m != 0); + const set &motif = analysis->motifs(); + vector motif_seq(motif.begin(), motif.end()); + + connect(&applyButton, SIGNAL(clicked()), this, SLOT(updateMotifs())); + layout.addWidget(&applyButton); + + for(size_t i=0; i != 10; ++i) + { + MotifDetail *detail = new MotifDetail; + if (i < motif_seq.size()) { + detail->setMotif(motif_seq[i]); + detail->setColor(analysis->colorMapper().lookup("motif", motif_seq[i])); + } + motif_details.push_back(detail); + layout.addWidget(detail); + } + setLayout(&layout); +} + +MotifEditor::MotifEditor(const MotifEditor& me) + : QWidget((QWidget*)me.parent()), + analysis(me.analysis), + applyButton(me.applyButton.text()) +{ +} + +void MotifEditor::updateMotifs() +{ + // This function is _sooo_ not thread safe + // erase motifs + Color motif_default = analysis->colorMapper().typeColor("motif"); + analysis->colorMapper().erase("motif"); + analysis->colorMapper().appendTypeColor("motif", motif_default); + + // add our motifs back + vector motifs; + vector colors; + + for(std::vector::iterator md_i = motif_details.begin(); + md_i != motif_details.end(); + ++md_i) + { + if ((*md_i)->motif().size() > 0) { + motifs.push_back((*md_i)->motif()); + colors.push_back((*md_i)->color()); + } + } + analysis->add_motifs(motifs, colors); + + emit changedMotifs(); +} + +//MotifEditor:: + diff --git a/qui/motif_editor/MotifEditor.hpp b/qui/motif_editor/MotifEditor.hpp new file mode 100644 index 0000000..b5a4579 --- /dev/null +++ b/qui/motif_editor/MotifEditor.hpp @@ -0,0 +1,37 @@ +#ifndef MOTIF_EDITOR_H +#define MOTIF_EDITOR_H + +#include + +#include +#include +#include + +#include "alg/mussa.hpp" +#include "qui/motif_editor/MotifDetail.hpp" + +class MotifEditor : public QWidget +{ + Q_OBJECT + +public: + MotifEditor(Mussa* m, QWidget *parent=0); + MotifEditor(const MotifEditor&); + +public slots: + // called to apply motif changes + void updateMotifs(); + +signals: + // emitted when the use has applied the motif changes + void changedMotifs(); + +private: + Mussa* analysis; + + QPushButton applyButton; + QVBoxLayout layout; + + std::vector motif_details; +}; +#endif diff --git a/qui/seqbrowser/SequenceBrowserWidget.cpp b/qui/seqbrowser/SequenceBrowserWidget.cpp index 3692af6..39da328 100644 --- a/qui/seqbrowser/SequenceBrowserWidget.cpp +++ b/qui/seqbrowser/SequenceBrowserWidget.cpp @@ -159,3 +159,8 @@ void SequenceBrowserWidget::setZoom(int z) { scrollable_browser.browser().setZoom(z); } + +void SequenceBrowserWidget::update() +{ + scrollable_browser.browser().update(); +} diff --git a/qui/seqbrowser/SequenceBrowserWidget.hpp b/qui/seqbrowser/SequenceBrowserWidget.hpp index 3460eaa..71dc6f1 100644 --- a/qui/seqbrowser/SequenceBrowserWidget.hpp +++ b/qui/seqbrowser/SequenceBrowserWidget.hpp @@ -54,6 +54,8 @@ public slots: //! ask the user where to save an image of the current browser view void promptSaveBrowserPixmap(); + void update(); + private: ScrollableSequenceBrowser scrollable_browser; SequenceBrowserSidebar left_sidebar;