provide a user interface to edit motifs
authorDiane Trout <diane@caltech.edu>
Tue, 21 Mar 2006 08:46:58 +0000 (08:46 +0000)
committerDiane Trout <diane@caltech.edu>
Tue, 21 Mar 2006 08:46:58 +0000 (08:46 +0000)
14 files changed:
alg/mussa.cpp
alg/mussa.hpp
alg/test/test_mussa.cpp
mussagl.pro
qui/MussaAlignedWindow.cpp
qui/MussaAlignedWindow.hpp
qui/MussaWindow.cpp
qui/MussaWindow.hpp
qui/motif_editor/MotifDetail.cpp [new file with mode: 0644]
qui/motif_editor/MotifDetail.hpp [new file with mode: 0644]
qui/motif_editor/MotifEditor.cpp [new file with mode: 0644]
qui/motif_editor/MotifEditor.hpp [new file with mode: 0644]
qui/seqbrowser/SequenceBrowserWidget.cpp
qui/seqbrowser/SequenceBrowserWidget.hpp

index ea3fb23a57d5b34c9ba06ad79ebef288870448e4..09cb297db3d57e4e0d329973a328188b708e89cb 100644 (file)
@@ -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<string>& motifs, 
+                       const vector<Color>& 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<Sequence>::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<string>& Mussa::motifs() const
 {
-  ifstream f;
-  f.open(filename.c_str(), ifstream::in);
-  load_motifs(f);
+  return motif_sequences;
 }
 
 AnnotationColors& Mussa::colorMapper()
index 8cf8ffa771644be5264b48f8263e2570816f7a75..b4a6e26d4638d5cbed7dc1f25c29ffc7d9a3ad41 100644 (file)
@@ -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<std::string>& motifs, 
+                    const std::vector<Color>& colors);
     //! load motifs from an ifstream
     /*! The file should look something like
      *  <sequence> <red> <green> <blue>
@@ -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<std::string>& 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)
index 1ca85a4144fed65fc055b99fd4d2e75c7224cdf9..97044d61b6e6155a1b424427e50b9e5e8ccf1359 100644 (file)
@@ -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<string> motifs;
+  motifs.push_back("AAGG");
+  vector<Color> 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() );
+}
index c7e5aeab432978c34764ce11d12f39f7feffcfb9..ab9f983b51d93ba8b8c93acb0e5962db3d32c52f 100644 (file)
@@ -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 \
index 06578895c295916d94b128eabdf13c9cb455be31..fbf2d6a53e1af0972d84bceb90cb02e63baf892a 100644 (file)
@@ -120,6 +120,11 @@ void MussaAlignedWindow::toggleViewAlignment(size_t alignment_index)
   computeMatchLines();
 }
 
+void MussaAlignedWindow::update()
+{
+  browser.update();
+}
+
 void MussaAlignedWindow::computeMatchLines()
 {
   const vector<Sequence>& raw_sequence = analysis.sequences();
index d7b642e1e42415ec337330cddca129732648388b..a6d5844d929c1eae70a5fe8e970387a6fec8d151 100644 (file)
@@ -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<int>& sel_paths);
   //! set menus (must be called after setSelectedPaths)
index c179b90b8b96d4d40911077552e8968041c73214..0dddbd8588cc8592826c27ec7605e7da8f89deb8 100644 (file)
@@ -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();
index 14c1e1f967a883c8eeb2321ad54f03035a3c101a..0d5f4127b2cc59a36ea3881987de043e5f4bf787 100644 (file)
@@ -9,6 +9,7 @@
 #include <QToolBar>
 
 #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<MussaAlignedWindow *> 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 (file)
index 0000000..333cb1f
--- /dev/null
@@ -0,0 +1,86 @@
+#include <QColorDialog>
+#include <QHBoxLayout>
+#include <QRegExp>
+
+#include "qui/motif_editor/MotifDetail.hpp"
+
+#include <iostream>
+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 (file)
index 0000000..0397976
--- /dev/null
@@ -0,0 +1,43 @@
+#ifndef _MOTIF_DETAIL_H
+#define _MOTIF_DETAIL_H
+
+#include <string>
+
+#include <QColor>
+#include <QLineEdit>
+#include <QPushButton>
+#include <QRegExpValidator>
+#include <QWidget>
+
+//#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 (file)
index 0000000..c1df29e
--- /dev/null
@@ -0,0 +1,68 @@
+#include "qui/motif_editor/MotifEditor.hpp"
+#include "alg/sequence.hpp"
+
+#include <set>
+#include <string>
+
+using namespace std;
+
+MotifEditor::MotifEditor(Mussa *m, QWidget *parent)
+  : QWidget(parent),
+    analysis(m),
+    applyButton("set motifs")
+{
+  assert (m != 0);
+  const set<string> &motif = analysis->motifs();
+  vector<string> 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<string> motifs;
+  vector<Color> colors;
+
+  for(std::vector<MotifDetail *>::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 (file)
index 0000000..b5a4579
--- /dev/null
@@ -0,0 +1,37 @@
+#ifndef MOTIF_EDITOR_H
+#define MOTIF_EDITOR_H
+
+#include <vector>
+
+#include <QPushButton>
+#include <QVBoxLayout>
+#include <QWidget>
+
+#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<MotifDetail *> motif_details;
+};
+#endif
index 3692af6872a39c3168c61a89f63dda023c639f2c..39da328feda87d374a8185e8b2868da3b82eb717 100644 (file)
@@ -159,3 +159,8 @@ void SequenceBrowserWidget::setZoom(int z)
 {
   scrollable_browser.browser().setZoom(z);
 }
+
+void SequenceBrowserWidget::update()
+{
+  scrollable_browser.browser().update();
+}
index 3460eaafbfad93aca0c44ba8c7f6a3046c444a75..71dc6f16582d1012afb98b23cd2611b5a8c3b3b9 100644 (file)
@@ -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;