1911c5de7b187e11924f44417faf36ec35c8c29d
[mussa.git] / alg / mussa.cpp
1 //  This file is part of the Mussa source distribution.
2 //  http://mussa.caltech.edu/
3 //  Contact author: Tristan  De Buysscher, tristan@caltech.edu
4
5 // This program and all associated source code files are Copyright (C) 2005
6 // the California Institute of Technology, Pasadena, CA, 91125 USA.  It is
7 // under the GNU Public License; please see the included LICENSE.txt
8 // file for more information, or contact Tristan directly.
9
10
11 //                        ----------------------------------------
12 //                          ---------- mussa_class.cc -----------
13 //                        ----------------------------------------
14
15 #include <boost/filesystem/path.hpp>
16 #include <boost/filesystem/operations.hpp>
17 #include <boost/filesystem/fstream.hpp>
18 namespace fs = boost::filesystem;
19
20 #include <iostream>
21 #include <sstream>
22
23 #include "mussa_exceptions.hpp"
24
25 #include "flp.hpp"
26 #include "io.hpp"
27 #include "mussa.hpp"
28 #include "motif_parser.hpp"
29
30 using namespace std;
31
32
33 Mussa::Mussa()
34   : color_mapper(new AnnotationColors)
35 {
36   clear();
37   connect(&the_paths, SIGNAL(progress(const QString&, int, int)), 
38           this, SIGNAL(progress(const QString&, int, int)));
39 }
40
41 Mussa::Mussa(const Mussa& m)
42   : analysis_name(m.analysis_name),
43     window(m.window),
44     threshold(m.threshold),
45     soft_thres(m.soft_thres),
46     ana_mode(m.ana_mode),
47     win_append(m.win_append),
48     thres_append(m.thres_append),
49     motif_sequences(m.motif_sequences),
50     color_mapper(m.color_mapper),
51     analysis_path(m.analysis_path),
52     dirty(m.dirty)
53 {
54   connect(&the_paths, SIGNAL(progress(const QString&, int, int)), 
55           this, SIGNAL(progress(const QString&, int, int)));
56 }
57
58 MussaRef Mussa::init() 
59 {
60   boost::shared_ptr<Mussa> m(new Mussa());
61   return m;
62 }
63
64 boost::filesystem::path Mussa::get_analysis_path() const
65 {
66   return analysis_path;
67 }
68
69 void Mussa::set_analysis_path(boost::filesystem::path pathname)
70 {
71   analysis_path = pathname;
72 }
73
74 // set all parameters to null state
75 void
76 Mussa::clear()
77 {
78   analysis_name = "";
79   ana_mode = TransitiveNway;
80   window = 0;
81   threshold = 0;
82   soft_thres = 0;
83   win_append = false;
84   thres_append = false;
85   motif_sequences.clear();
86   if(color_mapper) color_mapper->clear();
87   the_seqs.clear();
88   the_paths.clear();
89   analysis_path = fs::path();
90   set_dirty(false);
91 }
92
93 void Mussa::set_dirty(bool new_state)
94 {
95   if (dirty != new_state) {
96     dirty = new_state;
97     emit isModified(dirty);
98   }
99 }
100
101 bool Mussa::is_dirty() const
102 {
103   return dirty;
104 }
105
106 bool Mussa::empty() const
107 {
108   return the_seqs.empty();
109 }
110
111
112 // these 5 simple methods manually set the parameters for doing an analysis
113 // used so that the gui can take input from user and setup the analysis
114 // note - still need a set_append(bool, bool) method...
115 void
116 Mussa::set_name(string a_name)
117 {
118   analysis_name = a_name;
119   set_dirty(true);
120 }
121
122 string Mussa::get_name() const
123 {
124   return analysis_name;
125 }
126
127 string Mussa::get_title() const
128 {
129   fs::path analysis_path = get_analysis_path();
130   if (not analysis_path.empty()) {
131     return analysis_path.native_file_string();
132   } else if (get_name().size() > 0) {
133     return get_name();
134   } else {
135     return std::string("Unnamed");
136   }
137 }
138
139 int 
140 Mussa::size() const
141 {
142   if (the_seqs.size() > 0)
143     return the_seqs.size();
144   else
145     return 0;
146 }
147
148 void
149 Mussa::set_window(int a_window)
150 {
151   window = a_window;
152   set_dirty(true);
153 }
154
155 int Mussa::get_window() const
156 {
157   return window;
158 }
159
160 void
161 Mussa::set_threshold(int a_threshold)
162 {
163   threshold = a_threshold;
164   set_dirty(true);
165   if (a_threshold > soft_thres) {
166     soft_thres = a_threshold;
167   }
168 }
169
170 int Mussa::get_threshold() const
171 {
172   return threshold;
173 }
174
175 void
176 Mussa::set_soft_threshold(int new_threshold)
177 {
178   if (new_threshold < threshold) {
179     soft_thres = threshold;
180   } else if (new_threshold > window) {
181     soft_thres = window; 
182   } else {
183     soft_thres = new_threshold;
184   }
185 }
186
187 int Mussa::get_soft_threshold() const
188 {
189   return soft_thres;
190 }
191
192 void
193 Mussa::set_analysis_mode(enum analysis_modes new_ana_mode)
194 {
195   ana_mode = new_ana_mode;
196   set_dirty(true);
197 }
198
199 enum Mussa::analysis_modes Mussa::get_analysis_mode() const
200 {
201   return ana_mode;
202 }
203
204 string Mussa::get_analysis_mode_name() const
205 {
206   switch (ana_mode)
207   {
208   case TransitiveNway:
209     return string("Transitive");
210     break;
211   case RadialNway:
212     return string("Radial");
213     break;
214   case EntropyNway:
215     return string("Entropy");
216     break;
217   case RecursiveNway:
218     return string("[deprecated] Recursive");
219     break;
220   default:
221     throw runtime_error("invalid analysis mode type");
222     break;
223   }
224 }
225
226 const NwayPaths& Mussa::paths() const
227 {
228   return the_paths;
229 }
230
231 //template <class IteratorT>
232 //void Mussa::createLocalAlignment(IteratorT begin, IteratorT end)
233 void Mussa::createLocalAlignment(std::list<ConservedPath>::iterator begin, 
234                                  std::list<ConservedPath>::iterator end, 
235                                  std::list<ConservedPath::path_type>& result,
236                                  std::list<std::vector<bool> >& reversed)
237 {
238   const vector_sequence_type& raw_seq = the_seqs;
239   ConservedPath::path_type aligned_path;
240   size_t i2, i3;
241   int x_start, x_end;
242   int window_length, win_i;
243   int rc_1 = 0; 
244   int rc_2 = 0;
245   vector<bool> rc_list;
246   bool full_match;
247   vector<bool> matched;
248   int align_counter;
249
250   align_counter = 0;
251   for(std::list<ConservedPath>::iterator pathz_i=begin; pathz_i != end; ++pathz_i)
252   {
253     ConservedPath& a_path = *pathz_i;
254     window_length = a_path.window_size;
255     // determine which parts of the path are RC relative to first species
256     rc_list = a_path.reverseComplimented();
257     
258     // loop over each bp in the conserved region for all sequences
259     for(win_i = 0; win_i < window_length; win_i++)
260     {
261       aligned_path.clear();
262       // determine which exact base pairs match between the sequences
263       full_match = true;
264       for(i2 = 0; i2 < a_path.size()-1; i2++)
265       {
266         // assume not rc as most likely, adjust below
267         rc_1 = 0;
268         rc_2 = 0;
269         // no matter the case, any RC node needs adjustments
270         if (a_path[i2] < 0)
271           rc_1 = window_length-1;
272         if (a_path[i2+1] < 0)
273           rc_2 = window_length-1;        
274          
275         x_start = (abs(a_path[i2]-rc_1+win_i));
276         x_end =   (abs(a_path[i2+1]-rc_2+win_i));
277         
278         boost::shared_ptr<Sequence> cur(raw_seq[i2]) ;
279         boost::shared_ptr<Sequence> next(raw_seq[i2+1]);
280         // RC case handling
281         // ugh, and xor...only want rc coloring if just one of the nodes is rc
282         // if both nodes are rc, then they are 'normal' relative to each other
283         if((rc_list[i2] || rc_list[i2+1] )&&!(rc_list[i2] && rc_list[i2+1]))
284         { //the hideous rc matching logic - not complex, but annoying
285           if(!(( ((*cur)[x_start]=='A')&&((*next)[x_end]=='T')) ||
286                 (((*cur)[x_start]=='T')&&((*next)[x_end]=='A')) ||
287                 (((*cur)[x_start]=='G')&&((*next)[x_end]=='C')) ||
288                 (((*cur)[x_start]=='C')&&((*next)[x_end]=='G'))) ) 
289           {
290             full_match = false;
291           } else {
292             aligned_path.push_back(x_start);
293           }
294         }
295         else
296         { // forward match
297           if (!( ((*cur)[x_start] == (*next)[x_end]) &&
298                 ((*cur)[x_start] != 'N') && ((*next)[x_end] != 'N') ) ) {
299             full_match = false;
300           } else {
301             aligned_path.push_back(x_start);
302           }
303         }
304       }
305       // grab the last part of our path, assuming we matched
306       if (full_match)
307         aligned_path.push_back(x_end);
308
309       if (aligned_path.size() == a_path.size()) {
310         result.push_back(aligned_path);
311         reversed.push_back(rc_list);
312       }
313     }
314     align_counter++;
315   }
316 }
317
318
319 void Mussa::append_sequence(const Sequence& a_seq)
320 {
321   boost::shared_ptr<Sequence> seq_copy(new Sequence(a_seq));
322   the_seqs.push_back(seq_copy);
323   set_dirty(true);
324 }
325
326 void Mussa::append_sequence(boost::shared_ptr<Sequence> a_seq)
327 {
328   the_seqs.push_back(a_seq);
329   set_dirty(true);
330 }
331
332
333 const vector<SequenceRef>& 
334 Mussa::sequences() const
335 {
336   return the_seqs;
337 }
338
339 void Mussa::load_sequence(fs::path seq_file, fs::path annot_file, 
340                           int fasta_index, int sub_seq_start, int sub_seq_end,
341                           std::string *name)
342 {
343   boost::shared_ptr<Sequence> aseq(new Sequence);
344   aseq->load_fasta(seq_file, fasta_index, sub_seq_start, sub_seq_end);
345   if ( not annot_file.empty() ) {
346     aseq->load_annot(annot_file, sub_seq_start, sub_seq_end);
347   }
348   if (name != 0 and name->size() > 0 ) {
349     aseq->set_species(*name);
350   }
351   the_seqs.push_back(aseq);
352   set_dirty(true);
353 }
354
355 void Mussa::load_mupa_file(std::string para_file_path) {
356   load_mupa_file(boost::filesystem::path(para_file_path));
357 }
358
359 void
360 Mussa::load_mupa_file(fs::path para_file_path)
361 {
362   if (not fs::exists(para_file_path))
363   {
364     throw mussa_load_error("Config File: " + para_file_path.string() + " not found");
365   } else if (fs::is_directory(para_file_path)) {
366     throw mussa_load_error("Config File: " + para_file_path.string() + " is a directory.");
367   } else if (fs::is_empty(para_file_path)) {
368     throw mussa_load_error("Config File: " + para_file_path.string() + " is empty");
369   } else  {
370     // what directory is the mupa file in?
371     fs::path file_path_base( para_file_path.branch_path()) ;
372
373     fs::ifstream para_file;
374     para_file.open(para_file_path, ios::in);
375     
376     load_mupa_stream(para_file, file_path_base);
377     para_file.close();   
378   }
379 }
380
381 void
382 Mussa::load_mupa_stream(std::istream& para_file, fs::path& file_path_base)
383 {
384   bool error_occured = false;
385   string file_data_line;
386   string param, value; 
387   fs::path annot_file;
388   int split_index, fasta_index;
389   int sub_seq_start, sub_seq_end;
390   bool seq_params, did_seq;
391   string err_msg;
392   bool parsing_path;
393   string::size_type new_index, dir_index;
394
395   // initialize values
396   clear();
397
398   // setup loop by getting file's first line
399   getline(para_file, file_data_line);
400   split_index = file_data_line.find(" ");
401   param = file_data_line.substr(0,split_index);
402   value = file_data_line.substr(split_index+1);
403   
404   while (para_file)
405   {
406     did_seq = false;
407     if (param == "ANA_NAME")
408       analysis_name = value;
409     else if (param == "APPEND_WIN")
410       win_append = true;
411     else if (param == "APPEND_THRES")
412       thres_append = true;
413     else if (param == "SEQUENCE_NUM")
414       ; // ignore sequence_num now
415     else if (param == "WINDOW")
416       window = atoi(value.c_str());
417     else if (param == "THRESHOLD")
418       threshold = atoi(value.c_str());
419     else if (param == "SEQUENCE")
420     {
421       fs::path seq_file = file_path_base / value;
422       //cout << "seq_file_name " << seq_files.back() << endl;
423       fasta_index = 1;
424       annot_file = "";
425       sub_seq_start = 0;
426       sub_seq_end = 0;
427       seq_params = true;
428
429       while (para_file && seq_params)
430       {
431         multiplatform_getline(para_file,file_data_line);
432         split_index = file_data_line.find(" ");
433         param = file_data_line.substr(0,split_index);
434         value = file_data_line.substr(split_index+1);
435
436         if (param == "FASTA_INDEX")
437           fasta_index = atoi(value.c_str());
438         else if (param == "ANNOTATION")
439           annot_file = file_path_base / value;
440         else if (param == "SEQ_START")
441           sub_seq_start = atoi(value.c_str());
442         else if (param == "SEQ_END")
443         {
444           sub_seq_end = atoi(value.c_str());
445         }
446         //ignore empty lines or that start with '#'
447         else if ((param == "") || (param == "#")) {
448           // pass 
449         } else {
450           seq_params = false;
451         }
452       }
453       load_sequence(seq_file, annot_file, fasta_index, sub_seq_start, 
454                     sub_seq_end);
455       did_seq = true;
456     }
457     //ignore empty lines or that start with '#'
458     else if ( (param.size() == 0) || (param[0] == '#')) 
459     {} // pass
460     else
461     {
462       clog << "Illegal/misplaced mussa parameter in file\n";
463       clog << param << "\n";
464       error_occured = true;
465     }
466
467     if (!did_seq)
468     {
469       multiplatform_getline(para_file,file_data_line);
470       split_index = file_data_line.find(" ");
471       param = file_data_line.substr(0,split_index);
472       value = file_data_line.substr(split_index+1);
473       did_seq = false;
474     }
475   }
476
477   if (error_occured) {
478     throw mussa_load_error("Error parsing MUPA file");
479   }
480   soft_thres = threshold;
481   // no file was loaded, signal error
482   set_dirty(true);
483 }
484
485
486 void
487 Mussa::analyze()
488 {
489   if (the_seqs.size() < 2) {
490     throw mussa_analysis_error("you need to have at least 2 sequences to "
491                                "do an analysis.");
492   }
493   
494   seqcomp();
495   the_paths.setup(window, threshold);
496   nway();
497 }
498
499 void
500 Mussa::seqcomp()
501 {
502   vector<int> seq_lens;
503   vector<FLPs> empty_FLP_vector;
504   FLPs dummy_comp;
505   string save_file_string;
506
507   empty_FLP_vector.clear();
508   for(vector<Sequence>::size_type i = 0; i < the_seqs.size(); i++)
509   {
510     all_comps.push_back(empty_FLP_vector); 
511     for(vector<Sequence>::size_type i2 = 0; i2 < the_seqs.size(); i2++)
512       all_comps[i].push_back(dummy_comp);
513   }
514   for(vector<Sequence>::size_type i = 0; i < the_seqs.size(); i++) {
515     seq_lens.push_back(the_seqs[i]->size());
516   }
517   int seqcomps_done = 0;
518   int seqcomps_todo = (the_seqs.size() * (the_seqs.size()-1)) / 2;
519   emit progress("seqcomp", seqcomps_done, seqcomps_todo);
520
521   for(vector<Sequence>::size_type i = 0; i < the_seqs.size(); i++)
522     for(vector<Sequence>::size_type i2 = i+1; i2 < the_seqs.size(); i2++)
523     {
524       //cout << "seqcomping: " << i << " v. " << i2 << endl;
525       all_comps[i][i2].setup(window, threshold);
526       all_comps[i][i2].seqcomp(*the_seqs[i], *the_seqs[i2], false);
527       all_comps[i][i2].seqcomp(*the_seqs[i], the_seqs[i2]->rev_comp(),true);
528       ++seqcomps_done;
529       emit progress("seqcomp", seqcomps_done, seqcomps_todo);
530     }
531 }
532
533 void
534 Mussa::nway()
535 {
536
537   the_paths.set_soft_threshold(soft_thres);
538
539   if (ana_mode == TransitiveNway) {
540     the_paths.trans_path_search(all_comps);
541   }
542   else if (ana_mode == RadialNway) {
543     the_paths.radiate_path_search(all_comps);
544   }
545   else if (ana_mode == EntropyNway)
546   {
547     vector<Sequence> some_Seqs;
548     //unlike other methods, entropy needs to look at the sequence at this stage
549     some_Seqs.clear();
550     for(vector<Sequence>::size_type i = 0; i < the_seqs.size(); i++)
551     {
552       some_Seqs.push_back(*the_seqs[i]);
553     }
554
555     the_paths.setup_ent(ent_thres, some_Seqs); // ent analysis extra setup
556     the_paths.entropy_path_search(all_comps);
557   }
558
559   // old recursive transitive analysis function
560   else if (ana_mode == RecursiveNway)
561     the_paths.find_paths_r(all_comps);
562
563   the_paths.simple_refine();
564 }
565
566 void
567 Mussa::save(fs::path save_path)
568 {
569   fs::fstream save_file;
570   ostringstream append_info;
571   int dir_create_status;
572
573   if (save_path.empty()) {
574     if (not analysis_path.empty()) {
575       save_path = analysis_path;
576     } else if (not analysis_name.empty()) {
577       std::string save_name = analysis_name;
578        // gotta do bit with adding win & thres if to be appended
579        if (win_append) {
580         append_info.str("");
581         append_info <<  "_w" << window;
582         save_name += append_info.str();
583       }
584
585       if (thres_append) {
586         append_info.str("");
587         append_info <<  "_t" << threshold;
588         save_name += append_info.str();
589       }
590       save_path = save_name;
591     } else {
592       throw mussa_save_error("Need filename or analysis name to save");
593     }
594   }
595
596   if (not fs::exists(save_path)) {
597     fs::create_directory(save_path);
598   }
599   
600   std::string basename = save_path.leaf(); 
601   fs::path museq(basename + ".museq", fs::native);
602   
603   // save sequence and annots to a special mussa file
604   save_file.open(save_path / museq, ios::out);
605   save_file << "<Mussa_Sequence>" << endl;
606
607   for(vector<Sequence>::size_type i = 0; i < the_seqs.size(); i++)
608   {
609     the_seqs[i]->save(save_file);
610   }
611
612   save_file << "</Mussa_Sequence>" << endl;
613   save_file.close();
614
615   // if we have any motifs, save them.
616   if (motif_sequences.size()) {
617     fs::path mtl(basename + ".mtl", fs::native);
618     save_motifs(save_path / mtl);
619   }
620
621   // save nway paths to its mussa save file
622   fs::path muway(basename + ".muway", fs::native);
623   the_paths.save(save_path / muway);
624
625   for(vector<Sequence>::size_type i = 0; i < the_seqs.size(); i++) {
626     for(vector<Sequence>::size_type i2 = i+1; i2 < the_seqs.size(); i2++)
627     {
628       append_info.str("");
629       append_info <<  "_sp_" << i << "v" << i2;
630       fs::path flp(basename+append_info.str()+".flp", fs::native);
631       all_comps[i][i2].save(save_path / flp);
632     }
633   }
634
635   set_dirty(false);
636   analysis_path = save_path;
637 }
638
639 void
640 Mussa::save_muway(fs::path save_path)
641 {
642   the_paths.save(save_path);
643 }
644
645 void
646 Mussa::load(fs::path ana_file)
647 {
648   int i, i2;
649   fs::path file_path_base;
650   fs::path a_file_path; 
651   fs::path ana_path(ana_file);
652   bool parsing_path;
653   string err_msg;
654   ostringstream append_info;
655   vector<FLPs> empty_FLP_vector;
656   FLPs dummy_comp;
657
658
659   //--------------------------------------------------------
660   // Load Muway
661   //--------------------------------------------------------
662   analysis_path = ana_file;
663   analysis_name = ana_path.leaf();
664   fs::path muway(analysis_name+".muway", fs::native);
665   a_file_path = analysis_path / muway;
666   the_paths.load(a_file_path);
667   // perhaps this could be more elegent, but at least this'll let
668   // us know what our threshold and window sizes were when we load a muway
669   window = the_paths.get_window();
670   threshold = the_paths.get_threshold();
671   soft_thres = the_paths.get_soft_threshold();
672
673
674   //--------------------------------------------------------
675   // Load Sequence
676   //--------------------------------------------------------
677   //int seq_num = the_paths.sequence_count();
678
679   fs::path museq(analysis_name + ".museq", fs::native);
680   a_file_path = analysis_path / museq;
681
682   // this is a bit of a hack due to C++ not acting like it should with files
683   /*for (i = 1; i <= seq_num; i++)
684   {
685     boost::shared_ptr<Sequence> tmp_seq(new Sequence);
686     tmp_seq->load_museq(a_file_path, i);
687     the_seqs.push_back(tmp_seq);
688   }*/
689   
690   i = 1;
691   //int seq_num = 0;
692   boost::filesystem::fstream load_museq_fs;
693   load_museq_fs.open(a_file_path, std::ios::in);
694   boost::shared_ptr<Sequence> tmp_seq;
695   while (1)
696   {
697     tmp_seq = Sequence::load_museq(load_museq_fs);
698     
699     if (tmp_seq)
700     {
701       the_seqs.push_back(tmp_seq);
702     }
703     else
704     {
705       break;
706     }
707     
708     
709     //safe guard in case of an infinate loop.
710     //FIXME: If mussa can handle a comparison of 10000 sequences
711     // in the future, then this code should be fixed.
712     if (i == 10000)
713     {
714       throw mussa_load_error(" Run away sequence load!");
715     }
716     i++;
717   }
718   load_museq_fs.close();
719   
720   //--------------------------------------------------------
721   // Load Motifs
722   //--------------------------------------------------------
723   fs::path mtl(analysis_name + ".mtl", fs::native);
724   fs::path motif_file = analysis_path / mtl;
725   if (fs::exists(motif_file)) {
726     load_motifs(motif_file);
727   }
728   
729   vector<Sequence>::size_type seq_num = the_seqs.size();
730   empty_FLP_vector.clear();
731   for(i = 0; i < seq_num; i++)
732   {
733     all_comps.push_back(empty_FLP_vector); 
734     for(i2 = 0; i2 < seq_num; i2++)
735       all_comps[i].push_back(dummy_comp);
736   }
737   
738   
739   for(i = 0; i < seq_num; i++)
740   {
741     for(i2 = i+1; i2 < seq_num; i2++)
742     {
743       append_info.str("");
744       append_info << analysis_name <<  "_sp_" << i << "v" << i2 << ".flp";
745       //clog << append_info.str() << endl;
746       fs::path flp(append_info.str(), fs::native);
747       a_file_path = analysis_path / flp;
748       all_comps[i][i2].load(a_file_path);
749     }
750   }
751 }
752
753 void
754 Mussa::save_old()
755 {
756   fs::fstream save_file;
757
758   save_file.open(analysis_name, ios::out);
759
760   for(vector<Sequence>::size_type i = 0; i < the_seqs.size(); i++)
761     save_file << *(the_seqs[i]) << endl;
762
763   save_file << window << endl;
764   save_file.close();
765   //note more complex eventually since analysis_name may need to have
766   //window size, threshold and other stuff to modify it...
767   the_paths.save_old(analysis_name);
768 }
769
770
771 void
772 Mussa::load_old(char * load_file_path, int s_num)
773 {
774   fstream save_file;
775   string file_data_line; 
776   int i, space_split_i, comma_split_i;
777   vector<int> loaded_path;
778   string node_pair, node;
779   Sequence a_seq;
780
781   int seq_num = s_num;
782   the_paths.setup(0, 0);
783   save_file.open(load_file_path, ios::in);
784
785   // currently loads old mussa format
786
787   // get sequences
788   for(i = 0; i < seq_num; i++)
789   {
790     getline(save_file, file_data_line);
791     boost::shared_ptr<Sequence> a_seq(new Sequence(file_data_line));
792     the_seqs.push_back(a_seq);
793   }
794
795   // get window size
796   getline(save_file, file_data_line);
797   window = atoi(file_data_line.c_str());
798   // get paths
799
800   while (!save_file.eof())
801   {
802     loaded_path.clear();
803     getline(save_file, file_data_line);
804     if (file_data_line != "")
805     for(i = 0; i < seq_num; i++)
806     {
807       space_split_i = file_data_line.find(" ");
808       node_pair = file_data_line.substr(0,space_split_i);
809       //cout << "np= " << node_pair;
810       comma_split_i = node_pair.find(",");
811       node = node_pair.substr(comma_split_i+1);
812       //cout << "n= " << node << " ";
813       loaded_path.push_back(atoi (node.c_str()));
814       file_data_line = file_data_line.substr(space_split_i+1);
815     }
816     //cout << endl;
817     // FIXME: do we have any information about what the threshold should be?
818     the_paths.add_path(0, loaded_path);
819   }
820   save_file.close();
821
822   //the_paths.save("tmp.save");
823 }
824
825 void Mussa::add_motif(const Sequence& motif, const Color& color)
826 {
827   motif_sequences.insert(motif);
828   color_mapper->appendInstanceColor("motif", motif.get_sequence(), color);
829   set_dirty(true);
830 }
831
832 void Mussa::set_motifs(const vector<Sequence>& motifs, 
833                        const vector<Color>& colors)
834 {
835   if (motifs.size() != colors.size()) {
836     throw mussa_error("motif and color vectors must be the same size");
837   }
838
839   motif_sequences.clear();
840   for(size_t i = 0; i != motifs.size(); ++i)
841   {
842     add_motif(motifs[i], colors[i]);
843   }
844   update_sequences_motifs();
845 }
846
847 void Mussa::load_motifs(fs::path filename)
848 {
849   fs::ifstream f;
850   f.open(filename, ifstream::in);
851   load_motifs(f);
852 }
853   
854 void Mussa::load_motifs(std::istream &in)
855 {
856   std::string data;
857   const char *alphabet = Alphabet::dna_cstr;
858   motif_parser::ParsedMotifs parsed_motifs(motif_sequences, color_mapper);
859
860   // slurp our data into a string
861   std::streamsize bytes_read = 1;
862   while (in.good() and bytes_read) {
863     const std::streamsize bufsiz=512;
864     char buf[bufsiz];
865     bytes_read = in.readsome(buf, bufsiz);
866     data.append(buf, buf+bytes_read);
867   }
868   parsed_motifs.parse(data);
869   update_sequences_motifs();
870 }
871
872 void Mussa::save_motifs(fs::path filename)
873 {
874   fs::ofstream out_stream;
875   out_stream.open(filename, ofstream::out);
876   save_motifs(out_stream);
877 }
878
879 void Mussa::save_motifs(std::ostream& out)
880 {
881   for(motif_set::iterator motif_i = motif_sequences.begin();
882       motif_i != motif_sequences.end();
883       ++motif_i)
884   {
885     out << motif_i->get_sequence() << " ";
886     if (motif_i->get_name().size() > 0) {
887       out << "\"" << motif_i->get_name() << "\" ";
888     }
889     out << color_mapper->lookup("motif", motif_i->get_sequence());
890     out << std::endl;
891   }
892 }
893
894 void Mussa::update_sequences_motifs()
895 {
896   // once we've loaded all the motifs from the file, 
897   // lets attach them to the sequences
898   for(vector<SequenceRef >::iterator seq_i = the_seqs.begin();
899       seq_i != the_seqs.end();
900       ++seq_i)
901   {
902     // clear out old motifs
903     (*seq_i)->clear_motifs();
904     // for all the motifs in our set, attach them to the current sequence
905     for(set<Sequence>::iterator motif_i = motif_sequences.begin();
906         motif_i != motif_sequences.end();
907         ++motif_i)
908     {
909       (*seq_i)->add_motif(*motif_i);
910     }
911   }
912 }
913
914 const set<Sequence>& Mussa::motifs() const
915 {
916   return motif_sequences;
917 }
918
919 boost::shared_ptr<AnnotationColors> Mussa::colorMapper()
920 {
921   return color_mapper;
922 }