From 091f6cb459e979a8c531a0fd20e407b70887833c Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Wed, 15 Mar 2006 09:45:59 +0000 Subject: [PATCH] make the path view independent of type of connection I moved the opengl code for drawing multiple tracks with paths connecting various elements into GlTracks. This also includes the performance improvement of only drawing a track once. Unfortunately I disabled the selection code, as I'm not sure how much should live at the Qt level and how much should live at the opengl level. Even more difficult is the problem that a segment can belong to _many_ paths. Which makes my old each path having one pathid not so likely to work-- especially since there's a limit to how many names one can have. Unfortunately for a large dataset, there's a segfault on closing. --- alg/glsequence.cpp | 7 +- alg/glsequence.hpp | 2 + alg/gltracks.cpp | 327 ++++++++++++++++++++++++++++++++++++ alg/gltracks.hpp | 137 +++++++++++++++ alg/module.mk | 3 +- alg/sequence.cpp | 6 + alg/sequence.hpp | 2 + alg/test/module.mk | 1 + alg/test/test_color.cpp | 3 +- mussagl.pro | 2 + qui/PathScene.cpp | 364 +++------------------------------------- qui/PathScene.hpp | 67 ++------ qui/PathWidget.cpp | 23 +-- qui/PathWidget.hpp | 11 +- qui/PathWindow.cpp | 232 ++++++++++++++++++++----- qui/PathWindow.hpp | 33 +++- 16 files changed, 760 insertions(+), 460 deletions(-) create mode 100644 alg/gltracks.cpp create mode 100644 alg/gltracks.hpp diff --git a/alg/glsequence.cpp b/alg/glsequence.cpp index 674dcb0..d802d62 100644 --- a/alg/glsequence.cpp +++ b/alg/glsequence.cpp @@ -70,6 +70,11 @@ GLfloat GlSequence::y() const return seq_y; } +GLfloat GlSequence::height() const +{ + return seq_height; +} + GLfloat GlSequence::length() const { return seq.size(); @@ -194,6 +199,7 @@ void GlSequence::draw_box(GLfloat left, GLfloat right, glVertex3f(right, top, z); glEnd(); } + void GlSequence::draw_track(GLfloat left, GLfloat right) const { glColor3fv(drawColor.get()); @@ -228,7 +234,6 @@ void GlSequence::draw_annotations(GLfloat left, GLfloat right) const } - const int PT = 1; const int STROKE = 2; const int END =3; diff --git a/alg/glsequence.hpp b/alg/glsequence.hpp index c8c9b2b..3916687 100644 --- a/alg/glsequence.hpp +++ b/alg/glsequence.hpp @@ -30,6 +30,8 @@ public: void setY(GLfloat); //! get our current y (vertical) position GLfloat y() const; + //! how thick (high) the track we're drawing is + GLfloat height() const; //! how long is our sequence track? (computed from the sequence) GLfloat length() const; diff --git a/alg/gltracks.cpp b/alg/gltracks.cpp new file mode 100644 index 0000000..7f11c5b --- /dev/null +++ b/alg/gltracks.cpp @@ -0,0 +1,327 @@ +#include "alg/gltracks.hpp" + +#include +#include + +using namespace std; + +GlTracks::GlTracks() + : border(25), + max_ortho(400.0, 0.0, 600.0, 0.0), + cur_ortho(max_ortho), + viewport_size(600, 400), + viewport_center(0), + zoom_level(2), + color_mapper(), + track_container() +{ +} + +GlTracks::GlTracks(const GlTracks& gt) + : border(gt.border), + max_ortho(gt.max_ortho), + cur_ortho(gt.cur_ortho), + viewport_size(gt.viewport_size), + viewport_center(gt.viewport_center), + zoom_level(gt.zoom_level), + color_mapper(gt.color_mapper), + track_container(gt.track_container) +{ +} + +void GlTracks::initializeGL() +{ + glEnable(GL_DEPTH_TEST); + glClearColor(1.0, 1.0, 1.0, 0.0); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glShadeModel(GL_FLAT); +} + +void GlTracks::resizeGL(int width, int height) +{ + viewport_size.x = width; + viewport_size.y = height; + glViewport(0, 0, (GLsizei)width, (GLsizei)height); + //update_layout(); +} + +void GlTracks::paintGL() const +{ + glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); + + glPushMatrix(); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(cur_ortho.left, cur_ortho.right, + cur_ortho.bottom, cur_ortho.top, + -50.0, 50); + + draw(); + + glPopMatrix(); + glFlush(); +} + +float GlTracks::left() const +{ + return max_ortho.left; +} + +float GlTracks::right() const +{ + return max_ortho.right; +} + +void GlTracks::setViewportCenter(float x) +{ + update_viewport(x, zoom_level); + viewport_center = x; +} + +float GlTracks::viewportLeft() const +{ + return cur_ortho.left; +} + +float GlTracks::viewportCenter() const +{ + return viewport_center; +} + +float GlTracks::viewportRight() const +{ + return cur_ortho.right; +} + +float GlTracks::viewportHeight() const +{ + return cur_ortho.top - cur_ortho.bottom; +} + +float GlTracks::viewportWidth() const +{ + return cur_ortho.right - cur_ortho.left; +} + +void GlTracks::setZoom(int new_zoom) +{ + update_viewport(viewport_center, new_zoom); + zoom_level = new_zoom; +} + +int GlTracks::zoom() const +{ + return zoom_level; +} + +void GlTracks::setColorMapper(AnnotationColors& cm) +{ + color_mapper = cm; +} + +AnnotationColors& GlTracks::colorMapper() +{ + return color_mapper; +} + +void GlTracks::clear() +{ + //clear_links(); + //path_segments.clear(); + track_container.clear(); +} + +void GlTracks::push_sequence(const Sequence &s) +{ + GlSequence gs(s, color_mapper); + push_sequence(gs); +} + +void GlTracks::push_sequence(GlSequence &gs) +{ + //clear_links(); + pathid = 0; + track_container.push_back(gs); + update_layout(); + if (track_container.size() > 1) + path_segments.push_back(pair_segment_map()); +} + +const std::vector& GlTracks::tracks() const +{ + return track_container; +} + +void GlTracks::clear_links() +{ + path_segment_map_vector::iterator psmv_i; + for(psmv_i = path_segments.begin(); + psmv_i != path_segments.end(); + ++psmv_i) + { + psmv_i->clear(); + } +} + +void +GlTracks::link(vector path, vector rc, int length) +{ + if (path.size() < 2) { + // should i throw an error instead? + return; + } + if (path.size() != rc.size()) { + throw runtime_error("path and reverse compliment must be the same length"); + } + vector::iterator path_i = path.begin(); + vector::iterator rc_i = rc.begin(); + int track_i = 0; + int prev_x = *path_i; ++path_i; + bool prev_rc = *rc_i; ++rc_i; + while (path_i != path.end() and rc_i != rc.end()) + { + segment_key p(prev_x, *path_i); + pair_segment_map::iterator found_segment = path_segments[track_i].find(p); + if (found_segment == path_segments[track_i].end()) { + // not already found + float y1 = track_container[track_i].y(); + y1 -= track_container[track_i].height()/2; + float y2 = track_container[track_i+1].y(); + y2 -= track_container[track_i+1].height()/2; + + Segment s(prev_x, y1, *path_i, y2, prev_rc); + s.path_ids.push_back(pathid); + path_segments[track_i][p] = s; + } else { + //found + found_segment->second.path_ids.push_back(pathid); + } + prev_x = *path_i; + prev_rc = *rc_i; + ++track_i; + ++path_i; + ++rc_i; + } + // pathid is reset by push_sequence + ++pathid; +} + +void GlTracks::update_viewport(float center, int new_zoom) +{ + float max_width = max_ortho.width(); + // division by zero is a major bummer + if (new_zoom < 1) { + new_zoom = 1; + } + float new_max_width = max_width / new_zoom; + cur_ortho.left = center-new_max_width; + cur_ortho.right = center+new_max_width; +} + +void GlTracks::update_layout() +{ + typedef std::vector::iterator glseq_itor_type; + float available_height = (float)cur_ortho.top - 2 * (float)border; + float max_base_pairs = 0; + size_t track_count = track_container.size(); + + if (track_count > 1) { + // we have several tracks + float track_spacing = available_height / (track_count-1); + float y = available_height + (float)border; + for(glseq_itor_type seq_i = track_container.begin(); + seq_i != track_container.end(); + ++seq_i, y-=track_spacing) + { + seq_i->setX(0); + seq_i->setY(y); + if (seq_i->length() > max_base_pairs) + max_base_pairs = seq_i->length(); + } + } else if (track_count == 1) { + // center the single track + glseq_itor_type seq_i = track_container.begin(); + seq_i->setX(0); + seq_i->setY(viewport_size.x /2); + max_base_pairs = seq_i->length(); + } else { + // nothing to do as we're empty + return; + } + max_ortho.right = max_base_pairs + border; + max_ortho.left = -border; + max_ortho.top = viewport_size.x; + max_ortho.bottom = 0; + cur_ortho = max_ortho; + viewport_center = (cur_ortho.width()/2) + cur_ortho.left; +} + +void GlTracks::draw() const +{ + glMatrixMode(GL_MODELVIEW); + //glInitNames(); + //glPushName(MussaPaths); + draw_segments(); + draw_tracks(); + draw_selection(); + //glPopName(); +} + +void GlTracks::draw_selection() const +{ + /* draw selection box + glEnable(GL_BLEND); + glDepthMask(GL_FALSE); + if (selectedMode && !drawingBand) { + glColor4f(0.6, 0.6, 0.6, 0.9); + glRectf(previousBand.x(), previousBand.y(), + previousBand.right(), previousBand.bottom()); + } + glDepthMask(GL_TRUE); + glDisable(GL_BLEND); + */ +} + +void GlTracks::draw_tracks() const +{ + typedef std::vector::const_iterator glseq_citor_type; + for(glseq_citor_type seq_i = track_container.begin(); + seq_i != track_container.end(); + ++seq_i) + { + seq_i->draw(cur_ortho.left, cur_ortho.right); + } +} + +void GlTracks::draw_segments() const +{ + glLineWidth(0.1); + glBegin(GL_LINES); + // each vector contains path_segment_maps of all the connections + // between this track and the next + path_segment_map_vector::const_iterator psmv_i; + for(psmv_i = path_segments.begin(); + psmv_i != path_segments.end(); + ++psmv_i) + { + // these maps contain the pair index (used so we dont keep drawing the + // same segment) and the actual segment structure. + pair_segment_map::const_iterator psm_i; + for(psm_i = psmv_i->begin(); + psm_i != psmv_i->end(); + ++psm_i) + { + // the second element of our map pair is a segment + const Segment &s = psm_i->second; + // need to do something so we can detect our selection + if (not s.reversed) { + glColor3f(1.0, 0.0, 0.0); + } else { + glColor3f(0.0, 0.0, 1.0); + } + glVertex3f(s.start.x, s.start.y, -1); + glVertex3f(s.end.x, s.end.y, -1); + } + } + glEnd(); +} diff --git a/alg/gltracks.hpp b/alg/gltracks.hpp new file mode 100644 index 0000000..6df83ef --- /dev/null +++ b/alg/gltracks.hpp @@ -0,0 +1,137 @@ +#ifndef _GLTRACKS_H_ +#define _GLTRACKS_H_ + +#include +#include + +#include "alg/annotation_colors.hpp" +#include "alg/sequence.hpp" +#include "alg/glsequence.hpp" + +//! Manage rendering a collection of glSequences +class GlTracks +{ +public: + GlTracks(); + GlTracks(const GlTracks&); + + //! setup the opengl canvas + void initializeGL(); + //! called when our screen canvas is resized + void resizeGL(int width, int height); + //! render our scene + void paintGL() const; + + //! max world left coordinate + float left() const; + //! max world right coordinate + float right() const; + + void setViewportCenter(float x); + float viewportLeft() const; + float viewportCenter() const; + float viewportRight() const; + float viewportHeight() const; + float viewportWidth() const; + + void setZoom(int zoom_level); + int zoom() const; + + void setColorMapper(AnnotationColors& cm); + AnnotationColors& colorMapper(); + + //! clear our tracks and connections + void clear(); + + //! add a sequence to the back of our track container + void push_sequence(const Sequence &s); + //! add a glsequence to the back of our track container + void push_sequence(GlSequence &s); + //! return our track container + const std::vector& tracks() const; + + //! clear all the line segments between all the sequences + void clear_links(); + //! define a path + void link(std::vector path, std::vector isRC, int length); + + //! a useful point class + template struct point { + T x; + T y; + + point(T x_, T y_):x(x_), y(y_) {} + }; + + //! a useful rectangle, where 0,0 is in the lower left + template struct rect { + T top; + T left; + T bottom; + T right; + + rect(T t, T l, T b, T r) : top(t), left(l), bottom(b), right(r) {} + T width() { return right - left; } + T height() { return top - bottom; } + }; + + struct Segment + { + point start; + point end; + bool reversed; + std::list path_ids; + + Segment() : start(0.0, 0.0), end(0.0, 0.0) {} + Segment(float x1, float y1, float x2, float y2, bool isRC) + : start(x1, y1), end(x2, y2), reversed(isRC) {} + }; + + //! data structure holding our line segments + /*! the vector is of size track_container.size()-1 + * it's indexed by the pair x1, x2 (the two x coordinates between + * the two tracks + */ + typedef std::pair segment_key; + typedef std::map pair_segment_map; + typedef std::vector path_segment_map_vector; + path_segment_map_vector path_segments; +private: + //! recalculate the viewable world + /*! depending on the size of our canvas, our zoom level and + * how far we've been offset + */ + void update_viewport(float center, int new_zoom); + + //! determine where all the tracks should be placed + void update_layout(); + + //! master scene drawing function + /*! draw is broken out for the opengl selection code + */ + void draw() const; + //! draw glsequence tracks + void draw_tracks() const; + //! draw line segments between tracks + void draw_segments() const; + //! draw selection box + void draw_selection() const; + + //! number of pixels to reserve around the edges of our canvas + const int border; + //! the maximum dimensions that our scene is taking up (world coord) + rect max_ortho; + //! the current viewable region (world coord) + rect cur_ortho; + //! how many pixels our viewport is (screen coord) + point viewport_size; + //! the center of our current viewport (world coord) (used for scrollbar) + float viewport_center; + int zoom_level; + AnnotationColors color_mapper; + //! container of all the GlSequences loaded into our scene + std::vector track_container; + //! counter for each path added to us via connect + int pathid; +}; +#endif diff --git a/alg/module.mk b/alg/module.mk index 0d157ce..4a215ed 100644 --- a/alg/module.mk +++ b/alg/module.mk @@ -15,7 +15,8 @@ SOURCES.cpp := annotation_colors.cpp \ MUSSA_ALG_SRC := $(addprefix $(CURDIR), $(SOURCES.cpp)) MUSSA_ALG_OBJ := $(MUSSA_ALG_SRC:.cpp=$(OBJEXT)) -GLSOURCES.cpp := glsequence.cpp +GLSOURCES.cpp := glsequence.cpp \ + gltracks.cpp MUSSA_ALG_GL_SRC := $(addprefix $(CURDIR), $(GLSOURCES.cpp)) MUSSA_ALG_GL_OBJ := $(MUSSA_ALG_GL_SRC:.cpp=$(OBJEXT)) diff --git a/alg/sequence.cpp b/alg/sequence.cpp index 4161bfc..26215a4 100644 --- a/alg/sequence.cpp +++ b/alg/sequence.cpp @@ -83,6 +83,12 @@ Sequence &Sequence::operator=(const std::string& s) return *this; } +ostream& operator<<(ostream& out, const Sequence& seq) +{ + out << "Sequence(" << seq.get_seq() << ")"; + return out; +} + //! load a fasta file into a sequence /*! * \param file_path the location of the fasta file in the filesystem diff --git a/alg/sequence.hpp b/alg/sequence.hpp index e4771f2..eaf65e7 100644 --- a/alg/sequence.hpp +++ b/alg/sequence.hpp @@ -19,6 +19,7 @@ #include #include #include +#include // Sequence data class @@ -71,6 +72,7 @@ class Sequence //! assignment to constant sequences Sequence &operator=(const Sequence&); Sequence &operator=(const std::string &); + friend std::ostream& operator<<(std::ostream& out, const Sequence& seq); //! set sequence to a (sub)string containing nothing but AGCTN void set_filtered_sequence(const std::string& seq, diff --git a/alg/test/module.mk b/alg/test/module.mk index a23177f..81c8cce 100644 --- a/alg/test/module.mk +++ b/alg/test/module.mk @@ -4,6 +4,7 @@ SOURCES.cpp := test_annotation_color.cpp \ test_conserved_path.cpp \ test_flp.cpp \ test_glsequence.cpp \ + test_gltracks.cpp \ test_color.cpp \ test_main.cpp \ test_mussa.cpp \ diff --git a/alg/test/test_color.cpp b/alg/test/test_color.cpp index a3d4134..cdae872 100644 --- a/alg/test/test_color.cpp +++ b/alg/test/test_color.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "alg/color.hpp" @@ -39,5 +40,3 @@ BOOST_AUTO_TEST_CASE ( color ) BOOST_CHECK_CLOSE (colors[Color::BlueChannel ], dotthree, tolerance ); BOOST_CHECK_CLOSE (colors[Color::AlphaChannel ], dotfour, tolerance ); } - - diff --git a/mussagl.pro b/mussagl.pro index 82b4fd7..639ee43 100644 --- a/mussagl.pro +++ b/mussagl.pro @@ -23,6 +23,7 @@ HEADERS += mussa_exceptions.hpp \ alg/conserved_path.hpp \ alg/flp.hpp \ alg/glsequence.hpp \ + alg/gltracks.hpp \ alg/mussa.hpp \ alg/nway_paths.hpp \ alg/parse_options.hpp \ @@ -40,6 +41,7 @@ SOURCES += mussagl.cpp \ alg/flp.cpp \ alg/flp_seqcomp.cpp \ alg/glsequence.cpp \ + alg/gltracks.cpp \ alg/mussa.cpp \ alg/nway_entropy.cpp \ alg/nway_other.cpp \ diff --git a/qui/PathScene.cpp b/qui/PathScene.cpp index 7040b69..43195f6 100644 --- a/qui/PathScene.cpp +++ b/qui/PathScene.cpp @@ -17,270 +17,70 @@ using namespace std; -PathScene::PathScene(Mussa* analysis, QWidget *parent) : - QGLWidget(parent), - viewport_height(0), - viewport_width(0), - clipZ(30.0), - zoom(2), - maxOrtho2d(-50.0, -50, 3000000.0, 300.0), - curOrtho2d(maxOrtho2d), - viewport_center(((curOrtho2d.right()-curOrtho2d.left())/2)+curOrtho2d.left()), - selectedMode(false), - rubberBand(0), - drawingBand(false) +PathScene::PathScene(QWidget *parent) + : QGLWidget(parent) + //selectedMode(false), + //rubberBand(0), + //drawingBand(false) { - if (analysis == 0) - { - mussaAnalysis = new Mussa; - } - else - { - mussaAnalysis = analysis; - } - updateScene(); } QSize PathScene::sizeHint() const { - return QSize(400, 400); -} - -float PathScene::left() -{ - return maxOrtho2d.left(); -} - -float PathScene::right() -{ - return maxOrtho2d.right(); -} - -float PathScene::viewportLeft() -{ - return curOrtho2d.left(); -} - -float PathScene::viewportRight() -{ - return curOrtho2d.right(); + return QSize(GlTracks::viewportHeight(), GlTracks::viewportWidth()); } -float PathScene::viewportCenter() +void PathScene::setViewportCenter(float x) { - return viewport_center; -} + const float epsilon = 1e-10; + float center = fabs(GlTracks::viewportCenter()); + float abs_x = fabsf(x); + float difference = fabsf(abs_x - center); -void PathScene::updateViewport(float center, int new_zoom) -{ - float max_width = maxOrtho2d.width(); - if (new_zoom < 1) new_zoom = 1; - float new_max_width = max_width / new_zoom; - //curOrtho2d.setLeft(max(center-new_max_width, maxOrtho2d.left())); - //curOrtho2d.setRight(min(center+new_max_width, maxOrtho2d.right())); - curOrtho2d.setLeft(center-new_max_width); - curOrtho2d.setRight(center+new_max_width); - emit viewportChanged(); -} - -void PathScene::setViewportX(float x) -{ //hopefully this calculates a sufficiently reasonable == for a float - if (x != viewport_center ) + if (difference < epsilon * abs_x or difference < epsilon * center) { - updateViewport(x, zoom); - viewport_center = x; + GlTracks::setViewportCenter(x); update(); } } void PathScene::setZoom(int new_zoom) { - if (zoom != new_zoom) { - // try to figure out where we should be now? - updateViewport(viewport_center, new_zoom); - zoom = new_zoom; + if (new_zoom != GlTracks::zoom()) { + GlTracks::setZoom(new_zoom); update(); } } -void PathScene::setClipPlane(int newZ) +void PathScene::setClipPlane(int ) { +/* if (clipZ != (double) newZ){ clipZ = (double) newZ; update(); } -} - -void PathScene::loadMotifList() -{ - QString caption("Load a motif list"); - QString filter("Motif list(*.txt *.mtl)"); - QString path = QFileDialog::getOpenFileName(this, - caption, - QDir::currentPath(), - filter); - // user hit cancel? - if (path.isNull()) - return; - // try to load safely - try { - mussaAnalysis->load_motifs(path.toStdString()); - updateScene(); - } catch (runtime_error e) { - QString msg("Unable to load "); - msg += path; - msg += "\n"; - msg += e.what(); - QMessageBox::warning(this, "Load Motifs", msg); - } - assert (mussaAnalysis != 0); -} -void PathScene::loadMupa() -{ - QString caption("Load a mussa parameter file"); - QString filter("Mussa Parameters (*.mupa)"); - QString mupa_path = QFileDialog::getOpenFileName(this, - caption, - QDir::currentPath(), - filter); - // user hit cancel? - if (mupa_path.isNull()) - return; - // try to load safely - try { - Mussa *m = new Mussa; - m->load_mupa_file(mupa_path.toStdString()); - m->analyze(0, 0, Mussa::TransitiveNway, 0.0); - // only switch mussas if we loaded without error - delete mussaAnalysis; - mussaAnalysis = m; - updateScene(); - } catch (mussa_load_error e) { - QString msg("Unable to load "); - msg += mupa_path; - msg += "\n"; - msg += e.what(); - QMessageBox::warning(this, "Load Parameter", msg); - } - assert (mussaAnalysis != 0); -} - -void PathScene::loadSavedAnalysis() -{ - QString caption("Load a previously run analysis"); - QString muway_dir = QFileDialog::getExistingDirectory(this, - caption, - QDir::currentPath()); - // user hit cancel? - if (muway_dir.isNull()) - return; - // try to safely load - try { - Mussa *m = new Mussa; - m->load(muway_dir.toStdString()); - // only switch mussas if we loaded without error - delete mussaAnalysis; - mussaAnalysis = m; - updateScene(); - } catch (mussa_load_error e) { - QString msg("Unable to load "); - msg += muway_dir; - msg += "\n"; - msg += e.what(); - QMessageBox::warning(this, "Load Parameter", msg); - } - assert (mussaAnalysis != 0); -} - -void PathScene::setSoftThreshold(int threshold) -{ - if (mussaAnalysis->get_threshold() != threshold) { - mussaAnalysis->set_soft_thres(threshold); - mussaAnalysis->nway(); - update(); - } +*/ } //////////////////// // Rendering code void PathScene::initializeGL() { - glEnable(GL_DEPTH_TEST); - glClearColor(1.0, 1.0, 1.0, 0.0); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glShadeModel(GL_FLAT); + GlTracks::initializeGL(); } void PathScene::resizeGL(int width, int height) { - viewport_width = width; - viewport_height = height; - assert (geometry().width() == width); - assert (geometry().height() == height); - glViewport(0, 0, (GLsizei)width, (GLsizei)height); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - // I'm abusing this as Qt and OpenGL disagree about the direction of the - // y axis - glOrtho(curOrtho2d.left(), curOrtho2d.right(), - curOrtho2d.top(), curOrtho2d.bottom(), - -50.0, clipZ); + GlTracks::resizeGL(width, height); } void PathScene::paintGL() { - glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); - - glPushMatrix(); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(curOrtho2d.left(), curOrtho2d.right(), - curOrtho2d.top(), curOrtho2d.bottom(), - -50.0, clipZ); - glMatrixMode(GL_MODELVIEW); - mussaesque(); - glEnable(GL_BLEND); - glDepthMask(GL_FALSE); - if (selectedMode && !drawingBand) { - glColor4f(0.6, 0.6, 0.6, 0.9); - glRectf(previousBand.x(), previousBand.y(), - previousBand.right(), previousBand.bottom()); - } - glDepthMask(GL_TRUE); - glDisable(GL_BLEND); - glPopMatrix(); - glFlush(); -} - -void PathScene::updateScene() -{ - // Delete old glsequences - // FIXME: does this actually free the memory from the new'ed GlSequences? - tracks.clear(); - - // save a reference to our GlSequences - GlSequence *gl_seq; - float y = mussaAnalysis->sequences().size() * 100 ; - float max_base_pairs = 0; - typedef vector::const_iterator seqs_itor_t; - for(seqs_itor_t seq_itor = mussaAnalysis->sequences().begin(); - seq_itor != mussaAnalysis->sequences().end(); - ++seq_itor) - { - y = y - 100; - gl_seq = new GlSequence(*seq_itor, mussaAnalysis->colorMapper()); - gl_seq->setX(0); - gl_seq->setY(y); - if (gl_seq->length() > max_base_pairs ) max_base_pairs = gl_seq->length(); - tracks.push_back( *gl_seq ); - } - maxOrtho2d.setWidth(max_base_pairs + 100.0); - maxOrtho2d.setHeight((mussaAnalysis->sequences().size()) * 100 ); - curOrtho2d = maxOrtho2d; - viewport_center = (curOrtho2d.right()-curOrtho2d.left())/2+curOrtho2d.left(); + GlTracks::paintGL(); } +/* void PathScene::processSelection(GLuint hits, GLuint buffer[], GLuint bufsize) { const size_t pathz_count = mussaAnalysis->paths().refined_pathz.size(); @@ -408,122 +208,4 @@ void PathScene::mouseReleaseEvent( QMouseEvent *e) resizeGL(geometry().width(), geometry().height()); } - - -////// -// openGl rendering code - -void PathScene::draw_tracks() const -{ - glPushMatrix(); - glPushName(0); - for (vector::size_type i = 0; i != tracks.size(); ++i ) - { - glLoadName(i); - tracks[i].draw(curOrtho2d.left(), curOrtho2d.right()); - } - glPopName(); - glPopMatrix(); -} - -void PathScene::draw_lines() const -{ - GLfloat x; - GLfloat y; - GLuint pathid=0; - GLint objid=0; - bool reversed = false; - bool prevReversed = false; - const NwayPaths& nway = mussaAnalysis->paths(); - - glPushName(pathid); - glLineWidth(2); - vector::const_iterator track_itor; - - typedef list conserved_paths; - typedef conserved_paths::const_iterator const_conserved_paths_itor; - for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin(); - path_itor != nway.refined_pathz.end(); - ++path_itor, ++objid) - { - track_itor = tracks.begin(); - // since we were drawing to the start of a window, and opengl lines - // are centered around the two connecting points our lines were slightly - // offset. the idea of window_offset is to adjust them to the - // right for forward compliment or left for reverse compliment - // FIXME: figure out how to unit test these computations - GLfloat window_offset = (path_itor->window_size)/2.0; - - glBegin(GL_LINE_STRIP); - for (vector::const_iterator sp_itor = path_itor->begin(); - sp_itor != path_itor->end(); - ++sp_itor, ++track_itor) - { - x = *sp_itor; - y = track_itor->y(); - // at some point when we modify the pathz data structure to keep - // track of the score we can put grab the depth here. - // - // are we reverse complimented? - if ( x>=0) { - reversed = false; - } else { - reversed = true; - x = -x; // make positive - } - if (!reversed) - x += window_offset; // move right for forward compliment - else - x -= window_offset; // move left for reverse compliment - // the following boolean implements logical xor - if ( (reversed || prevReversed) && (!reversed || !prevReversed)) { - // we have a different orientation - if (not selectedMode or selectedPaths[objid] == true) { - // if we have nothing selected, or we're the highlight, be bright - glColor3f(0.0, 0.0, 1.0); - } else { - // else be dim - glColor3f(0.7, 0.7, 1.0); - } - } else { - // both current and previous path have the same orientation - if (not selectedMode or selectedPaths[objid] == true) { - glColor3f(1.0, 0.0, 0.0); - } else { - glColor3f(1.0, 0.7, 0.7); - } - } - prevReversed = reversed; - glVertex3f(x, y, -1.0); - } - glEnd(); - glLoadName(++pathid); - } - glPopName(); -} - -GLuint PathScene::make_line_list() -{ - GLuint line_list = glGenLists(1); - glNewList(line_list, GL_COMPILE); - - draw_lines(); - - glEndList(); - return line_list; - -} - -void PathScene::mussaesque() -{ - //static GLuint theLines = make_line_list(); - - glInitNames(); - glPushName(MussaPaths); - //glCallList(theLines); - draw_lines(); - glLoadName(MussaTracks); - draw_tracks(); - glPopName(); -} - +*/ diff --git a/qui/PathScene.hpp b/qui/PathScene.hpp index 547e113..46c905a 100644 --- a/qui/PathScene.hpp +++ b/qui/PathScene.hpp @@ -9,61 +9,41 @@ #include "alg/mussa.hpp" #include "alg/glsequence.hpp" +#include "alg/gltracks.hpp" class QMouseEvent; class QRubberBand; /*! \brief Render mussa sequences and paths */ -class PathScene: public QGLWidget +class PathScene: public QGLWidget, public GlTracks { Q_OBJECT public: - PathScene(Mussa *analysis=0, QWidget *parent=0); + PathScene(QWidget *parent=0); QSize sizeHint() const; - - Mussa* mussaAnalysis; - std::vector tracks; - - float left(); - float right(); - float viewportLeft(); - float viewportRight(); - float viewportCenter(); public slots: void setClipPlane(int z); //! set the center of the current viewport - void setViewportX(float x); + void setViewportCenter(float x); //! set our magnification level void setZoom(int); - //! load motifs - void loadMotifList(); - //! load a mussa parameter file (which specifies an analysis to run) - void loadMupa(); - //! load a previously run analysis - void loadSavedAnalysis(); - //! set the soft threshold used by the Nway_Path algorithm - void setSoftThreshold(int thres); - //! indicate that we should update our scene - void updateScene(); signals: //! emitted when our analysis has changed void analysisUpdated(); void viewportChanged(); -protected: - int viewport_height; - int viewport_width; - double clipZ; - int zoom; - QRectF maxOrtho2d; - QRectF curOrtho2d; - //! where the "center" of the viewport is - float viewport_center; +private: + //GlTracks tracks_view; + + void initializeGL(); + void resizeGL(int height, int width); + void paintGL(); + //! true if we have a selection bool selectedMode; //! indicate which paths are selected @@ -71,30 +51,19 @@ protected: //! which track is selected (it only makes sense to have one track selected). unsigned int selectedTrack; - void initializeGL(); - void resizeGL(int width, int height); - void paintGL(); - // recompute our current viewport dimensions, used by setViewportX & setZoom - void updateViewport(float left, int new_zoom); - - void mussaesque(); - //! draw all of our sequence tracks - void draw_tracks() const; - void draw_lines() const; - GLuint make_line_list(); //! convert opengl selections into the list of paths we should highlight void processSelection(GLuint hits, GLuint buffer[], GLuint bufsize); //! Provide a logical name for a type discriminator for our glName stack enum FeatureType { MussaTracks, MussaPaths }; //! \defgroup Selection - QRubberBand *rubberBand; - QPoint bandOrigin; - QRectF previousBand; - bool drawingBand; - void mousePressEvent(QMouseEvent *); - void mouseMoveEvent(QMouseEvent *); - void mouseReleaseEvent(QMouseEvent *); + //QRubberBand *rubberBand; + //QPoint bandOrigin; + //QRectF previousBand; + //bool drawingBand; + //void mousePressEvent(QMouseEvent *); + //void mouseMoveEvent(QMouseEvent *); + //void mouseReleaseEvent(QMouseEvent *); }; #endif diff --git a/qui/PathWidget.cpp b/qui/PathWidget.cpp index 6d9565b..03955a9 100644 --- a/qui/PathWidget.cpp +++ b/qui/PathWidget.cpp @@ -12,18 +12,19 @@ using namespace std; // whats the maximum reasonable range for a scrollbar const float max_scrollbar_range = 100000; -PathWidget::PathWidget(PathScene *scene, QWidget *parent) : +PathWidget::PathWidget(QWidget *parent) : QWidget(parent), - scene(scene), viewportBar(Qt::Horizontal) { QVBoxLayout *layout = new QVBoxLayout; - layout->addWidget(scene); + layout->addWidget(&scene); layout->addWidget(&viewportBar); - connect(&viewportBar, SIGNAL(valueChanged(int)), this, SLOT(setViewportX(int))); - connect(scene, SIGNAL(viewportChanged()), this, SLOT(updateScrollBar())); + connect(&viewportBar, SIGNAL(valueChanged(int)), + this, SLOT(setViewportX(int))); + connect(&scene, SIGNAL(viewportChanged()), + this, SLOT(updateScrollBar())); setLayout(layout); // sets range & scale @@ -32,11 +33,11 @@ PathWidget::PathWidget(PathScene *scene, QWidget *parent) : void PathWidget::updateScrollBar() { - float max_right = scene->right(); - float max_left = scene->left(); + float max_right = scene.right(); + float max_left = scene.left(); float max_range = max_right - max_left; - float cur_left = scene->viewportLeft(); - float cur_right = scene->viewportRight(); + float cur_left = scene.viewportLeft(); + float cur_right = scene.viewportRight(); float cur_center = ((cur_right-cur_left)/2)+cur_left; // set range to min thumb = (int)cur_center; @@ -44,10 +45,10 @@ void PathWidget::updateScrollBar() viewportBar.setValue(thumb); } -void PathWidget::setViewportX(int x) +void PathWidget::setViewportCenter(int x) { if (x != thumb) { thumb = x; - scene->setViewportX(thumb); + scene.setViewportCenter(thumb); } } diff --git a/qui/PathWidget.hpp b/qui/PathWidget.hpp index 32c1396..08c46bb 100644 --- a/qui/PathWidget.hpp +++ b/qui/PathWidget.hpp @@ -3,30 +3,29 @@ #include #include -class PathScene; + +#include "qui/PathScene.hpp" class PathWidget : public QWidget { Q_OBJECT public: - PathWidget(PathScene *, QWidget *parent=0); + PathWidget(QWidget *parent=0); + PathScene scene; public slots: //! update the scrollbar with current viewport information void updateScrollBar(); //! update scene with the properly scalled scrollbar offset - void setViewportX(int x); + void setViewportCenter(int x); private: - PathScene *scene; QScrollBar viewportBar; int thumb; float range; float scale; - - }; #endif diff --git a/qui/PathWindow.cpp b/qui/PathWindow.cpp index 67ce139..7ffbadc 100644 --- a/qui/PathWindow.cpp +++ b/qui/PathWindow.cpp @@ -6,62 +6,59 @@ #include #include #include -#include #include #include -#include #include #include "qui/PathScene.hpp" -#include "qui/PathWidget.hpp" #include "qui/PathWindow.hpp" -#include "qui/ThresholdWidget.hpp" #include "qui/ImageSaveDialog.hpp" +#include "mussa_exceptions.hpp" #include -PathWindow::PathWindow(Mussa *analysis, QWidget *) : +using namespace std; + +PathWindow::PathWindow(Mussa *analysis_, QWidget *parent) : + QMainWindow(parent), + analysis(analysis_), + path_view(this), + mussaViewTB("Path Views"), + zoomBox(), + threshold(), closeAction(0) // initialize one of the pointers to null as a saftey flag { - scene = new PathScene(analysis, this); - setupActions(); setupMainMenu(); //This next setWhatsThis function prevents // a segfault when using WhatsThis feature with // opengl widget. - scene->setWhatsThis(tr("Mussa in OpenGL!")); - // make a widget so we can have a scroll bar - PathWidget *path_widget = new PathWidget(scene, this); - setCentralWidget(path_widget); - - mussaViewTB = new QToolBar("Path Views"); - mussaViewTB->addAction(toggleMotifsAction); - - QSpinBox *zoom = new QSpinBox(); - zoom->setWhatsThis("zoom magnification factor"); - zoom->setRange(2,1000); - mussaViewTB->addWidget(zoom); - connect(zoom, SIGNAL(valueChanged(int)), scene, SLOT(setZoom(int))); + //scene->setWhatsThis(tr("Mussa in OpenGL!")); + setCentralWidget(&path_view); + + mussaViewTB.addAction(toggleMotifsAction); + + zoomBox.setWhatsThis("zoom magnification factor"); + zoomBox.setRange(2,1000); + mussaViewTB.addWidget(&zoomBox); + connect(&zoomBox, SIGNAL(valueChanged(int)), + &path_view.scene, SLOT(setZoom(int))); - ThresholdWidget *threshold = new ThresholdWidget; - threshold->setRange(19, 30); - threshold->setThreshold(19); - scene->setClipPlane(20); + threshold.setRange(19, 30); + threshold.setThreshold(19); + //scene->setClipPlane(20); // FIXME: for when we get the paths drawn at the appropriate depth //connect(threshold, SIGNAL(thresholdChanged(int)), // scene, SLOT(setClipPlane(int))); - connect(threshold, SIGNAL(thresholdChanged(int)), - scene, SLOT(setSoftThreshold(int))); - mussaViewTB->addWidget(threshold); + //connect(&threshold, SIGNAL(thresholdChanged(int)), + // &scene, SLOT(setSoftThreshold(int))); + mussaViewTB.addWidget(&threshold); - //Image Save Dialog - imageSaveDialog = new ImageSaveDialog(scene, this); - - addToolBar(mussaViewTB); + addToolBar(&mussaViewTB); statusBar()->showMessage("Welcome to mussa", 2000); + updateAnalysis(); } void PathWindow::setupActions() @@ -91,15 +88,15 @@ void PathWindow::setupActions() loadMotifListAction = new QAction(tr("Load Motif List"), this); connect(loadMotifListAction, SIGNAL(triggered()), - scene, SLOT(loadMotifList())); + this, SLOT(loadMotifList())); loadMupaAction = new QAction(tr("Load Mussa Parameters"), this); connect(loadMupaAction, SIGNAL(triggered()), - scene, SLOT(loadMupa())); + this, SLOT(loadMupa())); loadSavedAnalysisAction = new QAction(tr("Load &Analysis"), this); connect(loadSavedAnalysisAction, SIGNAL(triggered()), - scene, SLOT(loadSavedAnalysis())); + this, SLOT(loadSavedAnalysis())); loadSavedAnalysisAction->setIcon(QIcon("icons/fileopen.png")); saveMotifListAction = new QAction(tr("Save Motifs"), this); @@ -178,19 +175,109 @@ void PathWindow::createSubAnalysis() NotImplementedBox(); } + +void PathWindow::loadMotifList() +{ + QString caption("Load a motif list"); + QString filter("Motif list(*.txt *.mtl)"); + QString path = QFileDialog::getOpenFileName(this, + caption, + QDir::currentPath(), + filter); + // user hit cancel? + if (path.isNull()) + return; + // try to load safely + try { + analysis->load_motifs(path.toStdString()); + } catch (runtime_error e) { + QString msg("Unable to load "); + msg += path; + msg += "\n"; + msg += e.what(); + QMessageBox::warning(this, "Load Motifs", msg); + } + assert (analysis != 0); +} + void PathWindow::saveMotifList() { NotImplementedBox(); } +void PathWindow::loadMupa() +{ + QString caption("Load a mussa parameter file"); + QString filter("Mussa Parameters (*.mupa)"); + QString mupa_path = QFileDialog::getOpenFileName(this, + caption, + QDir::currentPath(), + filter); + // user hit cancel? + if (mupa_path.isNull()) + return; + // try to load safely + try { + Mussa *m = new Mussa; + m->load_mupa_file(mupa_path.toStdString()); + m->analyze(0, 0, Mussa::TransitiveNway, 0.0); + // only switch mussas if we loaded without error + delete analysis; + analysis = m; + updateAnalysis(); + } catch (mussa_load_error e) { + QString msg("Unable to load "); + msg += mupa_path; + msg += "\n"; + msg += e.what(); + QMessageBox::warning(this, "Load Parameter", msg); + } + assert (analysis != 0); +} + +void PathWindow::loadSavedAnalysis() +{ + QString caption("Load a previously run analysis"); + QString muway_dir = QFileDialog::getExistingDirectory(this, + caption, + QDir::currentPath()); + // user hit cancel? + if (muway_dir.isNull()) + return; + // try to safely load + try { + Mussa *m = new Mussa; + m->load(muway_dir.toStdString()); + // only switch mussas if we loaded without error + delete analysis; + analysis = m; + updateAnalysis(); + } catch (mussa_load_error e) { + QString msg("Unable to load "); + msg += muway_dir; + msg += "\n"; + msg += e.what(); + QMessageBox::warning(this, "Load Parameter", msg); + } + assert (analysis != 0); +} + +void PathWindow::setSoftThreshold(int threshold) +{ + if (analysis->get_threshold() != threshold) { + analysis->set_soft_thres(threshold); + analysis->nway(); + updateLinks(); + update(); + } +} + void PathWindow::showMussaToolbar() { - std::clog << "isVis?" << mussaViewTB->isVisible() <isVisible()) - mussaViewTB->hide(); + if (mussaViewTB.isVisible()) + mussaViewTB.hide(); else - mussaViewTB->show(); - std::clog << "isVis?" << mussaViewTB->isVisible() <size(); - imageSaveDialog->setSize(size.width(), size.height()); - int result = imageSaveDialog->exec(); - std::cout << "Result: " << result << "\n"; + size = path_view.scene.size(); + //Image Save Dialog + ImageSaveDialog imageSaveDialog(&path_view.scene, this); + imageSaveDialog.setSize(size.width(), size.height()); + int result = imageSaveDialog.exec(); + cout << "Result: " << result << "\n"; +} + +void PathWindow::updateAnalysis() +{ + cout << "analysis updated" << endl; + path_view.scene.clear(); + const vector& seqs = analysis->sequences(); + for(vector::const_iterator seq_i = seqs.begin(); + seq_i != seqs.end(); + ++seq_i) + { + path_view.scene.push_sequence(*seq_i); + } + updateLinks(); +} + +void PathWindow::updateLinks() +{ + path_view.scene.clear_links(); + bool reversed = false; + const NwayPaths& nway = analysis->paths(); + + typedef list conserved_paths; + typedef conserved_paths::const_iterator const_conserved_paths_itor; + for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin(); + path_itor != nway.refined_pathz.end(); + ++path_itor) + { + // since we were drawing to the start of a window, and opengl lines + // are centered around the two connecting points our lines were slightly + // offset. the idea of window_offset is to adjust them to the + // right for forward compliment or left for reverse compliment + // FIXME: figure out how to unit test these computations + //GLfloat window_offset = (path_itor->window_size)/2.0; + + size_t track_len = path_itor->track_indexes.size(); + vector normalized_path; + normalized_path.reserve(track_len); + vector rc_flags(false, track_len); + for (size_t track_i=0; track_i != track_len; ++track_i) + { + int x = path_itor->track_indexes[track_i]; + // at some point when we modify the pathz data structure to keep + // track of the score we can put grab the depth here. + // + // are we reverse complimented? + if ( x>=0) { + reversed = false; + } else { + reversed = true; + x = -x; // make positive + } + normalized_path.push_back(x); + rc_flags.push_back(reversed); + } + path_view.scene.link(normalized_path, rc_flags, path_itor->window_size); + } } diff --git a/qui/PathWindow.hpp b/qui/PathWindow.hpp index 96048b8..65bafdf 100644 --- a/qui/PathWindow.hpp +++ b/qui/PathWindow.hpp @@ -1,11 +1,17 @@ #ifndef _PATHWINDOW_H_ #define _PATHWINDOW_H_ + + #include #include +#include +#include + +#include "qui/PathWidget.hpp" +#include "qui/ThresholdWidget.hpp" + class QAction; -class PathScene; -class ImageSaveDialog; class Mussa; class PathWindow : public QMainWindow @@ -29,20 +35,31 @@ public slots: //! \defgroup MotifHandling Handling of motif lists //\@{ + //! load motifs + void loadMotifList(); void saveMotifList(); void toggleMotifs(); //\@} + //! load a mussa parameter file (which specifies an analysis to run) + void loadMupa(); + //! load a previously run analysis + void loadSavedAnalysis(); + //! set the soft threshold used by the Nway_Path algorithm + void setSoftThreshold(int thres); + void showMussaToolbar(); void promptSaveOpenGlPixmap(); protected: + Mussa *analysis; // display our wonderful mussa output - PathScene *scene; - QToolBar *mussaViewTB; - ImageSaveDialog *imageSaveDialog; - + PathWidget path_view; + QToolBar mussaViewTB; + QSpinBox zoomBox; + ThresholdWidget threshold; + QAction *aboutAction; QAction *closeAction; QAction *createNewAnalysisAction; @@ -62,6 +79,10 @@ protected: void setupMainMenu(); //! stub function to fill in QActions void NotImplementedBox(); + //! update the PathScene with our analysis + void updateAnalysis(); + //! update the view of conserved windows + void updateLinks(); }; #endif -- 2.30.2