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