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