length 1 segments should be drawn as lines
[mussa.git] / alg / glseqbrowser.cpp
1 #include "alg/glseqbrowser.hpp"
2 #include "mussa_exceptions.hpp"
3
4 #include <iostream>
5 #include <sstream>
6 #include <stdexcept>
7
8 using namespace std;
9
10 GlSeqBrowser::GlSeqBrowser()
11   : border_width(25),
12     cur_ortho(400.0, 0.0, 600.0, 0.0),
13     viewport_size(600, 400),
14     viewport_center((cur_ortho.right-cur_ortho.left)/2+cur_ortho.left),
15     zoom_level(2),
16     color_mapper(),
17     track_container()
18 {
19 }
20
21 GlSeqBrowser::GlSeqBrowser(const GlSeqBrowser& gt)
22   : border_width(gt.border_width),
23     cur_ortho(gt.cur_ortho),
24     viewport_size(gt.viewport_size),
25     viewport_center(gt.viewport_center),
26     zoom_level(gt.zoom_level),
27     color_mapper(gt.color_mapper),
28     track_container(gt.track_container)
29 {
30 }
31
32 void GlSeqBrowser::initializeGL()
33 {
34   glEnable(GL_DEPTH_TEST);
35   glClearColor(1.0, 1.0, 1.0, 0.0);
36   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
37   glShadeModel(GL_FLAT);
38 }
39
40 void GlSeqBrowser::resizeGL(int width, int height)
41 {
42   viewport_size.x = width;
43   viewport_size.y = height;
44   glViewport(0, 0, (GLsizei)width, (GLsizei)height);
45   update_viewport(viewport_center, zoom_level);
46 }
47
48 void GlSeqBrowser::paintGL() const
49 {
50   glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
51
52   glPushMatrix();
53   glMatrixMode(GL_PROJECTION);
54   glLoadIdentity();
55   glOrtho(cur_ortho.left, cur_ortho.right,
56           cur_ortho.bottom, cur_ortho.top,
57           -50.0, 50);
58
59   draw();
60
61   glPopMatrix();
62   glFlush();
63 }
64
65 void GlSeqBrowser::processSelection(GLuint hits, GLuint buffer[], GLuint bufsize, const rect<float>& r)
66 {
67   GLuint *ptr;
68   GLuint names;
69   GLuint consumed_names = 0;
70   float z1;
71   float z2;
72   GLuint objtype;
73   GLuint objid;
74   GLuint path_index = 0;
75   GLuint pair_key_0 = 0;
76   GLuint pair_key_1 = 0;
77   TrackRegion track;
78
79   selected_paths.clear();
80   selected_tracks.clear();
81
82   ptr = (GLuint *) buffer;
83   if (hits > 0)
84     selectedMode = true;
85   for (GLuint i=0; i < hits; ++i)
86   {
87     if ((i + 5) > bufsize) {
88       std::clog << "*** selection overflow***" << std::endl;
89     } else {
90       consumed_names = 0;
91       names = *ptr++;
92       z1 = ((float)*ptr++)/0x7fffffff;
93       z2 = ((float)*ptr++)/0x7fffffff;
94       objtype = *ptr++; ++consumed_names;
95       switch (objtype) {
96         case MussaSegment:
97           path_index = *ptr++; ++consumed_names;
98           pair_key_0 = *ptr++; ++consumed_names;
99           pair_key_1 = *ptr++; ++consumed_names;
100           if (path_index < path_segments.size()) {
101             segment_key k(pair_key_0, pair_key_1);
102             pair_segment_map::iterator psm_i;
103             psm_i = path_segments[path_index].find(k);
104             if (psm_i != path_segments[path_index].end()) {
105               Segment &seg = psm_i->second;
106               selected_paths.insert(seg.path_ids.begin(), seg.path_ids.end());
107             }
108             // else something else is wrong
109           } else {
110             // something wasn't right
111             clog << "invalid path_index " << path_index 
112                  << " should have been [0,"<<path_segments.size()
113                  << ") " << endl;
114           }
115           break;
116         case MussaTrack:
117         {
118           objid = *ptr++; ++consumed_names;
119
120           int left = track_container[objid]->leftbase(r.left);
121           int right = track_container[objid]->rightbase(r.right);
122           // the static_cast should be ok, since basepairs line up on 
123           // integral values
124           //TrackRegion track(objid, left, right); 
125           track.set(objid, left, right);
126           selected_tracks.push_back(track);
127           //clog << "selected track " << objid
128           //     << "(" << left << ", " << right << ")" << endl;
129         }
130         break;
131         default:
132           cout << "unknown type " << objtype << " ";
133           for(; consumed_names < names; ++consumed_names) {
134             cout << consumed_names << "," << *ptr++ << " ";
135           }
136           cout << endl;
137           break;
138       }
139     }
140   }
141 }
142
143 void GlSeqBrowser::selectRegion(int top, int left, int bottom, int right)
144 {
145   GLfloat x_scale = cur_ortho.width()/((float)viewport_size.x);
146   GLfloat y_scale = cur_ortho.height()/((float)viewport_size.y);
147   GLfloat x_left = cur_ortho.left + (left*x_scale);
148   GLfloat x_right = cur_ortho.left + (right * x_scale);
149
150   if (top > bottom) {
151     // woah, someone gave us a rectangle with the origin in the lower left
152     int temp = top;
153     bottom = top;
154     top = temp;
155   }
156   // swap the orientation of canvas coordinates
157   GLfloat y_top = cur_ortho.top-(bottom*y_scale);
158   GLfloat y_bottom = cur_ortho.top - top * y_scale;
159   selectedRegion = rect<float>(y_top, x_left, y_bottom, x_right);
160
161   // hopefully this will make a buffer big enough to receive 
162   // everything being selected
163   //const size_t pathz_count = mussaAnalysis->paths().refined_pathz.size();
164   //const GLuint select_buf_size = 1 + 5 * (pathz_count + sequences.size());
165   const GLuint select_buf_size = 500000;
166   GLuint selectBuf[select_buf_size];
167   glSelectBuffer(select_buf_size, selectBuf);
168   GLint hits;
169
170   (void)glRenderMode(GL_SELECT);
171   glPushMatrix();
172   glMatrixMode(GL_PROJECTION);
173   glLoadIdentity();
174   glOrtho(x_left, x_right, y_top, y_bottom, -50.0, 50.0);
175   glMatrixMode(GL_MODELVIEW);
176   glLoadIdentity();
177
178   draw();
179
180   glFlush();
181   glPopMatrix();
182   hits = glRenderMode(GL_RENDER);
183   processSelection(hits, selectBuf, select_buf_size, selectedRegion);
184 }
185
186 float GlSeqBrowser::border() const
187 {
188   return border_width;
189 }
190
191 float GlSeqBrowser::left() const
192
193   float left;
194   if (track_container.size() == 0)
195   {
196     return cur_ortho.left;
197   } else {
198     vector<boost::shared_ptr<GlSequence> >::const_iterator track_i = track_container.begin();    
199     left = (*track_i)->x();
200     for( ; track_i != track_container.end(); ++track_i)
201     {
202       if ((*track_i)->x() < left) {
203         left = (*track_i)->x();
204       }
205     }
206     return left-border_width;
207   }
208 }
209
210 float GlSeqBrowser::right() const
211
212   float right;
213   if (track_container.size() == 0) {
214     return cur_ortho.right;
215   } else {
216     vector<boost::shared_ptr<GlSequence> >::const_iterator track_i = track_container.begin();
217     right = (*track_i)->right();
218     for( ; track_i != track_container.end(); ++track_i) {
219       if ((*track_i)->right() > right)
220         right = (*track_i)->right();
221     }
222     return right+border_width;
223   }
224 }
225
226 void GlSeqBrowser::setViewportCenter(float x)
227 {
228   update_viewport(x, zoom_level);
229   viewport_center = x;
230 }
231
232 float GlSeqBrowser::viewportLeft() const
233
234   return cur_ortho.left; 
235 }
236
237 float GlSeqBrowser::viewportCenter() const
238 {
239   return viewport_center;
240 }
241
242 float GlSeqBrowser::viewportRight() const
243
244   return cur_ortho.right; 
245 }
246
247 float GlSeqBrowser::viewportHeight() const
248 {
249   return cur_ortho.top - cur_ortho.bottom;
250 }
251
252 float GlSeqBrowser::viewportWidth() const
253 {
254   return cur_ortho.right - cur_ortho.left;
255 }
256
257 double GlSeqBrowser::zoomOut()
258 {
259
260   if (right() - left() > 0) {
261     cur_ortho.left = left();
262     cur_ortho.right = right();
263     zoom_level =  (right() - left()) / (double)viewport_size.x;
264     return zoom_level;
265   } else {
266     // made up number representing 50 bp / pixel
267     return 50.0;
268   }
269 }
270
271 double GlSeqBrowser::zoomToSequence()
272 {
273   // (experimentally determined zoom level)
274   const double friendly_zoom = 0.10;
275   setZoom(friendly_zoom);
276   return friendly_zoom;
277 }
278
279 void GlSeqBrowser::setZoom(double new_zoom)
280 {
281   update_viewport(viewport_center, new_zoom);
282   zoom_level = new_zoom;
283 }
284
285 double GlSeqBrowser::zoom() const
286 {
287   return zoom_level;
288 }
289
290 void GlSeqBrowser::setColorMapper(boost::shared_ptr<AnnotationColors> cm)
291 {
292   color_mapper = cm;
293 }
294
295 const AnnotationColors& GlSeqBrowser::colorMapper()
296 {
297   return *color_mapper;
298 }
299
300 void GlSeqBrowser::clear()
301 {
302   clear_selection();
303   clear_links();
304   path_segments.clear();
305   track_container.clear();
306 }
307
308 void GlSeqBrowser::clear_selection()
309 {
310   selectedMode = false;
311   selectedRegion.clear();
312   selected_paths.clear();
313   selected_tracks.clear();
314 }
315
316 void GlSeqBrowser::push_sequence(const Sequence& s)
317 {
318   boost::shared_ptr<Sequence> seq_copy(new Sequence(s));
319   GlSequence gs(seq_copy, color_mapper);
320   push_sequence(gs);
321 }
322
323
324 void GlSeqBrowser::push_sequence(boost::shared_ptr<Sequence> s)
325 {
326   boost::shared_ptr<GlSequence> gs(new GlSequence(s, color_mapper));
327   push_sequence(gs);
328 }
329
330 void GlSeqBrowser::push_sequence(GlSequence gs)
331 {
332   boost::shared_ptr<GlSequence> new_gs(new GlSequence(gs));
333   push_sequence(new_gs);
334 }
335
336 void GlSeqBrowser::push_sequence(boost::shared_ptr<GlSequence> gs)
337 {
338   clear_links();
339   track_container.push_back(gs);
340   update_layout();
341   if (track_container.size() > 1)
342     path_segments.push_back(pair_segment_map());
343 }
344
345 const std::vector<boost::shared_ptr<GlSequence> >& GlSeqBrowser::sequences() const
346 {
347   return track_container;
348 }
349
350 void GlSeqBrowser::clear_links()
351 {
352   path_segments.clear();
353   for (int i = track_container.size()-1; i > 0; --i)
354   {
355     path_segments.push_back(pair_segment_map());
356   }
357   pathid = 0;
358 }
359
360 void 
361 GlSeqBrowser::link(const vector<int>& path, const vector<bool>& rc, int length)
362 {
363   if (path.size() < 2) {
364     // should i throw an error instead?
365     return;
366   }
367   if (path.size() != track_container.size() ) {
368     stringstream msg;
369     msg << "Path size [" << path.size() << "] and track size [" 
370         << track_container.size() << "] don't match" << endl;
371     throw mussa_error(msg.str());
372   }
373   if (path.size() != rc.size()) {
374     throw runtime_error("path and reverse compliment must be the same length");
375   }
376   vector<int>::const_iterator path_i = path.begin();
377   vector<bool>::const_iterator rc_i = rc.begin();
378   int track_i = 0;
379   int prev_x = *path_i; ++path_i;
380   bool prev_rc = *rc_i; ++rc_i;
381   while (path_i != path.end() and rc_i != rc.end())
382   {
383     segment_key p(prev_x, *path_i);
384     pair_segment_map::iterator found_segment = path_segments[track_i].find(p);
385     if (found_segment == path_segments[track_i].end()) {
386       // not already found
387       float y1 = track_container[track_i]->y();
388             y1 -= track_container[track_i]->height()/2;
389       float y2 = track_container[track_i+1]->y();
390             y2 += track_container[track_i+1]->height()/2;
391       
392       bool rcFlag = (prev_rc or *rc_i) and !(prev_rc and *rc_i);
393       Segment s(prev_x, y1, *path_i, y2, rcFlag, length);
394       s.path_ids.insert(pathid);
395       path_segments[track_i][p] = s;
396     } else {
397       //found
398       found_segment->second.path_ids.insert(pathid);
399       // make each segment the size of the largest of any link between these 
400       // two bases
401       if (found_segment->second.length < length) {
402         found_segment->second.length = length;
403       }
404     }
405     prev_x = *path_i;
406     prev_rc = *rc_i;
407     ++track_i;
408     ++path_i;
409     ++rc_i;
410   }
411   // pathid is reset by push_sequence
412   ++pathid;
413 }
414
415 void GlSeqBrowser::setSelectedPaths(std::vector<int> paths)
416 {
417   selected_paths.clear();
418   for(std::vector<int>::iterator itor = paths.begin();
419       itor != paths.end();
420       ++itor)
421   {
422     selected_paths.insert(*itor);
423   }
424 }
425
426 const set<int>& GlSeqBrowser::selectedPaths() const
427 {
428   return selected_paths;
429 }
430
431 void GlSeqBrowser::appendSelectedTrack(GLuint track, int start, int stop)
432 {
433   selected_tracks.push_back(TrackRegion(track, start, stop));
434 }
435
436 list<TrackRegion> GlSeqBrowser::selectedTracks() const 
437 {
438   return selected_tracks;
439 }
440
441 //! copy sequence from selected track using formating function
442 template<class Item>
443 void GlSeqBrowser::copySelectedTracks(std::list<Item>& result, 
444              Item (*formatter)(boost::shared_ptr<Sequence> s, 
445                                int left, 
446                                int right))
447 {
448   result.clear();
449
450   for(selected_track_iterator track_i = selected_tracks.begin();
451       track_i != selected_tracks.end();
452       ++track_i)
453   {
454     int track_index = track_i->track_id;
455     if (track_index >= track_container.size()) {
456       // should this be an exception instead?
457       clog << "track " << track_index << " > " << track_container.size() 
458            << endl;
459     } else {
460       // we should be safe
461       boost::shared_ptr<Sequence> seq = track_container[track_index]->sequence();
462       result.push_back(formatter(seq, track_i->left, track_i->right));
463     }
464   }
465 }
466
467 //! copy sequence from selected tracks as FASTA sequences
468 void GlSeqBrowser::copySelectedTracksAsFasta(std::string& copy_buffer)
469 {
470   std::list<std::string> result;
471   struct AsFasta {
472     static string formatter(boost::shared_ptr<Sequence> seq, 
473                             int left, 
474                             int right)
475     {
476       stringstream s;
477       s << ">" << seq->get_fasta_header() 
478         << "|" << "subregion=" << left << "-" << right+1
479         << std::endl
480         << seq->subseq(left, right-left+1) << std::endl;
481       return s.str();
482     }
483   };
484   copySelectedTracks(result, AsFasta::formatter);
485   // I wish there was some way to use for_each and bind here
486   for (list<string>::iterator result_i = result.begin();
487        result_i != result.end();
488        ++result_i)
489   {
490     copy_buffer.append(*result_i);
491   }
492 }
493
494 //! copy sequence from selected tracks as new sequences
495 void GlSeqBrowser::copySelectedTracksAsSequences(std::list<Sequence>& result)
496 {
497   struct AsSequence {
498     static Sequence formatter(boost::shared_ptr<Sequence> seq, 
499                               int left, 
500                               int right)
501     {
502       return seq->subseq(left, right-left+1);
503     }
504   };
505   copySelectedTracks(result, AsSequence::formatter);
506 }
507
508 void GlSeqBrowser::copySelectedTracksAsSeqLocation(
509     std::list<SequenceLocation>& result)
510 {
511   struct AsSeqLocation {
512     static SequenceLocation formatter(boost::shared_ptr<Sequence> seq, 
513                                       int left, 
514                                       int right)
515     {
516       return SequenceLocation(seq, left, right);
517     }
518   };
519   copySelectedTracks(result, AsSeqLocation::formatter);
520 }
521
522 //! copy sequence from selected tracks as plain sequences
523 void GlSeqBrowser::copySelectedTracksAsString(std::string& copy_buffer)
524 {
525   std::list<string> result;
526   struct AsString {
527     static string formatter(boost::shared_ptr<Sequence> seq, 
528                             int left, 
529                             int right)
530     {
531       stringstream s;
532       s << seq->subseq(left, right-left+1) << std::endl;
533       return s.str();
534     }
535   };
536
537   copySelectedTracks(result, AsString::formatter);
538   // I wish there was some way to use for_each and bind here
539   for (list<string>::iterator result_i = result.begin();
540        result_i != result.end();
541        ++result_i)
542   {
543     copy_buffer.append(*result_i);
544   }
545
546 }
547
548 void GlSeqBrowser::centerOnPath(const vector<int>& paths)
549 {
550   if (paths.size() != track_container.size()) {
551     throw mussa_error("Path length didn't match the number of sequences");
552   }
553
554   for(size_t track_i = 0; track_i != track_container.size(); ++track_i)
555   {
556     // -15 = shift more to the left
557     track_container[track_i]->setX((viewport_center-15) - paths[track_i]);
558   }
559 }
560
561 void GlSeqBrowser::update_viewport(float center, double new_zoom)
562 {
563   // limit how close we can get
564   if (new_zoom < 0.01) {
565     new_zoom = 0.01;
566   }
567   double new_width = (new_zoom * (float)viewport_size.x);
568   cur_ortho.left = center-new_width/2.0;
569   cur_ortho.right = center+new_width/2.0;
570 }
571
572 void GlSeqBrowser::update_layout()
573 {
574   typedef std::vector<boost::shared_ptr<GlSequence> >::iterator glseq_itor_type;
575   float available_height = (float)cur_ortho.top - 2 * (float)border_width;
576   float max_base_pairs = 0;
577   size_t track_count = track_container.size();
578
579   if (track_count > 1) {
580     // we have several sequences
581     float track_spacing = available_height / (track_count-1);
582     float y = available_height + (float)border_width;
583     for(glseq_itor_type seq_i = track_container.begin();
584         seq_i != track_container.end();
585         ++seq_i, y-=track_spacing)
586     {
587       (*seq_i)->setX(0);
588       (*seq_i)->setY(y);
589       if ((*seq_i)->size() > max_base_pairs)
590         max_base_pairs = (*seq_i)->size();
591     }
592   } else if (track_count == 1) {
593     // center the single track
594     glseq_itor_type seq_i = track_container.begin();
595     (*seq_i)->setX(0);
596     (*seq_i)->setY(viewport_size.x /2);
597     max_base_pairs = (*seq_i)->size();
598   } else {
599     // nothing to do as we're empty
600     return;
601   }
602   cur_ortho.right = max_base_pairs + border_width;
603   cur_ortho.left = -border_width;
604   cur_ortho.top = viewport_size.x;
605   cur_ortho.bottom = 0;
606   viewport_center = (cur_ortho.width()/2) + cur_ortho.left;
607   zoomOut();
608 }
609
610 void GlSeqBrowser::draw() const
611 {
612   glMatrixMode(GL_MODELVIEW);
613   glInitNames();
614   glPushName(MussaSegment);
615   draw_segments();
616   glLoadName(MussaTrack);
617   draw_tracks();
618   glPopName();
619   // a selection shouldn't have a glName associated with it
620   draw_selection();
621 }
622
623 void GlSeqBrowser::draw_selection() const
624 {
625   // draw selection box
626   glEnable(GL_BLEND);
627   glDepthMask(GL_FALSE);
628   if (selectedMode) {
629     glColor4f(0.6, 0.6, 0.6, 0.9);
630     glRectf(selectedRegion.left, selectedRegion.top, 
631             selectedRegion.right, selectedRegion.bottom);
632   }
633   glDepthMask(GL_TRUE);
634   glDisable(GL_BLEND);
635 }
636
637 void GlSeqBrowser::draw_tracks() const
638 {
639   for(size_t track_i = 0; track_i != track_container.size(); ++track_i)
640   {
641     glPushName(track_i);
642     track_container[track_i]->draw(cur_ortho.left, cur_ortho.right);
643     glPopName();
644   }
645 }
646
647 void GlSeqBrowser::draw_segments() const
648 {
649   glLineWidth(1);
650   glEnable(GL_BLEND);
651   glDepthMask(GL_FALSE);
652   const float zdepth = -1.0;
653   // each vector contains path_segment_maps of all the connections
654   // between this track and the next
655   path_segment_map_vector::const_iterator psmv_i;
656   for(psmv_i = path_segments.begin();
657       psmv_i != path_segments.end();
658       ++psmv_i)
659   {
660     path_segment_map_vector::difference_type path_index;
661     path_index = psmv_i - path_segments.begin();
662     // these maps contain the pair index (used so we dont keep drawing the
663     // same segment) and the actual segment structure.
664     pair_segment_map::const_iterator psm_i;
665     for(psm_i = psmv_i->begin();
666         psm_i != psmv_i->end();
667         ++psm_i)
668     {
669       // grab the index into our segment map
670       const segment_key& key = psm_i->first;
671       // the second element of our map pair is a segment
672       const Segment &s = psm_i->second;
673       // need to do something so we can detect our selection
674       vector<int> selected;
675       set_intersection(selected_paths.begin(), selected_paths.end(),
676                        s.path_ids.begin(), s.path_ids.end(),
677                        back_inserter(selected));
678
679       if (not s.reversed) {
680         if (selected_paths.size() == 0 or selected.size() > 0) {
681           glColor4f(1.0, 0.0, 0.0, 1.0);
682         } else {
683           glColor4f(1.0, 0.7, 0.7, 0.4);
684         }
685       } else { 
686         if (selected_paths.size() == 0 or selected.size() > 0) {
687           glColor4f(0.0, 0.0, 1.0, 1.0);
688         } else {
689           glColor4f(0.7, 0.7, 1.0, 0.4);
690         }
691       }
692       // save the multipart name for our segment
693       glPushName(path_index); glPushName(key.first); glPushName(key.second);
694       float seq_start_x = s.start.x 
695                         + track_container[path_index]->x();
696       float seq_end_x = s.end.x
697                       + track_container[path_index+1]->x();
698       if (s.length <= 1) {
699         // use lines for elements of length <=1.
700         // and try to center the line
701         const float offset = 0.5;
702         glBegin(GL_LINES);
703           glVertex3f(seq_start_x+offset, s.start.y, -1);
704           glVertex3f(seq_end_x  +offset, s.end.y, -1);
705         glEnd();
706       } else {
707         // otherwise use quads
708         // compute length
709         float seq_start_x_length = s.start.x 
710                                  + s.length
711                                  + track_container[path_index]->x();
712         float seq_end_x_length = s.end.x
713                                + s.length
714                                + track_container[path_index+1]->x();
715         glBegin(GL_QUADS);
716           glVertex3f(seq_start_x, s.start.y, zdepth);
717           glVertex3f(seq_end_x, s.end.y, zdepth);
718           glVertex3f(seq_end_x_length, s.end.y, zdepth);
719           glVertex3f(seq_start_x_length, s.start.y, zdepth);
720         glEnd();
721       }      
722       // clear the names
723       glPopName(); glPopName(); glPopName();
724     }
725   }
726   glDepthMask(GL_TRUE);
727   glDisable(GL_BLEND);
728 }